Class: CssCompare::CSS::Engine

Inherits:
Object
  • Object
show all
Includes:
Component
Defined in:
lib/css_compare/css/engine.rb

Overview

The CSS Engine that computes the values of the properties under all the declared parent_query_list in the stylesheet.

It can handle:

- simple property overriding
- property overriding with !import
- @import rules
- @media queries - PARTIAL SUPPORT ONLY!!!
- nested @media queries also know as nested conditional
  group rules
- @keyframes rules
- @namespace rules
- @charset rules
- @page rules
- @supports rules

However, the @media and @supports evaluations are not 100% reliable, since the parent_query_list of each directive are not interpreted and evaluated by the engine. Instead, they are stringified as a whole and used as the key for their selector-property pairs.

“When multiple conditional group rules are nested, a rule inside of both of them applies only when all of the rules’ parent_query_list are true.” The imports are dynamically loaded and evaluated with the root document together. The result shows the final values of each CSS properties and rules, just like a browser would interpret the linked CSS stylesheets.

Constant Summary collapse

GLOBAL_QUERY =
'all'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Component

#media_node, #root_node

Constructor Details

#initialize(input) ⇒ Engine

Note:

UTF-8 is assumed as default charset

Returns a new instance of Engine.

Parameters:

  • input (String, Sass::Tree::Node)

    the source file of the CSS project, or its AST

See Also:



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/css_compare/css/engine.rb', line 62

def initialize(input)
  @tree =
    begin
      if input.is_a?(String)
        Parser.new(input).parse.freeze
      elsif input.is_a?(Sass::Tree::Node)
        input.freeze
      else
        raise ArgumentError, "The engine's input must be either a path, or a Sass::Tree::Node"
      end
    end
  @filename = @tree.options[:filename]
  @engine = {}
  @selectors = {}
  @font_faces = {}
  @keyframes = {}
  @namespaces = {}
  @pages = {}
  @supports = {}
  @unsupported = []
  @charset = 'UTF-8'
end

Instance Attribute Details

#charsetObject

Returns the value of attribute charset.



54
55
56
# File 'lib/css_compare/css/engine.rb', line 54

def charset
  @charset
end

#engineHash<Symbol, Array<Component::Selector, String>]

The inner representation of the computed properties of each selector under every condition specified by the declared @media directives.

Returns:

  • (Hash<Symbol, Array<Component::Selector, String>])

    Hash<Symbol, Array<Component::Selector, String>]



46
47
48
# File 'lib/css_compare/css/engine.rb', line 46

def engine
  @engine
end

#keyframesObject

Returns the value of attribute keyframes.



54
55
56
# File 'lib/css_compare/css/engine.rb', line 54

def keyframes
  @keyframes
end

#namespacesObject

Returns the value of attribute namespaces.



54
55
56
# File 'lib/css_compare/css/engine.rb', line 54

def namespaces
  @namespaces
end

#pagesObject

Returns the value of attribute pages.



54
55
56
# File 'lib/css_compare/css/engine.rb', line 54

def pages
  @pages
end

#selectorsObject

Returns the value of attribute selectors.



54
55
56
# File 'lib/css_compare/css/engine.rb', line 54

def selectors
  @selectors
end

#supportsObject

Returns the value of attribute supports.



54
55
56
# File 'lib/css_compare/css/engine.rb', line 54

def supports
  @supports
end

#unsupportedArray<Sass::Tree::Node>

A list of nodes, that could not be evaluated due to being not supported by this engine.

Returns:

  • (Array<Sass::Tree::Node>)

    unsupported CSS nodes



52
53
54
# File 'lib/css_compare/css/engine.rb', line 52

def unsupported
  @unsupported
end

Instance Method Details

#==(other) ⇒ Boolean

Checks, whether two engines are equal.

They are equal only if the same symbols are defined and each and every component under those keys are also equal.

Parameters:

  • other (Engine)

    the engine to compare this with.

Returns:

  • (Boolean)


93
94
95
96
# File 'lib/css_compare/css/engine.rb', line 93

def ==(other)
  keys = @engine.keys
  keys.all? { |key| @engine[key] == other.engine[key] }
end

