cataract 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,6 +19,7 @@ module Cataract
19
19
  #
20
20
  # @attr_reader [Array<Rule>] rules Array of parsed CSS rules
21
21
  # @attr_reader [String, nil] charset The @charset declaration if present
22
+ # @attr_reader [Array<ImportStatement>] imports Array of @import statements
22
23
  class Stylesheet
23
24
  include Enumerable
24
25
 
@@ -28,6 +29,9 @@ module Cataract
28
29
  # @return [String, nil] The @charset declaration if present
29
30
  attr_reader :charset
30
31
 
32
+ # @return [Array<ImportStatement>] Array of @import statements
33
+ attr_reader :imports
34
+
31
35
  # Create a new empty stylesheet.
32
36
  #
33
37
  # @param options [Hash] Configuration options
@@ -48,11 +52,27 @@ module Cataract
48
52
  @rules = [] # Flat array of Rule structs
49
53
  @_media_index = {} # Hash: Symbol => Array of rule IDs
50
54
  @charset = nil
55
+ @imports = [] # Array of ImportStatement objects
51
56
  @_has_nesting = nil # Set by parser (nil or boolean)
52
57
  @_last_rule_id = nil # Tracks next rule ID for add_block
53
58
  @selectors = nil # Memoized cache of selectors
54
59
  end
55
60
 
61
+ # Initialize copy for proper deep duplication.
62
+ #
63
+ # Ensures that dup/clone creates a proper deep copy of the stylesheet,
64
+ # duplicating internal arrays and hashes so mutations don't affect the original.
65
+ #
66
+ # @param source [Stylesheet] Source stylesheet being copied
67
+ def initialize_copy(source)
68
+ super
69
+ @rules = source.instance_variable_get(:@rules).dup
70
+ @imports = source.instance_variable_get(:@imports).dup
71
+ @_media_index = source.instance_variable_get(:@_media_index).transform_values(&:dup)
72
+ @selectors = nil # Clear memoized cache
73
+ @_hash = nil # Clear cached hash
74
+ end
75
+
56
76
  # Parse CSS and return a new Stylesheet
57
77
  #
58
78
  # @param css [String] CSS string to parse
@@ -107,6 +127,12 @@ module Cataract
107
127
  @rules.each(&)
108
128
  end
109
129
 
130
+ def [](offset)
131
+ return unless @rules
132
+
133
+ @rules[offset]
134
+ end
135
+
110
136
  # Filter rules by media query symbol(s).
111
137
  #
112
138
  # Returns a chainable StylesheetScope that can be further filtered.
@@ -485,37 +511,66 @@ module Cataract
485
511
  self
486
512
  end
487
513
 
488
- # Remove rules matching criteria
514
+ # Remove rules from the stylesheet
489
515
  #
490
- # @param selector [String, nil] Selector to match (nil matches all)
491
- # @param media_types [Symbol, Array<Symbol>, nil] Media types to filter by (nil matches all)
516
+ # @param rules_or_css [String, Rule, AtRule, Array<Rule, AtRule>] Rules to remove.
517
+ # Can be a CSS string to parse (selectors will be matched), a single Rule/AtRule object,
518
+ # or an array of Rule/AtRule objects.
519
+ # @param media_types [Symbol, Array<Symbol>, nil] Optional media types to filter removal.
520
+ # Only removes rules that match these media types. Pass :all to include base rules.
492
521
  # @return [self] Returns self for method chaining
493
522
  #
494
- # @example Remove all rules with a specific selector
495
- # sheet.remove_rules!(selector: '.header')
523
+ # @example Remove rules by CSS string
524
+ # sheet.remove_rules!('.header { }')
525
+ # sheet.remove_rules!('.header { } .footer { }')
496
526
  #
497
527
  # @example Remove rules from specific media type
