kolor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ module Kolor
6
+ # Kolor::Logger provides styled, leveled logging for terminal output.
7
+ #
8
+ # It supports five log levels: `:info`, `:warn`, `:error`, `:success`, and `:debug`.
9
+ # Each level has a default ANSI style, which can be overridden per message.
10
+ #
11
+ # Logging behavior is controlled by environment variables:
12
+ # - `KOLOR_DEBUG` enables debug output
13
+ # - `KOLOR_VERBOSE` enables info output
14
+ #
15
+ # Warnings and errors are always shown unless suppressed via `suppress!`.
16
+ #
17
+ # @example Log a warning
18
+ # Kolor::Logger.warn("Something went wrong")
19
+ #
20
+ # @example Suppress warnings
21
+ # Kolor::Logger.suppress!
22
+ module Logger
23
+ DEBUG_ENV_KEY = :KOLOR_DEBUG
24
+ INFO_ENV_KEY = :KOLOR_VERBOSE
25
+
26
+ # Default ANSI styles for each log level
27
+ #
28
+ # @return [Hash{Symbol => Array<Symbol>}]
29
+ DEFAULT_STYLES = {
30
+ info: [:cyan],
31
+ warn: [:yellow, :bold],
32
+ error: [:red, :bold],
33
+ success: [:green],
34
+ debug: [:magenta]
35
+ }.freeze
36
+
37
+ # Display tags for each log level
38
+ #
39
+ # @return [Hash{Symbol => String}]
40
+ LEVEL_TAGS = {
41
+ info: 'INFO',
42
+ warn: 'WARN',
43
+ error: 'ERROR',
44
+ success: 'OK',
45
+ debug: 'DEBUG'
46
+ }.freeze
47
+
48
+ @suppress_warnings = false
49
+ @mutex = Mutex.new
50
+
51
+ class << self
52
+ # Checks if debug output is enabled via ENV
53
+ #
54
+ # @return [Boolean]
55
+ def show_debug? = !ENV[DEBUG_ENV_KEY.to_s].nil?
56
+
57
+ # Checks if info output is enabled via ENV
58
+ #
59
+ # @return [Boolean]
60
+ def show_info? = !ENV[INFO_ENV_KEY.to_s].nil?
61
+
62
+ # Logs an info-level message if verbose mode is enabled
63
+ #
64
+ # @param message [String]
65
+ # @param styles [Array<Symbol>, nil]
66
+ # @return [void]
67
+ def info(message, styles = nil)
68
+ log(:info, message, styles) if show_info?
69
+ end
70
+
71
+ # Logs a debug-level message if debug mode is enabled
72
+ #
73
+ # @param message [String]
74
+ # @param styles [Array<Symbol>, nil]
75
+ # @return [void]
76
+ def debug(message, styles = nil)
77
+ log(:debug, message, styles) if show_debug?
78
+ end
79
+
80
+ # Logs a warning message
81
+ #
82
+ # @param message [String]
83
+ # @param styles [Array<Symbol>, nil]
84
+ # @return [void]
85
+ def warn(message, styles = nil) log(:warn, message, styles) end
86
+
87
+ # Logs an error message
88
+ #
89
+ # @param message [String]
90
+ # @param styles [Array<Symbol>, nil]
91
+ # @return [void]
92
+ def error(message, styles = nil) log(:error, message, styles) end
93
+
94
+ # Logs a success message
95
+ #
96
+ # @param message [String]
97
+ # @param styles [Array<Symbol>, nil]
98
+ # @return [void]
99
+ def success(message, styles = nil) log(:success, message, styles) end
100
+
101
+ # Suppresses all warnings and errors
102
+ #
103
+ # @return [void]
104
+ def suppress! = @suppress_warnings = true
105
+
106
+ # Enables warnings and errors
107
+ #
108
+ # @return [void]
109
+ def enable! = @suppress_warnings = false
110
+
111
+ # Checks if warnings are currently suppressed
112
+ #
113
+ # @return [Boolean]
114
+ def suppress_warnings? = @suppress_warnings
115
+
116
+ private
117
+
118
+ # Internal log method used by all levels
119
+ #
120
+ # @param level [Symbol] log level
121
+ # @param message [String]
122
+ # @param styles [Array<Symbol>, nil]
123
+ # @return [void]
124
+ def log(level, message, styles)
125
+ return if @suppress_warnings
126
+
127
+ timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
128
+ tag = LEVEL_TAGS[level] || level.to_s.upcase
129
+ prefix = "[#{timestamp}] #{tag}: "
130
+
131
+ full = prefix + message.to_s
132
+ styled = apply_styles(full, styles || DEFAULT_STYLES[level])
133
+
134
+ @mutex.synchronize { $stderr.puts(styled) }
135
+ end
136
+
137
+ # Applies ANSI styles to a message
138
+ #
139
+ # @param message [String]
140
+ # @param styles [Array<Symbol>]
141
+ # @return [String]
142
+ def apply_styles(message, styles)
143
+ return message unless styles && message.respond_to?(:dup)
144
+
145
+ styles.reduce(message.dup) do |msg, style|
146
+ msg.respond_to?(style) ? msg.public_send(style) : msg
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kolor
4
+ VERSION = '1.0.0'
5
+ end
data/lib/kolor.rb ADDED
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+ # @!parse
3
+ # class String
4
+ # include Kolor
5
+ # end
6
+
7
+
8
+ # noinspection RubyResolve
9
+ require 'win32/console/ansi' if Gem.win_platform?
10
+ require_relative 'kolor/internal/version'
11
+ require_relative 'kolor/internal/enum'
12
+ require_relative 'kolor/enum/foreground'
13
+ require_relative 'kolor/enum/background'
14
+ require_relative 'kolor/enum/style'
15
+
16
+
17
+ # Kolor provides terminal text styling using ANSI escape codes.
18
+ #
19
+ # Available colors (foreground and background):
20
+ # black, red, green, yellow, blue, magenta, cyan, white
21
+ #
22
+ # Available styles:
23
+ # bold, underline, reversed
24
+ #
25
+ # @example Basic usage
26
+ # "this is red".red
27
+ # "this is red with a blue background".red.on_blue
28
+ # "this is red with an underline".red.underline
29
+ # "this is really bold and really blue".bold.blue
30
+ #
31
+ # @example Chaining styles
32
+ # "complex styling".red.on_white.bold.underline
33
+ #
34
+ # @example All color methods
35
+ # string.black / string.on_black
36
+ # string.red / string.on_red
37
+ # string.green / string.on_green
38
+ # string.yellow / string.on_yellow
39
+ # string.blue / string.on_blue
40
+ # string.magenta / string.on_magenta
41
+ # string.cyan / string.on_cyan
42
+ # string.white / string.on_white
43
+ #
44
+ # @example Disabling colors (for CI/CD environments)
45
+ # Kolor.disable!
46
+ # "text".red # => "text" (no ANSI codes)
47
+ # Kolor.enable!
48
+ #
49
+ # @example Stripping ANSI codes
50
+ # Kolor.strip("text".red.bold) # => "text"
51
+ module Kolor
52
+ # Regex to match ANSI escape codes
53
+ ANSI_REGEX = /\e\[[\d;]*m/.freeze
54
+
55
+ class << self
56
+ # Check if colorization is enabled
57
+ # @return [Boolean]
58
+ attr_reader :enabled
59
+ alias enabled? enabled
60
+
61
+ # Returns list of available colors
62
+ # @return [Array<Symbol>] sorted list of color names
63
+ def colors
64
+ @colors ||= Kolor::Enum::Foreground.keys.sort
65
+ end
66
+
67
+ # Enables colorization (default state)
68
+ # @return [Boolean] true
69
+ def enable!
70
+ @enabled = true
71
+ end
72
+
73
+ # Disables colorization (useful for CI/CD, logging to files, etc.)
74
+ # @return [Boolean] false
75
+ def disable!
76
+ @enabled = false
77
+ end
78
+
79
+ # Strips all ANSI escape codes from a string
80
+ # @param string [String] string with ANSI codes
81
+ # @return [String] clean string without ANSI codes
82
+ def strip(string)
83
+ result = string.to_s
84
+ result.gsub(ANSI_REGEX, '')
85
+ end
86
+
87
+ # Generates ANSI escape code for a style enum
88
+ # @param style_name [Symbol] symbolic name of the style (e.g. :bold, :underline)
89
+ # @return [String] ANSI escape code (e.g. "\e[1m") or empty string if disabled or unknown
90
+ def style_code(style_name)
91
+ return '' unless @enabled
92
+
93
+ style = Kolor::Enum::Style[style_name]
94
+ style ? "\e[#{style.value}m" : ''
95
+ end
96
+
97
+ # Generates ANSI escape code for a foreground color
98
+ # @param color_name [Symbol] name of the foreground color
99
+ # @return [String] ANSI escape code or empty string if disabled or unknown
100
+ def foreground_code(color_name)
101
+ return '' unless @enabled
102
+
103
+ color = Kolor::Enum::Foreground[color_name]
104
+ color ? "\e[#{color.value}m" : ''
105
+ end
106
+
107
+ # Generates ANSI escape code for a background color
108
+ # @param color_name [Symbol] name of the background color
109
+ # @return [String] ANSI escape code or empty string if disabled or unknown
110
+ def background_code(color_name)
111
+ return '' unless @enabled
112
+
113
+ color = Kolor::Enum::Background[color_name]
114
+ color ? "\e[#{color.value}m" : ''
115
+ end
116
+
117
+
118
+ # Clears all ANSI formatting
119
+ # @return [String] ANSI clear code or empty string if disabled
120
+ def clear_code
121
+ @enabled ? "\e[0m" : ''
122
+ end
123
+ end
124
+
125
+ # Enable by default but disable if NO_COLOR env var is set
126
+ @enabled = !ENV['NO_COLOR'] && !ENV['NO_COLORS']
127
+
128
+ # Wrapper class to enable method chaining with ANSI codes
129
+ class ColorizedString
130
+ attr_reader :string, :codes
131
+
132
+ def initialize(string, codes = [])
133
+ @string = string
134
+ @codes = codes
135
+ end
136
+
137
+ # Returns the fully colorized string
138
+ # @return [String] string with all ANSI codes applied
139
+ def to_s
140
+ return @string unless Kolor.enabled?
141
+
142
+ "#{@codes.join}#{@string}#{Kolor.clear_code}"
143
+ end
144
+
145
+ # Allow implicit string conversion
146
+ alias to_str to_s
147
+
148
+ # Clears all formatting and returns plain string
149
+ # @return [String] plain string without ANSI codes
150
+ def clear
151
+ @string
152
+ end
153
+
154
+ # Adds a new ANSI code to the chain
155
+ # @param code [String] ANSI escape code to add
156
+ # @return [ColorizedString] new ColorizedString with the added code
157
+ def add_code(code)
158
+ return self unless Kolor.enabled? && code
159
+
160
+ ColorizedString.new(@string, @codes + [code])
161
+ end
162
+
163
+ # Delegate string methods to the underlying string
164
+ def method_missing(method_name, *args, &block)
165
+ if @string.respond_to?(method_name)
166
+ result = @string.public_send(method_name, *args, &block)
167
+ result.is_a?(String) ? self.class.new(result, @codes) : result
168
+ else
169
+ super
170
+ end
171
+ end
172
+
173
+ def respond_to_missing?(method_name, include_private = false)
174
+ @string.respond_to?(method_name, include_private) || super
175
+ end
176
+
177
+ # Include Kolor module to get all color/style methods
178
+ include Kolor
179
+
180
+ private
181
+
182
+ # Override to use add_code for ColorizedString
183
+ def create_colorized_string(code)
184
+ add_code(code)
185
+ end
186
+ end
187
+
188
+ # Clears the line to the end (useful for dynamic terminal output)
189
+ # @return [String] string with clear-to-end-of-line code
190
+ def to_eol
191
+ return to_s unless Kolor.enabled?
192
+
193
+ str = to_s
194
+ modified = str.sub(/^(\e\[[\d;]*m)/, "\\1\e[0K")
195
+ modified == str ? "\e[0K#{str}" : modified
196
+ end
197
+
198
+ # Clears all ANSI formatting from the string
199
+ # @return [String] plain string without ANSI codes
200
+ def uncolorize
201
+ Kolor.strip(to_s)
202
+ end
203
+
204
+ alias decolorize uncolorize
205
+
206
+ private
207
+
208
+ # Creates a ColorizedString with the given ANSI code
209
+ # @param code [String] ANSI escape code to apply
210
+ # @return [String, ColorizedString] colorized string or self if disabled
211
+ def create_colorized_string(code)
212
+ return to_s unless Kolor.enabled?
213
+
214
+ if is_a?(ColorizedString)
215
+ add_code(code)
216
+ else
217
+ ColorizedString.new(self, [code])
218
+ end
219
+ end
220
+
221
+ public
222
+ # Generate foreground color methods
223
+ Kolor::Enum::Foreground.keys.each do |color|
224
+ define_method(color) do
225
+ create_colorized_string(Kolor.foreground_code(color))
226
+ end
227
+ end
228
+
229
+ # Generate background color methods (on_*)
230
+ Kolor::Enum::Background.keys.each do |color|
231
+ define_method("on_#{color}") do
232
+ create_colorized_string(Kolor.background_code(color))
233
+ end
234
+ end
235
+
236
+ # Generate style methods
237
+ Kolor::Enum::Style.keys.each do |style|
238
+ next if style == :clear
239
+
240
+ define_method(style) do
241
+ create_colorized_string(Kolor.style_code(style))
242
+ end
243
+ end
244
+ end
245
+
246
+ # Extend String class with Kolor methods
247
+ String.include(Kolor)
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kolor
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Łasačka
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '13.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '13.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.12'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.12'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rubocop
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.50'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.50'
54
+ description: Ruby library for terminal text styling using ANSI escape codes. Supports
55
+ basic colors, 256-color palette, RGB/true colors, gradients, themes, and CLI.
56
+ email:
57
+ - saikinmirai@gmail.com
58
+ executables:
59
+ - kolor
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE
64
+ - bin/kolor
65
+ - lib/kolor.rb
66
+ - lib/kolor/cli.rb
67
+ - lib/kolor/enum/background.rb
68
+ - lib/kolor/enum/foreground.rb
69
+ - lib/kolor/enum/style.rb
70
+ - lib/kolor/enum/theme.rb
71
+ - lib/kolor/extra.rb
72
+ - lib/kolor/internal/config.rb
73
+ - lib/kolor/internal/enum.rb
74
+ - lib/kolor/internal/logger.rb
75
+ - lib/kolor/internal/version.rb
76
+ homepage: https://github.com/lasaczka/kolor
77
+ licenses:
78
+ - BSD-3-Clause-Attribution
79
+ metadata:
80
+ homepage_uri: https://github.com/lasaczka/kolor
81
+ source_code_uri: https://github.com/lasaczka/kolor
82
+ changelog_uri: https://github.com/lasaczka/kolor/blob/main/CHANGELOG.md
83
+ bug_tracker_uri: https://github.com/lasaczka/kolor/issues
84
+ documentation_uri: https://rubydoc.info/gems/kolor/1.0.0
85
+ kolor:extra_status: experimental
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 3.0.0
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.7.2
101
+ specification_version: 4
102
+ summary: Modern terminal text styling with ANSI codes
103
+ test_files: []