console: only color outputs if stdout isa TTY
[nit.git] / lib / console.nit
index e922a84..1a68ff3 100644 (file)
 # limitations under the License.
 
 # Defines some ANSI Terminal Control Escape Sequences.
+#
+# The color methods (e.g. `Text::green`) format the text to appear colored
+# in a ANSI/VT100 terminal. By default, this coloring is skipped if stdout
+# is not a TTY, but it can be forced by setting `force_console_colors = true`.
 module console
 
 # A ANSI/VT100 escape sequence.
 abstract class TermEscape
        # The US-ASCII ESC character.
-       protected fun esc: Char do return 27.ascii
+       protected fun esc: Char do return 27.code_point
 
        # The Control Sequence Introducer (CSI).
        protected fun csi: String do return "{esc}["
@@ -44,8 +48,6 @@ end
 class TermMoveUp
        super TermDirectionalMove
 
-       init do end
-
        # Move by the specified number of cells.
        init by(magnitude: Int) do self.magnitude = magnitude
 
@@ -56,20 +58,16 @@ end
 class TermMoveDown
        super TermDirectionalMove
 
-       init do end
-
        # Move by the specified number of cells.
        init by(magnitude: Int) do self.magnitude = magnitude
 
        redef fun code do return "B"
 end
 
-# ANSI/VT100 code to move the cursor foward by `magnitude` columns (CUF).
+# ANSI/VT100 code to move the cursor forward by `magnitude` columns (CUF).
 class TermMoveFoward
        super TermDirectionalMove
 
-       init do end
-
        # Move by the specified number of cells.
        init by(magnitude: Int) do self.magnitude = magnitude
 
@@ -80,8 +78,6 @@ end
 class TermMoveBackward
        super TermDirectionalMove
 
-       init do end
-
        # Move by the specified number of cells.
        init by(magnitude: Int) do self.magnitude = magnitude
 
@@ -102,8 +98,6 @@ class TermMove
        # 1 is the left.
        var column: Int = 1
 
-       init do end
-
        # Move at the specified position.
        #
        # (1, 1) is the top-left corner of the display.
@@ -199,7 +193,7 @@ class TermCharFormat
                attributes.add_all(format.attributes)
        end
 
-       redef fun to_s: String do return "{csi}{attributes.join(";")}m"
+       redef fun to_s do return "{csi}{attributes.join(";")}m"
 
        # Apply the specified SGR and return `self`.
        private fun apply(sgr: String): TermCharFormat do
@@ -225,7 +219,7 @@ class TermCharFormat
        # Apply normal weight and return `self`.
        fun normal_weight: TermCharFormat do return apply("22")
 
-       # Add the attribute that disable inderlining and return `self`.
+       # Add the attribute that disable underlining and return `self`.
        fun not_underlined: TermCharFormat do return apply("24")
 
        # Add the attribute that disable blinking and return `self`.
@@ -249,7 +243,7 @@ class TermCharFormat
        # Apply a blue foreground and return `self`.
        fun blue_fg: TermCharFormat do return apply("34")
 
-       # Apply a mangenta foreground and return `self`.
+       # Apply a magenta foreground and return `self`.
        fun magenta_fg: TermCharFormat do return apply("35")
 
        # Apply a cyan foreground and return `self`.
@@ -261,89 +255,172 @@ class TermCharFormat
        # Apply the default foreground and return `self`.
        fun default_fg: TermCharFormat do return apply("39")
 
-       # Apply a black backgroud and return `self`.
+       # Apply a black background and return `self`.
        fun black_bg: TermCharFormat do return apply("40")
 
-       # Apply a red backgroud and return `self`.
+       # Apply a red background and return `self`.
        fun red_bg: TermCharFormat do return apply("41")
 
-       # Apply a green backgroud and return `self`.
+       # Apply a green background and return `self`.
        fun green_bg: TermCharFormat do return apply("42")
 
-       # Apply a yellow backgroud and return `self`.
+       # Apply a yellow background and return `self`.
        fun yellow_bg: TermCharFormat do return apply("43")
 
-       # Apply a blue backgroud and return `self`.
+       # Apply a blue background and return `self`.
        fun blue_bg: TermCharFormat do return apply("44")
 