498
- # sheet.remove_rules!(selector: '.header', media_types: :screen)
499
- #
500
- # @example Remove all rules from a media type
501
- # sheet.remove_rules!(media_types: :print)
502
- def remove_rules!(selector: nil, media_types: nil)
528
+ # sheet.remove_rules!('.header { }', media_types: :screen)
529
+ #
530
+ # @example Remove specific rule objects
531
+ # rules = sheet.select { |r| r.selector =~ /\.btn-/ }
532
+ # sheet.remove_rules!(rules)
533
+ #
534
+ # @example Remove rules with media filtering
535
+ # sheet.remove_rules!(sheet.with_selector('.header'), media_types: :print)
536
+ def remove_rules!(rules_or_css, media_types: nil)
537
+ # Determine if we're matching by selector (CSS string) or by object identity (rule objects)
538
+ if rules_or_css.is_a?(String)
539
+ # Parse CSS string and extract selectors for matching
540
+ parsed = Stylesheet.parse(rules_or_css)
541
+ selectors_to_remove = parsed.rules.filter_map(&:selector).to_set
542
+ match_by_selector = true
543
+ else
544
+ # Use rule objects directly
545
+ rules_to_remove = rules_or_css.is_a?(Array) ? rules_or_css : [rules_or_css]
546
+ return self if rules_to_remove.empty?
547
+
548
+ match_by_selector = false
549
+ end
550
+
503
551
  # Normalize media_types to array
504
552
  filter_media = media_types ? Array(media_types).map(&:to_sym) : nil
505
553
 
506
- # Find rules to remove
507
- rules_to_remove = []
554
+ # Find rule IDs to remove
555
+ rule_ids_to_remove = []
508
556
  @rules.each_with_index do |rule, rule_id|
509
- # Check selector match
510
- next if selector && rule.selector != selector
511
-
512
- # Check media type match
557
+ # Check if this rule matches
558
+ matches = if match_by_selector
559
+ # Match by selector for CSS string input
560
+ selectors_to_remove.include?(rule.selector)
561
+ else
562
+ # Match by object equality for rule collection input
563
+ rules_to_remove.any?(rule)
564
+ end
565
+ next unless matches
566
+
567
+ # Check media type match if filter is specified
513
568
  if filter_media
514
569
  rule_media_types = @_media_index.select { |_media, ids| ids.include?(rule_id) }.keys
515
570
  # Extract individual media types from complex queries
516
571
  individual_types = rule_media_types.flat_map { |key| Cataract.parse_media_types(key) }.uniq
517
572
 
518
- # If rule is not in any media query (base rule), skip if filtering by media
573
+ # If rule is not in any media query (base rule), skip unless :all is specified
519
574
  if individual_types.empty?
520
575
  next unless filter_media.include?(:all)
521
576
  else
@@ -524,11 +579,11 @@ module Cataract
524
579
  end
525
580
  end
526
581
 
527
- rules_to_remove << rule_id
582
+ rule_ids_to_remove << rule_id
528
583
  end
529
584
 
530
585
  # Remove rules and update media_index (sort in reverse to maintain indices during deletion)
531
- rules_to_remove.sort.reverse_each do |rule_id|
586
+ rule_ids_to_remove.sort.reverse_each do |rule_id|
532
587
  @rules.delete_at(rule_id)
533
588
 
534
589
  # Remove from media_index and update IDs for rules after this one
@@ -557,7 +612,6 @@ module Cataract
557
612
  # @param fix_braces [Boolean] Automatically close missing braces
558
613
  # @param media_types [Symbol, Array<Symbol>] Optional media query to wrap CSS in
559
614
  # @return [self] Returns self for method chaining
560
- # TODO: Move to C?
561
615
  def add_block(css, fix_braces: false, media_types: nil)
562
616
  css += ' }' if fix_braces && !css.strip.end_with?('}')
563
617
 
@@ -567,18 +621,11 @@ module Cataract
567
621
  css = "@media #{media_list} { #{css} }"
568
622
  end
569
623
 
570
- # Resolve @import statements if configured in constructor
571
- css_to_parse = if @options[:import]
572
- ImportResolver.resolve(css, @options[:import])
573
- else
574
- css
575
- end
576
-
577
624
  # Get current rule ID offset