#deep_copyEngine

Creates a deep copy of this object.

Returns:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/css_compare/css/engine.rb', line 190

def deep_copy
  copy = dup
  copy.selectors = @selectors.inject({}) do |result, (k, v)|
    result.update(k => v.deep_copy)
  end
  copy.keyframes = @keyframes.inject({}) do |result, (k, v)|
    result.update(k => v.deep_copy)
  end
  copy.pages = @supports.inject({}) do |result, (k, v)|
    result.update(k => v.deep_copy)
  end
  copy.supports = @supports.inject({}) do |result, (k, v)|
    result.update(k => v.deep_copy)
  end
  copy.engine = {
    :selectors => copy.selectors,
    :keyframes => copy.keyframes,
    :namespaces => copy.namespaces,
    :pages => copy.pages,
    :supports => copy.supports
  }
  copy
end

#evaluate(tree = @tree, parent_query_list = []) ⇒ Void

Note:

the second parameter has been added to ensure multiply nested @media, @support and @import rules.

Computes the values of each declared selector’s properties under each condition declared by the @media directives.

Parameters:

  • tree (Sass::Tree::RootNode) (defaults to: @tree)

    the tree that needs to be evaluates. The default option is the engine’s own tree. However, to support the @import directives, we’ll have to be able to pass a tree in a parameter.

  • parent_query_list (Array<String>) (defaults to: [])

    the list of parent queries

Returns:

  • (Void)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/css_compare/css/engine.rb', line 110

def evaluate(tree = @tree, parent_query_list = [])
  tree = @tree unless tree # if nil is passed explicitly
  tree.children.each do |node|
    if node.is_a?(Sass::Tree::MediaNode)
      process_media_node(node, parent_query_list)
    elsif node.is_a?(Sass::Tree::RuleNode)
      process_rule_node(node, parent_query_list)
    elsif node.is_a?(Sass::Tree::DirectiveNode)
      if node.is_a?(Sass::Tree::SupportsNode)
        process_supports_node(node)
      elsif node.is_a?(Sass::Tree::CssImportNode)
        process_import_node(node, parent_query_list)
      else
        begin
          case node.name
          when '@keyframes'
            process_keyframes_node(node, parent_query_list)
          when '@namespace'
            process_namespace_node(node)
          when '@page'
            process_page_node(node, parent_query_list)
          when '@font-face'
            process_font_face_node(node, parent_query_list)
          else
            # Unsupported DirectiveNodes, that have a name property
            @unsupported << node
          end
        rescue NotImplementedError
          # Unsupported DirectiveNodes, that do not implement a name getter
          @unsupported << node
        end
      end
    elsif node.is_a?(Sass::Tree::CharsetNode)
      process_charset_node(node)
    else
      # Unsupported Node
      @unsupported << node
    end
  end
  @engine[:selectors] = @selectors
  @engine[:font_faces] = @font_faces
  @engine[:keyframes] = @keyframes
  @engine[:namespaces] = @namespaces
  @engine[:pages] = @pages
  @engine[:supports] = @supports
  @engine[:charset] = @charset
  self
end

#to_jsonHash

Returns the inner representation of the processed CSS stylesheet.

Returns:

  • (Hash)


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/css_compare/css/engine.rb', line 163

def to_json
  engine = {
    :selectors => [],
    :font_faces => {},
    :keyframes => [],
    :namespaces => @namespaces,
    :pages => [],
    :supports => [],
    :charset => @charset
  }
  @selectors.inject(engine[:selectors]) { |arr, (_, s)| arr << s.to_json }
  @font_faces.each_with_object(engine[:font_faces]) do |(cond, font_families), arr|
    arr[cond] = font_families.inject([]) do |font_faces, (_, font_family)|
      font_faces + font_family.inject([]) do |sum, (_, font_face)|
        sum << font_face.to_json
      end
    end
  end
  @keyframes.inject(engine[:keyframes]) { |arr, (_, k)| arr << k.to_json }
  @pages.inject(engine[:pages]) { |arr, (_, p)| arr << p.to_json }
  @supports.inject(engine[:supports]) { |arr, (_, s)| arr << s.to_json }
  engine
end