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
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ doc
23
+ .rvmrc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Rico Sta. Cruz
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,60 @@
1
+ Lesstidy
2
+ ========
3
+
4
+ A CSS tidying tool.
5
+
6
+ Installation (for text editors)
7
+ -------------------------------
8
+
9
+ - For VIM users, install `lesstidy-vim`.
10
+ - For Textmate users, install `lesstidy
11
+
12
+ Installation (command line)
13
+ ---------------------------
14
+
15
+ To use the `lesstidy` command, type `gem install lesstidy --pre`.
16
+
17
+ Usage
18
+ -----
19
+
20
+ General usage is `lesstidy <input> [<output>]`.
21
+
22
+ Type `lesstidy --help` for tweaks.
23
+
24
+ .lesstidyopts file
25
+ ------------------
26
+
27
+ Want to impose a LessTidy options standard to your project? Put a file called
28
+ `.lesstidyopts` in your project's folder. Whenever lesstidy is used without
29
+ any style options, it finds this file (relative to the current directory).
30
+ Put each option in it's own line.
31
+
32
+ Example:
33
+
34
+ # .lesstidyopts
35
+ --preset=column
36
+ --wrap-width=140
37
+
38
+ Even the text editor plugins (vim, textmate, etc) will refer to this file.
39
+
40
+ Examples
41
+ --------
42
+
43
+ lesstidy --preset=compact input.css
44
+
45
+ To do
46
+ -----
47
+
48
+ - More types
49
+ - Mixin declarations
50
+ - Var declarations
51
+ - import lines
52
+ - .lesstidyopts
53
+
54
+ Done:
55
+
56
+ - Presets
57
+ - bin/lesstidy
58
+ - blackbox testing
59
+ - Comments
60
+ - recursive wrapping
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "lesstidy"
8
+ gem.summary = "CSS formatting tool"
9
+ gem.description = "LessTidy takes your CSS (or LessCSS) file and rewrites it in a more readable format."
10
+ gem.email = "rico@sinefunc.com"
11
+ gem.homepage = "http://github.com/rstacruz/lesstidy"
12
+ gem.authors = ["Rico Sta. Cruz", "Sinefunc, Inc."]
13
+ gem.add_dependency "treetop"
14
+ gem.add_development_dependency "contest"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "lesstidy #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1.pre
data/bin/lesstidy ADDED
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env ruby
2
+ lib_path = File.dirname(__FILE__) + "/../lib"
3
+ $:.unshift lib_path
4
+
5
+ require 'optparse'
6
+ require 'rubygems'
7
+ require 'lesstidy'
8
+ require 'ostruct'
9
+
10
+ options = OpenStruct.new({
11
+ :input => '-',
12
+ :output => '-',
13
+ :debug => false,
14
+ :style => Hash.new
15
+ })
16
+
17
+ opts = OptionParser.new do |o|
18
+ o.banner = "usage: lesstidy [<input>]"
19
+ o.separator ""
20
+
21
+ o.on("-d", "--debug", "show debug info instead") do
22
+ options.debug = true
23
+ end
24
+
25
+ o.on('-o', '--output FILE', 'output file') do |file|
26
+ options.output = file
27
+ end
28
+
29
+ # Help
30
+ o.on("-h", "--help", "show this message") do
31
+ puts opts
32
+ exit
33
+ end
34
+
35
+ Lesstidy::StyleParser.parse o, options.style
36
+
37
+ o.separator ""
38
+ o.separator "Available presets:"
39
+ Lesstidy::StyleParser.presets.each do |preset|
40
+ o.separator(" --preset=#{preset}")
41
+ end
42
+ end
43
+
44
+ begin
45
+ opts.parse!
46
+ rescue OptionParser::InvalidOption
47
+ puts opts
48
+ exit
49
+ end
50
+
51
+ if ARGV.size == 1
52
+ options.input = ARGV[0]
53
+ elsif ARGV.size > 1
54
+ puts opts
55
+ exit
56
+ end
57
+
58
+ begin
59
+ input = options.input == '-' ? STDIN : File.open(options.input, "r")
60
+ rescue Errno::ENOENT
61
+ STDERR << "Can't open input file for reading"
62
+ exit
63
+ end
64
+
65
+ begin
66
+ output = options.output == '-' ? STDOUT : File.open(options.output, "w")
67
+ rescue Errno::ENOENT
68
+ STDERR << "Can't open output file for reading"
69
+ exit
70
+ end
71
+
72
+ if options.style == {}
73
+ options.style = Lesstidy::StyleParser.defaults
74
+ end
75
+
76
+ contents = input.read
77
+ wrapper = {
78
+ :pre => /^[ \n\t]*/.match(contents).to_s,
79
+ :post => /[ \n\t]*$/.match(contents).to_s
80
+ }
81
+
82
+ begin
83
+ style = Lesstidy::Style.new(options.style)
84
+ doc = Lesstidy::Document.new(contents)
85
+
86
+ if options.debug
87
+ output << doc.inspect.strip
88
+ else
89
+ output << wrapper[:pre] << doc.to_css(style).strip << wrapper[:post] << "\n\n"
90
+ end
91
+
92
+ rescue Lesstidy::ParseError => e
93
+ output << "/* Parse error: #{e.message} */\n"
94
+ output << contents
95
+ end
96
+
97
+
98
+ input.close
99
+ output.close
100
+
101
+
@@ -0,0 +1,9 @@
1
+ --selector-width=19
2
+ --subrule-indent=2
3
+ --open-brace-spaces=s|s
4
+ --close-brace-spaces=|n
5
+ --semicolon-spaces=|s
6
+ --colon-spaces=|s
7
+ --no-selector-wrap
8
+ --wrap-width=120
9
+ --property-indent=3
@@ -0,0 +1,5 @@
1
+ --open-brace-spaces=s|s
2
+ --close-brace-spaces=|n
3
+ --no-wrap
4
+ --no-selector-wrap
5
+ --semicolon-spaces=|s
@@ -0,0 +1,3 @@
1
+ --preset=column
2
+ --selector-width=40
3
+ --wrap-width=400
data/lib/lesstidy.rb ADDED
@@ -0,0 +1,20 @@
1
+ require "treetop"
2
+
3
+ module Lesstidy
4
+ prefix = File.join(File.dirname(__FILE__), 'lesstidy')
5
+
6
+ Treetop.load "#{prefix}/grammar/less.treetop"
7
+
8
+ autoload :Nodes, "#{prefix}/nodes"
9
+ autoload :Renderer, "#{prefix}/renderer"
10
+ autoload :Document, "#{prefix}/document"
11
+ autoload :Style, "#{prefix}/style"
12
+ autoload :CUtil, "#{prefix}/cutil"
13
+ autoload :Preset, "#{prefix}/preset"
14
+ autoload :Config, "#{prefix}/config"
15
+ autoload :StyleParser, "#{prefix}/style_parser"
16
+
17
+ Error = Class.new(::StandardError)
18
+ PresetNotFoundError = Class.new(Error)
19
+ ParseError = Class.new(Error)
20
+ end
@@ -0,0 +1,33 @@
1
+ module Lesstidy
2
+ class Config
3
+ class << self
4
+ NoRootError = Class.new(Exception)
5
+
6
+ def paths
7
+ r = []
8
+ begin
9
+ r << find_project_root
10
+ rescue NoRootError; end
11
+ r
12
+ end
13
+
14
+ def find_project_root(start = Dir.pwd)
15
+ check = File.expand_path(start)
16
+ ret = nil
17
+ while ret.nil?
18
+ # See if any of these files exist
19
+ ['.lesstidy'].each do |config_name|
20
+ config_file = File.join(check, config_name)
21
+ ret ||= [check, config_file] if File.directory? config_file
22
+ end
23
+
24
+ # Traverse back (die if we reach the root)
25
+ old_check = check
26
+ check = File.expand_path(File.join(check, '..'))
27
+ raise NoRootError if check == old_check
28
+ end
29
+ ret
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,73 @@
1
+ module CUtil
2
+ class String < ::String
3
+ def prepend!(str)
4
+ self.replace(str + self)
5
+ end
6
+
7
+ def append!(str)
8
+ self << str
9
+ end
10
+
11
+ def wrap!(args = {})
12
+ temp = self.wrap(args)
13
+ self.replace(temp)
14
+ #self.replace(self.wrap(*args))
15
+ end
16
+
17
+ def lasplit(regex)
18
+ a = []
19
+ split(regex).each_slice(2) { |i| a << i.join('') }
20
+ a
21
+ end
22
+
23
+ def wrap(args = {})
24
+ # Inherit the given hashes
25
+ options = args
26
+ options[:regexp] ||= /([;,])/
27
+ options[:indent] ||= 0
28
+ options[:first_indent] ||= options[:indent]
29
+
30
+ width = options[:width]
31
+
32
+ first_indent = options[:first_indent]
33
+ indent = ''
34
+ indent = ' ' * options[:indent] if options[:indent]
35
+
36
+ ret = self.lasplit(options[:regexp]).inject(['']) do |a, chunk|
37
+ nl = a[-1] + chunk
38
+
39
+ line_width = first_indent ? (width - first_indent) : width
40
+ if nl.rstrip.size > line_width
41
+ # To wrap...
42
+ new_chunk = (indent + chunk.lstrip)
43
+ if a == ['']
44
+ a = [new_chunk]
45
+ else
46
+ a << new_chunk
47
+ end
48
+
49
+ first_indent = false
50
+
51
+ # If the new line exceeds the line width, rewrap!
52
+ if a[-1].size > width and not options[:no_rewrap]
53
+ a[-1] = String.new(a[-1]) unless a[-1].is_a? String
54
+ wrapped = [a[-1]]
55
+ wrapped = a[-1].wrap(args.merge({ :regexp => /( +)/, :no_rewrap => true, :array => true, :pad => false }))
56
+ a = a[0..-2]
57
+ a << wrapped if wrapped.size > 0
58
+ end
59
+ else
60
+ a[-1] = nl
61
+ end
62
+ a
63
+ end
64
+
65
+ # Pad the last line with spaces
66
+ ret[-1] = ret[-1] + (" " * [width - ret[-1].size, 0].max) if options[:pad]
67
+
68
+ # Stringify if needed
69
+ options[:array] ? ret : ret.join("\n")
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,20 @@
1
+ module Lesstidy
2
+ class Document < Lesstidy::Nodes::Document
3
+ def initialize(str)
4
+ super
5
+ parser = LessParser.new
6
+ tree = parser.parse(str)
7
+
8
+ if tree
9
+ tree.build self
10
+ else
11
+ raise Lesstidy::ParseError, parser.failure_message
12
+ end
13
+ end
14
+
15
+ def self.load(filename)
16
+ str = File.open(filename) { |f| f.read }
17
+ return self.new(str)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,117 @@
1
+ require File.dirname(__FILE__) + "/less_syntax.rb"
2
+
3
+ # Document
4
+ # Import
5
+ # Variable
6
+ # Comment
7
+ # Ruleset
8
+ # Selector
9
+ # Mixin
10
+ # Stylerule (prop/val)
11
+
12
+ module Lesstidy
13
+ grammar Less
14
+ rule document
15
+ ws document_item* ws {
16
+ def build(env = nil)
17
+ env ||= Lesstidy::Nodes::Document.new
18
+ cascade env
19
+ end
20
+ }
21
+ end
22
+
23
+ rule document_item
24
+ block_comment {
25
+ def build(env)
26
+ env << Lesstidy::Nodes::Comment.new(comment.text_value)
27
+ end
28
+ } /
29
+ ruleset
30
+ end
31
+
32
+ rule ruleset
33
+ ws selector_group ws '{' rule_group ws '}' {
34
+ def build( env )
35
+ ruleset = Lesstidy::Nodes::Ruleset.new
36
+ env << ruleset
37
+ cascade ruleset
38
+ end
39
+ }
40
+ end
41
+
42
+ # Anything inside a `#selector { ... }`
43
+ rule rule_group
44
+ rule_group_item*
45
+ end
46
+
47
+ rule rule_group_item
48
+ ruleset /
49
+ stylerule {
50
+ def build( ruleset )
51
+ rule = Lesstidy::Nodes::Rule.new property.text_value, value.text_value
52
+ ruleset << rule
53
+ end
54
+ } /
55
+ mixin {
56
+ } /
57
+ block_comment {
58
+ def build(ruleset)
59
+ ruleset << Lesstidy::Nodes::Comment.new(comment.text_value)
60
+ end
61
+ }
62
+ end
63
+
64
+ rule selector_group
65
+ selector ( ws ',' ws selector )*
66
+ end
67
+
68
+ rule selector
69
+ ( [A-Za-z0-9] / ':' / '>' / '*' / '(' / ')' / '[' / ']' / '-' / '_' / '#' / '.' / [\n\t ]+ / '&' )+ {
70
+ def build(ruleset)
71
+ sel = Lesstidy::Nodes::Selector.new self.text_value.strip
72
+ ruleset << sel
73
+ end
74
+ }
75
+ end
76
+
77
+ rule mixin
78
+ ws name:('.' [a-zA-Z0-9_\-]+) '(' params:(value_string) ')' ws ';' {
79
+ def build(ruleset)
80
+ mixin = Lesstidy::Nodes::Mixin.new name.text_value, params.text_value
81
+ ruleset << mixin
82
+ end
83
+ } /
84
+ ws name:('.' [a-zA-Z0-9_\-]*) ws ';' {
85
+ def build(ruleset)
86
+ mixin = Lesstidy::Nodes::Mixin.new name.text_value
87
+ ruleset << mixin
88
+ end
89
+ }
90
+ end
91
+
92
+ rule stylerule
93
+ ws property ws ':' ws value ws ';'
94
+ end
95
+
96
+ rule property
97
+ '-'? [A-Za-z]+ ( '-' [A-Za-z]* )*
98
+ end
99
+
100
+ rule value
101
+ (!';' . )*
102
+ end
103
+
104
+ # TODO: Add balancing of ()'s
105
+ rule value_string
106
+ [a-zA-Z0-9 \[\]\*\<\>\.,\#_\-@]*
107
+ end
108
+
109
+ rule block_comment
110
+ [\n\t ]* '/*' ' '* comment:(!(' '* '*/') . )* ' '* '*/'
111
+ end
112
+
113
+ rule ws
114
+ [\n\t ]*
115
+ end
116
+ end
117
+ end