578
625
  offset = @_last_rule_id || 0
579
626
 
580
- # Parse CSS with C function (returns hash)
581
- result = Cataract._parse_css(css_to_parse)
627
+ # Parse CSS first (this extracts @import statements into result[:imports])
628
+ result = Cataract._parse_css(css)
582
629
 
583
630
  # Merge rules with offsetted IDs
584
631
  new_rules = result[:rules]
@@ -600,6 +647,28 @@ module Cataract
600
647
  # Update last rule ID
601
648
  @_last_rule_id = offset + new_rules.length
602
649
 
650
+ # Merge imports with offsetted IDs
651
+ if result[:imports]
652
+ new_imports = result[:imports]
653
+ new_imports.each do |import|
654
+ import.id += offset
655
+ @imports << import
656
+ end
657
+
658
+ # Resolve imports if configured
659
+ if @options[:import]
660
+ # Extract imported_urls and depth from options
661
+ if @options[:import].is_a?(Hash)
662
+ imported_urls = @options[:import][:imported_urls] || []
663
+ depth = @options[:import][:depth] || 0
664
+ else
665
+ imported_urls = []
666
+ depth = 0
667
+ end
668
+ resolve_imports(new_imports, @options[:import], imported_urls: imported_urls, depth: depth)
669
+ end
670
+ end
671
+
603
672
  # Set charset if not already set
604
673
  @charset ||= result[:charset]
605
674
 
@@ -642,33 +711,171 @@ module Cataract
642
711
  end
643
712
  end
644
713
 
645
- # Merge all rules in this stylesheet according to CSS cascade rules
714
+ # Compare stylesheets for equality.
715
+ #
716
+ # Two stylesheets are equal if they have the same rules in the same order
717
+ # and the same media queries. Rule equality uses shorthand-aware comparison.
718
+ # Order matters because CSS cascade depends on rule order.
719
+ #
720
+ # Charset is ignored since it's file encoding metadata, not semantic content.
721
+ #
722
+ # @param other [Object] Object to compare with
723
+ # @return [Boolean] true if stylesheets are equal
724
+ def ==(other)
725
+ return false unless other.is_a?(Stylesheet)
726
+ return false unless rules == other.rules
727
+ return false unless @_media_index == other.instance_variable_get(:@_media_index)
728
+
729
+ true
730
+ end
731
+ alias eql? ==
732
+
733
+ # Generate hash code for this stylesheet.
734
+ #
735
+ # Hash is based on rules and media_index to match equality semantics.
736
+ #
737
+ # @return [Integer] hash code
738
+ def hash
739
+ @_hash ||= [self.class, rules, @_media_index].hash # rubocop:disable Naming/MemoizedInstanceVariableName
740
+ end
741
+
742
+ # Flatten all rules in this stylesheet according to CSS cascade rules.
646
743
  #
647
744
  # Applies specificity and !important precedence rules to compute the final
648
745
  # set of declarations. Also recreates shorthand properties from longhand
649
746
  # properties where possible.
650
747
  #
651
- # @return [Stylesheet] New stylesheet with a single merged rule
748
+ # @return [Stylesheet] New stylesheet with cascade applied
749
+ def flatten
750
+ Cataract.flatten(self)
751
+ end
752
+ alias cascade flatten
753
+
754
+ # Deprecated: Use flatten instead
652
755
  def merge
653
- # C function handles everything - returns new Stylesheet
654
- Cataract.merge(self)
756
+ warn 'Stylesheet#merge is deprecated, use #flatten instead', uplevel: 1
757
+ flatten
655
758
  end
656
759
 
657
- # Merge rules in-place, mutating the receiver.
760
+ # Flatten rules in-place, mutating the receiver.
658
761
  #
659
762
  # This is a convenience method that updates the stylesheet's internal
