lesstidy 0.0.1.pre
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/.gitignore +23 -0
- data/LICENSE +20 -0
- data/README.md +60 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/bin/lesstidy +101 -0
- data/data/presets/column +9 -0
- data/data/presets/terse +5 -0
- data/data/presets/wide-column +3 -0
- data/lib/lesstidy.rb +20 -0
- data/lib/lesstidy/config.rb +33 -0
- data/lib/lesstidy/cutil.rb +73 -0
- data/lib/lesstidy/document.rb +20 -0
- data/lib/lesstidy/grammar/less.treetop +117 -0
- data/lib/lesstidy/grammar/less_syntax.rb +30 -0
- data/lib/lesstidy/nodes.rb +107 -0
- data/lib/lesstidy/preset.rb +4 -0
- data/lib/lesstidy/renderer.rb +147 -0
- data/lib/lesstidy/style.rb +32 -0
- data/lib/lesstidy/style_parser.rb +117 -0
- data/test/d.rb +31 -0
- data/test/fixtures/a.control.css +7 -0
- data/test/fixtures/a.default.css +32 -0
- data/test/fixtures/a.inspect.txt +29 -0
- data/test/fixtures/a.terse.css +7 -0
- data/test/fixtures/fail/c.css +4 -0
- data/test/fixtures/fail/search1.css +20 -0
- data/test/fixtures/fail/search2.css +9 -0
- data/test/fixtures/fail/search3.css +7 -0
- data/test/fixtures/fail/search3b.css +5 -0
- data/test/fixtures/fail/spec.css +24 -0
- data/test/fixtures/spec.control.css +24 -0
- data/test/fixtures/spec.default.css +95 -0
- data/test/fixtures/spec.inspect.txt +82 -0
- data/test/fixtures/spec.terse.css +21 -0
- data/test/fixtures/test-2.control.css +4 -0
- data/test/fixtures/test-2.default.css +33 -0
- data/test/fixtures/test-2.inspect.txt +37 -0
- data/test/fixtures/test-2.terse.css +11 -0
- data/test/fixtures/test.control.css +1 -0
- data/test/fixtures/test.default.css +30 -0
- data/test/fixtures/test.inspect.txt +35 -0
- data/test/fixtures/test.terse.css +8 -0
- data/test/helper.rb +10 -0
- data/test/test_blackbox.rb +29 -0
- data/test/test_lesstidy.rb +4 -0
- metadata +138 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
class Treetop::Runtime::SyntaxNode
|
2
|
+
def cascade(env)
|
3
|
+
elements.each { |e| e.build env if e.respond_to? :build } if elements.is_a? Array
|
4
|
+
env
|
5
|
+
end
|
6
|
+
|
7
|
+
def build(env)
|
8
|
+
cascade env
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Treetop::Runtime::CompiledParser
|
13
|
+
def failure_message
|
14
|
+
o = lambda {|i, *args| i }
|
15
|
+
return nil unless (tf = terminal_failures) && tf.size > 0
|
16
|
+
msg = "on line #{failure_line}: expected " + (
|
17
|
+
tf.size == 1 ?
|
18
|
+
o[tf[0].expected_string, :yellow] :
|
19
|
+
"one of #{o[tf.map {|f| f.expected_string }.uniq * ' ', :yellow]}"
|
20
|
+
)
|
21
|
+
f = input[failure_index]
|
22
|
+
got = case f
|
23
|
+
when "\n" then o['\n', :cyan]
|
24
|
+
when nil then o["EOF", :cyan]
|
25
|
+
when ' ' then o["white-space", :cyan]
|
26
|
+
else o[f.chr, :yellow]
|
27
|
+
end
|
28
|
+
msg += " got #{got}" # after:\n\n#{input[index...failure_index]}\n"
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Lesstidy
|
2
|
+
module Nodes
|
3
|
+
class Node
|
4
|
+
include Lesstidy::Renderer
|
5
|
+
|
6
|
+
attr_reader :elements
|
7
|
+
attr_accessor :parent
|
8
|
+
|
9
|
+
def initialize(*a)
|
10
|
+
@elements = []
|
11
|
+
@parent = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(element)
|
15
|
+
@elements << element
|
16
|
+
element.parent = self
|
17
|
+
end
|
18
|
+
|
19
|
+
def depth
|
20
|
+
@parent.nil? ? 0 : (@parent.depth+1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
out = ""
|
25
|
+
out << "\n" + (". " * (1+depth)) + self.inspect_s
|
26
|
+
@elements.each { |e| out << e.inspect }
|
27
|
+
out
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect_s
|
31
|
+
"<#{self.class.to_s}>"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Document < Node
|
36
|
+
def inspect_s
|
37
|
+
"[document]"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Ruleset < Node
|
42
|
+
def initialize( *args )
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def inspect_s
|
47
|
+
"[ruleset]"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Comment < Node
|
52
|
+
attr_reader :comment
|
53
|
+
|
54
|
+
def initialize(content)
|
55
|
+
super
|
56
|
+
@comment = content
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect_s
|
60
|
+
"/* #{@comment} */"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Selector < Node
|
65
|
+
attr_reader :selector
|
66
|
+
|
67
|
+
def initialize(selector)
|
68
|
+
super
|
69
|
+
@selector = selector
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect_s
|
73
|
+
"[S] #{@selector}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Rule < Node
|
78
|
+
attr_reader :property
|
79
|
+
attr_reader :value
|
80
|
+
|
81
|
+
def initialize( property, value )
|
82
|
+
super
|
83
|
+
@property = property
|
84
|
+
@value = value
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect_s
|
88
|
+
"[R] #{@property}: #{value}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Mixin < Node
|
93
|
+
attr_reader :selector
|
94
|
+
attr_reader :params
|
95
|
+
|
96
|
+
def initialize(selector, params = nil)
|
97
|
+
super
|
98
|
+
@selector = selector
|
99
|
+
@params = params
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect_s
|
103
|
+
"[M] #{@selector}%s" % [(params ? "(#{params})" : '')]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Lesstidy
|
4
|
+
module Renderer
|
5
|
+
# args = new Args(*args)
|
6
|
+
# args.context
|
7
|
+
class Args < OpenStruct
|
8
|
+
def new(*args)
|
9
|
+
super args.inject({}) { |a, i| a.merge! i }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_css(*args)
|
14
|
+
# Try to delegate to `(classname)_to_css` (eg, rule_to_css).
|
15
|
+
klass = /([a-zA-Z_]+)$/.match(self.class.to_s)[1]
|
16
|
+
css_method = "#{klass}_to_css".downcase.to_sym
|
17
|
+
if respond_to? css_method
|
18
|
+
send css_method, *args
|
19
|
+
else
|
20
|
+
''
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def document_to_css(style = Style.new, *args)
|
26
|
+
@elements.inject('') do |a, element|
|
27
|
+
a << element.to_css(style, :context => :document); a
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def comment_to_css(style, *args)
|
32
|
+
args = Args.new *args
|
33
|
+
args.depth ||= 0
|
34
|
+
|
35
|
+
if args.context == :document
|
36
|
+
style.document_comment % ["/* #{@comment} */"]
|
37
|
+
|
38
|
+
elsif args.context == :ruleset
|
39
|
+
indent = ' ' * (args.depth * style.subrule_indent)
|
40
|
+
comment = "/* #{@comment} */"
|
41
|
+
"#{indent}#{comment}\n"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def ruleset_to_css(style, *args)
|
46
|
+
args = Args.new *args
|
47
|
+
args.depth ||= 0
|
48
|
+
|
49
|
+
selectors = @elements.select { |e| e.is_a? Nodes::Selector }
|
50
|
+
items = @elements.select { |e| e.is_a? Nodes::Mixin or e.is_a? Nodes::Rule }
|
51
|
+
rnc = @elements.select { |e| e.is_a? Nodes::Ruleset or e.is_a? Nodes::Comment }
|
52
|
+
|
53
|
+
# Start: selectors
|
54
|
+
r = ''
|
55
|
+
r << selectors_to_css(selectors, style, args)
|
56
|
+
|
57
|
+
# Items (properties, mixins, etc)
|
58
|
+
indent = r.split("\n")[-1].size
|
59
|
+
r << items_to_css(items, indent, style, args)
|
60
|
+
|
61
|
+
# Subrules and Comments (rnc)
|
62
|
+
r << rules_and_comments_to_css(rnc, style, args)
|
63
|
+
|
64
|
+
# Close brace
|
65
|
+
indent = args.depth * style.subrule_indent # Number of spaces
|
66
|
+
r << (' ' * indent) if /\n$/.match(r)
|
67
|
+
r << style.close_brace
|
68
|
+
end
|
69
|
+
|
70
|
+
def rule_to_css(style, *args)
|
71
|
+
# TODO: Property width
|
72
|
+
@property + style.colon + @value
|
73
|
+
end
|
74
|
+
|
75
|
+
def mixin_to_css(style, *args)
|
76
|
+
if @params
|
77
|
+
"%s(%s)" % [@selector, @params]
|
78
|
+
else
|
79
|
+
@selector
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def selector_to_css(style, *args)
|
84
|
+
@selector.strip.squeeze(' ')
|
85
|
+
end
|
86
|
+
|
87
|
+
# Gets the rendered string for a given set of Selectors
|
88
|
+
# Delegate method of ruleset_to_css
|
89
|
+
def selectors_to_css(sels, style, args)
|
90
|
+
indent = args.depth * style.subrule_indent # Number of spaces
|
91
|
+
indent_str = ' ' * indent
|
92
|
+
selector_strs = sels.map { |sel| sel.to_css(style) }
|
93
|
+
|
94
|
+
r = CUtil::String.new
|
95
|
+
r.replace selector_strs.join(style.comma)
|
96
|
+
r.prepend! indent_str
|
97
|
+
r.wrap! :width => style.selector_width,
|
98
|
+
:pad => true,
|
99
|
+
:no_rewrap => (!style.selector_wrap),
|
100
|
+
:indent => indent unless style.selector_width.nil?
|
101
|
+
r.append! style.open_brace
|
102
|
+
r.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gets the rendered string for a given set of Mixins and Rules (what's inside {}'s)
|
106
|
+
# Delegate method of ruleset_to_css
|
107
|
+
def items_to_css(items, indent, style, args)
|
108
|
+
items_css = items.map { |item| item.to_css(style) + style.semicolon }
|
109
|
+
|
110
|
+
indent_value = style.property_indent
|
111
|
+
|
112
|
+
if style.selector_width.nil? # Not column mode?
|
113
|
+
indent_value += style.subrule_indent * (args.depth)
|
114
|
+
else
|
115
|
+
indent_value += style.selector_width
|
116
|
+
end
|
117
|
+
|
118
|
+
r = CUtil::String.new
|
119
|
+
r.replace items_css.join('')
|
120
|
+
r.wrap! :width => style.wrap_width,
|
121
|
+
:indent => indent_value,
|
122
|
+
:first_indent => indent unless style.wrap_width.nil?
|
123
|
+
r.gsub! /^/, ' ' * indent_value if style.wrap_width.nil? and /\{.*\n.*$/.match(style.open_brace)
|
124
|
+
r.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
# Gets the rendered string for a given set of Rules and Comments
|
128
|
+
# Delegate method of ruleset_to_css
|
129
|
+
def rules_and_comments_to_css(items, style, args)
|
130
|
+
r = ''
|
131
|
+
if items.size > 0
|
132
|
+
item_strs = items.map do |item|
|
133
|
+
item.to_css(style,
|
134
|
+
:context => :ruleset,
|
135
|
+
:depth => (args.depth + 1))
|
136
|
+
end
|
137
|
+
r << style.subrule_before
|
138
|
+
r << item_strs.join('')
|
139
|
+
end
|
140
|
+
|
141
|
+
# Remove trailing \n's
|
142
|
+
r.gsub! /\n+$/, "\n"
|
143
|
+
r
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Lesstidy
|
4
|
+
class Style < OpenStruct
|
5
|
+
def initialize(args = {})
|
6
|
+
super defaults.merge(StyleParser.load_options(args))
|
7
|
+
end
|
8
|
+
|
9
|
+
def verify
|
10
|
+
# wrap_width > selector_width
|
11
|
+
# /\s*{\s/.match open_brace
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
def defaults
|
16
|
+
{ :wrap_width => nil, # nil for no wrap
|
17
|
+
:selector_width => nil, # nil for no columning
|
18
|
+
:open_brace => " {\n",
|
19
|
+
:close_brace => "}\n\n",
|
20
|
+
:document_comment => "\n%s\n",
|
21
|
+
:semicolon => ";\n", # Add NLs after if needed
|
22
|
+
:property_width => nil,
|
23
|
+
:colon => ": ", # Add spaces if needed
|
24
|
+
:comma => ", ", # Spaces if needed
|
25
|
+
:subrule_indent => 2, # Indentation of sub rules
|
26
|
+
:subrule_before => "\n", # Separators of subrules
|
27
|
+
:property_indent => 2, # How much to indent properties on their next line (will take selector width into account)
|
28
|
+
:selector_wrap => false, # Wrap long selectors?
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Lesstidy
|
2
|
+
class StyleParser
|
3
|
+
class << self
|
4
|
+
def preset_path(*args)
|
5
|
+
@preset_path ||= File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. data presets]))
|
6
|
+
File.join(@preset_path, *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_preset(preset_name = '')
|
10
|
+
return {} if preset_name == 'default'
|
11
|
+
|
12
|
+
preset_file = File.join(preset_path, preset_name)
|
13
|
+
|
14
|
+
raise PresetNotFoundError, preset_file unless File.exists?(preset_file)
|
15
|
+
preset_data = File.open(preset_file) { |f| f.read.split("\n") }
|
16
|
+
|
17
|
+
load_options preset_data
|
18
|
+
end
|
19
|
+
|
20
|
+
def presets
|
21
|
+
['default'] + Dir[preset_path '**'].map { |file| File.basename(file) }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the defaults
|
25
|
+
def defaults
|
26
|
+
dir = Dir.pwd
|
27
|
+
while true do
|
28
|
+
fname = File.join(dir, '.lesstidyopts')
|
29
|
+
|
30
|
+
return File.open(fname) { |f| f.read.split("\n").reject(&:empty?) } \
|
31
|
+
if File.exists?(fname)
|
32
|
+
|
33
|
+
_dir = File.expand_path(File.join(dir, '..'))
|
34
|
+
return [] if dir == _dir
|
35
|
+
dir = _dir
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Input: array of options, or hash, or string?
|
40
|
+
# Output: hash to be fed into Style.new
|
41
|
+
def load_options(args)
|
42
|
+
opts = {}
|
43
|
+
|
44
|
+
if args.is_a? Array
|
45
|
+
parser = OptionParser.new { |o| parse(o, opts) }
|
46
|
+
parser.parse args
|
47
|
+
end
|
48
|
+
|
49
|
+
opts = opts.merge args if args.is_a? Hash
|
50
|
+
opts = load_preset(args) if args.is_a? String
|
51
|
+
opts = load_preset(opts[:preset]).merge(opts) unless opts[:preset].nil?
|
52
|
+
opts
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse(o, opt)
|
56
|
+
o.separator ""
|
57
|
+
o.separator "Style options:"
|
58
|
+
o.on('-p', '--preset', '=PRESET', "preset name to load") do |v|
|
59
|
+
opt[:preset] = v
|
60
|
+
end
|
61
|
+
|
62
|
+
o.on('--no-wrap', "disables wrapping") do
|
63
|
+
opt[:wrap_width] = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
o.on('--wrap-width', '=N', "wrap width (overrides --no-wrap)") do |n|
|
67
|
+
opt[:wrap_width] = n.to_i
|
68
|
+
end
|
69
|
+
|
70
|
+
o.on('--property-width', '=N', "width of properties (NOT implemented)") do |n|
|
71
|
+
opt[:property_width] = n.to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
o.on('--property-indent', '=N', "number of spaces to indent properties with") do |n|
|
75
|
+
opt[:property_indent] = n.to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
# Yeh, no-sel-wrap and sel-width are NOT mutex... ambiguous :|
|
79
|
+
o.on('--no-selector-wrap', 'disables wrapping of long selectors (LessCSS compatibility)') do
|
80
|
+
opt[:selector_wrap] = false
|
81
|
+
end
|
82
|
+
|
83
|
+
o.on('--selector-wrap', 'enables wrapping of long selectors') do
|
84
|
+
opt[:selector_wrap] = true
|
85
|
+
end
|
86
|
+
|
87
|
+
o.on('--no-selector-column', 'disables selector column mode') do |n|
|
88
|
+
opt[:selector_width] = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
o.on('--selector-width', '=N', 'selector column mode; width of selectors before wrapping') do |n|
|
92
|
+
opt[:selector_width] = n.to_i
|
93
|
+
end
|
94
|
+
|
95
|
+
o.on('--subrule-indent', '=N', 'number of spaces to indent a subrule with') do |n|
|
96
|
+
opt[:subrule_indent] = n.to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
o.on('--open-brace-spaces', '=FORMAT', '') do |fmt|
|
100
|
+
opt[:open_brace] = fmt.tr('sn', " \n").gsub('|', '{')
|
101
|
+
end
|
102
|
+
|
103
|
+
o.on('--close-brace-spaces', '=FORMAT', '') do |fmt|
|
104
|
+
opt[:close_brace] = fmt.tr('sn', " \n").gsub('|', '}')
|
105
|
+
end
|
106
|
+
|
107
|
+
o.on('--semicolon-spaces', '=FORMAT', '') do |fmt|
|
108
|
+
opt[:semicolon] = fmt.tr('sn', " \n").gsub('|', ';')
|
109
|
+
end
|
110
|
+
|
111
|
+
o.on('--colon-spaces', '=FORMAT', '') do |fmt|
|
112
|
+
opt[:colon] = fmt.tr('sn', " \n").gsub('|', ':')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/test/d.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "../lib/lesstidy"
|
2
|
+
|
3
|
+
def t( a )
|
4
|
+
CSSParser.new.parse a
|
5
|
+
end
|
6
|
+
|
7
|
+
def try( a )
|
8
|
+
style = Lesstidy::Style.new
|
9
|
+
|
10
|
+
p = CSSParser.new.parse a
|
11
|
+
puts (p.nil? ? '[FAIL]' : '[ OK ]') + " #{a}"
|
12
|
+
|
13
|
+
if p.respond_to? :build
|
14
|
+
node = p.build
|
15
|
+
puts node.inspect
|
16
|
+
puts node.to_css style
|
17
|
+
end
|
18
|
+
|
19
|
+
puts ""
|
20
|
+
end
|
21
|
+
|
22
|
+
#try ""
|
23
|
+
#try "div { color: red; }"
|
24
|
+
#try "div{color:red;}"
|
25
|
+
#try "div,div { color: red; }"
|
26
|
+
#try "div,div { .lol(); color: red; }"
|
27
|
+
#try "div,div { color: red; font-weight: bold; }"
|
28
|
+
#try "div,div { color: red; font-weight: bold; a { color: red; } }"
|
29
|
+
#try "#menu a,div { color: red; font-weight: bold; } #menu ul li > div, td, tr, #menu a:hover div #lol yes yes .something span.clear-fix, table { text-align: center; .black; font-weight: bold; border: solid 2px #882828; cursor: default; background-repeat: no-repeat; } a:hover { .corner(5px); background: url(foo.png); span { font-weight: bold; } span, a:hover, a:active, a:hover span, a:active span { text-decoration: underline; strong em { color: blue; } } }"
|
30
|
+
try File.open('fixtures/test-2.control.css') { |f| f.read }
|
31
|
+
|