Class: Cataract::StylesheetScope

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/cataract/stylesheet_scope.rb

Overview

Chainable query scope for filtering Stylesheet rules.

Inspired by ActiveRecord's Relation, StylesheetScope provides a fluent interface for filtering and querying CSS rules. Scopes are lazy - filters are only applied during iteration.

Examples:

Chaining filters

sheet.with_media(:print).with_specificity(10..).select(&:selector?)

Inspect shows results

scope = sheet.with_media(:screen)
scope.inspect #=> "#<Cataract::StylesheetScope [...]>"

Instance Method Summary collapse

Constructor Details

#initialize(stylesheet, filters = {}) ⇒ StylesheetScope

Returns a new instance of StylesheetScope.



20
21
22
23
# File 'lib/cataract/stylesheet_scope.rb', line 20

def initialize(stylesheet, filters = {})
  @stylesheet = stylesheet
  @filters = filters
end

Instance Method Details

#==(other) ⇒ Boolean

Compare the scope to another object.

Forces evaluation of the scope and compares as an array.

Parameters:

  • other (Object)

    Object to compare with

Returns:

  • (Boolean)

    true if equal



236
237
238
# File 'lib/cataract/stylesheet_scope.rb', line 236

def ==(other)
  to_a == other
end

#[](index) ⇒ Rule, ...

Access a rule by index.

Forces evaluation of the scope.

Parameters:

  • index (Integer)

    Index of the rule to access

Returns:

  • (Rule, AtRule, nil)

    Rule at the given index, or nil



217
218
219
# File 'lib/cataract/stylesheet_scope.rb', line 217

def [](index)
  to_a[index]
end

#base_onlyStylesheetScope

Filter to only base rules (rules not inside any @media query).

Examples:

Get base rules only

sheet.base_only.map(&:selector)
sheet.base_only.with_property('color').to_a

Returns:



92
93
94
# File 'lib/cataract/stylesheet_scope.rb', line 92

def base_only
  StylesheetScope.new(@stylesheet, @filters.merge(base_only: true))
end

#each {|rule| ... } ⇒ Enumerator

Iterate over filtered rules.

Yields:

  • (rule)

    Each rule matching the filters

Yield Parameters:

Returns:

  • (Enumerator)

    Enumerator if no block given



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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/cataract/stylesheet_scope.rb', line 129

def each
  return enum_for(:each) unless block_given?

  # Get base rules set
  rules = if @filters[:base_only]
            # Get rules not in any media query (media_query_id is nil)
            @stylesheet.rules.select { |r| r.is_a?(Rule) && r.media_query_id.nil? }
          elsif @filters[:media]
            media_array = Array(@filters[:media])

            # :all is a special case meaning "all rules"
            if media_array.include?(:all)
              @stylesheet.rules
            else
              # Use media_index for efficient lookup (it handles compound media queries)
              matching_rule_ids = Set.new
              media_array.each do |media_sym|
                rule_ids = @stylesheet.media_index[media_sym]
                matching_rule_ids.merge(rule_ids) if rule_ids
              end
              # Filter rules by ID
              @stylesheet.rules.select { |r| matching_rule_ids.include?(r.id) }
            end
          else
            @stylesheet.rules
          end

  # Apply additional filters during iteration
  rules.each do |rule|
    # Specificity filter
    if @filters[:specificity]
      next if rule.specificity.nil? # AtRules have nil specificity
      next unless case @filters[:specificity]
                  when Range
                    @filters[:specificity].cover?(rule.specificity)
                  else
                    @filters[:specificity] == rule.specificity
                  end
    end

    # Selector filter (String or Regexp)
    if @filters[:selector] && !case @filters[:selector]
                               when String
                                 rule.selector == @filters[:selector]
                               when Regexp
                                 @filters[:selector] =~ rule.selector
                               end
      next
    end

    # Property filter
    if @filters[:property]
      prefix_match = @filters.fetch(:property_prefix_match, false)
      unless rule.has_property?(@filters[:property], @filters[:property_value], prefix_match: prefix_match)
        next
      end
    end

    # At-rule type filter
    if @filters[:at_rule_type] && !rule.at_rule_type?(@filters[:at_rule_type])
      next
    end

    # Important filter
    if @filters[:important] && !rule.has_important?(@filters[:important_property])
      next
    end

    yield rule
  end
end

#empty?Boolean

Check if the scope has no matching rules.

Forces evaluation of the scope.

Returns:

  • (Boolean)

    true if no rules match the filters



226
227
228
# File 'lib/cataract/stylesheet_scope.rb', line 226

def empty?
  to_a.empty?
end

#inspectString

Human-readable representation showing filtered results.

Forces evaluation of the scope and displays results.