660
- # rules and media_index with the merged result. The Stylesheet object
661
- # itself is mutated (same object_id), but note that the C merge function
763
+ # rules and media_index with the flattened result. The Stylesheet object
764
+ # itself is mutated (same object_id), but note that the C flatten function
662
765
  # still allocates new arrays internally.
663
766
  #
664
767
  # @return [self] Returns self for method chaining
665
- def merge!
666
- merged = Cataract.merge(self)
667
- @rules = merged.instance_variable_get(:@rules)
668
- @_media_index = merged.instance_variable_get(:@_media_index)
669
- @_has_nesting = merged.instance_variable_get(:@_has_nesting)
768
+ def flatten!
769
+ flattened = Cataract.flatten(self)
770
+ @rules = flattened.instance_variable_get(:@rules)
771
+ @_media_index = flattened.instance_variable_get(:@_media_index)
772
+ @_has_nesting = flattened.instance_variable_get(:@_has_nesting)
670
773
  self
671
774
  end
775
+ alias cascade! flatten!
776
+
777
+ # Deprecated: Use flatten! instead
778
+ def merge!
779
+ warn 'Stylesheet#merge! is deprecated, use #flatten! instead', uplevel: 1
780
+ flatten!
781
+ end
782
+
783
+ # Concatenate another stylesheet's rules into this one and apply cascade.
784
+ #
785
+ # Adds all rules from the other stylesheet to this one, then applies
786
+ # CSS cascade to resolve conflicts. Media queries are merged.
787
+ #
788
+ # @param other [Stylesheet] Stylesheet to concatenate
789
+ # @return [self] Returns self for method chaining
790
+ def concat(other)
791
+ raise ArgumentError, 'Argument must be a Stylesheet' unless other.is_a?(Stylesheet)
792
+
793
+ # Get the current offset for rule IDs
794
+ offset = @rules.length
795
+
796
+ # Add rules with updated IDs
797
+ other.rules.each do |rule|
798
+ new_rule = rule.dup
799
+ new_rule.id = @rules.length
800
+ @rules << new_rule
801
+ end
802
+
803
+ # Merge media_index with offsetted IDs
804
+ other.instance_variable_get(:@_media_index).each do |media_sym, rule_ids|
805
+ offsetted_ids = rule_ids.map { |id| id + offset }
806
+ if @_media_index[media_sym]
807
+ @_media_index[media_sym].concat(offsetted_ids)
808
+ else
809
+ @_media_index[media_sym] = offsetted_ids
810
+ end
811
+ end
812
+
813
+ # Update nesting flag if other has nesting
814
+ other_has_nesting = other.instance_variable_get(:@_has_nesting)
815
+ @_has_nesting = true if other_has_nesting
816
+
817
+ # Clear memoized cache
818
+ @selectors = nil
819
+
820
+ # Apply cascade in-place
821
+ flatten!
822
+ end
823
+
824
+ # Combine two stylesheets into a new one and apply cascade.
825
+ #
826
+ # Creates a new stylesheet containing rules from both stylesheets,
827
+ # then applies CSS cascade to resolve conflicts.
828
+ #
829
+ # @param other [Stylesheet] Stylesheet to combine with
830
+ # @return [Stylesheet] New stylesheet with combined and cascaded rules
831
+ def +(other)
832
+ result = dup
833
+ result.concat(other)
834
+ result
835
+ end
836
+
837
+ # Remove matching rules from this stylesheet.
838
+ #
839
+ # Creates a new stylesheet with rules that don't match any rules in the
840
+ # other stylesheet. Uses Rule#== for matching (shorthand-aware).
841
+ # Does NOT apply cascade to the result.
842
+ #
843
+ # @param other [Stylesheet] Stylesheet containing rules to remove
844
+ # @return [Stylesheet] New stylesheet with matching rules removed
845
+ def -(other)
846
+ raise ArgumentError, 'Argument must be a Stylesheet' unless other.is_a?(Stylesheet)
847
+
848
+ result = dup
849
+
850
+ # Remove matching rules using Rule#==
851
+ rules_to_remove_ids = []
852
+ result.rules.each_with_index do |rule, idx|
853
+ rules_to_remove_ids << idx if other.rules.include?(rule)
854
+ end
855
+
856
+ # Remove in reverse order to maintain indices
857
+ rules_to_remove_ids.reverse_each do |idx|
858
+ result.rules.delete_at(idx)
859
+
860
+ # Update media_index: remove this rule ID and decrement higher IDs
861
+ result.instance_variable_get(:@_media_index).each_value do |ids|
862
+ ids.delete(idx)
863
+ ids.map! { |id| id > idx ? id - 1 : id }
864
+ end
865
+ end
866
+
867
+ # Re-index remaining rules
868
+ result.rules.each_with_index { |rule, new_id| rule.id = new_id }
869
+
870
+ # Clean up empty media_index entries
871
+ result.instance_variable_get(:@_media_index).delete_if { |_media, ids| ids.empty? }
872
+
873
+ # Clear memoized cache
874
+ result.instance_variable_set(:@selectors, nil)
875
+ result.instance_variable_set(:@_hash, nil)
876
+
877
+ result
878
+ end
672
879
 
