yass-css 0.1.0-x86_64-linux → 0.2.0-x86_64-linux

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea34c5802afae12c1fb031904b416c2797dbe07ee058253b7c4f2bb05665612f
4
- data.tar.gz: d0e64ec23a442d7a11bfd9c73052dd90d5b5304f334ff6410f61f3ad28ae1029
3
+ metadata.gz: b67d47c573422d558b9b6c7635c76e9d385172d38e8ec602f53357ba069c1a62
4
+ data.tar.gz: 0e8cd4ea4be36be0a931deea9a7e4c329b3307b8bf12465aebbb9973124aaa65
5
5
  SHA512:
6
- metadata.gz: b820356ab0bc5578affdf588f6af5f32fc1c6ddbbb2e2df1b53d7ebe9d24fa39c05c1aa114875a5f31ab2c1b00b031e6320aabcd1bb69d6860438152d3bd6143
7
- data.tar.gz: 503f5fe900d8e8c1e45f0fff2693205fd8586eb07316749acd59eb47718140334c728a23fe7dd1ecd3b264709cda5a50f1ca133ca8cea5fe772efe769d20b502
6
+ metadata.gz: eb063f0f79ff8aa9d7ac106fadd39fa08d54f398b466a575b1dd17ebef878bbe332bcbb8e33c15dde4b47b8306a3724c089f007e7e837deb30f1eb8c8943850d
7
+ data.tar.gz: 8e7df3c260bdebeeedc1c13be81dccff6303f73cd8c5dead6516fed50f4129863113702601e7ccc805f489c9797f0ab647babeb6d1f1f55938b4e822cbe945bd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2026-03-25
3
+ ## [0.2.0] - 2026-04-19
4
+
5
+ - Fully support all style declarations.
6
+ - Add support for `@media` rules.
7
+ - Add support for `@font-face` rules.
8
+
9
+ ## [0.1.0] - 2026-03-26
4
10
 
5
11
  - Initial release
data/README.md CHANGED
@@ -1,38 +1,134 @@
1
1
  # Yass
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ Yass is a Ruby wrapper around the [Stylo](https://github.com/servo/stylo) CSS engine, the parser behind the Firefox and Servo browsers developed by Mozilla.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/yass`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ ## Rationale
6
6
 
7
- ## Installation
7
+ I needed a CSS parser in Ruby but couldn't find one that could handle nested CSS rules. Stylo was born out of that need, and aims to be a complete CSS parser for Ruby, covering the entire CSS spec.
8
+
9
+ Stylo is itself under active development, and Yass does not yet wrap the entire Stylo API. If Yass doesn't support a feature you need, consider adding the missing functionality and submitting a pull request.
8
10
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
11
+ ## Installation
10
12
 
11
13
  Install the gem and add to the application's Gemfile by executing:
12
14
 
13
15
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
16
+ bundle add yass-css
15
17
  ```
16
18
 
17
19
  If bundler is not being used to manage dependencies, install the gem by executing:
18
20
 
19
21
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
22
+ gem install yass-css
21
23
  ```
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ There are two main ways to use Yass: by navigating the object hierarchy by hand, or by using the visitor pattern.
28
+
29
+ By "navigating the object hierarchy," I just mean "calling methods on objects." Here's an example.
30
+
31
+ ```ruby
32
+ require "yass"
33
+
34
+ sheet = Yass::Parser.parse(<<~CSS)
35
+ h1 {
36
+ align-items: center;
37
+ display: flex;
38
+ position: absolute;
39
+ }
40
+ CSS
41
+
42
+ # this is the whole h1 { ... } rule
43
+ first_rule = sheet.rules.first
44
+ puts first_rule.class # => Yass::StyleRule
45
+
46
+ # this is the h1 selector
47
+ first_selector = first_rule.selectors.first.children.first
48
+ puts first_selector.value # => "h1"
49
+ ```
50
+
51
+ The entire object hierarchy can be discovered by dropping into a console session (via `bin/console`) and playing around.
52
+
53
+ ### The Visitor Pattern
54
+
55
+ Yass also comes with a `Yass::Visitor` class that implements the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern).
56
+
57
+ Some people love the visitor pattern, and some people seem to hate it. If you're in the second camp, feel free to skip this section.
58
+
59
+ Visitor classes contain `visit_*` methods that the library calls in-order as it traverses the object hierarchy contained within a parsed stylesheet. The methods the visitor supports can be examined by reading the code in lib/yass/visitor.rb.
60
+
61
+ Let's write a visitor class that prints all the class names in the given stylesheet:
62
+
63
+ ```ruby
64
+ class MyVisitor < Yass::Visitor
65
+ def visit_selector_klass(node)
66
+ # node is an instance of Yass::Selector::Klass
67
+ puts node.value
68
+ end
69
+ end
70
+ ```
71
+
72
+ Now we can use our visitor to visit a stylesheet:
73
+
74
+ ```ruby
75
+ sheet = Yass::Parser.parse(<<~CSS)
76
+ .left {
77
+ float: left;
78
+ }
79
+
80
+ .right {
81
+ float: right;
82
+ }
83
+ CSS
84
+
85
+ visitor = MyVisitor.new
86
+
87
+ # this will result in "left" and "right" being printed to stdout
88
+ visitor.visit(sheet)
89
+ ```
90
+
91
+ ## Examples
92
+
93
+ For more examples, see the examples/ directory.
26
94
 
