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.
Files changed (47) hide show
  1. data/.gitignore +23 -0
  2. data/LICENSE +20 -0
  3. data/README.md +60 -0
  4. data/Rakefile +54 -0
  5. data/VERSION +1 -0
  6. data/bin/lesstidy +101 -0
  7. data/data/presets/column +9 -0
  8. data/data/presets/terse +5 -0
  9. data/data/presets/wide-column +3 -0
  10. data/lib/lesstidy.rb +20 -0
  11. data/lib/lesstidy/config.rb +33 -0
  12. data/lib/lesstidy/cutil.rb +73 -0
  13. data/lib/lesstidy/document.rb +20 -0
  14. data/lib/lesstidy/grammar/less.treetop +117 -0
  15. data/lib/lesstidy/grammar/less_syntax.rb +30 -0
  16. data/lib/lesstidy/nodes.rb +107 -0
  17. data/lib/lesstidy/preset.rb +4 -0
  18. data/lib/lesstidy/renderer.rb +147 -0
  19. data/lib/lesstidy/style.rb +32 -0
  20. data/lib/lesstidy/style_parser.rb +117 -0
  21. data/test/d.rb +31 -0
  22. data/test/fixtures/a.control.css +7 -0
  23. data/test/fixtures/a.default.css +32 -0
  24. data/test/fixtures/a.inspect.txt +29 -0
  25. data/test/fixtures/a.terse.css +7 -0
  26. data/test/fixtures/fail/c.css +4 -0
  27. data/test/fixtures/fail/search1.css +20 -0
  28. data/test/fixtures/fail/search2.css +9 -0
  29. data/test/fixtures/fail/search3.css +7 -0
  30. data/test/fixtures/fail/search3b.css +5 -0
  31. data/test/fixtures/fail/spec.css +24 -0
  32. data/test/fixtures/spec.control.css +24 -0
  33. data/test/fixtures/spec.default.css +95 -0
  34. data/test/fixtures/spec.inspect.txt +82 -0
  35. data/test/fixtures/spec.terse.css +21 -0
  36. data/test/fixtures/test-2.control.css +4 -0
  37. data/test/fixtures/test-2.default.css +33 -0
  38. data/test/fixtures/test-2.inspect.txt +37 -0
  39. data/test/fixtures/test-2.terse.css +11 -0
  40. data/test/fixtures/test.control.css +1 -0
  41. data/test/fixtures/test.default.css +30 -0
  42. data/test/fixtures/test.inspect.txt +35 -0
  43. data/test/fixtures/test.terse.css +8 -0
  44. data/test/helper.rb +10 -0
  45. data/test/test_blackbox.rb +29 -0
  46. data/test/test_lesstidy.rb +4 -0
  47. 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,4 @@
1
+ module Lesstidy
2
+ class Preset
3
+ end
4
+ 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
+