673
880
  private
674
881
 
@@ -678,6 +885,101 @@ module Cataract
678
885
  # @return [Hash<Symbol, Array<Integer>>]
679
886
  attr_reader :_media_index
680
887
 
888
+ # Resolve @import statements by fetching and merging imported stylesheets
889
+ #
890
+ # @param imports [Array<ImportStatement>] Import statements to resolve
891
+ # @param options [Hash] Import resolution options
892
+ # @param imported_urls [Array<String>] URLs already imported (for circular detection)
893
+ # @param depth [Integer] Current import depth (for depth limit)
894
+ # @return [void]
895
+ def resolve_imports(imports, options, imported_urls: [], depth: 0)
896
+ # Normalize options with safe defaults
897
+ opts = ImportResolver.normalize_options(options)
898
+
899
+ # Check depth limit
900
+ if depth > opts[:max_depth]
901
+ raise ImportError, "Import nesting too deep: exceeded maximum depth of #{opts[:max_depth]}"
902
+ end
903
+
904
+ # Get or create fetcher
905
+ fetcher = opts[:fetcher] || ImportResolver::DefaultFetcher.new
906
+
907
+ imports.each do |import|
908
+ next if import.resolved # Skip already resolved imports
909
+
910
+ url = import.url
911
+ media = import.media
912
+
913
+ # Validate URL
914
+ ImportResolver.validate_url(url, opts)
915
+
916
+ # Check for circular references
917
+ raise ImportError, "Circular import detected: #{url}" if imported_urls.include?(url)
918
+
919
+ # Fetch imported CSS
920
+ imported_css = fetcher.call(url, opts)
921
+
922
+ # Parse imported CSS recursively
923
+ imported_urls_copy = imported_urls.dup
924
+ imported_urls_copy << url
925
+ imported_sheet = Stylesheet.parse(imported_css, import: opts.merge(imported_urls: imported_urls_copy, depth: depth + 1))
926
+
927
+ # Wrap rules in @media if import had media query
928
+ if media
929
+ imported_sheet.rules.each do |rule|
930
+ # Find rule's current media (if any) from imported sheet's media index
931
+ # A rule may be in multiple media entries (e.g., :screen and :"screen and (min-width: 768px)")
932
+ # We want the most specific one (longest string)
933
+ # TODO: Extract this logic to a helper method to keep it consistent across codebase
934
+ rule_media = nil
935
+ imported_sheet.instance_variable_get(:@_media_index).each do |m, ids|
936
+ # Keep the longest/most specific media query
937
+ if ids.include?(rule.id) && (rule_media.nil? || m.to_s.length > rule_media.to_s.length)
938
+ rule_media = m
939
+ end
940
+ end
941
+
942
+ # Combine media queries: "import_media and rule_media"
943
+ combined_media = if rule_media
944
+ # Combine: "media and rule_media"
945
+ :"#{media} and #{rule_media}"
946
+ else
947
+ media
948
+ end
949
+
950
+ # Update media index
951
+ if @_media_index[combined_media]
952
+ @_media_index[combined_media] << rule.id
953
+ else
954
+ @_media_index[combined_media] = [rule.id]
955
+ end
956
+ end
957
+ end
958
+
959
+ # Merge imported rules into this stylesheet
960
+ # Insert at current position (before any remaining local rules)
961
+ insert_position = import.id
962
+ imported_sheet.rules.each_with_index do |rule, idx|
963
+ @rules.insert(insert_position + idx, rule)
964
+ end
965
+
966
+ # Merge media index
967
+ imported_sheet.instance_variable_get(:@_media_index).each do |media_sym, rule_ids|
968
+ if @_media_index[media_sym]
969
+ @_media_index[media_sym].concat(rule_ids)
970
+ else
971
+ @_media_index[media_sym] = rule_ids.dup
972
+ end
973
+ end
974
+
975
+ # Merge charset (first one wins per CSS spec)
976
+ @charset ||= imported_sheet.instance_variable_get(:@charset)
977
+
978
+ # Mark as resolved
979
+ import.resolved = true
980
+ end
981
+ end
982
+
681
983
  # Check if a rule matches any of the requested media queries