27
95
  ## Development
28
96
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
97
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake` to compile the native extension and run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ ### Running Tests
100
+
101
+ Simply execute:
102
+
103
+ ```bash
104
+ bundle exec rake
105
+ ```
106
+
107
+ This will compile the native extension and run the tests immediately afterwards. To compile only, run:
108
+
109
+ ```bash
110
+ bundle exec rake compile
111
+ ```
112
+
113
+ To run the tests only, run:
114
+
115
+ ```bash
116
+ bundle exec rake spec
117
+ ```
118
+
119
+ To run the full test suite (much slower) that attempts to parse the ~33k example CSS files from the [Web Platform Test (WPT) suite](https://github.com/web-platform-tests/wpt), run:
120
+
121
+ ```ruby
122
+ bundle exec rake spec:full
123
+ ```
124
+
125
+ ## Releasing
30
126
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
127
+ Releasing Yass is done by creating a new release on GitHub. Doing so will trigger a special CI job capable of building the native extension for all supported plaforms and publishing a "fat" .gem file to rubygems.org.
32
128
 
33
129
  ## Contributing
34
130
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/yass. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/yass/blob/main/CODE_OF_CONDUCT.md).
131
+ Bug reports and pull requests are welcome on GitHub at https://github.com/camertron/yass. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/camertron/yass/blob/main/CODE_OF_CONDUCT.md).
36
132
 
37
133
  ## License
38
134
 
@@ -40,4 +136,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
136
 
41
137
  ## Code of Conduct
42
138
 
43
- Everyone interacting in the Yass project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/yass/blob/main/CODE_OF_CONDUCT.md).
139
+ Everyone interacting in the Yass project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/camertron/yass/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -25,6 +25,15 @@ end
25
25
 
26
26
  task default: %i[compile spec]
27
27
 
28
+ namespace :spec do
29
+ desc 'Run full specs suit'
30
+ task full: [:full_spec_env, :compile, :spec]
31
+
32
+ task :full_spec_env do
33
+ ENV['FULL_SPEC'] = 'true'
34
+ end
35
+ end
36
+
28
37
  # For whatever reason, the :native task that comes with rb-sys doesn't work, so
29
38
  # we define our own
30
39
  task :native, [:platform] do |task, args|
@@ -45,3 +54,77 @@ task :native, [:platform] do |task, args|
45
54
 
46
55
  sh(env, "bundle", "exec", "rb-sys-dock", "--platform", args[:platform], "--build")
47
56
  end
57
+
58
+ task :codegen do
59
+ require "yass"
60
+ require_relative "codegen/rust_file_set"
61
+
62
+ general_files = Yass::Codegen::RustFileSet::new(
63
+ Dir.glob("ext/yass/src/general/**/*.rs")
64
+ )
65
+
66
+ File.write("lib/yass/general.rb", <<~RUBY)
67
+ # frozen_string_literal: true
68
+
69
+ #{general_files.module_tree.to_ruby_classes}
70
+ RUBY
71
+
72
+ declaration_files = Yass::Codegen::RustFileSet.new(Dir.glob("ext/yass/src/declarations/**/*.rs"))
73
+
74
+ File.write("lib/yass/declarations.rb", <<~RUBY)
75
+ # frozen_string_literal: true
76
+
77
+ #{declaration_files.module_tree.to_ruby_classes}
78
+ RUBY
79
+
80
+ selector_files = Yass::Codegen::RustFileSet.new(Dir.glob("ext/yass/src/selectors/**/*.rs"))
81
+
82
+ File.write("lib/yass/selectors.rb", <<~RUBY)
83
+ # frozen_string_literal: true
84
+
85
+ #{selector_files.module_tree.to_ruby_classes}
86
+ RUBY
87
+
88
+ rule_files = Yass::Codegen::RustFileSet.new([
89
+ "ext/yass/src/rules/rule.rs",
90
+ "ext/yass/src/rules/style_rule.rs",
91
+ "ext/yass/src/rules/media_rule.rs",
92
+ "ext/yass/src/rules/font_face_rule.rs",
93
+ *Dir.glob("ext/yass/src/rules/fonts/**/*.rs"),
94
+ ])
95
+
96
+ File.write("lib/yass/rules.rb", <<~RUBY)
97
+ # frozen_string_literal: true
98
+
99
+ #{rule_files.module_tree.to_ruby_classes}
100
+ RUBY
101
+
102
+ all_modules = general_files.module_tree
103
+ .merge(declaration_files.module_tree)
104
+ .merge(selector_files.module_tree)
105
+ .merge(rule_files.module_tree)
106
+
107
+ visitor_class = <<~RUBY
108
+ # frozen_string_literal: true
109
+
110
+ module Yass
111
+ class Visitor
112
+ def visit(node)
113
+ node.accept(self) if node
114
+ end
115
+
116
+ def visit_list(nodes)
117
+ nodes.each { |node| visit(node) }
118
+ end
119
+
120
+ def visit_stylesheet(node)
121
+ visit_list(node.rules)
122
+ end
123
+
124
+ #{all_modules.to_visitor_methods(indent: " ").join("\n")}
125
+ end
126
+ end
127
+ RUBY
128
+
129
+ File.write("lib/yass/visitor.rb", visitor_class)
130
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "utils"
4
+
5
+ module Yass
6
+ module Codegen
7
+ class RubyModule
8
+ attr_reader :name
9
+ attr_accessor :rust_struct
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+
15
+ def children
16
+ @children ||= {}
17
+ end
18
+ end
19
+
20
+ class RubyModuleTree
21
+ class << self
22
+ def from_structs(rust_structs)
23
+ root = RubyModule.new(nil)
24
+
25
+ rust_structs.each do |rust_struct|
26
+ current = root
27
+
28
+ rust_struct.ruby_class_name.split("::").each do |ns|
29
+ if !current.children.include?(ns)
30
+ current.children[ns] = RubyModule.new(ns)
31
+ end
32
+
33
+ current = current.children[ns]
34
+ end
35
+
36
+ current.rust_struct = rust_struct
37
+ end
38
+
39
+ new(root)
40
+ end
41
+ end
42
+
43
+ include Utils
44
+
45
+ attr_reader :root
46
+
47
+ def initialize(root)
48
+ @root = root
49
+ end
50
+
51
+ def to_ruby_classes(indent: "")
52
+ to_ruby_classes_helper(root.children["Yass"], ["Yass"], indent)
53
+ end
54
+
55
+ def to_visitor_methods(indent:)
56
+ to_visitor_methods_helper(root.children["Yass"], ["Yass"], indent)
57
+ end
58
+
59
+ def merge(other)
60
+ if root.name != other.root.name
61
+ raise ArgumentError, "this tree and other tree have different names, '#{name}' vs '#{other.root.name}'"
62
+ end
63
+
64
+ new_root = merge_helper(root, other.root)
65
+ self.class.new(new_root)
66
+ end
67
+
68
+ private
69
+
70
+ def merge_helper(first, second)
71
+ RubyModule.new(first.name).tap do |mod|
72
+ first.children.each do |key, first_child_mod|
73
+ if (second_child_mod = second.children[key])
74
+ mod.children[key] = merge_helper(first_child_mod, second_child_mod)
75
+ else
76
+ mod.children[key] = first_child_mod
77
+ end
78
+ end
79
+
80
+ second.children.each do |key, second_child_mod|
81
+ # merge for identical keys should happen in first pass above
82
+ if !first.children.include?(key)
83
+ mod.children[key] = second_child_mod
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def to_ruby_classes_helper(mod, nesting, indent = "")
90
+ class_or_module = Kernel.const_get(nesting.join("::"))
91
+ class_or_module_def = class_or_module.class == Class ? "class #{mod.name}" : "module #{mod.name}"
92
+
93
+ children = mod.children.map do |mod_name, child|
94
+ to_ruby_classes_helper(child, [*nesting, mod_name], indent + " ")
95
+ end
96
+
97
+ accept_method = if mod.rust_struct
98
+ [
99
+ "#{indent} def accept(visitor)",
100
+ "#{indent} visitor.visit_#{nesting_to_s(nesting[1..-1])}(self)",
101
+ "#{indent} end",
102
+ ]
103
+ end
104
+
105
+ kind_method = if mod.rust_struct
106
+ [
107
+ "#{indent} def kind",
108
+ "#{indent} :#{underscore(nesting.last).downcase}",
109
+ "#{indent} end",
110
+ ]
111
+ end
112
+
113
+ constants = if mod.rust_struct
114
+ method_names = class_or_module.instance_methods - Object.methods - [:accept, :to_h]
115
+
116
+ if method_names.size > 0
117
+ [
118
+ "#{indent} RUBY_METHODS = %i(#{method_names.map(&:to_s).sort.join(" ")}).freeze"
119
+ ]
120
+ end
121
+ end
122
+
123
+ includes = if mod.rust_struct
124
+ ["#{indent} include ::Yass::Node"]
125
+ end
126
+
127
+ lines = [
128
+ "#{indent}#{class_or_module_def}",
129
+ *join_line_groups(
130
+ constants,
131
+ includes,
132
+ accept_method,
133
+ kind_method,
134
+ (children.empty? ? [] : [children.join("\n\n")]),
135
+ ),
136
+ "#{indent}end",
137
+ ]
138
+
139
+ lines.join("\n")
140
+ end
141
+
142
+ def to_visitor_methods_helper(mod, nesting, indent)
143
+ line_groups = []
144
+
145
+ if mod.rust_struct
146
+ method_name = "visit_#{nesting_to_s(nesting[1..-1])}"
147
+
148
+ line_groups << [
149
+ "#{indent}def #{method_name}(node)",
150
+ *(debug? ? ["#{indent} puts \"Visiting #{method_name}\""] : []),
151
+ *mod.rust_struct.rust_methods.map do |rust_method|
152
+ if rust_method.visit_kind == :single
153
+ [
154
+ "#{indent} visit(node.#{rust_method.name})"
155
+ ]
156
+ else
157
+ [
158
+ "#{indent} visit_list(node.#{rust_method.name})"
159
+ ]
160
+ end
161
+ end,
162
+ "#{indent}end",
163
+ ]
164
+ end
165
+
166
+ mod.children.each do |mod_name, child|
167
+ line_groups << to_visitor_methods_helper(child, [*nesting, mod_name], indent)
168
+ end
169
+
170
+ join_line_groups(*line_groups)
171
+ end
172
+
173
+ def debug?
174
+ !!ENV["DEBUG"]
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruby_module_tree"
4
+
5
+ module Yass
6
+ module Codegen
7
+ class RustStruct
8
+ attr_reader :name, :ruby_class_name, :rust_methods
9
+
10
+ def initialize(name:, ruby_class_name:, rust_methods:)
11
+ @name = name
12
+ @ruby_class_name = ruby_class_name
13
+ @rust_methods = rust_methods
14
+ end
15
+ end
16
+
17
+ class RustMethod
18
+ attr_reader :name, :return_type, :visit_kind
19
+
20
+ def initialize(name:, return_type:, visit_kind:)
21
+ @name = name
22
+ @return_type = return_type
23
+ @visit_kind = visit_kind
24
+ end
25
+ end
26
+
27
+ class RustFileSet
28
+ attr_reader :rust_file_paths
29
+
30
+ def initialize(rust_file_paths)
31
+ @rust_file_paths = rust_file_paths
32
+ end
33
+
34
+ def structs
35
+ @structs ||= rust_file_paths.flat_map do |rust_file|
36
+ rust_code = File.read(rust_file)
37
+
38
+ rust_code.scan(/#\[magnus(?:\:\:wrap)?\(class = "([\w:]+)"(?:, mark)?\)\]\s+pub struct (\w+)/).map do |ruby_class_name, rust_struct_name|
39
+ next unless ruby_class_name && rust_struct_name
40
+
41
+ m = rust_code.match(/impl\s+#{rust_struct_name}\s+(\{)/)
42
+ next unless m
43
+
44
+ start = pos = m.end(1)
45
+ count = 1
46
+
47
+ while count > 0
48
+ case rust_code[pos]
49
+ when "{"
50
+ count += 1
51
+ when "}"
52
+ count -= 1
53
+ end
54
+
55
+ pos += 1
56
+ end
57
+
58
+ impl_body = rust_code[start..pos]
59
+ rust_method_names = impl_body.scan(/pub fn (\w+)\(ruby: &Ruby, rb_self: typed_data::Obj<Self>\) -> ([\w<>:, ]+) \{/)
60
+ rust_methods = []
61
+
62
+ rust_method_names.each do |name, return_type|
63
+ if ["Option<Value>", "Value"].include?(return_type)
64
+ rust_methods << RustMethod.new(name: name, return_type: return_type, visit_kind: :single)
65
+ elsif ["RArray", "Result<RArray, Error>"].include?(return_type)
66
+ rust_methods << RustMethod.new(name: name, return_type: return_type, visit_kind: :multiple)
67
+ end
68
+ end
69
+
70
+ RustStruct.new(
71
+ name: rust_struct_name,
72
+ ruby_class_name: ruby_class_name,
73
+ rust_methods: rust_methods
74
+ )
75
+ end
76
+ end
77
+ end
78
+
79
+ def module_tree
80
+ @module_tree ||= RubyModuleTree.from_structs(structs)
81
+ end
82
+ end
83
+ end
84
+ end
data/codegen/utils.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yass
4
+ module Codegen
5
+ module Utils
6
+ def underscore(str)
7
+ str.gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
8
+ end
9
+
10
+ def nesting_to_s(nesting)
11
+ nesting
12
+ .map { |part| underscore(part).downcase }
13
+ .map { |part| part == "declarations" ? "declaration" : part }
14
+ .join("_")
15
+ end
16
+
17
+ def join_line_groups(*line_groups)
18
+ result = line_groups.compact.reject(&:empty?).flat_map do |group|
19
+ [*group.flatten, ""]
20
+ end
21
+
22
+ result.pop
23
+ result
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
- FROM rbsys/x64-mingw-ucrt:0.9.124
1
+ FROM rbsys/x64-mingw-ucrt:0.9.126
2
2
 
3
3
  RUN apt-get update && apt-get install -y python3
@@ -0,0 +1,32 @@
1
+ require "yass"
2
+ require "pp"
3
+
4
+ sheet = Yass::Parser.parse(<<~CSS)
5
+ h1 {
6
+ align-items: center;
7
+ display: flex;
8
+ position: absolute;
9
+ }
10
+ CSS
11
+
12
+ # this is the whole h1 { ... } rule
13
+ first_rule = sheet.rules.first
14
+ puts first_rule.class # => Yass::StyleRule
15
+
16
+ # this is the h1 selector
17
+ first_selector = first_rule.selectors.first.children.first
18
+ puts first_selector.value # => "h1"
19
+
20
+ # this is all the declarations inside the h1 rule
21
+ declarations = first_rule.declarations
22
+
23
+ # this is the first align-items declaration
24
+ declaration = declarations.first
25
+ puts declaration.kind # => :align_items
26
+ puts declaration.class # => Yass::Declarations::AlignItems
27
+ puts declaration.value # => :center
28
+
29
+ # this will turn the entire stylesheet into a JSON-compatible
30
+ # data structure (only arrays, hashes, strings, etc) and
31
+ # pretty-print it
32
+ pp sheet.to_h
@@ -0,0 +1,19 @@
1
+ require "yass"
2
+
3
+ sheet = Yass::Parser.parse(<<~CSS)
4
+ @font-face {
5
+ font-family: "Bitstream Vera Serif Bold";
6
+ src: url("https://mdn.github.io/shared-assets/fonts/FiraSans-Regular.woff2");
7
+ }
8
+ CSS
9
+
10
+ # this is the whole @font-face { ... } rule
11
+ first_rule = sheet.rules.first
12
+ puts first_rule.class # => Yass::FontFaceRule
13
+
14
+ family = first_rule.family
15
+ puts family.name # => "Bitstream Vera Serif Bold"
16
+
17
+ first_source = first_rule.sources.first
18
+ puts first_source.class # => Yass::Font::Source::Url
19
+ puts first_source.specified_url # => "https://mdn.github.io/shared-assets/fonts/FiraSans-Regular.woff2"
@@ -0,0 +1,31 @@
1
+ require "yass"
2
+
3
+ sheet = Yass::Parser.parse(<<~CSS)
4
+ @media screen and (max-width: 600px) {
5
+ .container {
6
+ flex-direction: column;
7
+ }
8
+ }
9
+ CSS
10
+
11
+ # this is the whole @media { ... } rule
12
+ first_rule = sheet.rules.first
13
+ puts first_rule.class # => Yass::MediaRule
14
+
15
+ # this is the "screen and ..." part
16
+ first_query = first_rule.media_queries.first
17
+ puts first_query.media_type.value # => "screen"
18
+ puts first_query.query_condition.value # => "(max-width: 600px)"
19
+
20
+ # Sadly Stylo doesn't give us access to the full parse tree for the
21
+ # query condition, so the best we can do is emit the unparsed string.
22
+
23
+ # this is the .container rule
24
+ first_nested_rule = first_rule.rules.first
25
+ first_selector = first_nested_rule.selectors.first.children.first
26
+ puts first_selector.value # => "container"
27
+
28
+ # this is the flex-direction declaration
29
+ first_declaration = first_nested_rule.declarations.first
30
+ puts first_declaration.kind # => :flex_direction
31
+ puts first_declaration.value # => :column
@@ -0,0 +1,41 @@
1
+ require "yass"
2
+
3
+ sheet = Yass::Parser.parse(<<~CSS)
4
+ h1 {
5
+ align-items: center;
6
+ display: flex;
7
+ position: absolute;
8
+ }
9
+
10
+ .left {
11
+ float: left;
12
+ }
13
+
14
+ .right {
15
+ float: right;
16
+ }
17
+
18
+ #thing {
19
+ visibility: hidden;
20
+ }
21
+ CSS
22
+
23
+ class MyVisitor < Yass::Visitor
24
+ def visit_selector_local_name(node)
25
+ # prints "h1"
26
+ puts node.value
27
+ end
28
+
29
+ def visit_selector_klass(node)
30
+ # prints "left" and "right"
31
+ puts node.value
32
+ end
33
+
34
+ def visit_selector_id(node)
35
+ # prints "thing"
36
+ puts node.value
37
+ end
38
+ end
39
+
40
+ visitor = MyVisitor.new
41
+ visitor.visit(sheet)