less_to_sass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +70 -0
  4. data/bin/less2sass +12 -0
  5. data/bin/sass2less +12 -0
  6. data/lib/less2sass/constants.rb +5 -0
  7. data/lib/less2sass/error.rb +100 -0
  8. data/lib/less2sass/exec/base.rb +83 -0
  9. data/lib/less2sass/exec/conversion.rb +102 -0
  10. data/lib/less2sass/exec.rb +2 -0
  11. data/lib/less2sass/js/less_parser.js +48 -0
  12. data/lib/less2sass/less/ast_handler.rb +33 -0
  13. data/lib/less2sass/less/environment.rb +212 -0
  14. data/lib/less2sass/less/parser.rb +131 -0
  15. data/lib/less2sass/less/tree/alpha_node.rb +9 -0
  16. data/lib/less2sass/less/tree/anonymous_node.rb +43 -0
  17. data/lib/less2sass/less/tree/assignment_node.rb +10 -0
  18. data/lib/less2sass/less/tree/attribute_node.rb +11 -0
  19. data/lib/less2sass/less/tree/call_node.rb +65 -0
  20. data/lib/less2sass/less/tree/color_node.rb +27 -0
  21. data/lib/less2sass/less/tree/combinator_node.rb +18 -0
  22. data/lib/less2sass/less/tree/comment_node.rb +58 -0
  23. data/lib/less2sass/less/tree/condition_node.rb +13 -0
  24. data/lib/less2sass/less/tree/detached_ruleset_node.rb +10 -0
  25. data/lib/less2sass/less/tree/dimension_node.rb +26 -0
  26. data/lib/less2sass/less/tree/directive_node.rb +40 -0
  27. data/lib/less2sass/less/tree/element_node.rb +32 -0
  28. data/lib/less2sass/less/tree/expression_node.rb +73 -0
  29. data/lib/less2sass/less/tree/extend_node.rb +14 -0
  30. data/lib/less2sass/less/tree/import_node.rb +14 -0
  31. data/lib/less2sass/less/tree/keyword_node.rb +49 -0
  32. data/lib/less2sass/less/tree/media_node.rb +12 -0
  33. data/lib/less2sass/less/tree/mixin_call_node.rb +13 -0
  34. data/lib/less2sass/less/tree/mixin_definition_node.rb +24 -0
  35. data/lib/less2sass/less/tree/negative_node.rb +9 -0
  36. data/lib/less2sass/less/tree/node.rb +212 -0
  37. data/lib/less2sass/less/tree/operation_node.rb +63 -0
  38. data/lib/less2sass/less/tree/paren_node.rb +9 -0
  39. data/lib/less2sass/less/tree/quoted_node.rb +64 -0
  40. data/lib/less2sass/less/tree/rule_node.rb +119 -0
  41. data/lib/less2sass/less/tree/ruleset_call_node.rb +9 -0
  42. data/lib/less2sass/less/tree/ruleset_node.rb +82 -0
  43. data/lib/less2sass/less/tree/selector_node.rb +27 -0
  44. data/lib/less2sass/less/tree/unicode_descriptor_node.rb +9 -0
  45. data/lib/less2sass/less/tree/unit_node.rb +17 -0
  46. data/lib/less2sass/less/tree/url_node.rb +22 -0
  47. data/lib/less2sass/less/tree/value_node.rb +53 -0
  48. data/lib/less2sass/less/tree/variable_node.rb +43 -0
  49. data/lib/less2sass/less/tree.rb +34 -0
  50. data/lib/less2sass/less.rb +2 -0
  51. data/lib/less2sass/sass/ast_handler.rb +57 -0
  52. data/lib/less2sass/sass/parser.rb +20 -0
  53. data/lib/less2sass/sass.rb +2 -0
  54. data/lib/less2sass/util.rb +36 -0
  55. data/lib/less2sass.rb +9 -0
  56. metadata +163 -0