Returns:

  • (String)

    Inspection string



256
257
258
259
260
261
262
263
264
265
# File 'lib/cataract/stylesheet_scope.rb', line 256

def inspect
  rules = to_a
  if rules.empty?
    '#<Cataract::StylesheetScope []>'
  else
    preview = rules.first(3).map(&:selector).join(', ')
    more = rules.length > 3 ? ', ...' : ''
    "#<Cataract::StylesheetScope [#{preview}#{more}] (#{rules.length} rules)>"
  end
end

#sizeInteger Also known as: length

Get the number of rules matching the filters.

Forces evaluation of the scope.

Returns:

  • (Integer)

    Number of matching rules



206
207
208
# File 'lib/cataract/stylesheet_scope.rb', line 206

def size
  to_a.size
end

#to_aryArray

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Implicit conversion to Array for Ruby coercion.

This allows StylesheetScope to be used transparently as an Array in comparisons and other operations.

Returns:

  • (Array)

    Array of matching rules



247
248
249
# File 'lib/cataract/stylesheet_scope.rb', line 247

def to_ary
  to_a
end

#with_at_rule_type(type) ⇒ StylesheetScope

Filter by at-rule type.

Examples:

Find all @keyframes

sheet.with_at_rule_type(:keyframes)

Find all @font-face

sheet.with_at_rule_type(:font_face)

Parameters:

  • type (Symbol)

    At-rule type to match (:keyframes, :font_face, etc.)

Returns:



106
107
108
# File 'lib/cataract/stylesheet_scope.rb', line 106

def with_at_rule_type(type)
  StylesheetScope.new(@stylesheet, @filters.merge(at_rule_type: type))
end

#with_important(property = nil) ⇒ StylesheetScope

Filter to rules with !important declarations.

Examples:

Find all rules with any !important

sheet.with_important

Find rules with color !important

sheet.with_important('color')

Parameters:

  • property (String, nil) (defaults to: nil)

    Optional property name to match

Returns:



120
121
122
# File 'lib/cataract/stylesheet_scope.rb', line 120

def with_important(property = nil)
  StylesheetScope.new(@stylesheet, @filters.merge(important: true, important_property: property))
end

#with_media(media) ⇒ StylesheetScope

Filter by media query symbol(s).

Examples:

sheet.with_media(:print).with_media(:screen) # overwrites to :screen

Parameters:

  • media (Symbol, Array<Symbol>)

    Media query symbol(s)

Returns:



32
33
34
# File 'lib/cataract/stylesheet_scope.rb', line 32

def with_media(media)
  StylesheetScope.new(@stylesheet, @filters.merge(media: media))
end

#with_property(property, value = nil, prefix_match: false) ⇒ StylesheetScope

Filter by CSS property name and optional value.

Examples:

Find rules with color property

sheet.with_property('color')

Find rules with specific property value

sheet.with_property('position', 'absolute')
sheet.with_property('color', 'red')

Find all margin-related properties (margin, margin-top, etc.)

sheet.with_property('margin', prefix_match: true)

Parameters:

  • property (String)

    CSS property name to match

  • value (String, nil) (defaults to: nil)

    Optional property value to match

  • prefix_match (Boolean) (defaults to: false)

    Whether to match by prefix (default: false)

Returns:



81
82
83
# File 'lib/cataract/stylesheet_scope.rb', line 81

def with_property(property, value = nil, prefix_match: false)
  StylesheetScope.new(@stylesheet, @filters.merge(property: property, property_value: value, property_prefix_match: prefix_match))
end

#with_selector(selector) ⇒ StylesheetScope

Filter by CSS selector.

Examples:

Exact string match

sheet.with_selector('body')
sheet.with_media(:print).with_selector('.header')

Pattern matching

sheet.with_selector(/\.btn-/)  # All .btn-* classes
sheet.with_selector(/^#/)      # All ID selectors

Parameters:

  • selector (String, Regexp)

    CSS selector to match (exact string or pattern)

Returns:



61
62
63
# File 'lib/cataract/stylesheet_scope.rb', line 61

def with_selector(selector)
  StylesheetScope.new(@stylesheet, @filters.merge(selector: selector))
end

#with_specificity(specificity) ⇒ StylesheetScope

Filter by CSS specificity.

Examples:

sheet.with_specificity(10)      # exactly 10
sheet.with_specificity(10..)    # 10 or higher
sheet.with_specificity(5...10)  # between 5 and 9

Parameters:

  • specificity (Integer, Range)

    Specificity value or range

Returns:



45
46
47
# File 'lib/cataract/stylesheet_scope.rb', line 45

def with_specificity(specificity)
  StylesheetScope.new(@stylesheet, @filters.merge(specificity: specificity))
end