-       # Apply a mangenta backgroud and return `self`.
+       # Apply a magenta background and return `self`.
        fun magenta_bg: TermCharFormat do return apply("45")
 
-       # Apply a cyan backgroud and return `self`.
+       # Apply a cyan background and return `self`.
        fun cyan_bg: TermCharFormat do return apply("46")
 
-       # Apply a white backgroud and return `self`.
+       # Apply a white background and return `self`.
        fun white_bg: TermCharFormat do return apply("47")
 
-       # Apply the default backgroud and return `self`.
+       # Apply the default background and return `self`.
        fun default_bg: TermCharFormat do return apply("49")
 end
 
-# Redefine the `String` class to add functions to color the string.
-redef class String
+# Services to color terminal output
+redef class Text
        private fun apply_format(f: TermCharFormat): String do
-               return "{f}{self}{normal}"
+               if stdout_isatty or force_console_colors then
+                       return "{f}{self}{normal}"
+               else return to_s
        end
 
        private fun normal: TermCharFormat do return new TermCharFormat
 
        # Make the text appear in dark gray (or black) in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun gray: String do return apply_format(normal.black_fg)
 
        # Make the text appear in red in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun red: String do return apply_format(normal.red_fg)
 
        # Make the text appear in green in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun green: String do return apply_format(normal.green_fg)
 
        # Make the text appear in yellow in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun yellow: String do return apply_format(normal.yellow_fg)
 
        # Make the text appear in blue in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun blue: String do return apply_format(normal.blue_fg)
 
-       # Make the text appear in mangenta in a ANSI/VT100 terminal.
+       # Make the text appear in magenta in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun purple: String do return apply_format(normal.magenta_fg)
 
        # Make the text appear in cyan in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun cyan: String do return apply_format(normal.cyan_fg)
 
        # Make the text appear in light gray (or white) in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun light_gray: String do return apply_format(normal.white_fg)
 
        # Make the text appear in bold in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun bold: String do return apply_format(normal.bold)
 
        # Make the text underlined in a ANSI/VT100 terminal.
        #
-       # WARNING: SEE: `TermCharFormat`
+       # SEE: `TermCharFormat`
        fun underline: String do return apply_format(normal.underline)
 end
+
+# A dynamic progress bar displayable in console.
+#
+# Example:
+# ~~~nitish
+# var max = 10
+# var current = 0
+# var pb = new TermProgress(max, current)
+#
+# pb.display
+# for i in [current + 1 .. max] do
+#      nanosleep(1, 0)
+#      pb.update(i)
+# end
+#
+# print "\ndone"
+# ~~~
+#
+# Progress bar can accept metadata to display a small amount of data.
+#
+# Example with metadata:
+# ~~~nitish
+# var pb = new TermProgress(10, 0)
+# for i in [0..10] do
+#      pb.update(i, "Step {i}")
+# end
+# ~~~
+class TermProgress
+
+       # Max value of the progress bar (business value).
+       var max_value: Int
+
+       # Current value of the progress bar (business value).
+       var current_value: Int
+
+       # Number of columns used to display the progress bar.
+       var max_columns = 70 is writable
+
+       # Get the current percent value.
+       fun current_percentage: Int do
+               return current_value * 100 / max_value
+       end
+
+       # Display the progress bar.
+       #
+       # `metadata`  can be used to pass a small amount of data to display after
+       # the progress bar.
+       fun display(metadata: nullable String) do
+               var percent = current_percentage
+               var p = current_value * max_columns / max_value
+               printn "\r{percent}% ["
+               for i in [1..max_columns] do
+                       if i < p then
+                               printn "="
+                       else if i == p then
+                               printn ">"
+                       else
+                               printn " "
+                       end
+               end
+               printn "]"
+               if metadata != null then printn " ({metadata})"
+       end
+
+       # Update and display the progress bar.
+       #
+       # See `display`.
+       fun update(new_current: Int, metadata: nullable String) do
+               current_value = new_current
+               display(metadata)
+       end
+end
+
+redef class Sys
+       private var stdout_isatty: Bool = 1.isatty is lazy
+
+       # Force coloring terminal output, even if stdout is not a TTY?
+       #
+       # Defaults to `false`.
+       var force_console_colors = false is writable
+end