styles 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +725 -0
- data/Rakefile +9 -0
- data/bin/styles +11 -0
- data/lib/styles.rb +18 -0
- data/lib/styles/application.rb +190 -0
- data/lib/styles/colors.rb +289 -0
- data/lib/styles/core_ext.rb +12 -0
- data/lib/styles/engine.rb +73 -0
- data/lib/styles/line.rb +55 -0
- data/lib/styles/properties.rb +34 -0
- data/lib/styles/properties/background_color.rb +15 -0
- data/lib/styles/properties/base.rb +68 -0
- data/lib/styles/properties/border.rb +147 -0
- data/lib/styles/properties/color.rb +16 -0
- data/lib/styles/properties/display.rb +10 -0
- data/lib/styles/properties/font_weight.rb +13 -0
- data/lib/styles/properties/function.rb +7 -0
- data/lib/styles/properties/margin.rb +83 -0
- data/lib/styles/properties/match_background_color.rb +28 -0
- data/lib/styles/properties/match_color.rb +21 -0
- data/lib/styles/properties/match_font_weight.rb +23 -0
- data/lib/styles/properties/match_text_decoration.rb +36 -0
- data/lib/styles/properties/padding.rb +81 -0
- data/lib/styles/properties/text_align.rb +10 -0
- data/lib/styles/properties/text_decoration.rb +20 -0
- data/lib/styles/properties/width.rb +11 -0
- data/lib/styles/rule.rb +67 -0
- data/lib/styles/stylesheet.rb +103 -0
- data/lib/styles/sub_engines.rb +4 -0
- data/lib/styles/sub_engines/base.rb +16 -0
- data/lib/styles/sub_engines/color.rb +115 -0
- data/lib/styles/sub_engines/layout.rb +158 -0
- data/lib/styles/sub_engines/pre_processor.rb +19 -0
- data/lib/styles/version.rb +3 -0
- data/styles.gemspec +26 -0
- data/test/application_test.rb +92 -0
- data/test/colors_test.rb +162 -0
- data/test/engine_test.rb +59 -0
- data/test/integration_test.rb +136 -0
- data/test/line_test.rb +24 -0
- data/test/properties/background_color_test.rb +36 -0
- data/test/properties/base_test.rb +11 -0
- data/test/properties/border_test.rb +154 -0
- data/test/properties/color_test.rb +28 -0
- data/test/properties/display_test.rb +26 -0
- data/test/properties/font_weight_test.rb +24 -0
- data/test/properties/function_test.rb +28 -0
- data/test/properties/margin_test.rb +98 -0
- data/test/properties/match_background_color_test.rb +71 -0
- data/test/properties/match_color_test.rb +79 -0
- data/test/properties/match_font_weight_test.rb +34 -0
- data/test/properties/match_text_decoration_test.rb +38 -0
- data/test/properties/padding_test.rb +87 -0
- data/test/properties/text_align_test.rb +107 -0
- data/test/properties/text_decoration_test.rb +25 -0
- data/test/properties/width_test.rb +41 -0
- data/test/rule_test.rb +39 -0
- data/test/stylesheet_test.rb +245 -0
- data/test/sub_engines/color_test.rb +144 -0
- data/test/sub_engines/layout_test.rb +110 -0
- data/test/test_helper.rb +5 -0
- metadata +184 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
[String, Regexp, Symbol].each do |klass|
|
3
|
+
klass.class_eval do
|
4
|
+
|
5
|
+
# Associates the receiver object with a hash of properties and adds the
|
6
|
+
# receiver and properties to the array of rules being parsed, in the global
|
7
|
+
# variable <tt>$current_stylesheet_rules</tt>.
|
8
|
+
def -(properties_hash)
|
9
|
+
$current_stylesheet_rules << [self, properties_hash]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
module Styles
|
4
|
+
|
5
|
+
# Takes one or more Stylesheets and applies the rules from them to lines of text.
|
6
|
+
class Engine
|
7
|
+
attr_reader :stylesheets
|
8
|
+
|
9
|
+
def initialize(*stylesheets)
|
10
|
+
@stylesheets = [stylesheets].flatten
|
11
|
+
end
|
12
|
+
|
13
|
+
# Process a line according to the rules that comprise all of the Stylesheets.
|
14
|
+
#
|
15
|
+
# For all the rules that are applicable to this line, find the last defined of each type of
|
16
|
+
# property and apply it.
|
17
|
+
#
|
18
|
+
# Returns nil if the line is hidden or otherwise should not be displayed.
|
19
|
+
def process(line)
|
20
|
+
applicable_rules = rules.find_all { |rule| rule.applicable?(line) }
|
21
|
+
|
22
|
+
simple_properties = {}
|
23
|
+
multiple_names_properties = {}
|
24
|
+
applicable_rules.each do |rule|
|
25
|
+
rule.properties.each do |property|
|
26
|
+
if property.class.multiple_names?
|
27
|
+
(multiple_names_properties[property.class.to_sym] ||= []) << property
|
28
|
+
else
|
29
|
+
simple_properties[property.class.to_sym] = property
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
properties = simple_properties.values
|
35
|
+
|
36
|
+
multiple_names_properties.keys.each do |basic_name|
|
37
|
+
props = multiple_names_properties[basic_name]
|
38
|
+
prop_class = props.first.class
|
39
|
+
properties << prop_class.new(props)
|
40
|
+
end
|
41
|
+
|
42
|
+
line_obj = ::Styles::Line.new(line, properties)
|
43
|
+
|
44
|
+
sub_engines.each do |sub_engine|
|
45
|
+
line_obj = sub_engine.process(line_obj)
|
46
|
+
return nil if line_obj.text.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
line_obj.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Returns instances SubEngines in the order that they should be used in processing.
|
55
|
+
def sub_engines
|
56
|
+
@sub_engines ||= begin
|
57
|
+
[
|
58
|
+
::Styles::SubEngines::PreProcessor.new,
|
59
|
+
::Styles::SubEngines::Color.new,
|
60
|
+
::Styles::SubEngines::Layout.new
|
61
|
+
]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def rules
|
66
|
+
stylesheets.map(&:rules).flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def color
|
70
|
+
::Term::ANSIColor
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/styles/line.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Styles
|
2
|
+
class Line
|
3
|
+
attr_accessor :applicable_properties, :top, :right, :bottom, :left
|
4
|
+
attr_reader :original
|
5
|
+
attr_writer :text
|
6
|
+
|
7
|
+
def initialize(line, properties=nil)
|
8
|
+
@original = line
|
9
|
+
@original.freeze
|
10
|
+
@applicable_properties = properties
|
11
|
+
|
12
|
+
@text = @original.dup
|
13
|
+
@top = @right = @bottom = @left = ''
|
14
|
+
end
|
15
|
+
|
16
|
+
# The current content of the line, possibly already altered by processing. By content we mean
|
17
|
+
# the line without additional layout characters (margin, padding, border) being applied around
|
18
|
+
# it. This does, however, include color information that may have been added.
|
19
|
+
def text
|
20
|
+
@text.nil? ? nil : @text.dup
|
21
|
+
end
|
22
|
+
|
23
|
+
# The line's main text content surrounded by any extra layout characters that may have been
|
24
|
+
# applied (margin, padding, border). When processing is complete this represents the complete
|
25
|
+
# result to be written to the output stream.
|
26
|
+
def to_s
|
27
|
+
return '' unless text
|
28
|
+
bottom_and_newline = "\n#{bottom}" if bottom && !bottom.empty?
|
29
|
+
"#{top}#{left}#{text}#{right}#{bottom_and_newline}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def total_width
|
33
|
+
colors.uncolor("#{left}#{text}#{right}").size
|
34
|
+
end
|
35
|
+
|
36
|
+
def content_width
|
37
|
+
colors.uncolor(text).size
|
38
|
+
end
|
39
|
+
|
40
|
+
def prop(property_name)
|
41
|
+
prop_name = property_name.to_sym
|
42
|
+
applicable_properties.find { |prop| prop.class.to_sym == prop_name }
|
43
|
+
end
|
44
|
+
|
45
|
+
def prop?(property_name)
|
46
|
+
!!prop(property_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def colors
|
52
|
+
::Styles::Colors
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
%w[
|
2
|
+
base
|
3
|
+
display
|
4
|
+
color
|
5
|
+
background_color
|
6
|
+
font_weight
|
7
|
+
text_decoration
|
8
|
+
match_color
|
9
|
+
match_background_color
|
10
|
+
match_font_weight
|
11
|
+
match_text_decoration
|
12
|
+
text_align
|
13
|
+
padding
|
14
|
+
margin
|
15
|
+
border
|
16
|
+
width
|
17
|
+
function
|
18
|
+
].each { |property| require "styles/properties/#{property}" }
|
19
|
+
|
20
|
+
module Styles
|
21
|
+
module Properties
|
22
|
+
|
23
|
+
def self.all_property_classes
|
24
|
+
constants = ::Styles::Properties.constants - [:Base]
|
25
|
+
constants.map { |con| ::Styles::Properties.const_get(con) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.find_class_by_property_name(name)
|
29
|
+
name = name.to_sym
|
30
|
+
all_property_classes.find { |klass| klass.names.include? name }
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Styles
|
2
|
+
module Properties
|
3
|
+
class BackgroundColor < Base
|
4
|
+
sub_engine :color
|
5
|
+
|
6
|
+
VALUES = (Styles::Colors::COLOR_VALUES + [:none]).freeze
|
7
|
+
|
8
|
+
# Convert foreground colors to background
|
9
|
+
def color_to_use
|
10
|
+
return :no_bg_color if value == :none
|
11
|
+
value =~ /^on_/ ? value : "on_#{value}".to_sym
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
module Styles
|
4
|
+
module Properties
|
5
|
+
class Base
|
6
|
+
attr_accessor :selector, :name, :value
|
7
|
+
|
8
|
+
def self.sub_engines
|
9
|
+
@sub_engines ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Macro to specify a sub-engine that this property is processed by
|
13
|
+
def self.sub_engine(name)
|
14
|
+
sub_engine_class = ::Styles::SubEngines.const_get(camelize(name.to_s))
|
15
|
+
sub_engines << sub_engine_class
|
16
|
+
|
17
|
+
begin
|
18
|
+
include sub_engine_class::PropertyMixin
|
19
|
+
rescue NameError
|
20
|
+
# do nothing if PropertyMixin does not exist for SubEngine
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# The name of this property, for use in stylesheets, as a Symbol
|
25
|
+
def self.to_sym
|
26
|
+
underscore(name).split('/').last.to_sym
|
27
|
+
end
|
28
|
+
|
29
|
+
# Macro to specify other names that a property class uses, besides the main +to_sym+ version
|
30
|
+
def self.other_names(*names)
|
31
|
+
@other_names = names
|
32
|
+
@names = self.names
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.names
|
36
|
+
@names ||= [to_sym, instance_variable_get('@other_names')].flatten.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.multiple_names?
|
40
|
+
names.size > 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(selector, name, value)
|
44
|
+
@selector, @name, @value = selector, name, value
|
45
|
+
end
|
46
|
+
|
47
|
+
def colors
|
48
|
+
::Styles::Colors
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.underscore(word)
|
54
|
+
word = word.dup
|
55
|
+
word.gsub!(/::/, '/')
|
56
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
57
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
58
|
+
word.tr!("-", "_")
|
59
|
+
word.downcase!
|
60
|
+
word
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.camelize(word)
|
64
|
+
word.capitalize.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Styles
|
2
|
+
module Properties
|
3
|
+
class Border < Base
|
4
|
+
# :dashed the same as :dotted
|
5
|
+
STYLES = [:solid, :dashed, :dotted, :double]
|
6
|
+
|
7
|
+
SOLID_CHARS = { top: "\u2500", right: "\u2502", bottom: "\u2500", left: "\u2502",
|
8
|
+
top_left: "\u250C", top_right: "\u2510", bottom_left: "\u2514", bottom_right: "\u2518" }.freeze
|
9
|
+
SOLID_BOLD_CHARS = { top: "\u2501", right: "\u2503", bottom: "\u2501", left: "\u2503",
|
10
|
+
top_left: "\u250F", top_right: "\u2513", bottom_left: "\u2517", bottom_right: "\u251B" }.freeze
|
11
|
+
DOUBLE_CHARS = { top: "\u2550", right: "\u2551", bottom: "\u2550", left: "\u2551",
|
12
|
+
top_left: "\u2554", top_right: "\u2557", bottom_left: "\u255A", bottom_right: "\u255D" }.freeze
|
13
|
+
|
14
|
+
# dotted doesn't have its own corners, reuse solid
|
15
|
+
DOTTED_CHARS = { top: "\u2504", right: "\u2506", bottom: "\u2504", left: "\u2506",
|
16
|
+
top_left: "\u250F", top_right: "\u2513", bottom_left: "\u2517", bottom_right: "\u251B" }.freeze
|
17
|
+
DASHED_CHARS = DOTTED_CHARS
|
18
|
+
|
19
|
+
STYLE_CHARS = {
|
20
|
+
solid: SOLID_CHARS, dotted: DOTTED_CHARS, dashed: DASHED_CHARS, double: DOUBLE_CHARS
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
sub_engine :layout
|
24
|
+
other_names :border_left, :border_right, :border_top, :border_bottom
|
25
|
+
|
26
|
+
attr_reader :top, :right, :bottom, :left
|
27
|
+
|
28
|
+
def initialize(*args)
|
29
|
+
if args.size == 1 && args.first.is_a?(Array)
|
30
|
+
@sub_properties = args.first
|
31
|
+
else
|
32
|
+
super
|
33
|
+
@sub_properties = nil
|
34
|
+
end
|
35
|
+
compute_all_border_values
|
36
|
+
end
|
37
|
+
|
38
|
+
def all_border_values
|
39
|
+
[top, right, bottom, left]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Generate methods for returning the proper characters to make up this border
|
43
|
+
[
|
44
|
+
:top_char, :right_char, :bottom_char, :left_char,
|
45
|
+
:top_left_char, :top_right_char, :bottom_left_char, :bottom_right_char
|
46
|
+
].each do |method_name|
|
47
|
+
str = method_name.to_s
|
48
|
+
position = str.sub(/_char$/, '')
|
49
|
+
|
50
|
+
define_method(method_name) do |*args|
|
51
|
+
raise ArgumentError, "Wrong number of arguments to #{method_name}: #{args.size}" if args.size > 1
|
52
|
+
|
53
|
+
return '' if position.split('_').any? { |side| send(side) == :none }
|
54
|
+
|
55
|
+
side_style = send(str.split('_').first)
|
56
|
+
return '' if side_style == :none
|
57
|
+
times = args.size == 1 ? args.first.to_i : 1
|
58
|
+
# TODO: check color validity
|
59
|
+
style, color = side_style
|
60
|
+
|
61
|
+
begin_color, end_color = if color != :default
|
62
|
+
[colors[color], colors[:reset]]
|
63
|
+
else
|
64
|
+
['', '']
|
65
|
+
end
|
66
|
+
|
67
|
+
"#{begin_color}#{STYLE_CHARS[style][position.to_sym] * times}#{end_color}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Draws the top line portion of a border with the specified inner width
|
72
|
+
def top_line_chars(width)
|
73
|
+
return '' if top == :none
|
74
|
+
tl = top_left_char.empty? ? (left == :none ? '' : ' ') : top_left_char
|
75
|
+
tr = top_right_char.empty? ? (right == :none ? '' : ' ') : top_right_char
|
76
|
+
"#{tl}#{top_char(width)}#{tr}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Draws the bottom line portion of a border with the specified inner width
|
80
|
+
def bottom_line_chars(width)
|
81
|
+
return '' if bottom == :none
|
82
|
+
bl = bottom_left_char.empty? ? (left == :none ? '' : ' ') : bottom_left_char
|
83
|
+
br = bottom_right_char.empty? ? (right == :none ? '' : ' ') : bottom_right_char
|
84
|
+
"#{bl}#{bottom_char(width)}#{br}"
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def compute_all_border_values
|
90
|
+
if @sub_properties
|
91
|
+
set_all_border_values(:none)
|
92
|
+
|
93
|
+
@sub_properties.each do |sub_prop|
|
94
|
+
if sub_prop.name == :border
|
95
|
+
set_all_border_values(parse_value(sub_prop.value))
|
96
|
+
else
|
97
|
+
set_border_value($1, parse_value(value))
|
98
|
+
set_border_value(sub_prop.name.to_s.sub(/^border_/, ''), parse_value(sub_prop.value))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
if name == :border
|
103
|
+
set_all_border_values(parse_value(value))
|
104
|
+
elsif name.to_s =~ /border_(\w+)/
|
105
|
+
set_all_border_values(:none)
|
106
|
+
set_border_value($1, parse_value(value))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Converts a "raw" value into an array of values specifying a border. Returns an array
|
112
|
+
# of the parsed sub-values, the style first and color second,
|
113
|
+
#
|
114
|
+
# Values can be in the following forms.
|
115
|
+
#
|
116
|
+
# Only a style, with default color assumed
|
117
|
+
# 'solid'
|
118
|
+
# or
|
119
|
+
# :solid
|
120
|
+
#
|
121
|
+
# A style and a color
|
122
|
+
# 'dotted red'
|
123
|
+
def parse_value(val)
|
124
|
+
return val if val == :none
|
125
|
+
default = [:solid, :default]
|
126
|
+
if val.is_a?(Symbol) && STYLES.include?(val)
|
127
|
+
[val, :default]
|
128
|
+
elsif val.is_a?(String)
|
129
|
+
parts = val.split
|
130
|
+
style = (parts[0] && STYLES.include?(parts[0].to_sym)) ? parts[0].to_sym : :solid
|
131
|
+
color = parts[1] ? parts[1].to_sym : :default
|
132
|
+
[style, color]
|
133
|
+
else
|
134
|
+
default
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def set_all_border_values(val)
|
139
|
+
@top = @right = @bottom = @left = val
|
140
|
+
end
|
141
|
+
|
142
|
+
def set_border_value(which, val)
|
143
|
+
instance_variable_set("@#{which}".to_sym, val)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|