@@ -0,0 +1,32 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ # Represents the elements of a {SelectorNode}.
5
+ # These elements are usually separated by a
6
+ # {CombinatorNode}.
7
+ #
8
+ # No equivalent in Sass.
9
+ class ElementNode < Node
10
+ # @return [Tree::CombinatorNode]
11
+ attr_accessor :combinator
12
+ # @return [String]
13
+ attr_accessor :value
14
+ # @return [Integer]
15
+ attr_accessor :index
16
+ # @return [Hash]
17
+ attr_accessor :currentFileInfo
18
+
19
+ # @see Node#to_sass
20
+ def to_sass
21
+ if @value.is_a?(VariableNode)
22
+ @value.to_sass
23
+ elsif @value.is_a?(String)
24
+ @combinator.to_s + @value
25
+ else
26
+ raise FeatureConversionError, self
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,73 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ # Represents the expression in the Less AST.
5
+ #
6
+ # Sass does not have an Expression node. It is usually
7
+ # represented as the `expr` member of the {::Sass::Tree::VariableNode},
8
+ # that represents a variable definition.
9
+ #
10
+ # The Sass equivalent is either {::Sass::Script::Value::Base}
11
+ # wrapped in {::Sass::Script::Tree::Literal} or {::Sass::Tree::Node}.
12
+ class ExpressionNode < Node
13
+ attr_accessor :value
14
+
15
+ # @return [::Sass::Script::Tree::Literal, ::Sass::Script::Tree::ListLiteral, ::Sass::Tree::Node]
16
+ # @see Node#to_sass
17
+ def to_sass
18
+ if @value.is_a?(Array)
19
+ # TODO: document solution of: method to_sass is invoked on "to right":String, deal with it
20
+ if is_multiword_keyword?
21
+ multiword_keyword_argument
22
+ elsif should_be_literal?
23
+ @value.inject([]) { |value, elem| value << elem.to_s }.join(' ')
24
+ else
25
+ elements = @value.inject([]) do |value, elem|
26
+ node = elem.to_sass
27
+ node = node(::Sass::Script::Tree::Literal.new(node), line) if node.is_a?(::Sass::Script::Value::Base)
28
+ value << node
29
+ end
30
+ node(::Sass::Script::Tree::ListLiteral.new(elements, :space), line)
31
+ end
32
+ else
33
+ value = @value.to_sass
34
+ return value unless value.is_a?(::Sass::Script::Value::Base)
35
+ node(::Sass::Script::Tree::Literal.new(value), line)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ LITERAL_PROPERTIES = %w(font transition).freeze
42
+
43
+ # @todo: should be checked once more
44
+ def is_multiword_keyword?
45
+ @value.select { |elem| !elem.is_a?(KeywordNode) }.empty?
46
+ end
47
+
48
+ # Checks, whether the expression contains a {VariableNode}.
49
+ # Some properties in Less store their values as string literals
50
+ # instead of list literals.
51
+ #
52
+ # @return [Boolean] true if the expression should be converted
53
+ # to {::Sass::Script::Tree::Literal}
54
+ def should_be_literal?
55
+ grandparent = @parent.parent
56
+ return false unless grandparent.is_a?(RuleNode)
57
+ LITERAL_PROPERTIES.include?(grandparent.name.value) && !contains_variables?
58
+ end
59
+
60
+ # Creates a {::Sass::Script::Tree::ListLiteral} out of
61
+ # multiple keywords - usually referencing a complex CSS keyword.
62
+ #
63
+ # Example (`to right` is an example of multiword keyword):
64
+ # `list-style-image: linear-gradient(to right, rgba(255,0,0,0), rgba(255,0,0,1));`
65
+ #
66
+ def multiword_keyword_argument
67
+ keywords = @value.inject([]) { |value, elem| value << elem.to_sass }
68
+ node(::Sass::Script::Tree::ListLiteral.new(keywords, :space), line)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,14 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ class ExtendNode < Node
5
+ attr_accessor :selector
6
+ attr_accessor :option
7
+ attr_accessor :index
8
+ attr_accessor :object_id
9
+ attr_accessor :parent_ids
10
+ attr_accessor :currentFileInfo
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ class ImportNode < Node
5
+ attr_accessor :options
6
+ attr_accessor :index
7
+ attr_accessor :path
8
+ attr_accessor :features
9
+ attr_accessor :currentFileInfo
10
+ attr_accessor :css
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,49 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ # Represents the CSS property names and
5
+ # a little bit more.
6
+ #
7
+ # It can represent the name of a CSS rule,
8
+ # or it can be a part of a variable definition's
9
+ # value.
10
+ #
11
+ # In the latter case its Sass equivalents are:
12
+ # - {::Sass::Script::Value::Bool}
13
+ # - {::Sass::Script::Value::Null}
14
+ class KeywordNode < Node
15
+ attr_accessor :value
16
+
17
+ def to_s
18
+ @value.to_s
19
+ end
20
+
21
+ def empty?
22
+ @value.empty?
23
+ end
24
+
25
+ # Returns a SassScript Value node.
26
+ #
27
+ # Usually will be called in case of a variable
28
+ # definition and its parent node would be a
29
+ # {ExpressionNode}, which would wrap it up into a
30
+ # {::Sass::Script::Tree::Literal}.
31
+ #
32
+ # @raise FeatureConversionError if this node's value is not expected.
33
+ # @return [::Sass::Script::Value::Base]
34
+ # @see Node#to_sass
35
+ def to_sass
36
+ case @value
37
+ when 'true' then ::Sass::Script::Value::Bool.new(true)
38
+ when 'false' then ::Sass::Script::Value::Bool.new(false)
39
+ when 'null' then ::Sass::Script::Value::Null.new
40
+ else
41
+ raise FeatureConversionError, self unless @value.respond_to?(:to_s)
42
+ string = ::Sass::Script::Value::String.new(@value.to_s)
43
+ node(::Sass::Script::Tree::Literal.new(string), line)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,12 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ class MediaNode < Node
5
+ attr_accessor :index
6
+ attr_accessor :currentFileInfo
7
+ attr_accessor :features
8
+ attr_accessor :rules
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ class MixinCallNode < Node
5
+ attr_accessor :selector
6
+ attr_accessor :arguments
7
+ attr_accessor :index
8
+ attr_accessor :currentFileInfo
9
+ attr_accessor :important
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ class MixinDefinitionNode < Node
5
+ attr_accessor :name
6
+ attr_accessor :selectors
7
+ attr_accessor :params
8
+ attr_accessor :condition
9
+ attr_accessor :variadic
10
+ attr_accessor :arity
11
+ attr_accessor :rules
12
+ attr_accessor :_lookups
13
+ attr_accessor :required
14
+ attr_accessor :optionalParameters
15
+ attr_accessor :frames
16
+
17
+ # @see Node#creates_environment?
18
+ def creates_environment?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ class NegativeNode < Node
5
+ attr_accessor :value
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,212 @@
1
+ require 'less2sass/less/environment'
2
+
3
+ module Less2Sass
4
+ module Less
5
+ module Tree
6
+ # The base node class of the Less AST.
7
+ class Node
8
+ include Enumerable
9
+
10
+ # The parent node of this node.
11
+ #
12
+ # @return [Less2Sass::Less::Tree::Node]
13
+ attr_accessor :parent
14
+
15
+ # Whether or not this node has parent node.
16
+ #
17
+ # @return [Boolean]
18
+ attr_reader :has_parent
19
+
20
+ # The child nodes of this node.
21
+ #
22
+ # @return [Array<Less2Sass::Less::Tree::Node>]
23
+ attr_accessor :children
24
+
25
+ # Whether or not this node has child nodes.
26
+ #
27
+ # @return [Boolean]
28
+ attr_reader :has_children
29
+
30
+ # Sets the line number of the node
31
+ #
32
+ # @return [Void]
33
+ attr_writer :line
34
+
35
+ # This member gets set if the node's children contain
36
+ # referenced {VariableNode}s.
37
+ #
38
+ # @return [Array<String>] referenced variable names
39
+ attr_accessor :ref_vars
40
+
41
+ # The environment, where the nodes sits lexically.
42
+ # TODO: Consider to set a reference to the lexical scope (the environment) of the node.
43
+ # @return [Less2Sass::Less::Environment]
44
+ # attr_reader :env
45
+
46
+ # The current line number the conversion process is at
47
+ @@lines = 0
48
+
49
+ def initialize(parent)
50
+ @children = []
51
+ @parent = parent
52
+ @creates_new_line = false
53
+ end
54
+
55
+ # @private
56
+ def parent=(parent)
57
+ @has_parent ||= !parent.nil?
58
+ @parent = parent
59
+ end
60
+
61
+ # @private
62
+ def children=(children)
63
+ @has_children ||= !children.empty?
64
+ @children = children
65
+ end
66
+
67
+ # @private
68
+ def ref_vars
69
+ @ref_vars || get_referenced_variable_names
70
+ end
71
+
72
+ # Tells, whether the node creates a new context
73
+ # or variable environment.
74
+ #
75
+ # @return [Boolean]
76
+ def creates_context?
77
+ false
78
+ end
79
+
80
+ # Appends a child to the node.
81
+ #
82
+ # @param [Less2Sass::Less::Tree::Node, Array<Less2Sass::Less::Tree::Node>] child
83
+ # The child node or nodes
84
+ def <<(child)
85
+ return if child.nil?
86
+ if child.is_a?(Array)
87
+ child.each { |c| self << c }
88
+ else
89
+ @has_children = true
90
+ child.parent = self
91
+ @children << child
92
+ end
93
+ end
94
+
95
+ # Compares this node and another object (only other {Less2Sass::Less::Tree::Node}s will be equal).
96
+ #
97
+ # @param [Object] other The object to compare with
98
+ # @return [Boolean] Whether or not this node and the other object
99
+ # are the same
100
+ def ==(other)
101
+ self.class == other.class && other.children == children
102
+ end
103
+
104
+ # Iterates through each node in the tree rooted at this node
105
+ # in a pre-order walk.
106
+ #
107
+ # @yield node
108
+ # @yieldparam node [Less2Sass::Less::Tree::Node] a node in the tree
109
+ def each
110
+ yield self
111
+ children.each { |c| c.each { |n| yield n } }
112
+ end
113
+
114
+ # The base implementation of transform.
115
+ #
116
+ # Transforms the tree by its traversal. Each respective node type
117
+ # should implement the specific transformation and calling `#super`.
118
+ # The position, where `#super` is called determines the walking order.
119
+ # The standard should be post-order walk - transforming the child nodes
120
+ # first and self as last.
121
+ #
122
+ # Should be overridden by subclasses adding specific implementation.
123
+ #
124
+ # @param [Less2Sass::Less::Environment] env parent environment
125
+ # @return [Void]
126
+ def transform(env = nil)
127
+ # TODO: Consider to set a reference to the lexical scope (the environment) of the node.
128
+ # @env = env
129
+ @children.each { |c| c.transform(env) }
130
+ end
131
+
132
+ # Returns the line number and sets the class level
133
+ # current line number based on the passed option.
134
+ #
135
+ # @param [Symbol] opt the line number option
136
+ # @option opt [Symbol] :new tells the method to
137
+ # return the next line and increments @@lines
138
+ # @option opt [Symbol] :current tells the method to
139
+ # return the current line the converter is "processing"
140
+ #
141
+ # @return [Fixnum] the line number based on the given option
142
+ def line(opt = :current)
143
+ case opt
144
+ when :current
145
+ @@lines
146
+ when :new
147
+ @@lines = @@lines.next
148
+ else
149
+ raise StandardError, "Option can't be other than :current or :new"
150
+ end
151
+ end
152
+
153
+ # Converts this node to the equivalent Sass node
154
+ #
155
+ # Should be overwritten by subclasses.
156
+ # All subclasses should also set the line number
157
+ # of the resulting Sass node.
158
+ #
159
+ # @raise NotImplementedError if called on the base node object
160
+ # or subclass method not implemented, yet.
161
+ def to_sass
162
+ raise NotImplementedError
163
+ end
164
+
165
+ # Loops through the children nodes and
166
+ # searches for variable occurrences.
167
+ #
168
+ # @return [Array<String>] list of variable names
169
+ # contained in the value's expressions
170
+ # @note Use on {Less2Sass::Less::Tree::RuleNode}s. It's a good practice
171
+ # to check first if it's a variable definition.
172
+ # See (Less2Sass::Less::Tree::RuleNode#is_variable_definition?)
173
+ def get_referenced_variable_names
174
+ if is_a?(VariableNode)
175
+ [@name]
176
+ else
177
+ @children.inject([]) { |vars, c| vars + c.get_referenced_variable_names }
178
+ end
179
+ end
180
+
181
+ # Checks, whether a variable is referenced in the scope of the node.
182
+ #
183
+ # @return [Boolean]
184
+ def contains_variables?
185
+ variables = nil
186
+ each do |child|
187
+ if child.is_a?(VariableNode)
188
+ variables = true
189
+ break
190
+ end
191
+ end
192
+ variables
193
+ end
194
+
195
+ protected
196
+
197
+ # Sets up a node with the mandatory options (line, options).
198
+ #
199
+ # @param [::Sass::Tree::Node, ::Sass::Script::Tree::Node] node
200
+ # the node to be set up
201
+ # @param [Fixnum] line the line number, where the node sits lexically
202
+ # @param [Hash] options the options to pass to the node
203
+ # @return [::Sass::Tree::Node, ::Sass::Script::Tree::Node] the set up node
204
+ def node(node, line, options = { :style => :nested })
205
+ node.line = line if line
206
+ node.options = options if node.class.method_defined?(:options=)
207
+ node
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,63 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ # Node representing the operation with 2 operands.
5
+ #
6
+ # Converts to {::Sass::Script::Tree::Operation}.
7
+ class OperationNode < Node
8
+ attr_accessor :op
9
+ attr_accessor :operands
10
+ attr_accessor :isSpaced
11
+
12
+ def to_s
13
+ @operands[0].to_s + @op + @operands[1].to_s
14
+ end
15
+
16
+ # @see Node#to_sass
17
+ def to_sass
18
+ node(::Sass::Script::Tree::Operation.new(@operands[0].to_sass, @operands[1].to_sass, sass_operator), line)
19
+ end
20
+
21
+ # A hash from operator strings to the corresponding token types.
22
+ #
23
+ # Copied from Sass' lexer
24
+ # @see https://github.com/sass/sass/blob/stable/lib/sass/script/lexer.rb#L44-L69
25
+ OPERATORS = {
26
+ '+' => :plus,
27
+ '-' => :minus,
28
+ '*' => :times,
29
+ '/' => :div,
30
+ '%' => :mod,
31
+ '=' => :single_eq,
32
+ ':' => :colon,
33
+ '(' => :lparen,
34
+ ')' => :rparen,
35
+ ',' => :comma,
36
+ 'and' => :and,
37
+ 'or' => :or,
38
+ 'not' => :not,
39
+ '==' => :eq,
40
+ '!=' => :neq,
41
+ '>=' => :gte,
42
+ '<=' => :lte,
43
+ '>' => :gt,
44
+ '<' => :lt,
45
+ '#{' => :begin_interpolation,
46
+ '}' => :end_interpolation,
47
+ ';' => :semicolon,
48
+ '{' => :lcurly,
49
+ '...' => :splat
50
+ }.freeze
51
+
52
+ # Returns the sass operator symbol, that is used internally.
53
+ #
54
+ # @param [String] op the operator to look for
55
+ # @return [Symbol] the operator symbol
56
+ def sass_operator(op = @op)
57
+ raise Less2Sass::OperatorConversionError, op unless OPERATORS[op]
58
+ OPERATORS[op]
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,9 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ class ParenNode < Node
5
+ attr_accessor :value
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ module Less2Sass
2
+ module Less
3
+ module Tree
4
+ # Represents a single or double-quoted string in Less.
5
+ #
6
+ # The Sass equivalent is a {::Sass::Script::Value::String}
7
+ # wrapped in {::Sass::Script::Tree::Literal}.
8
+ class QuotedNode < Node
9
+ attr_accessor :escaped
10
+ attr_accessor :value
11
+ attr_accessor :quote
12
+ attr_accessor :index
13
+ attr_accessor :currentFileInfo
14
+
15
+ # Returns the Sass equivalent for a quoted string.
16
+ #
17
+ # @return [::Sass::Script::Tree::Literal] simple string literal
18
+ # @return [::Sass::Script::Tree::StringInterpolation] interpolation node,
19
+ # if the string contains interpolations
20
+ # @see Node#to_sass
21
+ def to_sass
22
+ if string_interpolation?
23
+ interpolation_node(@value)
24
+ else
25
+ node(::Sass::Script::Tree::Literal.new(::Sass::Script::Value::String.new(@value, :string)), line)
26
+ end
27
+ end
28
+
29
+ # Returns the string's raw value without passing the type of quote.
30
+ #
31
+ # Sass has its own standards regarding the quotes, it should be in
32
+ # Sass' competences to choose what type of quote to use.
33
+ def to_s
34
+ @value
35
+ end
36
+
37
+ # Checks, whether the quoted string contains an interpolation.
38
+ #
39
+ # @return [Boolean]
40
+ def string_interpolation?(value = @value)
41
+ !value.index(INTERPOLATION).nil?
42
+ end
43
+
44
+ private
45
+
46
+ INTERPOLATION = /(@\{[\w_-]*\})/
47
+ VARIABLE_NAME = /@\{([\w_-]*)\}/
48
+
49
+ # Creates a {::Sass::Script::Tree::StringInterpolation} node.
50
+ #
51
+ # @param [String] value the string containing interpolation(s)
52
+ # @return [::Sass::Script::Tree::StringInterpolation] the interpolation node
53
+ def interpolation_node(value)
54
+ parts = value.split(INTERPOLATION, 2) # Must be split maximally into 3 parts
55
+ parts[2] = '' if parts.length == 2 # Always must have 3 parts
56
+ before = node(::Sass::Script::Tree::Literal.new(::Sass::Script::Value::String.new(parts[0], :string)), line)
57
+ mid = node(::Sass::Script::Tree::Variable.new(parts[1].match(VARIABLE_NAME)[1]), line)
58
+ after = string_interpolation?(parts[2]) ? interpolation_node(parts[2]) : node(::Sass::Script::Tree::Literal.new(::Sass::Script::Value::String.new(parts[2], :string)), line)
59
+ node(::Sass::Script::Tree::StringInterpolation.new(before, mid, after), line)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end