colsole 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a430d377ca6b98614b6d3127bf1f35f2dc259b2bb4378bdc61570519d5126130
4
- data.tar.gz: c2e890a8104283b070323f9916db75bc9ec692d81e49630159b2127b0b8d83f9
3
+ metadata.gz: 2fd26345db2525e6804965d0686301be91d7c27d9fe3894550a781e73e1c2f72
4
+ data.tar.gz: f44de99c02442edeae3723d405be87fe29d845c130460ee16a23bf0aab254901
5
5
  SHA512:
6
- metadata.gz: 4bc5fc5638d1cb43b0a88d487686fd7afc918626446220b2874a419443bf2d4ac2dd24ac3711fe9f13a62fea07d80d0e1cea4a15d2da398b7f18ceba819fb401
7
- data.tar.gz: 3232586c10d02fdc7fd4ca3adc517cae833c62440eeec552bf2e50bc23d1d5192718212cbc7bd6dcda7647c404c55b11ac7427b7972911a8062c9e722a2a21c5
6
+ metadata.gz: ff6fb2074275bf9479ab0ba400bec991aa1d5972488c0c21c143ab012851733452aca15549278d770f8427c05ff5bcfad077f53c2649860ceb063934ebe0cba0
7
+ data.tar.gz: 8bd0e29dc5216b94ade0bec78636122bf015bbe502061655312cf52aaecd4c3462bac97310bf4e5f35d7ad0a51ad9fe5f4093d806b36968b4597f9df3789574a
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- Colsole
2
- ==================================================
1
+ # Colsole
3
2
 
