hansi 0.1.0 → 0.1.1

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
  SHA1:
3
- metadata.gz: a461aa8d4598e475054d9705667d8e88c810e361
4
- data.tar.gz: 7eac7bb43dd1eb6b66d160d0564b1ecdb5d9fe27
3
+ metadata.gz: e1c99a2ac8ce470c64e37c42a2e94af03d80a0fe
4
+ data.tar.gz: 8c8e39771a0a0c2fab16b746d937643655b3cdd7
5
5
  SHA512:
6
- metadata.gz: 5fc500637d6eb4d2ca9618f86821b9f3f4dc37cfdb755c2650818c02966b71efc0d9ac0de7dbf099d42d36fbb1695e952f5368d64d9ce90a04e2d813e248debb
7
- data.tar.gz: 16ad2067360b221d219e6c13c0408b79e7c77c5bdedb24f8775184958a8c84bcddb884a4a115d57fa0f986ba7d7c0f85eadf49726505bfebd46a96216bfa395f
6
+ metadata.gz: a394c636b3fd0e8d4456377ad2b2c79223374612f9c71cf3a4f24b5df325bca79c01ebd1a9f0057e3155ef428af796f7d70532eb52d7796a2ba1d185347c8cd9
7
+ data.tar.gz: 64b7011cbd5a09b5aec138544197f86de4a703f773dcaba9c1c06ba99e6d55b1e54830fd54a0581a67aa039fb739f79f5fe85b801223ecafafcee11386619f4a
@@ -0,0 +1,2 @@
1
+ .coverage
2
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ -r bundler/setup
2
+ -r support
3
+ --color
4
+ --tty
@@ -0,0 +1,2 @@
1
+ rvm: [2.0.0, 2.1.5]
2
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Konstantin Haase
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,230 @@
1
+ # Der ANSI Hansi
2
+
3
+ Welcome to *The ANSI Hansi*. This is your *Hipster ANSI color library*.
4
+
5
+ It allows you to produce colorized console output.
6
+
7
+ Many ANSI color libraries are already out there. However, this library tackles a few issues most of them have:
8
+
9
+ * It supports **8 color**, **16 color**, **88 color**, **256 color** and **24 bit (True Color)** mode.
10
+ * It transparently **converts colors between modes** without you having to worry about this. It uses the YUV distance (rather than the RGB distance) to closer match how our brain perceives color similarity.
11
+ * It supports **proper color nesting**.
12
+ * It can **automatically detect** how many colors the current terminal supports.
13
+ * It does **not enforce a DSL** to be used (in fact, it does not include a DSL, but it makes it really easy to build your own DSL).
14
+ * Makes it easy to define **string templates** for highlighting, but doesn't force you to use them.
15
+ * It supports **themes**, but doesn't force you to use them. Themes make semantic coloring easier.
16
+ * It supports converting **ANSI colors to CSS rules**. It does not support parsing an ANSI colored string into HTML, this would be outside the scope of this library.
17
+ * It has zero dependencies and does not include a native extension.
18
+ * It does not monkey patch anything.
19
+
20
+ ## General Usage
21
+
22
+ The main API entry points are `Hansi.render(input)` , which will generate a colorized string, depending on the input, and `Hansi[input]`, which will give you a color object. You can use Hansi for your purposes by just using one of the two, but they give you different levels of access.
23
+
24
+ The following code:
25
+
26
+ ``` ruby
27
+ require 'hansi'
28
+
29
+ # simple usage
30
+ puts Hansi.render(:red, "WARNING!!")
31
+ puts Hansi.render("Hello *world*!", "*" => :bold)
32
+
33
+ # generate some colors
34
+ steps = (0..255).step(15)
35
+ render = -> options { print Hansi.render(Hansi[options], ':') }
36
+
37
+ steps.each do |red|
38
+ steps.each { |green| render[ red: red, green: green ]}
39
+ steps.each { |blue| render[ red: red, green: 255 - blue, blue: blue]}
40
+ steps.each { |blue| render[ red: red, blue: 255 - blue ]}
41
+ puts
42
+ end
43
+ ```
44
+
45
+ Will result in output similar to this screenshot (if your terminal supports true color):
46
+
47
+ ![](hansi.png)
48
+
49
+ ### Rendering Strings
50
+
51
+ You can render a string in a given color:
52
+
53
+ ``` ruby
54
+ puts Hansi.render(:red, "this is red")
55
+
56
+ red = Hansi["#f00"]
57
+ puts Hansi.render(red, "this is red")
58
+ ```
59
+
60
+ You can render an s-expression stile nested array (good for building your own DSL on top):
61
+
62
+ ``` ruby
63
+ sexp = [:red, "Hello", [:yellow, ENV["USER"]], "- how are you?"]
64
+ puts Hansi.render(sexp, join: " ")
65
+ ```
66
+
67
+ It is also possible to use template strings. These can use simple markup (anything enclosed between certain characters), or HTML style tags, or a combination of the two.
68
+
69
+ ``` ruby
70
+ # a simple template
71
+ puts Hansi.render("foo *bar* _blah_", "*" => :red, "_" => :green)
72
+
73
+ # escaping a character
74
+ puts Hansi.render('foo *bar* _blah\_blah_', "*" => :red, "_" => :green)
75
+
76
+ # using tags, with interpolation
77
+ puts Hansi.render("<gold>Hello <underline>%s</underline>!</gold>", ENV['USER'], tags: true)
78
+ ```
79
+
80
+ You can also use `render` to turn a color object into its ANIS code.
81
+
82
+ The `render` method takes a `mode` option to enforce a color mode.
83
+
84
+ ``` ruby
85
+ color = Hansi["#f80"]
86
+ Hansi.render(color, mode: 256) # => "\e[38;5;208m"
87
+ Hansi.render(color, mode: 16) # => "\e[33m"
88
+ ```
89
+
90
+ ### Themes
91
+
92
+ The render method takes a `theme` option. This option can have one of the following values:
93
+
94
+ * An instance of `Hansi::Theme`.
95
+ * A symbol for a predefined theme (currently `:default` or `:solarized`).
96
+ * A hash mapping rules (symbols) to other rules or color values.
97
+ * An array of any of the above.
98
+
99
+ ``` ruby
100
+
101
+ my_theme = Hansi::Theme.new(foo: :yellow)
102
+
103
+ puts Hansi.render("<foo>hi</foo>", tags: true, theme: my_theme) # bright yellow
104
+ puts Hansi.render("<foo>hi</foo>", tags: true, theme: { foo: :yellow }) # bright yellow
105
+ puts Hansi.render("<foo>hi</foo>", tags: true, theme: [:solarized, my_theme]) # solarized yellow
106
+ puts Hansi.render("<yellow>hi</yellow>", tags: true, theme: :solarized) # solarized yellow
107
+ ```
108
+
109
+ When creating a theme, you can also pass in other themes to inherit rules from:
110
+
111
+ ``` ruby
112
+ my_theme = Hansi::Theme.new(:solarized, em: :base0, b: :base1)
113
+ puts Hansi.render("This <em>is</em> <b>important</b>!", theme: my_theme)
114
+ ```
115
+
116
+ It is also possible to register your theme globally:
117
+
118
+ ``` ruby
119
+ Hansi::Theme[:my_theme] = Hansi::Theme.new(:solarized, em: :base0, b: :base1)
120
+ puts Hansi.render("This <em>is</em> <b>important</b>!", theme: :my_theme)
121
+ ```
122
+
123
+ ### Color Objects
124
+
125
+ You can get access to a color object via `Hansi[]` or `Hansi::Theme#[]`.
126
+
127
+ ``` ruby
128
+ Hansi[:yellow].red # => 255
129
+ Hansi::Theme[:solarized][:yellow].red # => 181
130
+
131
+ Hansi["#ff8300"].to_ansi(mode: 256) # => "\e[38;5;208m"
132
+ Hansi["#ff8300"].to_web_name # => :darkorange
133
+ Hansi["#ff8300"].to_web_name(exact: true) # => nil
134
+ ```
135
+
136
+ You can also use a color object to find the closest color in a set of colors:
137
+
138
+ ``` ruby
139
+ colors = [
140
+ Hansi[:red],
141
+ Hansi[:green],
142
+ Hansi[:blue],
143
+ Hansi[:orange]
144
+ ]
145
+
146
+ Hansi[:yellow].closest(colors) # => Hansi[:orange]
147
+ ```
148
+
149
+ ## Advanced Usage
150
+
151
+ ### Enforcing the default color mode
152
+
153
+ You can override the color mode Hansi has detected for the current terminal:
154
+
155
+ ``` ruby
156
+ puts "Detected colors: %d" % Hansi.mode
157
+ Hansi.mode = Hansi::TRUE_COLOR
158
+ ```
159
+
160
+ ### Create your own DSL
161
+
162
+ Rather than defining a DSL, Hansi aims to be easily integrated with whatever tooling you use for building command line applications.
163
+
164
+ Combining for instance the s-expression style rendering with `Hansi.color_names` makes creaking a method name based DSL straight forward:
165
+
166
+ ``` ruby
167
+ module ColorDSL
168
+ def color(*args)
169
+ Struct.new(:to_ary, :to_s).new(args, Hansi.render(args))
170
+ end
171
+
172
+ Hansi.color_names.each do |name|
173
+ define_method(name) { |*args| color(name, *args) }
174
+ end
175
+ end
176
+
177
+ extend ColorDSL
178
+ puts "Hello #{red("w", green("o", blue("r"), "l"), "d")}!"
179
+ ```
180
+
181
+ ### Generating CSS
182
+
183
+ Hansi does not turn ANSI escape codes into HTML for you, this would be outside of the scope for this project. However, depending on your use case, you might be able to generate semantic HTML yourself from whichever data structure you use.
184
+
185
+ In this case, Hansi can generate CSS rules for you.
186
+
187
+ ``` ruby
188
+ Hansi[:red].to_css_rule # => "color: #ff0000;"
189
+ ```
190
+
191
+ It can also generate a full stylesheet for a theme:
192
+
193
+ ``` ruby
194
+ my_theme = Hansi::Theme.new(headline: :green, text: :springgreen, aside: :text, important: :bold)
195
+ puts my_theme.to_css
196
+ ```
197
+
198
+ ``` css
199
+ .headline {
200
+ color: #008000;
201
+ }
202
+
203
+ .text, .aside {
204
+ color: #00ff7f;
205
+ }
206
+
207
+ .important {
208
+ font-weight: bold;
209
+ }
210
+ ```
211
+
212
+ You can pass a block for generating the css selector for a given rule name:
213
+
214
+ ``` ruby
215
+ puts my_theme.to_css { |name| ".hansi .#{name}" }
216
+ ```
217
+
218
+ ``` css
219
+ .hansi .headline {
220
+ color: #008000;
221
+ }
222
+
223
+ .hansi .text, .hansi .aside {
224
+ color: #00ff7f;
225
+ }
226
+
227
+ .hansi .important {
228
+ font-weight: bold;
229
+ }
230
+ ```
@@ -0,0 +1,22 @@
1
+ $:.unshift File.expand_path("../lib", __FILE__)
2
+ require "hansi/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "hansi"
6
+ s.version = Hansi::VERSION
7
+ s.author = "Konstantin Haase"
8
+ s.email = "konstantin.mailinglists@googlemail.com"
9
+ s.homepage = "https://github.com/rkh/hansi"
10
+ s.summary = %q{Hipster ANSI color library}
11
+ s.description = %q{Der ANSI Hansi - create colorized console output.}
12
+ s.license = 'MIT'
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.required_ruby_version = '>= 2.0.0'
17
+
18
+ s.add_development_dependency 'tool', '~> 0.2'
19
+ s.add_development_dependency 'rspec'
20
+ s.add_development_dependency 'simplecov'
21
+ s.add_development_dependency 'coveralls'
22
+ end
Binary file
@@ -0,0 +1,53 @@
1
+ module Hansi
2
+ TRUE_COLOR = 256**3
3
+
4
+ def self.[](*args)
5
+ ColorParser.parse(*args)
6
+ end
7
+
8
+ def self.mode
9
+ @mode ||= mode_for(ENV)
10
+ end
11
+
12
+ def self.mode=(value)
13
+ @mode = value
14
+ end
15
+
16
+ def self.mode_for(env, **options)
17
+ ModeDetector.new(env, **options).mode
18
+ end
19
+
20
+ def self.render(*input, **options)
21
+ renderer_for(input.first).render(*input, **options)
22
+ end
23
+
24
+ def self.renderer_for(input)
25
+ case input
26
+ when String then StringRenderer
27
+ when Symbol, Array then SexpRenderer
28
+ when AnsiCode then ColorRenderer
29
+ else raise ArgumentError, "don't know how to render %p" % input
30
+ end
31
+ end
32
+
33
+ def self.reset
34
+ Hansi[:reset].to_ansi
35
+ end
36
+
37
+ def self.color_names
38
+ PALETTES['web'].keys
39
+ end
40
+
41
+ require 'hansi/ansi_code'
42
+ require 'hansi/color'
43
+ require 'hansi/special'
44
+
45
+ require 'hansi/color_parser'
46
+ require 'hansi/color_renderer'
47
+ require 'hansi/mode_detector'
48
+ require 'hansi/palettes'
49
+ require 'hansi/sexp_renderer'
50
+ require 'hansi/string_renderer'
51
+ require 'hansi/theme'
52
+ require 'hansi/themes'
53
+ end
@@ -0,0 +1,16 @@
1
+ module Hansi
2
+ class AnsiCode
3
+ def to_ansi_code(**options)
4
+ end
5
+
6
+ def to_css_rule
7
+ "/* cannot convert #{inspect} to css */"
8
+ end
9
+
10
+ def to_css(*names, &block)
11
+ block ||= -> key { ".#{key}" }
12
+ name = names.map(&block).join(', ')
13
+ "#{name} {\n #{to_css_rule.gsub(/;\n?\s+(\S)/, ";\n \\1")}\n}\n"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,115 @@
1
+ module Hansi
2
+ class Color < AnsiCode
3
+ attr_reader :red, :green, :blue, :distance_cache
4
+ protected :distance_cache
5
+
6
+ def initialize(red, green, blue)
7
+ @red, @green, @blue = red, green, blue
8
+ @distance_cache = {}
9
+ end
10
+
11
+ def hash
12
+ to_i.hash
13
+ end
14
+
15
+ def ==(other)
16
+ other.class == self.class and other.to_i == self.to_i
17
+ end
18
+
19
+ def eql?(other)
20
+ other.class.eql?(self.class) and other.to_i.eql?(self.to_i)
21
+ end
22
+
23
+ def distance(other)
24
+ distance_cache[other.to_i] ||= other.distance_cache[to_i] || begin
25
+ y1, u1, v1 = to_yuv
26
+ y2, u2, v2 = other.to_yuv
27
+ dy, du, dv = y1 - y2, u1 - u2, v1 - v2
28
+ Math.sqrt(dy**2 + du**2 + dv**2)
29
+ end
30
+ end
31
+
32
+ def closest(set)
33
+ if set.respond_to? :to_hash
34
+ hash = set.to_hash
35
+ hash.key(closest(hash.values))
36
+ elsif set.include? self
37
+ self
38
+ else
39
+ set.grep(Color).min_by { |c| distance(c) }
40
+ end
41
+ end
42
+
43
+ def to_yuv
44
+ @yuv ||= begin
45
+ y = (0.257 * red) + (0.504 * green) + (0.098 * blue) + 16
46
+ u = -(0.148 * red) - (0.291 * green) + (0.439 * blue) + 128
47
+ v = (0.439 * red) - (0.368 * green) - (0.071 * blue) + 128
48
+ [y, u, v].freeze
49
+ end
50
+ end
51
+
52
+ def to_i
53
+ (red << 16) + (green << 8) + blue
54
+ end
55
+
56
+ def inspect
57
+ "#<%p:%d,%d,%d>" % [self.class, red, green, blue]
58
+ end
59
+
60
+ def to_s
61
+ "#%06x" % to_i
62
+ end
63
+
64
+ def to_css_rule
65
+ "color: #{self};"
66
+ end
67
+
68
+ def to_ansi(mode: Hansi.mode, **options)
69
+ case Integer === mode ? mode : Integer(mode.to_s[/\d+/])
70
+ when 256 then to_ansi_256colors(**options)
71
+ when 24, TRUE_COLOR then to_ansi_24bit(**options)
72
+ when 88 then to_ansi_88colors(**options)
73
+ when 16 then to_ansi_16colors(**options)
74
+ when 8 then to_ansi_8colors(**options)
75
+ when 1, 0 then ""
76
+ else raise ArgumentError, "unknown mode %p" % mode
77
+ end
78
+ end
79
+
80
+ def to_ansi_24bit(**options)
81
+ "\e[38;2;%d;%d;%dm" % [red, green, blue]
82
+ end
83
+
84
+ def to_ansi_256colors(**options)
85
+ from_palette('xterm-256color', 'xterm', **options)
86
+ end
87
+
88
+ def to_ansi_88colors(**options)
89
+ from_palette('xterm-88color', 'xterm', **options)
90
+ end
91
+
92
+ def to_ansi_16colors(**options)
93
+ from_palette('xterm', **options)
94
+ end
95
+
96
+ def to_ansi_8colors(**options)
97
+ from_palette('ansi', **options)
98
+ end
99
+
100
+ def to_web_name(**options)
101
+ from_palette('web', **options)
102
+ end
103
+
104
+ def from_palette(main, *fallback, exact: false)
105
+ @from_palette ||= { true => {}, false => {} }
106
+ cached = @from_palette[exact]
107
+ cached[main] ||= PALETTES[main].key(self)
108
+ cached[main] ||= closest(PALETTES[main]) unless exact
109
+ cached[main] ||= from_palette(*fallback, exact: exact) if fallback.any?
110
+ cached[main]
111
+ end
112
+
113
+ private :from_palette
114
+ end
115
+ end