Class: Cataract::Stylesheet

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/cataract/stylesheet.rb,
ext/cataract/cataract.c

Overview

Represents a parsed CSS stylesheet with rule management and merging capabilities.

The Stylesheet class stores parsed CSS rules in a flat array structure that preserves insertion order. Media queries are tracked via an index that maps media query symbols to rule IDs, allowing efficient filtering and serialization.

Examples:

Parse and query CSS

sheet = Cataract::Stylesheet.parse("body { color: red; }")
sheet.size #=> 1
sheet.rules.first.selector #=> "body"

Work with media queries

sheet = Cataract.parse_css("@media print { .footer { color: blue; } }")
sheet.media_queries #=> [:print]
sheet.with_media(:print).first.selector #=> ".footer"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Stylesheet

Create a new empty stylesheet.

Parameters:

  • options (Hash) (defaults to: {})

    Configuration options

Options Hash (options):

  • :import (Boolean, Hash) — default: false

    Enable @import resolution. Pass true for defaults, or a hash with:

    • :allowed_schemes [Array<String>] URI schemes to allow (default: [‘https’])

    • :extensions [Array<String>] File extensions to allow (default: [‘css’])

    • :max_depth [Integer] Maximum import nesting (default: 5)

    • :base_path [String] Base directory for relative imports

  • :io_exceptions (Boolean) — default: true

    Whether to raise exceptions on I/O errors (file not found, network errors, etc.)



42
43
44
45
46
47
48
49
50
51
# File 'lib/cataract/stylesheet.rb', line 42

def initialize(options = {})
  @options = {
    import: false,
    io_exceptions: true
  }.merge(options)

  @rules = [] # Flat array of Rule structs
  @_media_index = {} # Hash: Symbol => Array of rule IDs
  @charset = nil
end

Instance Attribute Details

#charsetString? (readonly)

Returns The @charset declaration if present.

Returns:

  • (String, nil)

    The @charset declaration if present



22
23
24
# File 'lib/cataract/stylesheet.rb', line 22

def charset
  @charset
end

#rulesArray<Rule> (readonly)

Returns Array of parsed CSS rules.

Returns:

  • (Array<Rule>)

    Array of parsed CSS rules



22
23
24
# File 'lib/cataract/stylesheet.rb', line 22

def rules
  @rules
end

Class Method Details

.load_file(filename, base_dir = '.', **options) ⇒ Stylesheet

Load CSS from a file and return a new Stylesheet.

Parameters:

  • filename (String)

    Path to the CSS file

  • base_dir (String) (defaults to: '.')

    Base directory for resolving the filename (default: ‘.’)

  • options (Hash)

    Options passed to Stylesheet.new

Returns:

  • (Stylesheet)

    A new Stylesheet containing the parsed CSS



70
71
72
73
74
# File 'lib/cataract/stylesheet.rb', line 70

def self.load_file(filename, base_dir = '.', **options)
  sheet = new(options)
  sheet.load_file(filename, base_dir)
  sheet
end

.load_uri(uri, **options) ⇒ Stylesheet

Load CSS from a URI and return a new Stylesheet.