4
3
  [![Gem Version](https://badge.fury.io/rb/colsole.svg)](https://badge.fury.io/rb/colsole)
5
4
  [![Build Status](https://github.com/DannyBen/colsole/workflows/Test/badge.svg)](https://github.com/DannyBen/colsole/actions?query=workflow%3ATest)
@@ -9,32 +8,30 @@ Colsole
9
8
 
10
9
  Utility functions for colorful console applications.
11
10
 
12
- ---
11
+ > **Upgrade Note**
12
+ >
13
+ > This README is for the latest version of colsole (0.8.x), which is compatible
14
+ > with older versions. Version 1.x will NOT be compatible.
15
+ >
16
+ > See [Uprading](#upgrading) below.
13
17
 
18
+ ## Install
14
19
 
15
- Install
16
- --------------------------------------------------
20
+ Add to your Gemfile:
17
21
 
18
22
  ```
19
- $ gem install colsole
23
+ $ gem 'colsole', '>= 0.6.0', '< 2.0'
20
24
  ```
21
25
 
22
- Features
23
- --------------------------------------------------
24
-
25
- - Print colored messages
26
- - Color parts of a message
27
- - Print neatly aligned status messages
28
- - Word wrap with indentation consideration
26
+ ## Examples
29
27
 
30
- See the [Examples file][1] for more.
28
+ See the [Examples file](https://github.com/DannyBen/colsole/blob/master/example.rb).
31
29
 
32
- Primary Functions
33
- --------------------------------------------------
30
+ ## Primary Functions
34
31
 
35
32
  ### `say "anything"`
36
33
 
37
- An alternative to puts.
34
+ An alternative to puts with line wrapping, colors and more.
38
35
 
39
36
  ```ruby
40
37
  say "Hello"
@@ -47,30 +44,27 @@ say "appears in "
47
44
  say "one line"
48
45
  ```
49
46
 
50
- Embed color markers in the string:
47
+ Embed [color markers](#colors) in the string:
51
48
 
52
49
  ```ruby
53
- say "!txtred!I am RED !txtgrn!I am GREEN"
50
+ say "This is r`red`, and this gu`entire phrase is green underlined`"
54
51
  ```
55
52
 
56
- ### `say_status :status, "message" [, :color]`
57
-
58
- Print a message with a colored status
53
+ Provide the `replace: true` option after a space terminated "said" string to
54
+ rewrite the line:
59
55
 
60
56
  ```ruby
61
- say_status :create, "perpetual energy"
57
+ # space terminated string to say it without a newline
58
+ say "downloading data... "
59
+ # long process here...
60
+ say "download complete.", replace: true
62
61
  ```
63
62
 
64
- You can provide a color in the regulat 6 letter code:
65
-
66
- ```ruby
67
- say_status :error, "does not compute", :txtred
68
- ```
69
63
 
70
64
  ### `word_wrap " string" [, length]`
71
65
 
72
- Wrap long lines while keeping words intact, and keeping
73
- indentation based on the leading spaces in your string:
66
+ Wrap long lines while keeping words intact, and keeping indentation based on the
67
+ leading spaces in your string:
74
68
 
75
69
  ```ruby
76
70
  say word_wrap(" one two three four five", 15)
@@ -84,37 +78,21 @@ say word_wrap(" one two three four five", 15)
84
78
  If `length` is not provided, `word_wrap` will attempt to determine it
85
79
  automatically based on the width of the terminal.
86
80
 
87
-
88
- ### `resay "anything"`
89
-
90
- Use resay after a space terminated "said" string to rewrite the line
91
-
92
- ```ruby
93
- say "downloading... "
94
- # long process here...
95
- resay "downloaded."
96
- ```
97
-
98
-
99
81
  ### `say! "anything to stderr"`
100
82
 
101
83
  Use say! to output to stderr with color markers:
102
84
 
103
85
  ```ruby
104
- say! "!txtred!Error!txtrst!: This just did not work"
86
+ # red inverted ERROR
87
+ say! "ri` ERROR ` This just did not work"
105
88
  ```
106
89
 
107
- Utility / Support Functions
108
- --------------------------------------------------
90
+ ## Utility / Support Functions
109
91
 
110
- ### `colorize "!txtred!Hello"`
92
+ ### `colorize "string"`
111
93
 
112
94
  Parses and returns a color-flagged string.
113
95
 
114
- Respects pipe and auto terminates colored strings.
115
-
116
- Call without text to see a list/demo of all available colors.
117
-
118
96
  ### `terminal?`
119
97
 
120
98
  Returns true if we are running in an interactive terminal
@@ -123,21 +101,76 @@ Returns true if we are running in an interactive terminal
123
101
 
124
102
  Checks if the provided string is a command in the path.
125
103
 
126
- ### `detect_terminal_size fallback_value`
104
+ ### `terminal_size [fallback_cols, fallback_rows]`
127
105
 
128
106
  Returns an array `[width, height]` of the terminal, or the supplied
129
- `fallback_value` if it is unable to detect.
107
+ fallback if it is unable to detect.
108
+
109
+ ### `terminal_width` / `terminal_height`
110
+
111
+ Returns only the terminal width or height. This is a shortcut to
112
+ `terminal_size[0]` / terminal_size[1].
130
113
 
131
- ### `terminal_width`
132
114
 
133
- Returns only the terminal width. This is a shortcut to
134
- `detect_terminal_size[0]`.
115
+ ## Colors
135
116
 
117
+ Strings that are surrounded by backticks, and preceded by a color code and
118
+ optional styling markers will be converted to the respective ANSI color.
119
+
120
+ ```ruby
121
+ say "this is b`blue` and ru`this is red underlined`"
122
+ ```
136
123
 
137
- Color Codes
138
- --------------------------------------------------
124
+ The one letter color code is required, followed by up to 3 style code.
139
125
 
140
- [![Color Codes](https://raw.githubusercontent.com/DannyBen/colsole/master/color-codes.png)](https://raw.githubusercontent.com/DannyBen/colsole/master/color-codes.png)
126
+ | Color Code | Color
127
+ |------------|-------
128
+ | `n` | no color
129
+ | `k` | black
130
+ | `r` | red
131
+ | `g` | green
132
+ | `y` | yellow
133
+ | `b` | blue
134
+ | `m` | magenta
135
+ | `c` | cyan
136
+ | `w` | white
141
137
 
138
+ | Style Code | Style
139
+ |------------|-------
140
+ | `b` | bold
141
+ | `u` | underlined
142
+ | `i` | inverted
143
+ | `z` | terminate
142
144
 
143
- [1]: https://github.com/DannyBen/colsole/blob/master/example.rb
145
+ ## Upgrading
146
+
147
+ Version 0.8.x changes several things, including the syntax of the color
148
+ markers. For easy transition, it is compatible with older versions.
149
+
150
+ Follow these steps to upgrade:
151
+
152
+ ```ruby
153
+
154
+ # => Require a more flexible version
155
+ # change this
156
+ gem 'colsole'
157
+ # to this (to avoid conflicts with other gems that require 0.x)
158
+ gem 'colsole', '>= 0.6.0', '< 2.0'
159
+
160
+ # => Remove 'say_status'
161
+ # It will no longer be supported in 1.0.0
162
+ say_status "text"
163
+
164
+ # => Replace 'resay'
165
+ # 'resay' is replaced with 'say replace: true'
166
+ # change this
167
+ resay "text"
168
+ # to this
169
+ say "text", replace: true
170
+
171
+ # => Change color markers syntax
172
+ # replace this
173
+ say "the !txtblu!blue"
174
+ # with this
175
+ say "the b`blue`"
176
+ ```
@@ -0,0 +1,55 @@
1
+ # This file contains methods that are called by the main Colsole module
2
+ # for compatibility with older versions of colsole.
3
+ # Do not use these methods directly.
4
+ module Colsole
5
+ def old_colorize(text)
6
+ reset = colors['txtrst']
7
+ reset_called_last = true
8
+
9
+ out = text.gsub(/!([a-z]{6})!/) do
10
+ reset_called_last = $1 == 'txtrst'
11
+ colors[$1]
12
+ end
13
+
14
+ reset_called_last or out = "#{out}#{reset}"
15
+ out
16
+ end
17
+
18
+ def old_strip_colors(text)
19
+ text.gsub(/!([a-z]{6})!/, '')
20
+ end
21
+
22
+ def resay(text)
23
+ say text, replace: true
24
+ end
25
+
26
+ def say_status(status, message = nil, color = nil)
27
+ color ||= (message ? :txtgrn : :txtblu)
28
+ say "!#{color}!#{status.to_s.rjust 12} !txtrst! #{message}".strip
29
+ end
30
+
31
+ def colors
32
+ @colors ||= begin
33
+ esc = 27.chr
34
+ pattern = "#{esc}[%{decor};%{fg}m"
35
+
36
+ decors = { txt: 0, bld: 1, und: 4, rev: 7 }
37
+ color_codes = { blk: 0, red: 1, grn: 2, ylw: 3, blu: 4, pur: 5, cyn: 6, wht: 7 }
38
+ colors = {}
39
+
40
+ decors.each do |dk, dv|
41
+ color_codes.each do |ck, cv|
42
+ key = "#{dk}#{ck}"
43
+ val = pattern % { decor: dv, fg: "3#{cv}" }
44
+ colors[key] = val
45
+ end
46
+ end
47
+
48
+ colors['txtbld'] = "#{esc}[1m"
49
+ colors['txtund'] = "#{esc}[4m"
50
+ colors['txtrev'] = "#{esc}[7m"
51
+ colors['txtrst'] = "#{esc}[0m"
52
+ colors
53
+ end
54
+ end
55
+ end
@@ -1,3 +1,3 @@
1
1
  module Colsole
2
- VERSION = "0.7.2"
3
- end
2
+ VERSION = '0.8.0'
3
+ end
data/lib/colsole.rb CHANGED
@@ -1,103 +1,91 @@
1
- require "colsole/version"
2
-
3
- # Colsole - Colorful Console Applications
4
- #
5
- # This class provides several utility functions for console application
6
- # developers.
7
- #
8
- # - #colorize string - return a colorized strings
9
- # - #say string - print a string with colors
10
- # - #say! string - print a string with colors to stderr
11
- # - #resay string - same as say, but overwrite current line
12
- # - #say_status symbol, string [, color] - print a message with status
13
- # - #word_wrap string - wrap a string and maintain indentation
14
- # - #detect_terminal_size
15
- # - #terminal_width
16
- #
17
- # Credits:
18
- # terminal width detection by Gabrial Horner https://github.com/cldwalker
1
+ require 'io/console'
2
+ require 'colsole/compat'
19
3
 
4
+ # Utility functions for colorful console applications.
20
5
  module Colsole
21
- # Prints a color-flagged string.
22
- # Use color flags (like !txtred!) to change color in the string.
23
- # Space terminated strings will leave the cursor at the same line.
24
- def say(text, force_color = false)
25
- last = text[-1, 1]
26
- if terminal? and (last == ' ' or last == '\t')
27
- print colorize(text, force_color)
6
+ ANSI_COLORS = {
7
+ 'n' => '', # no color
8
+ 'k' => "\e[30m", # black
9
+ 'r' => "\e[31m", # red
10
+ 'g' => "\e[32m", # green
11
+ 'y' => "\e[33m", # yellow
12
+ 'b' => "\e[34m", # blue
13
+ 'm' => "\e[35m", # magenta
14
+ 'c' => "\e[36m", # cyan
15
+ 'w' => "\e[37m", # white
16
+ }
17
+
18
+ ANSI_STYLES = {
19
+ 'b' => "\e[1m", # bold
20
+ 'u' => "\e[4m", # underlined
21
+ 'i' => "\e[7m", # inverted
22
+ 'z' => "\e[0m", # terminate
23
+ }
24
+
25
+ # Output a string with optional color markers to stdout.
26
+ # If text is ended with a white space, you can call again with replace: true
27
+ # to replace that line
28
+ def say(text, replace: false)
29
+ internal_say text, $stdout, replace: replace
30
+ end
31
+
32
+ # Output a string with optional color markers to stderr.
33
+ # Behaves similarly to `#say`.
34
+ def say!(text, replace: false)
35
+ internal_say text, $stderr, replace: replace
36
+ end
37
+
38
+ # Returns true if stdout/stderr are a terminal
39
+ def terminal?(stream = $stdout)
40
+ case ENV['TTY']
41
+ when 'on' then true
42
+ when 'off' then false
28
43
  else
29
- print colorize("#{text}\n", force_color)
44
+ stream.tty?
30
45
  end
31
46
  end
32
47
 
33
- # Prints a color-flagged string to STDERR
34
- # Use color flags (like !txtred!) to change color in the string.
35
- def say!(text, force_color = false)
36
- $stderr.puts colorize(text, force_color, :stderr)
37
- end
38
-
39
- # Erase the current output line, and say a new string.
40
- # This should be used after a space terminated say().
41
- def resay(text, force_color = false)
42
- text = "\033[2K\r#{text}" if terminal?
43
- say text, force_color
44
- end
45
-
46
- # Prints a line with a colored status and message.
47
- # Status can be a symbol or a string. Color is optional, defaults to
48
- # green (:txtgrn) when there is a message, and to blue (:txtblu) when
49
- # there is only a status
50
- def say_status(status, message = nil, color = nil)
51
- color ||= (message ? :txtgrn : :txtblu)
52
- say "!#{color}!#{status.to_s.rjust 12} !txtrst! #{message}".strip
53
- end
54
-
55
- # Returns true if stdout/stderr is interactive terminal
56
- def terminal?(stream = :stdout)
57
- stream == :stdout ? out_terminal? : err_terminal?
58
- end
59
-
60
- # Returns true if stdout is interactive terminal
61
- def out_terminal?
62
- ENV['TTY'] == 'on' ? true : ENV['TTY'] == 'off' ? false : $stdout.tty?
63
- end
64
-
65
- # Returns true if stderr is interactive terminal
66
- def err_terminal?
67
- ENV['TTY'] == 'on' ? true : ENV['TTY'] == 'off' ? false : $stderr.tty?
68
- end
69
-
70
- # Determines if a shell command exists.
48
+ # Returns true if the command exists in the path
71
49
  def command_exist?(command)
72
50
  ENV['PATH'].split(File::PATH_SEPARATOR).any? do |dir|
73
51
  File.exist?(File.join dir, command) or File.exist?(File.join dir, "#{command}.exe")
74
52
  end
75
53
  end
76
54
 
77
- # Returns [width, height] of terminal when detected, or a default
78
- # value otherwise.
79
- def detect_terminal_size(default = [80,30])
80
- if (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/)
81
- result = [ENV['COLUMNS'].to_i, ENV['LINES'].to_i]
82
- elsif (RUBY_PLATFORM =~ /java/ || (!STDIN.tty? && ENV['TERM'])) && command_exist?('tput')
83
- result = [`tput cols 2>&1`.to_i, `tput lines 2>&1`.to_i]
84
- elsif STDIN.tty? && command_exist?('stty')
85
- result = `stty size 2>&1`.scan(/\d+/).map { |s| s.to_i }.reverse
55
+ # Returns true if we can and should use colors in this stream
56
+ def use_colors?(stream = $stdout)
57
+ ENV['FORCE_COLOR'] || (!ENV['NO_COLOR'] && terminal?(stream))
58
+ end
59
+
60
+ # Returns the terminal size as [columns, rows].
61
+ # Environment variables can be used to cheat.
62
+ def terminal_size(default = [80, 30])
63
+ result = if (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/)
64
+ [ENV['COLUMNS'].to_i, ENV['LINES'].to_i]
86
65
  else
66
+ safe_get_tty_size default
67
+ end
68
+
69
+ unless result[0].is_a?(Integer) && result[1].is_a?(Integer) && result[0].positive? && result[1].positive?
87
70
  result = default
88
71
  end
89
- result = default unless result[0].is_a? Integer and result[1].is_a? Integer and result[0] > 0 and result[1] > 0
72
+
90
73
  result
91
74
  end
92
75
 
93
- # Returns terminal width
76
+ # Returns the columns part of the `#terminal_size`
94
77
  def terminal_width
95
- detect_terminal_size[0]
78
+ terminal_size[0]
79
+ end
80
+
81
+ # Returns the rows part of the `#terminal_size`
82
+ def terminal_height
83
+ terminal_size[1]
96
84
  end
97
85
 
98
86
  # Converts a long string to be wrapped keeping words in tact.
99
- # If the string starts with one or more spaces, they will be
100
- # preserved in all subsequent lines (i.e., remain indented).
87
+ # If the string starts with one or more spaces, they will be preserved in
88
+ # all subsequent lines (i.e., remain indented).
101
89
  def word_wrap(text, length = nil)
102
90
  length ||= terminal_width
103
91
  lead = text[/^\s*/]
@@ -105,7 +93,7 @@ module Colsole
105
93
  length -= lead.length
106
94
  text.split("\n").collect! do |line|
107
95
  if line.length > length
108
- line.gsub!(/([^\s]{#{length}})([^\s$])/, "\\1 \\2")
96
+ line.gsub!(/([^\s]{#{length}})([^\s$])/, '\\1 \\2')
109
97
  line.gsub(/(.{1,#{length}})(\s+|$)/, "#{lead}\\1\n").rstrip
110
98
  else
111
99
  "#{lead}#{line}"
@@ -113,68 +101,50 @@ module Colsole
113
101
  end * "\n"
114
102
  end
115
103
 
116
- # Parses and returns a color-flagged string.
117
- # Respects pipe and auto terminates colored strings.
118
- # Call without text to see a list/demo of all available colors.
119
- def colorize(text = nil, force_color = false, stream = :stdout)
120
- return show_color_demo if text.nil?
121
- return strip_color_markers(text) unless terminal?(stream) || force_color
122
- colorize! text
104
+ # Convert color markers to ansi colors.
105
+ def colorize(string)
106
+ # compatibility later
107
+ compat_string = old_colorize string
108
+
109
+ process_color_markers compat_string do |color, styles, text|
110
+ "#{styles}#{color}#{text}#{ANSI_STYLES['z']}"
111
+ end
123
112
  end
124
113
 
125
- private
114
+ # Remove color markers.
115
+ def strip_colors(string)
116
+ # compatibility layer
117
+ compat_string = old_strip_colors string
126
118
 
127
- def colors
128
- @colors ||= prepare_colors
119
+ process_color_markers(compat_string) { |_color, _styles, text| text }
129
120
  end
130
121
 
131
- def colorize!(text)
132
- reset = colors['txtrst']
133
- reset_called_last = true
122
+ private
134
123
 
135
- out = text.gsub(/\!([a-z]{6})\!/) do |m|
136
- reset_called_last = $1 == "txtrst"
137
- colors[$1]
124
+ def process_color_markers(string)
125
+ string.gsub(/([rgybmcn])([ubi]{0,3})`([^`]*)`/) do
126
+ color = ANSI_COLORS[$1]
127
+ styles = $2.chars.map { |a| ANSI_STYLES[a] }.join
128
+ text = $3
129
+ yield color, styles, text
138
130
  end
139
-
140
- reset_called_last or out = "#{out}#{reset}"
141
- out
142
131
  end
143
132
 
144
- # Create a colors array with keys such as :txtgrn and :bldgrn
145
- # and values which are the escape codes for the colors.
146
- def prepare_colors
147
- esc = 27.chr
148
- pattern = "#{esc}[%{decor};%{fg}m"
149
-
150
- decors = { txt: 0, bld: 1, und: 4, rev: 7 }
151
- color_codes = { blk: 0, red: 1, grn: 2, ylw: 3, blu: 4, pur: 5, cyn: 6, wht: 7 }
152
- colors = {}
153
-
154
- decors.each do |dk, dv|
155
- color_codes.each do |ck, cv|
156
- key = "#{dk}#{ck}"
157
- val = pattern % { decor: dv, fg: "3#{cv}" }
158
- colors[key] = val
159
- end
160
- end
161
- colors['txtbld'] = "#{esc}[1m"
162
- colors['txtund'] = "#{esc}[4m"
163
- colors['txtrev'] = "#{esc}[7m"
164
- colors['txtrst'] = "#{esc}[0m"
165
- colors
166
- end
133
+ def internal_say(text, stream, replace: false)
134
+ text = "\033[2K\r#{text}" if replace && terminal?
135
+ last = text[-1, 1]
136
+ handler = use_colors?(stream) ? :colorize : :strip_colors
167
137
 
168
- def show_color_demo
169
- i = colors.count
170
- colors.keys.each do |k|
171
- puts colorize "#{k} = !#{k}! #{i} bottles of beer on the wall !txtrst!"
172
- i -= 1
138
+ if terminal? && ((last == ' ') || (last == '\t'))
139
+ stream.print send(handler, text)
140
+ else
141
+ stream.print send(handler, "#{text}\n")
173
142
  end
174
143
  end
175
144
 
176
- def strip_color_markers(text)
177
- text.gsub(/\!([a-z]{6})\!/, '')
145
+ def safe_get_tty_size(default = [80, 30])
146
+ $stdout.winsize.reverse
147
+ rescue Errno::ENOTTY
148
+ default
178
149
  end
179
-
180
150
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: colsole
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Ben Shitrit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-31 00:00:00.000000000 Z
11
+ date: 2023-01-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Utility functions for making colorful console applications
14
14
  email: db@dannyben.com
@@ -18,11 +18,13 @@ extra_rdoc_files: []
18
18
  files:
19
19
  - README.md
20
20
  - lib/colsole.rb
21
+ - lib/colsole/compat.rb
21
22
  - lib/colsole/version.rb
22
23
  homepage: https://github.com/DannyBen/colsole
23
24
  licenses:
24
25
  - MIT
25
- metadata: {}
26
+ metadata:
27
+ rubygems_mfa_required: 'true'
26
28
  post_install_message:
27
29
  rdoc_options: []
28
30
  require_paths:
@@ -31,14 +33,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
31
33
  requirements:
32
34
  - - ">="
33
35
  - !ruby/object:Gem::Version
34
- version: 2.3.0
36
+ version: 2.6.0
35
37
  required_rubygems_version: !ruby/object:Gem::Requirement
36
38
  requirements:
37
39
  - - ">="
38
40
  - !ruby/object:Gem::Version
39
41
  version: '0'
40
42
  requirements: []
41
- rubygems_version: 3.0.3
43
+ rubygems_version: 3.4.3
42
44
  signing_key:
43
45
  specification_version: 4
44
46
  summary: Colorful Console Applications