cataract 0.3.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.gitignore +0 -3
- data/.rubocop_todo.yml +11 -19
- data/.yardopts +9 -0
- data/BENCHMARKS.md +40 -40
- data/CHANGELOG.md +17 -0
- data/ext/cataract/cataract.c +193 -111
- data/ext/cataract/cataract.h +13 -3
- data/ext/cataract/css_parser.c +298 -54
- data/ext/cataract/flatten.c +6 -2
- data/ext/cataract/specificity.c +13 -2
- data/lib/cataract/at_rule.rb +4 -2
- data/lib/cataract/conditional_group.rb +72 -0
- data/lib/cataract/declarations.rb +14 -4
- data/lib/cataract/native.rb +31 -0
- data/lib/cataract/pure/byte_constants.rb +70 -66
- data/lib/cataract/pure/declarations.rb +125 -0
- data/lib/cataract/pure/flatten.rb +1203 -1182
- data/lib/cataract/pure/parser.rb +1953 -1715
- data/lib/cataract/pure/serializer.rb +653 -706
- data/lib/cataract/pure/specificity.rb +173 -144
- data/lib/cataract/pure.rb +74 -101
- data/lib/cataract/rule.rb +14 -7
- data/lib/cataract/stylesheet.rb +84 -25
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +41 -30
- data/lib/tasks/profile.rake +6 -3
- metadata +5 -2
- data/lib/cataract/pure/helpers.rb +0 -35
data/lib/cataract/stylesheet.rb
CHANGED
|
@@ -29,6 +29,9 @@ module Cataract
|
|
|
29
29
|
# @return [Array<MediaQuery>] Array of media query objects
|
|
30
30
|
attr_reader :media_queries
|
|
31
31
|
|
|
32
|
+
# @return [Array<ConditionalGroup>] Array of conditional-group objects (@supports/@layer/@container/@scope)
|
|
33
|
+
attr_reader :conditional_groups
|
|
34
|
+
|
|
32
35
|
# @return [Hash<Symbol, Array<Integer>>] Cached index mapping media query text to rule IDs
|
|
33
36
|
# Lazily build and return media_index.
|
|
34
37
|
# Only builds the index when first accessed, not eagerly during parse.
|
|
@@ -112,6 +115,13 @@ module Cataract
|
|
|
112
115
|
# Type validation
|
|
113
116
|
raise TypeError, "options must be a Hash, got #{options.class}" unless options.is_a?(Hash)
|
|
114
117
|
|
|
118
|
+
# Which backend (Cataract::Backends::Native / ::Pure) produced this
|
|
119
|
+
# stylesheet - defaults to whichever this process picked as active.
|
|
120
|
+
# Not part of the public options contract (used internally by
|
|
121
|
+
# Backends::Native.parse / Backends::Pure.parse), so it's popped off
|
|
122
|
+
# before @options is built.
|
|
123
|
+
@backend = options.delete(:backend) || Cataract::Backends.active
|
|
124
|
+
|
|
115
125
|
# Support :imports as alias for :import (backwards compatibility)
|
|
116
126
|
options[:import] = options.delete(:imports) if options.key?(:imports) && !options.key?(:import)
|
|
117
127
|
|
|
@@ -153,6 +163,8 @@ module Cataract
|
|
|
153
163
|
@_next_selector_list_id = 0 # Counter for selector list IDs
|
|
154
164
|
@_media_query_lists = {} # Hash: list_id => Array of MediaQuery IDs (for "screen, print" grouping)
|
|
155
165
|
@_next_media_query_list_id = 0 # Counter for media query list IDs
|
|
166
|
+
@conditional_groups = [] # Array of ConditionalGroup objects (@supports/@layer/@container/@scope)
|
|
167
|
+
@_next_conditional_group_id = 0 # Counter for ConditionalGroup IDs
|
|
156
168
|
@charset = nil
|
|
157
169
|
@imports = [] # Array of ImportStatement objects
|
|
158
170
|
@_has_nesting = nil # Set by parser (nil or boolean)
|
|
@@ -169,6 +181,7 @@ module Cataract
|
|
|
169
181
|
# @param source [Stylesheet] Source stylesheet being copied
|
|
170
182
|
def initialize_copy(source)
|
|
171
183
|
super
|
|
184
|
+
@backend = source.backend
|
|
172
185
|
@options = source.options.dup
|
|
173
186
|
@rules = source.rules.dup
|
|
174
187
|
@media_queries = source.media_queries.dup
|
|
@@ -179,6 +192,8 @@ module Cataract
|
|
|
179
192
|
@_next_selector_list_id = source.next_selector_list_id
|
|
180
193
|
@_media_query_lists = source.media_query_lists.transform_values(&:dup)
|
|
181
194
|
@_next_media_query_list_id = source.next_media_query_list_id
|
|
195
|
+
@conditional_groups = source.conditional_groups.dup
|
|
196
|
+
@_next_conditional_group_id = source.next_conditional_group_id
|
|
182
197
|
@parser_options = source.parser_options.dup
|
|
183
198
|
clear_memoized_caches
|
|
184
199
|
@_hash = nil # Clear cached hash
|
|
@@ -466,7 +481,8 @@ module Cataract
|
|
|
466
481
|
# @example Filter to multiple media types
|
|
467
482
|
# sheet.to_s(media: [:screen, :print]) # => "@media screen { ... } @media print { ... }"
|
|
468
483
|
def to_s(media: :all)
|
|
469
|
-
|
|
484
|
+
@backend.stylesheet_to_s(filter_rules_by_media(media), @charset, @_has_nesting || false, @_selector_lists, @media_queries, @_media_query_lists,
|
|
485
|
+
@conditional_groups)
|
|
470
486
|
end
|
|
471
487
|
alias to_css to_s
|
|
472
488
|
|
|
@@ -492,7 +508,8 @@ module Cataract
|
|
|
492
508
|
#
|
|
493
509
|
# @see #to_s For compact single-line output
|
|
494
510
|
def to_formatted_s(media: :all)
|
|
495
|
-
|
|
511
|
+
@backend.stylesheet_to_formatted_s(filter_rules_by_media(media), @charset, @_has_nesting || false, @_selector_lists, @media_queries, @_media_query_lists,
|
|
512
|
+
@conditional_groups)
|
|
496
513
|
end
|
|
497
514
|
|
|
498
515
|
# Get number of rules
|
|
@@ -718,7 +735,7 @@ module Cataract
|
|
|
718
735
|
parse_options = build_parse_options(effective_base_uri, effective_absolute_paths)
|
|
719
736
|
|
|
720
737
|
# Parse CSS first (this extracts @import statements into result[:imports])
|
|
721
|
-
result =
|
|
738
|
+
result = @backend.parse(css, parse_options)
|
|
722
739
|
|
|
723
740
|
merge_parsed_block!(result, effective_base_uri, effective_base_dir)
|
|
724
741
|
|
|
@@ -761,8 +778,9 @@ module Cataract
|
|
|
761
778
|
# Compare stylesheets for equality.
|
|
762
779
|
#
|
|
763
780
|
# Two stylesheets are equal if they have the same rules in the same order
|
|
764
|
-
# and the same media queries. Rule equality uses
|
|
765
|
-
# Order matters because CSS cascade depends
|
|
781
|
+
# and the same media queries and conditional groups. Rule equality uses
|
|
782
|
+
# shorthand-aware comparison. Order matters because CSS cascade depends
|
|
783
|
+
# on rule order.
|
|
766
784
|
#
|
|
767
785
|
# Charset is ignored since it's file encoding metadata, not semantic content.
|
|
768
786
|
#
|
|
@@ -772,6 +790,7 @@ module Cataract
|
|
|
772
790
|
return false unless other.is_a?(Stylesheet)
|
|
773
791
|
return false unless rules == other.rules
|
|
774
792
|
return false unless @media_queries == other.media_queries
|
|
793
|
+
return false unless @conditional_groups == other.conditional_groups
|
|
775
794
|
|
|
776
795
|
true
|
|
777
796
|
end
|
|
@@ -779,11 +798,12 @@ module Cataract
|
|
|
779
798
|
|
|
780
799
|
# Generate hash code for this stylesheet.
|
|
781
800
|
#
|
|
782
|
-
# Hash is based on rules and
|
|
801
|
+
# Hash is based on rules, media_queries, and conditional_groups to match
|
|
802
|
+
# equality semantics.
|
|
783
803
|
#
|
|
784
804
|
# @return [Integer] hash code
|
|
785
805
|
def hash
|
|
786
|
-
@_hash ||= [self.class, rules, @media_queries].hash # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
806
|
+
@_hash ||= [self.class, rules, @media_queries, @conditional_groups].hash # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
787
807
|
end
|
|
788
808
|
|
|
789
809
|
# Flatten all rules in this stylesheet according to CSS cascade rules.
|
|
@@ -794,7 +814,9 @@ module Cataract
|
|
|
794
814
|
#
|
|
795
815
|
# @return [Stylesheet] New stylesheet with cascade applied
|
|
796
816
|
def flatten
|
|
797
|
-
|
|
817
|
+
result = @backend.flatten(self)
|
|
818
|
+
result.backend = @backend
|
|
819
|
+
result
|
|
798
820
|
end
|
|
799
821
|
alias cascade flatten
|
|
800
822
|
|
|
@@ -813,7 +835,7 @@ module Cataract
|
|
|
813
835
|
#
|
|
814
836
|
# @return [self] Returns self for method chaining
|
|
815
837
|
def flatten!
|
|
816
|
-
flattened =
|
|
838
|
+
flattened = @backend.flatten(self)
|
|
817
839
|
@rules = flattened.rules
|
|
818
840
|
@media_index = flattened.media_index_cache
|
|
819
841
|
@_has_nesting = flattened.has_nesting
|
|
@@ -842,11 +864,13 @@ module Cataract
|
|
|
842
864
|
list_id_offset = merge_selector_lists!(other.selector_lists, rule_id_offset: offset)
|
|
843
865
|
mq_id_offset = merge_media_queries!(other.media_queries)
|
|
844
866
|
merge_media_query_lists!(other.media_query_lists, mq_id_offset: mq_id_offset)
|
|
867
|
+
cg_id_offset = merge_conditional_groups!(other.conditional_groups)
|
|
845
868
|
|
|
846
869
|
# Add rules with updated IDs and cross-references
|
|
847
870
|
other.rules.each do |rule|
|
|
848
871
|
new_rule = rule.dup
|
|
849
|
-
rebase_rule!(new_rule, rule_id_offset: offset, list_id_offset: list_id_offset, mq_id_offset: mq_id_offset
|
|
872
|
+
rebase_rule!(new_rule, rule_id_offset: offset, list_id_offset: list_id_offset, mq_id_offset: mq_id_offset,
|
|
873
|
+
cg_id_offset: cg_id_offset)
|
|
850
874
|
@rules << new_rule
|
|
851
875
|
end
|
|
852
876
|
|
|
@@ -930,11 +954,16 @@ module Cataract
|
|
|
930
954
|
# raw cache these need).
|
|
931
955
|
|
|
932
956
|
attr_reader :options, :parser_options
|
|
957
|
+
attr_accessor :backend
|
|
933
958
|
|
|
934
959
|
def next_media_query_id
|
|
935
960
|
@_next_media_query_id
|
|
936
961
|
end
|
|
937
962
|
|
|
963
|
+
def next_conditional_group_id
|
|
964
|
+
@_next_conditional_group_id
|
|
965
|
+
end
|
|
966
|
+
|
|
938
967
|
def next_selector_list_id
|
|
939
968
|
@_next_selector_list_id
|
|
940
969
|
end
|
|
@@ -987,6 +1016,28 @@ module Cataract
|
|
|
987
1016
|
mq_id_offset
|
|
988
1017
|
end
|
|
989
1018
|
|
|
1019
|
+
# Offset and append ConditionalGroup objects into @conditional_groups,
|
|
1020
|
+
# without mutating the source objects. Unlike merge_media_queries!,
|
|
1021
|
+
# each group's own parent_id (pointing at another ConditionalGroup, for
|
|
1022
|
+
# nesting) must also be rebased by the same offset.
|
|
1023
|
+
#
|
|
1024
|
+
# @param source_groups [Array<ConditionalGroup>, nil]
|
|
1025
|
+
# @return [Integer] id offset applied to each merged ConditionalGroup
|
|
1026
|
+
def merge_conditional_groups!(source_groups)
|
|
1027
|
+
cg_id_offset = @_next_conditional_group_id
|
|
1028
|
+
return cg_id_offset if source_groups.nil? || source_groups.empty?
|
|
1029
|
+
|
|
1030
|
+
source_groups.each do |group|
|
|
1031
|
+
new_group = group.dup
|
|
1032
|
+
new_group.id += cg_id_offset
|
|
1033
|
+
new_group.parent_id += cg_id_offset if new_group.parent_id
|
|
1034
|
+
@conditional_groups << new_group
|
|
1035
|
+
end
|
|
1036
|
+
@_next_conditional_group_id += source_groups.size
|
|
1037
|
+
|
|
1038
|
+
cg_id_offset
|
|
1039
|
+
end
|
|
1040
|
+
|
|
990
1041
|
# Offset and merge a selector_lists hash (list_id => [rule_ids]) into
|
|
991
1042
|
# @_selector_lists. List ids are always rebased onto the next available
|
|
992
1043
|
# id; rule ids are rebased by rule_id_offset (0 when the caller defers
|
|
@@ -1045,18 +1096,21 @@ module Cataract
|
|
|
1045
1096
|
end
|
|
1046
1097
|
|
|
1047
1098
|
# Rebase a rule/at-rule's own id and its cross-references (Rule's
|
|
1048
|
-
# selector_list_id, and media_query_id on both Rule
|
|
1049
|
-
# given offsets, mutating it in place. Callers
|
|
1050
|
-
# Stylesheet they don't own (e.g. concat) must dup
|
|
1099
|
+
# selector_list_id, and media_query_id/conditional_group_id on both Rule
|
|
1100
|
+
# and AtRule) by the given offsets, mutating it in place. Callers
|
|
1101
|
+
# merging from a live Stylesheet they don't own (e.g. concat) must dup
|
|
1102
|
+
# the rule first.
|
|
1051
1103
|
#
|
|
1052
1104
|
# @param rule [Rule, AtRule]
|
|
1053
1105
|
# @param rule_id_offset [Integer]
|
|
1054
1106
|
# @param list_id_offset [Integer]
|
|
1055
1107
|
# @param mq_id_offset [Integer]
|
|
1108
|
+
# @param cg_id_offset [Integer]
|
|
1056
1109
|
# @return [void]
|
|
1057
|
-
def rebase_rule!(rule, rule_id_offset:, list_id_offset: 0, mq_id_offset: 0)
|
|
1110
|
+
def rebase_rule!(rule, rule_id_offset:, list_id_offset: 0, mq_id_offset: 0, cg_id_offset: 0)
|
|
1058
1111
|
rule.id += rule_id_offset
|
|
1059
1112
|
rule.media_query_id += mq_id_offset if rule.media_query_id
|
|
1113
|
+
rule.conditional_group_id += cg_id_offset if rule.conditional_group_id
|
|
1060
1114
|
return unless rule.is_a?(Rule)
|
|
1061
1115
|
|
|
1062
1116
|
rule.selector_list_id += list_id_offset if rule.selector_list_id
|
|
@@ -1080,7 +1134,7 @@ module Cataract
|
|
|
1080
1134
|
# rules, media queries, selector/media-query lists, and index, then
|
|
1081
1135
|
# resolve any @import statements it contained.
|
|
1082
1136
|
#
|
|
1083
|
-
# @param result [Hash] Return value of
|
|
1137
|
+
# @param result [Hash] Return value of the backend's parse
|
|
1084
1138
|
# @return [void]
|
|
1085
1139
|
def merge_parsed_block!(result, effective_base_uri, effective_base_dir)
|
|
1086
1140
|
offset = @_last_rule_id || 0
|
|
@@ -1088,10 +1142,12 @@ module Cataract
|
|
|
1088
1142
|
list_id_offset = merge_selector_lists!(result[:_selector_lists], rule_id_offset: offset)
|
|
1089
1143
|
mq_id_offset = merge_media_queries!(result[:media_queries])
|
|
1090
1144
|
merge_media_query_lists!(result[:_media_query_lists], mq_id_offset: mq_id_offset)
|
|
1145
|
+
cg_id_offset = merge_conditional_groups!(result[:conditional_groups])
|
|
1091
1146
|
|
|
1092
1147
|
new_rules = result[:rules]
|
|
1093
1148
|
new_rules.each do |rule|
|
|
1094
|
-
rebase_rule!(rule, rule_id_offset: offset, list_id_offset: list_id_offset, mq_id_offset: mq_id_offset
|
|
1149
|
+
rebase_rule!(rule, rule_id_offset: offset, list_id_offset: list_id_offset, mq_id_offset: mq_id_offset,
|
|
1150
|
+
cg_id_offset: cg_id_offset)
|
|
1095
1151
|
@rules << rule
|
|
1096
1152
|
end
|
|
1097
1153
|
@_last_rule_id = offset + new_rules.length
|
|
@@ -1263,7 +1319,8 @@ module Cataract
|
|
|
1263
1319
|
# Build parse options for imported CSS
|
|
1264
1320
|
parse_opts = {
|
|
1265
1321
|
import: opts.merge(imported_urls: imported_urls_copy, depth: depth + 1, base_uri: imported_base_uri),
|
|
1266
|
-
parser: @parser_options.dup # Inherit parent's parser options (including selector_lists)
|
|
1322
|
+
parser: @parser_options.dup, # Inherit parent's parser options (including selector_lists)
|
|
1323
|
+
backend: @backend # Imported stylesheets are produced by the same backend as their parent
|
|
1267
1324
|
}
|
|
1268
1325
|
|
|
1269
1326
|
# If URL conversion is enabled (base_uri present), enable it for imported files too
|
|
@@ -1301,7 +1358,8 @@ module Cataract
|
|
|
1301
1358
|
# @return [void]
|
|
1302
1359
|
def merge_imported_sheet!(imported_sheet, import)
|
|
1303
1360
|
mq_id_offset = merge_media_queries!(imported_sheet.media_queries)
|
|
1304
|
-
|
|
1361
|
+
cg_id_offset = merge_conditional_groups!(imported_sheet.conditional_groups)
|
|
1362
|
+
rebase_imported_rules!(imported_sheet.rules, mq_id_offset, cg_id_offset, import.media_query_id)
|
|
1305
1363
|
|
|
1306
1364
|
insert_position = import.id
|
|
1307
1365
|
imported_sheet.rules.each_with_index do |rule, idx|
|
|
@@ -1312,15 +1370,16 @@ module Cataract
|
|
|
1312
1370
|
merge_media_query_lists!(imported_sheet.media_query_lists, mq_id_offset: mq_id_offset)
|
|
1313
1371
|
end
|
|
1314
1372
|
|
|
1315
|
-
# Rebase every imported rule/at-rule's media_query_id
|
|
1316
|
-
# stylesheet's own @media_queries
|
|
1317
|
-
#
|
|
1318
|
-
#
|
|
1319
|
-
# rule's media
|
|
1373
|
+
# Rebase every imported rule/at-rule's media_query_id and
|
|
1374
|
+
# conditional_group_id onto this stylesheet's own @media_queries/
|
|
1375
|
+
# @conditional_groups (now that the imported sheet's own copies have
|
|
1376
|
+
# been merged in), then, if the @import statement itself had a media
|
|
1377
|
+
# qualifier, combine it with (or assign it to) each rule's media
|
|
1378
|
+
# context.
|
|
1320
1379
|
#
|
|
1321
1380
|
# @return [void]
|
|
1322
|
-
def
|
|
1323
|
-
imported_rules.each { |rule| rebase_rule!(rule, rule_id_offset: 0, mq_id_offset: mq_id_offset) }
|
|
1381
|
+
def rebase_imported_rules!(imported_rules, mq_id_offset, cg_id_offset, import_media_query_id)
|
|
1382
|
+
imported_rules.each { |rule| rebase_rule!(rule, rule_id_offset: 0, mq_id_offset: mq_id_offset, cg_id_offset: cg_id_offset) }
|
|
1324
1383
|
|
|
1325
1384
|
return unless import_media_query_id
|
|
1326
1385
|
|
data/lib/cataract/version.rb
CHANGED
data/lib/cataract.rb
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'cataract/version'
|
|
4
|
-
require_relative 'cataract/error'
|
|
5
|
-
require_relative 'cataract/constants'
|
|
6
|
-
|
|
7
|
-
# Load struct definitions first (before C extension or pure Ruby)
|
|
8
|
-
require_relative 'cataract/declaration'
|
|
9
|
-
require_relative 'cataract/rule'
|
|
10
|
-
require_relative 'cataract/at_rule'
|
|
11
|
-
require_relative 'cataract/media_query'
|
|
12
|
-
require_relative 'cataract/import_statement'
|
|
13
|
-
|
|
14
|
-
# Load pure Ruby or C extension based on ENV var
|
|
15
|
-
if %w[1 true].include?(ENV.fetch('CATARACT_PURE', nil)) || RUBY_ENGINE == 'jruby'
|
|
16
|
-
require_relative 'cataract/pure'
|
|
17
|
-
else
|
|
18
|
-
require_relative 'cataract/native_extension'
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Load supporting Ruby files (used by both implementations)
|
|
22
|
-
require_relative 'cataract/stylesheet_scope'
|
|
23
|
-
require_relative 'cataract/stylesheet'
|
|
24
|
-
require_relative 'cataract/declarations'
|
|
25
|
-
require_relative 'cataract/import_resolver'
|
|
26
|
-
|
|
27
3
|
# Cataract is a high-performance CSS parser written in C with a Ruby interface.
|
|
28
4
|
#
|
|
29
5
|
# It provides fast CSS parsing, rule querying, cascade merging, and serialization.
|
|
30
6
|
# Designed for performance-critical applications that need to process large amounts of CSS.
|
|
31
7
|
#
|
|
8
|
+
# Pure Ruby and native each live entirely under their own Backends::Pure /
|
|
9
|
+
# Backends::Native namespace (no shared method/constant is ever defined by
|
|
10
|
+
# a backend directly on Cataract or on a shared value type), so both can be
|
|
11
|
+
# required in the same process - e.g. to compare their output directly.
|
|
12
|
+
# Backends.active is the one this process picked by default (below); it's
|
|
13
|
+
# what shared value types (Stylesheet, Declarations, Rule) fall back to
|
|
14
|
+
# when they aren't otherwise told which backend produced them.
|
|
15
|
+
#
|
|
32
16
|
# @example Basic usage
|
|
33
17
|
# require 'cataract'
|
|
34
18
|
#
|
|
@@ -44,6 +28,33 @@ require_relative 'cataract/import_resolver'
|
|
|
44
28
|
# @see Stylesheet Main class for working with parsed CSS
|
|
45
29
|
# @see Rule Represents individual CSS rules
|
|
46
30
|
module Cataract
|
|
31
|
+
module Backends
|
|
32
|
+
class << self
|
|
33
|
+
attr_accessor :active
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if %w[1 true].include?(ENV.fetch('CATARACT_PURE', nil)) || RUBY_ENGINE == 'jruby'
|
|
39
|
+
require_relative 'cataract/pure'
|
|
40
|
+
Cataract::Backends.active = Cataract::Backends::Pure
|
|
41
|
+
else
|
|
42
|
+
require_relative 'cataract/native'
|
|
43
|
+
Cataract::Backends.active = Cataract::Backends::Native
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module Cataract
|
|
47
|
+
# Mirror the active backend's identity constants at the top level.
|
|
48
|
+
#
|
|
49
|
+
# Native is a Module (IMPLEMENTATION/COMPILE_FLAGS live on it directly);
|
|
50
|
+
# Pure is a frozen instance (they live on its class instead), so reach
|
|
51
|
+
# through .class only when active isn't already a Module itself.
|
|
52
|
+
backend_const_holder = Backends.active.is_a?(Module) ? Backends.active : Backends.active.class
|
|
53
|
+
IMPLEMENTATION = backend_const_holder::IMPLEMENTATION
|
|
54
|
+
COMPILE_FLAGS = backend_const_holder::COMPILE_FLAGS
|
|
55
|
+
NATIVE_EXTENSION_LOADED = true if defined?(Backends::Native) && Backends.active == Backends::Native
|
|
56
|
+
PURE_RUBY_LOADED = true if defined?(Backends::Pure) && Backends.active == Backends::Pure
|
|
57
|
+
|
|
47
58
|
class << self
|
|
48
59
|
# Parse a CSS string into a Stylesheet object.
|
|
49
60
|
#
|
|
@@ -75,10 +86,8 @@ module Cataract
|
|
|
75
86
|
#
|
|
76
87
|
# @see Stylesheet#parse
|
|
77
88
|
# @see Stylesheet.parse
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Stylesheet.parse(css, **options)
|
|
81
|
-
end
|
|
89
|
+
def parse_css(css, **options)
|
|
90
|
+
Stylesheet.parse(css, **options)
|
|
82
91
|
end
|
|
83
92
|
|
|
84
93
|
# Flatten CSS rules according to CSS cascade rules.
|
|
@@ -113,9 +122,11 @@ module Cataract
|
|
|
113
122
|
#
|
|
114
123
|
# @note This is a module-level convenience method. The same functionality is available
|
|
115
124
|
# as an instance method: `stylesheet.flatten`
|
|
116
|
-
# @note Implemented in C (see ext/cataract/flatten.c)
|
|
117
125
|
#
|
|
118
126
|
# @see Stylesheet#flatten
|
|
119
|
-
|
|
127
|
+
def flatten(stylesheet_or_css)
|
|
128
|
+
stylesheet_or_css = Stylesheet.parse(stylesheet_or_css) if stylesheet_or_css.is_a?(String)
|
|
129
|
+
stylesheet_or_css.flatten
|
|
130
|
+
end
|
|
120
131
|
end
|
|
121
132
|
end
|
data/lib/tasks/profile.rake
CHANGED
|
@@ -35,7 +35,7 @@ namespace :profile do
|
|
|
35
35
|
puts "CSS size: #{css_content.bytesize} bytes"
|
|
36
36
|
puts
|
|
37
37
|
|
|
38
|
-
require_relative '../../lib/cataract
|
|
38
|
+
require_relative '../../lib/cataract'
|
|
39
39
|
require 'json'
|
|
40
40
|
|
|
41
41
|
# Use higher sampling rate (interval in microseconds, default is 1000)
|
|
@@ -74,6 +74,9 @@ namespace :profile do
|
|
|
74
74
|
abort('stackprof gem not found. Install with: gem install stackprof')
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Ensure we're using pure Ruby implementation
|
|
78
|
+
ENV['CATARACT_PURE'] = '1'
|
|
79
|
+
|
|
77
80
|
fixture_path = File.expand_path('../../test/fixtures/bootstrap.css', __dir__)
|
|
78
81
|
unless File.exist?(fixture_path)
|
|
79
82
|
abort("Fixture not found: #{fixture_path}")
|
|
@@ -94,7 +97,7 @@ namespace :profile do
|
|
|
94
97
|
puts "CSS size: #{css_content.bytesize} bytes"
|
|
95
98
|
puts
|
|
96
99
|
|
|
97
|
-
require_relative '../../lib/cataract
|
|
100
|
+
require_relative '../../lib/cataract'
|
|
98
101
|
require 'json'
|
|
99
102
|
|
|
100
103
|
# Parse once outside profiling to get stylesheet
|
|
@@ -162,7 +165,7 @@ namespace :profile do
|
|
|
162
165
|
puts "CSS size: #{css_content.bytesize} bytes"
|
|
163
166
|
puts
|
|
164
167
|
|
|
165
|
-
require_relative '../../lib/cataract
|
|
168
|
+
require_relative '../../lib/cataract'
|
|
166
169
|
require 'json'
|
|
167
170
|
|
|
168
171
|
# Parse once outside profiling to get stylesheet
|
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Cook
|
|
@@ -29,6 +29,7 @@ files:
|
|
|
29
29
|
- ".overcommit.yml"
|
|
30
30
|
- ".rubocop.yml"
|
|
31
31
|
- ".rubocop_todo.yml"
|
|
32
|
+
- ".yardopts"
|
|
32
33
|
- BENCHMARKS.md
|
|
33
34
|
- CHANGELOG.md
|
|
34
35
|
- Gemfile
|
|
@@ -69,6 +70,7 @@ files:
|
|
|
69
70
|
- lib/cataract.rb
|
|
70
71
|
- lib/cataract/at_rule.rb
|
|
71
72
|
- lib/cataract/color_conversion.rb
|
|
73
|
+
- lib/cataract/conditional_group.rb
|
|
72
74
|
- lib/cataract/constants.rb
|
|
73
75
|
- lib/cataract/declaration.rb
|
|
74
76
|
- lib/cataract/declarations.rb
|
|
@@ -76,10 +78,11 @@ files:
|
|
|
76
78
|
- lib/cataract/import_resolver.rb
|
|
77
79
|
- lib/cataract/import_statement.rb
|
|
78
80
|
- lib/cataract/media_query.rb
|
|
81
|
+
- lib/cataract/native.rb
|
|
79
82
|
- lib/cataract/pure.rb
|
|
80
83
|
- lib/cataract/pure/byte_constants.rb
|
|
84
|
+
- lib/cataract/pure/declarations.rb
|
|
81
85
|
- lib/cataract/pure/flatten.rb
|
|
82
|
-
- lib/cataract/pure/helpers.rb
|
|
83
86
|
- lib/cataract/pure/parser.rb
|
|
84
87
|
- lib/cataract/pure/serializer.rb
|
|
85
88
|
- lib/cataract/pure/specificity.rb
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Pure Ruby CSS parser - Helper methods
|
|
4
|
-
# NO REGEXP ALLOWED - char-by-char parsing only
|
|
5
|
-
|
|
6
|
-
module Cataract
|
|
7
|
-
# Check if a byte is whitespace (space, tab, newline, CR)
|
|
8
|
-
# @param byte [Integer] Byte value from String#getbyte
|
|
9
|
-
# @return [Boolean] true if whitespace
|
|
10
|
-
def self.is_whitespace?(byte)
|
|
11
|
-
byte == BYTE_SPACE || byte == BYTE_TAB || byte == BYTE_NEWLINE || byte == BYTE_CR
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Check if byte is a letter (a-z, A-Z)
|
|
15
|
-
# @param byte [Integer] Byte value from String#getbyte
|
|
16
|
-
# @return [Boolean] true if letter
|
|
17
|
-
def self.letter?(byte)
|
|
18
|
-
(byte >= BYTE_LOWER_A && byte <= BYTE_LOWER_Z) ||
|
|
19
|
-
(byte >= BYTE_UPPER_A && byte <= BYTE_UPPER_Z)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Check if byte is a digit (0-9)
|
|
23
|
-
# @param byte [Integer] Byte value from String#getbyte
|
|
24
|
-
# @return [Boolean] true if digit
|
|
25
|
-
def self.digit?(byte)
|
|
26
|
-
byte >= BYTE_DIGIT_0 && byte <= BYTE_DIGIT_9
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Check if byte is alphanumeric, hyphen, or underscore (CSS identifier char)
|
|
30
|
-
# @param byte [Integer] Byte value from String#getbyte
|
|
31
|
-
# @return [Boolean] true if valid identifier character
|
|
32
|
-
def self.ident_char?(byte)
|
|
33
|
-
letter?(byte) || digit?(byte) || byte == BYTE_HYPHEN || byte == BYTE_UNDERSCORE
|
|
34
|
-
end
|
|
35
|
-
end
|