682
984
  #
683
985
  # @param rule_id [Integer] Rule ID to check
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cataract
4
- VERSION = '0.1.4'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/cataract.rb CHANGED
@@ -6,6 +6,7 @@ require_relative 'cataract/version'
6
6
  require_relative 'cataract/declaration'
7
7
  require_relative 'cataract/rule'
8
8
  require_relative 'cataract/at_rule'
9
+ require_relative 'cataract/import_statement'
9
10
 
10
11
  # Load pure Ruby or C extension based on ENV var
11
12
  if %w[1 true].include?(ENV.fetch('CATARACT_PURE', nil)) || RUBY_ENGINE == 'jruby'
@@ -34,8 +35,8 @@ require_relative 'cataract/import_resolver'
34
35
  # # Query rules
35
36
  # sheet.select(&:selector?).each { |rule| puts "#{rule.selector}: #{rule.declarations}" }
36
37
  #
37
- # # Merge with cascade rules
38
- # merged = sheet.merge
38
+ # # Flatten with cascade rules
39
+ # flattened = sheet.flatten
39
40
  #
40
41
  # @see Stylesheet Main class for working with parsed CSS
41
42
  # @see Rule Represents individual CSS rules
@@ -73,48 +74,51 @@ module Cataract
73
74
  # @see Stylesheet.parse
74
75
  unless method_defined?(:parse_css)
75
76
  def parse_css(css, imports: false)
76
- # Resolve @import statements if requested
77
- css = ImportResolver.resolve(css, imports) if imports
78
-
79
- Stylesheet.parse(css)
77
+ # Pass import options to Stylesheet.parse
78
+ # The new flow: parse first (extract @import), then resolve them
79
+ if imports
80
+ Stylesheet.parse(css, import: imports)
81
+ else
82
+ Stylesheet.parse(css)
83
+ end
80
84
  end
81
85
  end
82
86
 
83
- # Merge CSS rules according to CSS cascade rules.
87
+ # Flatten CSS rules according to CSS cascade rules.
84
88
  #
85
- # Takes a Stylesheet or CSS string and merges all rules according to CSS cascade
86
- # precedence rules. Returns a new Stylesheet with a single merged rule containing
89
+ # Takes a Stylesheet or CSS string and flattens all rules according to CSS cascade
90
+ # precedence rules. Returns a new Stylesheet with flattened rules containing
87
91
  # the final computed declarations.
88
92
  #
