Class: Cataract::Rule

Inherits:
Struct
  • Object
show all
Defined in:
lib/cataract/rule.rb,
lib/cataract/rule.rb

Overview

Rules are created by the parser and stored in Stylesheet objects. Each rule contains:

  • An ID (position in the stylesheet)
  • A CSS selector string
  • An array of Declaration structs
  • A specificity value (calculated lazily)
  • Parent rule ID for nested rules (nil if top-level)
  • Nesting style (0=implicit, 1=explicit, nil=not nested)

Media query information is stored separately in Stylesheet's media_index.

Examples:

Access rule properties

sheet = Cataract.parse_css("body { color: red; font-size: 14px; }")
rule = sheet.rules.first
rule.selector #=> "body"
rule.specificity #=> 1
rule.declarations.length #=> 2

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#declarationsArray<Declaration>

Array of CSS property declarations

Returns:

  • (Array<Declaration>)

    the current value of declarations



29
30
31
# File 'lib/cataract/rule.rb', line 29

def declarations
  @declarations
end

#idInteger

The rule's position in the stylesheet (0-indexed)

Returns:

  • (Integer)

    the current value of id



29
30
31
# File 'lib/cataract/rule.rb', line 29

def id
  @id
end

#media_query_idInteger?

ID of the MediaQuery this rule belongs to (nil if not in media query)

Returns:

  • (Integer, nil)

    the current value of media_query_id



29
30
31
# File 'lib/cataract/rule.rb', line 29

def media_query_id
  @media_query_id
end

#nesting_styleInteger?

0=implicit, 1=explicit, nil=not nested

Returns:

  • (Integer, nil)

    the current value of nesting_style



29
30
31
# File 'lib/cataract/rule.rb', line 29

def nesting_style
  @nesting_style
end

#parent_rule_idInteger?

Parent rule ID for nested rules

Returns:

  • (Integer, nil)

    the current value of parent_rule_id



29
30
31
# File 'lib/cataract/rule.rb', line 29

def parent_rule_id
  @parent_rule_id
end

#selectorString

The CSS selector (e.g., "body", ".class", "#id")

Returns:

  • (String)

    the current value of selector



29
30
31
# File 'lib/cataract/rule.rb', line 29

def selector
  @selector
end

#selector_list_idInteger?

ID linking rules from same selector list (e.g., "h1, h2")

Returns:

  • (Integer, nil)

    the current value of selector_list_id



29
30
31
# File 'lib/cataract/rule.rb', line 29

def selector_list_id
  @selector_list_id
end

#specificityInteger

Get the CSS specificity value for this rule's selector.

Specificity is calculated lazily on first access and then cached. The calculation follows the CSS specification:

  • Inline styles: not applicable to parsed stylesheets
  • IDs: count of #id selectors
  • Classes/attributes/pseudo-classes: count of .class, [attr], :pseudo
  • Elements/pseudo-elements: count of element, ::pseudo

Examples:

Get specificity

rule = Cataract.parse_css("#header .nav a").rules.first
rule.specificity #=> 111 (1 ID + 1 class + 1 element)

Returns:

  • (Integer)

    CSS specificity value



29
30
31
# File 'lib/cataract/rule.rb', line 29

def specificity
  @specificity
end

Class Method Details

.make(id:, selector:, declarations:, specificity: nil, parent_rule_id: nil, nesting_style: nil, selector_list_id: nil, media_query_id: nil) ⇒ Rule

Factory method for creating Rules with keyword arguments (for tests/readability). C code and hot paths should use positional arguments directly via Rule.new.

Examples:

Create a rule with keyword arguments

Rule.make(
  id: 0,
  selector: '.foo',
  declarations: [Declaration.new('color', 'red', false)],
  specificity: 10,
  parent_rule_id: nil,
  nesting_style: nil,
  selector_list_id: nil,
  media_query_id: nil
)

Parameters:

  • id (Integer)

    The rule's position in the stylesheet

  • selector (String)

    CSS selector

  • declarations (Array<Declaration>)

    Array of declarations

  • specificity (Integer, nil) (defaults to: nil)

    Specificity value (nil to calculate lazily)

  • parent_rule_id (Integer, nil) (defaults to: nil)

    Parent rule ID for nested rules

  • nesting_style (Integer, nil) (defaults to: nil)

    Nesting style (0=implicit, 1=explicit, nil=not nested)

  • selector_list_id (Integer, nil) (defaults to: nil)

    Selector list ID for grouping

  • media_query_id (Integer, nil) (defaults to: nil)

    MediaQuery ID for rules in media queries

