ansi_chameleon 0.0.1

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.
data/Changelog ADDED
@@ -0,0 +1,2 @@
1
+ 0.0.1 (January 30, 2012)
2
+ Initial release.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyrigth (c) 2012 Jacek Mikrut
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ AnsiChameleon
2
+ =============
3
+
4
+ AnsiChameleon is a Ruby Gem that **colorizes text terminal output** by converting custom HTML-like tags into color ANSI escape sequences.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ As a Ruby Gem, AnsiChameleon can be installed either by running
10
+
11
+ ```bash
12
+ gem install ansi_chameleon
13
+ ```
14
+
15
+ or adding
16
+
17
+ ```ruby
18
+ gem "ansi_chameleon"
19
+ ```
20
+
21
+ to the Gemfile and then invoking `bundle install`.
22
+
23
+ Usage
24
+ -----
25
+
26
+ For **single use**:
27
+
28
+ ```ruby
29
+ require "ansi_chameleon"
30
+
31
+ puts AnsiChameleon.render(
32
+ "There are <number>3</number> oranges on the table.",
33
+ :number => { :fg_color => :blue }
34
+ )
35
+ ```
36
+
37
+ For **multiple use** this is more efficient:
38
+
39
+ ```ruby
40
+ require "ansi_chameleon"
41
+
42
+ text_renderer = AnsiChameleon::TextRenderer.new(:number => { :fg_color => :blue })
43
+
44
+ puts text_renderer.render("There are <number>2</number> oranges on the table.")
45
+ puts text_renderer.render("Now, there is only <number>1</number> orange.")
46
+ ```
47
+
48
+ Details
49
+ -------
50
+
51
+ In order to produce a colorized output AnsiChameleon needs a **text** and a **style sheet**.
52
+
53
+ ### Text
54
+
55
+ Text to colorize is expected to include HTML-like tags of custom names, that will then be converted according to the rules defined in provided style sheet.
56
+
57
+ Tags not included in the style sheet but found in the text being rendered are put to the output without modification.
58
+
59
+ ### Style sheet
60
+
61
+ A style sheet is simply a CSS-like Hash structure with keys that correspond to custom tag names and property names:
62
+
63
+ ```ruby
64
+ style_sheet = {
65
+ # default style declarations (for text not wrapped in tags)
66
+ :fg_color => :green,
67
+ :bg_color => :black,
68
+
69
+ :number => {
70
+ # style declarations for <number> tag
71
+ :fg_color => :blue,
72
+ },
73
+ :message => {
74
+ # style declarations for <message> tag
75
+ :fg_color => :red,
76
+ :bg_color => :white,
77
+
78
+ :number => {
79
+ # style declarations for <number> tag nested in <message> tag
80
+ :fg_color => :inherit
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ Tag names in a style sheet can be written as either :symbols or "strings".
87
+
88
+ #### Properties
89
+
90
+ * `:foreground_color` (aliases: `:foreground`, `:fg_color`)
91
+ * `:background_color` (aliases: `:background`, `:bg_color`)
92
+
93
+ Available color values: `:black`, `:red`, `:green`, `:yellow`, `:blue`, `:magenta`, `:cyan`, `:white`.
94
+
95
+ * `:effect`
96
+
97
+ Available effect values: `:none`, `:bright`, `:underline`, `:blink`, `:reverse`.
98
+
99
+ Also, the `:inherit` value can be set for a property, which means that its value will be inherited from the surrounding tag (or be the default value).
100
+
101
+ Similarly to tag names, property names and values can be provided as :symbols or "strings".
102
+
103
+ License
104
+ -------
105
+
106
+ License is included in the LICENSE file.
@@ -0,0 +1,14 @@
1
+ require "ansi_chameleon/version"
2
+ require "ansi_chameleon/array_utils"
3
+ require "ansi_chameleon/sequence_generator"
4
+ require "ansi_chameleon/style_property_name_translator"
5
+ require "ansi_chameleon/style_sheet_handler"
6
+ require "ansi_chameleon/text_rendering"
7
+ require "ansi_chameleon/text_renderer"
8
+
9
+ module AnsiChameleon
10
+
11
+ def self.render(text, style_sheet)
12
+ TextRenderer.new(style_sheet).render(text)
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module AnsiChameleon
2
+ module ArrayUtils
3
+
4
+ def self.item_spread_map(array, items)
5
+ remaining_items = items.dup
6
+
7
+ map = array.inject('') do |agg, array_item|
8
+ if array_item == remaining_items.first
9
+ agg << '1'
10
+ remaining_items.shift
11
+ else
12
+ agg << '0'
13
+ end
14
+ agg
15
+ end
16
+
17
+ remaining_items.any? ? nil : map
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ module AnsiChameleon
2
+ module SequenceGenerator
3
+
4
+ class UnknownColorName < ArgumentError; end
5
+ class UnknownEffectName < ArgumentError; end
6
+
7
+ COLORS = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
8
+ EFFECTS = { :none => 0, :bright => 1, :underline => 4, :blink => 5, :reverse => 7 }
9
+
10
+ def self.effect_code(name)
11
+ EFFECTS[name.to_sym] or raise UnknownEffectName.new("Unknown effect name #{name.inspect}")
12
+ end
13
+
14
+ def self.foreground_color_code(name)
15
+ index = COLORS.index(name.to_sym) or raise UnknownColorName.new("Unknown foreground color name #{name.inspect}")
16
+ 30 + index
17
+ end
18
+
19
+ def self.background_color_code(name)
20
+ index = COLORS.index(name.to_sym) or raise UnknownColorName.new("Unknown background color name #{name.inspect}")
21
+ 40 + index
22
+ end
23
+
24
+ def self.generate(effect_name, foreground_color_name=nil, background_color_name=nil)
25
+ if effect_name.to_sym == :reset
26
+ "\033[0m"
27
+ else
28
+ "\033[#{effect_code(effect_name)};#{foreground_color_code(foreground_color_name)};#{background_color_code(background_color_name)}m"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module AnsiChameleon
2
+ module StylePropertyNameTranslator
3
+
4
+ def self.translate(property_name)
5
+ case property_name
6
+
7
+ when :background_color, :background, :bg_color
8
+ :background_color_name
9
+
10
+ when :foreground_color, :foreground, :fg_color
11
+ :foreground_color_name
12
+
13
+ when :effect
14
+ :effect_name
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ module AnsiChameleon
2
+ class StyleSheetHandler
3
+
4
+ def initialize(style_sheet_hash, property_name_translator=nil)
5
+ @tag_names = []
6
+ @default_values = {}
7
+ @map = {}
8
+ @property_name_translator = property_name_translator
9
+ populate_map(style_sheet_hash)
10
+ end
11
+
12
+ attr_reader :tag_names, :default_values, :map, :property_name_translator
13
+
14
+ def value_for(*tag_names_chain, property_name)
15
+ strongest = strongest_style_definition(*(tag_names_chain.map { |tag_name| tag_name.to_sym }), property_name.to_sym)
16
+ strongest && strongest[:value]
17
+ end
18
+
19
+ private
20
+
21
+ def populate_map(style_definition_hash, tag_names_chain=[])
22
+ style_definition_hash.each do |key, value|
23
+
24
+ case value
25
+ when Hash
26
+ tag_name = key.to_sym
27
+
28
+ @tag_names << tag_name unless @tag_names.include?(tag_name)
29
+ populate_map(value, tag_names_chain + [tag_name])
30
+
31
+ else
32
+ property_name = property_name_translator ? property_name_translator.translate(key) : key.to_sym
33
+
34
+ @map[property_name] ||= []
35
+ @map[property_name] << { :value => value, :tag_names_chain => tag_names_chain }
36
+ @default_values[property_name] = value if tag_names_chain.none?
37
+
38
+ end
39
+ end
40
+ end
41
+
42
+ def strongest_style_definition(*tag_names_chain, property_name)
43
+ (@map[property_name] || [])
44
+ .map { |entry|
45
+ entry.merge(:item_spread_map => AnsiChameleon::ArrayUtils.item_spread_map(tag_names_chain, entry[:tag_names_chain])) }
46
+ .select { |entry|
47
+ not entry[:item_spread_map].nil? }
48
+ .compact
49
+ .max_by { |entry|
50
+ entry[:item_spread_map].reverse }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
1
+ module AnsiChameleon
2
+ class TextRenderer
3
+
4
+ def initialize(style_sheet)
5
+ @style_sheet_handler = StyleSheetHandler.new(style_sheet, StylePropertyNameTranslator)
6
+ end
7
+
8
+ def render(text)
9
+ text_rendering = TextRendering.new(@style_sheet_handler)
10
+
11
+ chunks(text).each do |chunk|
12
+ case
13
+ when opening_tag?(chunk)
14
+ text_rendering.push_opening_tag(tag_name(chunk))
15
+
16
+ when closing_tag?(chunk)
17
+ text_rendering.push_closing_tag(tag_name(chunk))
18
+
19
+ else
20
+ text_rendering.push_text(chunk)
21
+
22
+ end
23
+ end
24
+
25
+ text_rendering.to_s
26
+ end
27
+
28
+ private
29
+
30
+ def tag_names_regex
31
+ @tag_names_regex ||= @style_sheet_handler.tag_names.map { |tag_name| "(?<=<#{tag_name}>)|(?<=<\/#{tag_name}>)|(?=<\/?#{tag_name}>)" }.join('|')
32
+ end
33
+
34
+ def chunk_regex
35
+ @chunk_regex ||= /(?<= |\t)|(?= |\t)#{'|' + tag_names_regex unless tag_names_regex.empty?}/
36
+ end
37
+
38
+ def opening_tag_regex
39
+ @opening_tag_regex ||= /^<(?<=[^\/])(?:#{@style_sheet_handler.tag_names.join('|')})>$/
40
+ end
41
+
42
+ def closing_tag_regex
43
+ @closing_tag_regex ||= /^<\/#{@style_sheet_handler.tag_names.join('|')}>$/
44
+ end
45
+
46
+ def chunks(text)
47
+ text.split(chunk_regex)
48
+ end
49
+
50
+ def opening_tag?(chunk)
51
+ chunk =~ opening_tag_regex
52
+ end
53
+
54
+ def closing_tag?(chunk)
55
+ chunk =~ closing_tag_regex
56
+ end
57
+
58
+ def tag_name(chunk)
59
+ chunk.match(/^<\/?(?<tag_name>.*)>$/)[:tag_name].to_sym
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,76 @@
1
+ module AnsiChameleon
2
+ class TextRendering
3
+
4
+ DEFAULT_STYLE = {
5
+ :effect_name => :none,
6
+ :foreground_color_name => :white,
7
+ :background_color_name => :black
8
+ }
9
+
10
+ def initialize(style_sheet_handler)
11
+ @style_sheet_handler = style_sheet_handler
12
+ @stack = []
13
+ @current_style = DEFAULT_STYLE.merge(@style_sheet_handler.default_values)
14
+
15
+ @rendered_text = ''
16
+ @rendered_text << sequence_for(@current_style)
17
+ end
18
+
19
+ def push_opening_tag(tag_name)
20
+ @stack.push({ :tag_name => tag_name, :outer_style => @current_style })
21
+
22
+ @current_style = deduce_current_style
23
+ @rendered_text << sequence_for(@current_style)
24
+ end
25
+
26
+ def push_closing_tag(tag_name)
27
+ unless tag_names_chain.last == tag_name
28
+ raise SyntaxError.new("Encountered </#{tag_name}> tag that had not been opened yet")
29
+ end
30
+
31
+ @current_style = @stack.pop[:outer_style]
32
+ @rendered_text << sequence_for(@current_style)
33
+ end
34
+
35
+ def push_text(text)
36
+ @rendered_text << text
37
+ end
38
+
39
+ def to_s
40
+ if @stack.any?
41
+ tag_names = @stack.map { |data| "<#{data[:tag_name]}>" }.join(', ')
42
+ msg_prefix = @stack.size == 1 ? "Tag #{tag_names} has" : "Tags #{tag_names} have"
43
+ raise SyntaxError.new(msg_prefix + " been opened but not closed yet")
44
+ end
45
+
46
+ @rendered_text + AnsiChameleon::SequenceGenerator.generate(:reset)
47
+ end
48
+
49
+ private
50
+
51
+ def sequence_for(style)
52
+ AnsiChameleon::SequenceGenerator.generate(
53
+ style[:effect_name],
54
+ style[:foreground_color_name],
55
+ style[:background_color_name]
56
+ )
57
+ end
58
+
59
+ def tag_names_chain
60
+ @stack.map { |data| data[:tag_name] }
61
+ end
62
+
63
+ def property_names
64
+ DEFAULT_STYLE.keys
65
+ end
66
+
67
+ def deduce_current_style
68
+ property_names.inject({}) do |style, property_name|
69
+ property_value = @style_sheet_handler.value_for(*tag_names_chain, property_name)
70
+ style[property_name] = [:inherit, nil].include?(property_value) ? @current_style[property_name] : property_value
71
+ style
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module AnsiChameleon
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ansi_chameleon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jacek Mikrut
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-30 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &86851630 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *86851630
25
+ description: Colorizes text terminal output by converting custom HTML-like tags into
26
+ color ANSI escape sequences.
27
+ email: jacekmikrut.software@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/ansi_chameleon/array_utils.rb
33
+ - lib/ansi_chameleon/version.rb
34
+ - lib/ansi_chameleon/sequence_generator.rb
35
+ - lib/ansi_chameleon/text_rendering.rb
36
+ - lib/ansi_chameleon/style_property_name_translator.rb
37
+ - lib/ansi_chameleon/style_sheet_handler.rb
38
+ - lib/ansi_chameleon/text_renderer.rb
39
+ - lib/ansi_chameleon.rb
40
+ - README.md
41
+ - LICENSE
42
+ - Changelog
43
+ homepage: http://github.com/jacekmikrut/ansi_chameleon
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.12
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Text terminal output coloring tool.
67
+ test_files: []