cataract 0.1.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.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-tidy +30 -0
  3. data/.github/workflows/ci-macos.yml +12 -0
  4. data/.github/workflows/ci.yml +77 -0
  5. data/.github/workflows/test.yml +76 -0
  6. data/.gitignore +45 -0
  7. data/.overcommit.yml +38 -0
  8. data/.rubocop.yml +83 -0
  9. data/BENCHMARKS.md +201 -0
  10. data/CHANGELOG.md +1 -0
  11. data/Gemfile +27 -0
  12. data/LICENSE +21 -0
  13. data/RAGEL_MIGRATION.md +60 -0
  14. data/README.md +292 -0
  15. data/Rakefile +209 -0
  16. data/benchmarks/benchmark_harness.rb +193 -0
  17. data/benchmarks/benchmark_merging.rb +121 -0
  18. data/benchmarks/benchmark_optimization_comparison.rb +168 -0
  19. data/benchmarks/benchmark_parsing.rb +153 -0
  20. data/benchmarks/benchmark_ragel_removal.rb +56 -0
  21. data/benchmarks/benchmark_runner.rb +70 -0
  22. data/benchmarks/benchmark_serialization.rb +180 -0
  23. data/benchmarks/benchmark_shorthand.rb +109 -0
  24. data/benchmarks/benchmark_shorthand_expansion.rb +176 -0
  25. data/benchmarks/benchmark_specificity.rb +124 -0
  26. data/benchmarks/benchmark_string_allocation.rb +151 -0
  27. data/benchmarks/benchmark_stylesheet_to_s.rb +62 -0
  28. data/benchmarks/benchmark_to_s_cached.rb +55 -0
  29. data/benchmarks/benchmark_value_splitter.rb +54 -0
  30. data/benchmarks/benchmark_yjit.rb +158 -0
  31. data/benchmarks/benchmark_yjit_workers.rb +61 -0
  32. data/benchmarks/profile_to_s.rb +23 -0
  33. data/benchmarks/speedup_calculator.rb +83 -0
  34. data/benchmarks/system_metadata.rb +81 -0
  35. data/benchmarks/templates/benchmarks.md.erb +221 -0
  36. data/benchmarks/yjit_tests.rb +141 -0
  37. data/cataract.gemspec +34 -0
  38. data/cliff.toml +92 -0
  39. data/examples/color_conversion_visual_test/color_conversion_test.html +3603 -0
  40. data/examples/color_conversion_visual_test/generate.rb +202 -0
  41. data/examples/color_conversion_visual_test/template.html.erb +259 -0
  42. data/examples/css_analyzer/analyzer.rb +164 -0
  43. data/examples/css_analyzer/analyzers/base.rb +33 -0
  44. data/examples/css_analyzer/analyzers/colors.rb +133 -0
  45. data/examples/css_analyzer/analyzers/important.rb +88 -0
  46. data/examples/css_analyzer/analyzers/properties.rb +61 -0
  47. data/examples/css_analyzer/analyzers/specificity.rb +68 -0
  48. data/examples/css_analyzer/templates/report.html.erb +575 -0
  49. data/examples/css_analyzer.rb +69 -0
  50. data/examples/github_analysis.html +5343 -0
  51. data/ext/cataract/cataract.c +1086 -0
  52. data/ext/cataract/cataract.h +174 -0
  53. data/ext/cataract/css_parser.c +1435 -0
  54. data/ext/cataract/extconf.rb +48 -0
  55. data/ext/cataract/import_scanner.c +174 -0
  56. data/ext/cataract/merge.c +973 -0
  57. data/ext/cataract/shorthand_expander.c +902 -0
  58. data/ext/cataract/specificity.c +213 -0
  59. data/ext/cataract/value_splitter.c +116 -0
  60. data/ext/cataract_color/cataract_color.c +16 -0
  61. data/ext/cataract_color/color_conversion.c +1687 -0
  62. data/ext/cataract_color/color_conversion.h +136 -0
  63. data/ext/cataract_color/color_conversion_lab.c +571 -0
  64. data/ext/cataract_color/color_conversion_named.c +259 -0
  65. data/ext/cataract_color/color_conversion_oklab.c +547 -0
  66. data/ext/cataract_color/extconf.rb +23 -0
  67. data/ext/cataract_old/cataract.c +393 -0
  68. data/ext/cataract_old/cataract.h +250 -0
  69. data/ext/cataract_old/css_parser.c +933 -0
  70. data/ext/cataract_old/extconf.rb +67 -0
  71. data/ext/cataract_old/import_scanner.c +174 -0
  72. data/ext/cataract_old/merge.c +776 -0
  73. data/ext/cataract_old/shorthand_expander.c +902 -0
  74. data/ext/cataract_old/specificity.c +213 -0
  75. data/ext/cataract_old/stylesheet.c +290 -0
  76. data/ext/cataract_old/value_splitter.c +116 -0
  77. data/lib/cataract/at_rule.rb +97 -0
  78. data/lib/cataract/color_conversion.rb +18 -0
  79. data/lib/cataract/declarations.rb +332 -0
  80. data/lib/cataract/import_resolver.rb +210 -0
  81. data/lib/cataract/rule.rb +131 -0
  82. data/lib/cataract/stylesheet.rb +716 -0
  83. data/lib/cataract/stylesheet_scope.rb +257 -0
  84. data/lib/cataract/version.rb +5 -0
  85. data/lib/cataract.rb +107 -0
  86. data/lib/tasks/gem.rake +158 -0
  87. data/scripts/fuzzer/run.rb +828 -0
  88. data/scripts/fuzzer/worker.rb +99 -0
  89. data/scripts/generate_benchmarks_md.rb +155 -0
  90. metadata +135 -0
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cataract
4
+ # Represents a CSS rule with a selector and declarations.
5
+ #
6
+ # Rule is a C struct defined as: `Struct.new(:id, :selector, :declarations, :specificity)`
7
+ #
8
+ # Rules are created by the parser and stored in Stylesheet objects. Each rule
9
+ # contains:
10
+ # - An ID (position in the stylesheet)
11
+ # - A CSS selector string
12
+ # - An array of Declaration structs
13
+ # - A specificity value (calculated lazily)
14
+ #
15
+ # Media query information is stored separately in Stylesheet's media_index.
16
+ #
17
+ # @example Access rule properties
18
+ # sheet = Cataract.parse_css("body { color: red; font-size: 14px; }")
19
+ # rule = sheet.rules.first
20
+ # rule.selector #=> "body"
21
+ # rule.specificity #=> 1
22
+ # rule.declarations.length #=> 2
23
+ #
24
+ # @attr [Integer] id The rule's position in the stylesheet (0-indexed)
25
+ # @attr [String] selector The CSS selector (e.g., "body", ".class", "#id")
26
+ # @attr [Array<Declaration>] declarations Array of CSS property declarations
27
+ # @attr [Integer, nil] specificity CSS specificity value (calculated lazily)
28
+ class Rule
29
+ # Silence warning about method redefinition. We redefine below to lazily calculate
30
+ # specificity
31
+ undef_method :specificity if method_defined?(:specificity)
32
+
33
+ # Get the CSS specificity value for this rule's selector.
34
+ #
35
+ # Specificity is calculated lazily on first access and then cached.
36
+ # The calculation follows the CSS specification:
37
+ # - Inline styles: not applicable to parsed stylesheets
38
+ # - IDs: count of #id selectors
39
+ # - Classes/attributes/pseudo-classes: count of .class, [attr], :pseudo
40
+ # - Elements/pseudo-elements: count of element, ::pseudo
41
+ #
42
+ # @return [Integer] CSS specificity value
43
+ #
44
+ # @example Get specificity
45
+ # rule = Cataract.parse_css("#header .nav a").rules.first
46
+ # rule.specificity #=> 111 (1 ID + 1 class + 1 element)
47
+ def specificity
48
+ return self[:specificity] unless self[:specificity].nil?
49
+
50
+ # Calculate and cache
51
+ calculated = Cataract.calculate_specificity(selector)
52
+ self[:specificity] = calculated
53
+ calculated
54
+ end
55
+
56
+ # Check if this is a selector-based rule (vs an at-rule like @keyframes).
57
+ #
58
+ # @return [Boolean] Always returns true for Rule objects
59
+ def selector?
60
+ true
61
+ end
62
+
63
+ # Check if this is an at-rule.
64
+ #
65
+ # @return [Boolean] Always returns false for Rule objects
66
+ def at_rule?
67
+ false
68
+ end
69
+
70
+ # Check if this is a specific at-rule type.
71
+ #
72
+ # @param _type [Symbol] At-rule type (e.g., :keyframes, :font_face)
73
+ # @return [Boolean] Always returns false for Rule objects
74
+ def at_rule_type?(_type)
75
+ false
76
+ end
77
+
78
+ # Check if this rule has a declaration with the specified property and optional value.
79
+ #
80
+ # @param property [String] CSS property name to match
81
+ # @param value [String, nil] Optional value to match
82
+ # @return [Boolean] true if rule has matching declaration
83
+ #
84
+ # @example Check for color property
85
+ # rule.has_property?('color') #=> true
86
+ #
87
+ # @example Check for specific property value
88
+ # rule.has_property?('color', 'red') #=> true
89
+ def has_property?(property, value = nil)
90
+ declarations.any? do |decl|
91
+ property_matches = decl.property == property
92
+ value_matches = value.nil? || decl.value == value
93
+ property_matches && value_matches
94
+ end
95
+ end
96
+
97
+ # Check if this rule has any !important declarations, optionally for a specific property.
98
+ #
99
+ # @param property [String, nil] Optional property name to match
100
+ # @return [Boolean] true if rule has matching !important declaration
101
+ #
102
+ # @example Check for any !important
103
+ # rule.has_important? #=> true
104
+ #
105
+ # @example Check for color !important
106
+ # rule.has_important?('color') #=> true
107
+ def has_important?(property = nil)
108
+ if property
109
+ declarations.any? { |d| d.property == property && d.important }
110
+ else
111
+ declarations.any?(&:important)
112
+ end
113
+ end
114
+
115
+ # Compare rules by their attributes rather than object identity.
116
+ #
117
+ # Two rules are equal if they have the same id, selector, declarations, and specificity.
118
+ #
119
+ # @param other [Object] Object to compare with
120
+ # @return [Boolean] true if rules have same attributes
121
+ def ==(other)
122
+ return false unless other.is_a?(Rule)
123
+
124
+ id == other.id &&
125
+ selector == other.selector &&
126
+ declarations == other.declarations &&
127
+ specificity == other.specificity
128
+ end
129
+ alias eql? ==
130
+ end
131
+ end