Returns:

  • (Rule)

    New rule instance



65
66
67
# File 'lib/cataract/rule.rb', line 65

def self.make(id:, selector:, declarations:, specificity: nil, parent_rule_id: nil, nesting_style: nil, selector_list_id: nil, media_query_id: nil)
  new(id, selector, declarations, specificity, parent_rule_id, nesting_style, selector_list_id, media_query_id)
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Compare rules for logical equality based on CSS semantics.

Two rules are equal if they have the same selector and declarations. Shorthand properties are expanded before comparison, so margin: 10px equals margin-top: 10px; margin-right: 10px; ...

Internal implementation details (id, specificity) are not considered since they don't affect the CSS semantics.

Can also compare against a CSS string, which is parsed and compared.

Parameters:

  • other (Object)

    Object to compare with (Rule or String)

Returns:

  • (Boolean)

    true if rules have same selector and declarations



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/cataract/rule.rb', line 176

def ==(other)
  case other
  when Rule
    return false unless selector == other.selector

    expanded_declarations == other.expanded_declarations
  when String
    # Parse CSS string and compare to first rule
    parsed = Cataract.parse_css(other)
    return false unless parsed.rules.size == 1

    self == parsed.rules.first
  else
    false
  end
end

#at_rule?Boolean

Check if this is an at-rule.

Returns:

  • (Boolean)

    Always returns false for Rule objects



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

def at_rule?
  false
end

#at_rule_type?(_type) ⇒ Boolean

Check if this is a specific at-rule type.

Parameters:

  • _type (Symbol)

    At-rule type (e.g., :keyframes, :font_face)

Returns:

  • (Boolean)

    Always returns false for Rule objects



114
115
116
# File 'lib/cataract/rule.rb', line 114

def at_rule_type?(_type)
  false
end

#has_important?(property = nil) ⇒ Boolean

Check if this rule has any !important declarations, optionally for a specific property.

Examples:

Check for any !important

rule.has_important? #=> true

Check for color !important

rule.has_important?('color') #=> true

Parameters:

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

    Optional property name to match

Returns:

  • (Boolean)

    true if rule has matching !important declaration



155
156
157
158
159
160
161
# File 'lib/cataract/rule.rb', line 155

def has_important?(property = nil)
  if property
    declarations.any? { |d| d.property == property && d.important }
  else
    declarations.any?(&:important)
  end
end

#has_property?(property, value = nil, prefix_match: false) ⇒ Boolean

Check if this rule has a declaration with the specified property and optional value.

Examples:

Check for color property

rule.has_property?('color') #=> true

Check for specific property value

rule.has_property?('color', 'red') #=> true

Check for any margin-related property

rule.has_property?('margin', prefix_match: true) #=> true if has margin, margin-top, etc.

Parameters:

  • property (String)

    CSS property name to match

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

    Optional value to match

  • prefix_match (Boolean) (defaults to: false)

    Whether to match by prefix (default: false)

Returns:

  • (Boolean)

    true if rule has matching declaration



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/cataract/rule.rb', line 133

def has_property?(property, value = nil, prefix_match: false)
  declarations.any? do |decl|
    property_matches = if prefix_match
                         decl.property.start_with?(property)
                       else
                         decl.property == property
                       end
    value_matches = value.nil? || decl.value == value
    property_matches && value_matches
  end
end

#hashInteger

Generate hash code for this rule.

Hash is based on selector and expanded declarations to match the equality semantics. This allows rules to be used as Hash keys or in Sets correctly.

rubocop:disable Naming/MemoizedInstanceVariableName

Returns:

  • (Integer)

    hash code



202
203
204
# File 'lib/cataract/rule.rb', line 202

def hash
  @_hash ||= [self.class, selector, expanded_declarations].hash
end

#selector?Boolean

Check if this is a selector-based rule (vs an at-rule like @keyframes).

Returns:

  • (Boolean)

    Always returns true for Rule objects



99
100
101
# File 'lib/cataract/rule.rb', line 99

def selector?
  true
end