Parameters:

  • uri (String)

    URI to load CSS from (http://, https://, or file://)

  • options (Hash)

    Options passed to Stylesheet.new

Returns:

  • (Stylesheet)

    A new Stylesheet containing the parsed CSS



81
82
83
84
85
# File 'lib/cataract/stylesheet.rb', line 81

def self.load_uri(uri, **options)
  sheet = new(options)
  sheet.load_uri(uri, options)
  sheet
end

.parse(css, **options) ⇒ Stylesheet

Parse CSS and return a new Stylesheet

Parameters:

  • css (String)

    CSS string to parse

  • options (Hash)

    Options passed to Stylesheet.new

Returns:



58
59
60
61
62
# File 'lib/cataract/stylesheet.rb', line 58

def self.parse(css, **options)
  sheet = new(options)
  sheet.add_block(css)
  sheet
end

Instance Method Details

#add_block(css, fix_braces: false, media_types: nil) ⇒ self

Add CSS block to stylesheet

TODO: Move to C?

Parameters:

  • css (String)

    CSS string to add

  • fix_braces (Boolean) (defaults to: false)

    Automatically close missing braces

  • media_types (Symbol, Array<Symbol>) (defaults to: nil)

    Optional media query to wrap CSS in

Returns:

  • (self)

    Returns self for method chaining



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
# File 'lib/cataract/stylesheet.rb', line 556

def add_block(css, fix_braces: false, media_types: nil)
  css += ' }' if fix_braces && !css.strip.end_with?('}')

  # Convenience wrapper: wrap in @media if media_types specified
  if media_types
    media_list = Array(media_types).join(', ')
    css = "@media #{media_list} { #{css} }"
  end

  # Resolve @import statements if configured in constructor
  css_to_parse = if @options[:import]
                   ImportResolver.resolve(css, @options[:import])
                 else
                   css
                 end

  # Get current rule ID offset
  offset = @_last_rule_id || 0

  # Parse CSS with C function (returns hash)
  result = Cataract._parse_css(css_to_parse)

  # Merge rules with offsetted IDs
  new_rules = result[:rules]
  new_rules.each do |rule|
    rule.id += offset
    @rules << rule
  end

  # Merge media_index with offsetted IDs
  result[:_media_index].each do |media_sym, rule_ids|
    offsetted_ids = rule_ids.map { |id| id + offset }
    if @_media_index[media_sym]
      @_media_index[media_sym].concat(offsetted_ids)
    else
      @_media_index[media_sym] = offsetted_ids
    end
  end

  # Update last rule ID
  @_last_rule_id = offset + new_rules.length

  # Set charset if not already set
  @charset ||= result[:charset]

  # Track if we have any nesting (for serialization optimization)
  @_has_nesting = result[:_has_nesting]

  self
end

#add_rule(selector:, declarations:, media_types: nil) ⇒ self

Add a single rule

Parameters:

  • selector (String)

    CSS selector

  • declarations (String)

    CSS declarations (property: value pairs)

  • media_types (Symbol, Array<Symbol>) (defaults to: nil)

    Optional media types to wrap rule in

Returns:

  • (self)

    Returns self for method chaining



613
614
615
616
617
# File 'lib/cataract/stylesheet.rb', line 613

def add_rule(selector:, declarations:, media_types: nil)
  # Wrap in CSS syntax and add as block
  css = "#{selector} { #{declarations} }"
  add_block(css, media_types: media_types)
end

#base_onlyStylesheetScope

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

Returns a chainable StylesheetScope that can be further filtered.

Examples:

Get base rules only

sheet.base_only.map(&:selector)

Chain with property filter

sheet.base_only.with_property('color').to_a

Returns:



200
201
202
# File 'lib/cataract/stylesheet.rb', line 200

def base_only
  StylesheetScope.new(self, base_only: true)
end

#base_rulesArray<Rule>

Get all rules without media query (rules that apply to all media)

Returns:

  • (Array<Rule>)

    Rules with no media query



245
246
247
248
249
# File 'lib/cataract/stylesheet.rb', line 245

def base_rules
  # Rules not in any media_index entry
  media_rule_ids = @_media_index.values.flatten.uniq
  @rules.select.with_index { |_rule, idx| !media_rule_ids.include?(idx) }
end

#clear!self

Clear all rules

Returns:

  • (self)

    Returns self for method chaining



407
408
409
410
411
412
413
# File 'lib/cataract/stylesheet.rb', line 407

def clear!
  @rules.clear
  @_media_index.clear
  @charset = nil
  @selectors = nil # Clear memoized cache
  self
end

#convert_colors!Object

Add convert_colors! instance method to Stylesheet



140
# File 'ext/cataract_color/color_conversion.c', line 140

VALUE rb_stylesheet_convert_colors(int argc, VALUE *argv, VALUE self);

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

Iterate over all rules (required by Enumerable).

Yields both selector-based rules (Rule) and at-rules (AtRule). Use rule.selector? to filter for selector-based rules only.

Examples:

Iterate over all rules

sheet.each { |rule| puts rule.selector }

Filter to selector-based rules only

sheet.select(&:selector?).each { |rule| puts rule.specificity }

Yields:

  • (rule)

    Block to execute for each rule

Yield Parameters:

Returns:

  • (Enumerator)

    Returns enumerator if no block given



101
102
103
104
105
# File 'lib/cataract/stylesheet.rb', line 101

def each(&)
  return enum_for(:each) unless block_given?

  @rules.each(&)
end

#empty?Boolean

Check if stylesheet is empty

Returns:

  • (Boolean)

    true if no rules



400
401
402
# File 'lib/cataract/stylesheet.rb', line 400

def empty?
  @rules.empty?
end

#inspectObject



629
630
631
632
633
634
635
636
637
638
# File 'lib/cataract/stylesheet.rb', line 629

def inspect
  total_rules = size
  if total_rules.zero?
    '#<Cataract::Stylesheet empty>'
  else
    preview = @rules.first(3).map(&:selector).join(', ')
    more = total_rules > 3 ? ', ...' : ''
    "#<Cataract::Stylesheet #{total_rules} rules: #{preview}#{more}>"
  end
end

#load_file(filename, base_dir = '.', _media_types = :all) ⇒ self

Load CSS from a local file and add to this stylesheet.

Parameters:

  • filename (String)

    Path to the CSS file

  • base_dir (String) (defaults to: '.')

    Base directory for resolving the filename (default: ‘.’)

Returns:

  • (self)

    Returns self for method chaining



420
421
422
423
424
425
426
427
# File 'lib/cataract/stylesheet.rb', line 420

def load_file(filename, base_dir = '.', _media_types = :all)
  # Normalize file path and convert to file:// URI
  file_path = File.expand_path(filename, base_dir)
  file_uri = "file://#{file_path}"

  # Delegate to load_uri which handles imports and base_path
  load_uri(file_uri)
end

#load_uri(uri, options = {}) ⇒ self

Load CSS from a URI and add to this stylesheet.

Parameters:

  • uri (String)

    URI to load CSS from (http://, https://, or file://)

  • options (Hash) (defaults to: {})

    Additional options

Returns:

  • (self)

    Returns self for method chaining



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/cataract/stylesheet.rb', line 434

def load_uri(uri, options = {})
  require 'uri'
  require 'net/http'

  uri_obj = URI(uri)
  css_content = nil
  file_path = nil

  case uri_obj.scheme
  when 'http', 'https'
    response = Net::HTTP.get_response(uri_obj)
    unless response.is_a?(Net::HTTPSuccess)
      raise IOError, "Failed to load URI: #{uri} (#{response.code} #{response.message})"
    end

    css_content = response.body
  when 'file', nil
    # file:// URI or relative path
    path = uri_obj.scheme == 'file' ? uri_obj.path : uri
    # Handle base_uri if provided
    if options[:base_uri]
      base = URI(options[:base_uri])
      path = File.join(base.path, path) if base.scheme == 'file' || base.scheme.nil?
    end
    file_path = File.expand_path(path)

    # If imports are enabled and base_path not already set, set it for resolving relative imports
    if @options[:import].is_a?(Hash) && @options[:import][:base_path].nil?
      file_dir = File.dirname(file_path)
      @options[:import] = @options[:import].merge(base_path: file_dir)
    end

    css_content = File.read(file_path)
  else
    raise ArgumentError, "Unsupported URI scheme: #{uri_obj.scheme}"
  end

  add_block(css_content)
  self
rescue Errno::ENOENT
  raise IOError, "File not found: #{uri}" if @options[:io_exceptions]

  self
rescue StandardError => e
  raise IOError, "Error loading URI: #{uri} - #{e.message}" if @options[:io_exceptions]

  self
end

#media_queriesArray<Symbol>

Get all unique media query symbols

Returns:

  • (Array<Symbol>)

    Array of unique media query symbols



254
255
256
# File 'lib/cataract/stylesheet.rb', line 254

def media_queries
  @_media_index.keys
end

#mergeStylesheet

Merge all rules in this stylesheet according to CSS cascade rules

Applies specificity and !important precedence rules to compute the final set of declarations. Also recreates shorthand properties from longhand properties where possible.

Returns:

  • (Stylesheet)

    New stylesheet with a single merged rule



647
648
649
650
# File 'lib/cataract/stylesheet.rb', line 647

def merge
  # C function handles everything - returns new Stylesheet
  Cataract.merge(self)
end

#merge!self

Merge rules in-place, mutating the receiver.

This is a convenience method that updates the stylesheet’s internal rules and media_index with the merged result. The Stylesheet object itself is mutated (same object_id), but note that the C merge function still allocates new arrays internally.

Returns:

  • (self)

    Returns self for method chaining



660
661
662
663
664
665
666
# File 'lib/cataract/stylesheet.rb', line 660

def merge!
  merged = Cataract.merge(self)
  @rules = merged.instance_variable_get(:@rules)
  @_media_index = merged.instance_variable_get(:@_media_index)
  @_has_nesting = merged.instance_variable_get(:@_has_nesting)
  self
end

#remove_rules!(selector: nil, media_types: nil) ⇒ self

Remove rules matching criteria

Examples:

Remove all rules with a specific selector

sheet.remove_rules!(selector: '.header')

Remove rules from specific media type

sheet.remove_rules!(selector: '.header', media_types: :screen)

Remove all rules from a media type

sheet.remove_rules!(media_types: :print)

Parameters:

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

    Selector to match (nil matches all)

  • media_types (Symbol, Array<Symbol>, nil) (defaults to: nil)

    Media types to filter by (nil matches all)

Returns:

  • (self)

    Returns self for method chaining



497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
# File 'lib/cataract/stylesheet.rb', line 497

def remove_rules!(selector: nil, media_types: nil)
  # Normalize media_types to array
  filter_media = media_types ? Array(media_types).map(&:to_sym) : nil

  # Find rules to remove
  rules_to_remove = Set.new
  @rules.each_with_index do |rule, rule_id|
    # Check selector match
    next if selector && rule.selector != selector

    # Check media type match
    if filter_media
      rule_media_types = @_media_index.select { |_media, ids| ids.include?(rule_id) }.keys
      # Extract individual media types from complex queries
      individual_types = rule_media_types.flat_map { |key| Cataract.parse_media_types(key) }.uniq

      # If rule is not in any media query (base rule), skip if filtering by media
      if individual_types.empty?
        next unless filter_media.include?(:all)
      else
        # Check if rule's media types intersect with filter
        next unless individual_types.intersect?(filter_media)
      end
    end

    rules_to_remove << rule_id
  end

  # Remove rules and update media_index
  rules_to_remove.sort.reverse_each do |rule_id|
    @rules.delete_at(rule_id)

    # Remove from media_index and update IDs for rules after this one
    @_media_index.each_value do |ids|
      ids.delete(rule_id)
      # Decrement IDs greater than removed ID
      ids.map! { |id| id > rule_id ? id - 1 : id }
    end
  end

  # Clean up empty media_index entries
  @_media_index.delete_if { |_media, ids| ids.empty? }

  # Update rule IDs in remaining rules
  @rules.each_with_index { |rule, new_id| rule.id = new_id }

  # Clear memoized cache
  @selectors = nil

  self
end

#selectorsArray<String>

Get all selectors

Returns:

  • (Array<String>)

    Array of all selectors



261
262
263
# File 'lib/cataract/stylesheet.rb', line 261

def selectors
  @selectors ||= @rules.map(&:selector)
end

#sizeInteger Also known as: length, rules_count

Get number of rules

Returns:

  • (Integer)

    Number of rules



391
392
393
# File 'lib/cataract/stylesheet.rb', line 391

def size
  @rules.length
end

#to_formatted_s(media: :all) ⇒ String

Serialize to formatted CSS string with indentation and newlines.

Converts the stylesheet to a human-readable CSS string with proper indentation. Rules are formatted with each declaration on its own line, and media queries are properly indented. Optionally filters output to specific media queries.

Examples:

Get all CSS formatted

sheet.to_formatted_s
# => "body {\n  color: black;\n}\n@media print {\n  .footer {\n    color: red;\n  }\n}\n"

Filter to specific media type

sheet.to_formatted_s(:print)

Parameters:

  • which_media (Symbol, Array<Symbol>)

    Optional media filter (default: :all)

    • :all - Output all rules including base rules and all media queries

    • :screen, :print, etc. - Output only rules from specified media query

    • :screen, :print
      • Output rules from multiple media queries

Returns:

  • (String)

    Formatted CSS string

See Also:



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/cataract/stylesheet.rb', line 346

def to_formatted_s(media: :all)
  which_media = media
  # Normalize to array for consistent filtering
  which_media_array = which_media.is_a?(Array) ? which_media : [which_media]

  # If :all is present, return everything (no filtering)
  if which_media_array.include?(:all)
    Cataract._stylesheet_to_formatted_s(@rules, @_media_index, @charset, @_has_nesting || false)
  else
    # Collect all rule IDs that match the requested media types
    matching_rule_ids = Set.new

    # Include rules not in any media query (they apply to all media)
    media_rule_ids = @_media_index.values.flatten.uniq
    all_rule_ids = (0...@rules.length).to_a
    non_media_rule_ids = all_rule_ids - media_rule_ids
    matching_rule_ids.merge(non_media_rule_ids)

    # Include rules from requested media types
    which_media_array.each do |media_sym|
      if @_media_index[media_sym]
        matching_rule_ids.merge(@_media_index[media_sym])
      end
    end

    # Build filtered rules array (keep original IDs, no recreation needed)
    filtered_rules = matching_rule_ids.sort.map! { |rule_id| @rules[rule_id] }

    # Build filtered media_index (keep original IDs, just filter to included rules)
    filtered_media_index = {}
    which_media_array.each do |media_sym|
      if @_media_index[media_sym]
        filtered_media_index[media_sym] = @_media_index[media_sym] & matching_rule_ids.to_a
      end
    end

    # C serialization with filtered data
    # Note: Filtered rules might still contain nesting, so pass the flag
    Cataract._stylesheet_to_formatted_s(filtered_rules, filtered_media_index, @charset, @_has_nesting || false)
  end
end

#to_hHash

Convert to hash

Returns:

  • (Hash)

    Hash representation



622
623
624
625
626
627
# File 'lib/cataract/stylesheet.rb', line 622

def to_h
  {
    rules: @rules,
    charset: @charset
  }
end

#to_s(media: :all) ⇒ String Also known as: to_css

Serialize to CSS string

Converts the stylesheet to a CSS string. Optionally filters output to only include rules from specific media queries.

Important: When filtering to specific media types, base rules (rules not inside any @media block) are NOT included. Only rules explicitly inside the requested @media queries are output. Use :all to include base rules.

Examples:

Get all CSS

sheet.to_s                 # => "body { color: black; } @media print { .footer { color: red; } }"
sheet.to_s(media: :all)    # => "body { color: black; } @media print { .footer { color: red; } }"

Filter to specific media type (excludes base rules)

sheet.to_s(media: :print)  # => "@media print { .footer { color: red; } }"
# Note: base rules like "body { color: black; }" are NOT included

Filter to multiple media types

sheet.to_s(media: [:screen, :print])  # => "@media screen { ... } @media print { ... }"

Parameters:

  • media (Symbol, Array<Symbol>) (defaults to: :all)

    Media type(s) to include (default: :all)

    • :all - Output all rules including base rules and all media queries

    • :screen, :print, etc. - Output only rules from specified media query

    • :screen, :print
      • Output rules from multiple media queries

Returns:

  • (String)

    CSS string



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/cataract/stylesheet.rb', line 290

def to_s(media: :all)
  which_media = media
  # Normalize to array for consistent filtering
  which_media_array = which_media.is_a?(Array) ? which_media : [which_media]

  # If :all is present, return everything (no filtering)
  if which_media_array.include?(:all)
    Cataract._stylesheet_to_s(@rules, @_media_index, @charset, @_has_nesting || false)
  else
    # Collect all rule IDs that match the requested media types
    matching_rule_ids = Set.new
    which_media_array.each do |media_sym|
      if @_media_index[media_sym]
        matching_rule_ids.merge(@_media_index[media_sym])
      end
    end

    # Build filtered rules array (keep original IDs, no recreation needed)
    filtered_rules = matching_rule_ids.sort.map! { |rule_id| @rules[rule_id] }

    # Build filtered media_index (keep original IDs, just filter to included rules)
    filtered_media_index = {}
    which_media_array.each do |media_sym|
      if @_media_index[media_sym]
        filtered_media_index[media_sym] = @_media_index[media_sym] & matching_rule_ids.to_a
      end
    end

    # C serialization with filtered data
    # Note: Filtered rules might still contain nesting, so pass the flag
    Cataract._stylesheet_to_s(filtered_rules, filtered_media_index, @charset, @_has_nesting || false)
  end
end

#with_at_rule_type(type) ⇒ StylesheetScope

Filter by at-rule type.

Returns a chainable StylesheetScope that can be further filtered.

Examples:

Find all @keyframes

sheet.with_at_rule_type(:keyframes).map(&:selector)

Find all @font-face

sheet.with_at_rule_type(:font_face).to_a

Chain with media filter

sheet.with_media(:screen).with_at_rule_type(:keyframes).to_a

Parameters:

  • type (Symbol)

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

Returns:



219
220
221
# File 'lib/cataract/stylesheet.rb', line 219

def with_at_rule_type(type)
  StylesheetScope.new(self, at_rule_type: type)
end

#with_important(property = nil) ⇒ StylesheetScope

Filter to rules with !important declarations.

Returns a chainable StylesheetScope that can be further filtered.

Examples:

Find all rules with any !important

sheet.with_important.map(&:selector)

Find rules with color !important

sheet.with_important('color').to_a

Chain with media filter

sheet.with_media(:screen).with_important.to_a

Parameters:

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

    Optional property name to match

Returns:



238
239
240
# File 'lib/cataract/stylesheet.rb', line 238

def with_important(property = nil)
  StylesheetScope.new(self, important: true, important_property: property)
end

#with_media(media) ⇒ StylesheetScope

Filter rules by media query symbol(s).

Returns a chainable StylesheetScope that can be further filtered.

Examples:

Get print media rules

sheet.with_media(:print).each { |rule| puts rule.selector }
sheet.with_media(:print).select(&:selector?).map(&:selector)

Get rules from multiple media queries

sheet.with_media([:screen, :print]).map(&:selector)

Chain filters

sheet.with_media(:print).with_specificity(10..).to_a

Parameters:

  • media (Symbol, Array<Symbol>)

    Media query symbol(s) to filter by

Returns:



123
124
125
# File 'lib/cataract/stylesheet.rb', line 123

def with_media(media)
  StylesheetScope.new(self, media: media)
end

#with_property(property, value = nil) ⇒ StylesheetScope

Filter rules by CSS property name and optional value.

Returns a chainable StylesheetScope that can be further filtered.

Examples:

Find all rules with color property

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

Find rules with position: absolute

sheet.with_property('position', 'absolute').to_a

Chain with media filter

sheet.with_media(:screen).with_property('z-index').to_a

Parameters:

  • property (String)

    CSS property name to match

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

    Optional property value to match

Returns:



185
186
187
# File 'lib/cataract/stylesheet.rb', line 185

def with_property(property, value = nil)
  StylesheetScope.new(self, property: property, property_value: value)
end

#with_selector(selector) ⇒ StylesheetScope

Filter rules by CSS selector.

Returns a chainable StylesheetScope that can be further filtered. Supports both exact string matching and regular expression patterns.

Examples:

Find body rules (exact match)

sheet.with_selector('body').to_a

Find all .btn-* classes (pattern match)

sheet.with_selector(/\.btn-/).map(&:selector)

Find body rules in print media

sheet.with_media(:print).with_selector('body').each { |r| puts r }

Chain multiple filters

sheet.with_selector('.header').with_specificity(10..).to_a

Parameters:

  • selector (String, Regexp)

    CSS selector to match (exact or pattern)

Returns:



165
166
167
# File 'lib/cataract/stylesheet.rb', line 165

def with_selector(selector)
  StylesheetScope.new(self, selector: selector)
end

#with_specificity(specificity) ⇒ StylesheetScope

Filter rules by CSS specificity.

Returns a chainable StylesheetScope that can be further filtered.

Examples:

Get high-specificity rules

sheet.with_specificity(100..).each { |rule| puts rule.selector }

Get exact specificity

sheet.with_specificity(10).map(&:selector)

Chain with media filter

sheet.with_media(:print).with_specificity(10..50).to_a

Parameters:

  • specificity (Integer, Range)

    Specificity value or range

Returns:



142
143
144
# File 'lib/cataract/stylesheet.rb', line 142

def with_specificity(specificity)
  StylesheetScope.new(self, specificity: specificity)
end