89
- # @param stylesheet_or_css [Stylesheet, String] The stylesheet to merge, or a CSS string to parse and merge
90
- # @return [Stylesheet] A new Stylesheet with merged rules
93
+ # @param stylesheet_or_css [Stylesheet, String] The stylesheet to flatten, or a CSS string to parse and flatten
94
+ # @return [Stylesheet] A new Stylesheet with flattened rules
91
95
  #
92
- # Merge rules (in order of precedence):
96
+ # Flatten rules (in order of precedence):
93
97
  # 1. !important declarations win over non-important
94
98
  # 2. Higher specificity wins
95
99
  # 3. Later declarations with same specificity and importance win
96
100
  # 4. Shorthand properties are created from longhand when possible (e.g., margin-* -> margin)
97
101
  #
98
- # @example Merge a stylesheet
102
+ # @example Flatten a stylesheet
99
103
  # sheet = Cataract.parse_css(".test { color: red; } #test { color: blue; }")
100
- # merged = Cataract.merge(sheet)
101
- # merged.rules.first.declarations #=> [#<Declaration property="color" value="blue" important=false>]
104
+ # flattened = Cataract.flatten(sheet)
105
+ # flattened.rules.first.declarations #=> [#<Declaration property="color" value="blue" important=false>]
102
106
  #
103
- # @example Merge with !important
107
+ # @example Flatten with !important
104
108
  # sheet = Cataract.parse_css(".test { color: red !important; } #test { color: blue; }")
105
- # merged = Cataract.merge(sheet)
106
- # merged.rules.first.declarations #=> [#<Declaration property="color" value="red" important=true>]
109
+ # flattened = Cataract.flatten(sheet)
110
+ # flattened.rules.first.declarations #=> [#<Declaration property="color" value="red" important=true>]
107
111
  #
108
112
  # @example Shorthand creation
109
113
  # css = ".test { margin-top: 10px; margin-right: 10px; margin-bottom: 10px; margin-left: 10px; }"
110
- # merged = Cataract.merge(Cataract.parse_css(css))
111
- # # merged contains single "margin: 10px" declaration instead of four longhand properties
114
+ # flattened = Cataract.flatten(Cataract.parse_css(css))
115
+ # # flattened contains single "margin: 10px" declaration instead of four longhand properties
112
116
  #
113
117
  # @note This is a module-level convenience method. The same functionality is available
114
- # as an instance method: `stylesheet.merge`
115
- # @note Implemented in C (see ext/cataract/merge.c)
118
+ # as an instance method: `stylesheet.flatten`
119
+ # @note Implemented in C (see ext/cataract/flatten.c)
116
120
  #
117
- # @see Stylesheet#merge
118
- # Cataract.merge is defined in C via rb_define_module_function
121
+ # @see Stylesheet#flatten
122
+ # Cataract.flatten is defined in C via rb_define_module_function
119
123
  end
120
124
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cataract
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Cook
@@ -55,8 +55,8 @@ files:
55
55
  - ext/cataract/cataract.h
56
56
  - ext/cataract/css_parser.c
57
57
  - ext/cataract/extconf.rb
58
+ - ext/cataract/flatten.c
58
59
  - ext/cataract/import_scanner.c
59
- - ext/cataract/merge.c
60
60
  - ext/cataract/shorthand_expander.c
61
61
  - ext/cataract/specificity.c
62
62
  - ext/cataract/value_splitter.c
@@ -83,11 +83,12 @@ files:
83
83
  - lib/cataract/declaration.rb
84
84
  - lib/cataract/declarations.rb
85
85
  - lib/cataract/import_resolver.rb
86
+ - lib/cataract/import_statement.rb
86
87
  - lib/cataract/pure.rb
87
88
  - lib/cataract/pure/byte_constants.rb
89
+ - lib/cataract/pure/flatten.rb
88
90
  - lib/cataract/pure/helpers.rb
89
91
  - lib/cataract/pure/imports.rb
90
- - lib/cataract/pure/merge.rb
91
92
  - lib/cataract/pure/parser.rb
92
93
  - lib/cataract/pure/serializer.rb
93
94
  - lib/cataract/pure/specificity.rb