yass-css 0.1.0-aarch64-linux → 0.3.0-aarch64-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 +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +107 -11
- data/Rakefile +83 -2
- data/codegen/ruby_module_tree.rb +178 -0
- data/codegen/rust_file_set.rb +84 -0
- data/codegen/utils.rb +27 -0
- data/dockerfiles/windows.dockerfile +1 -1
- data/examples/basic_example.rb +32 -0
- data/examples/font_example.rb +19 -0
- data/examples/media_example.rb +31 -0
- data/examples/visitor_example.rb +41 -0
- data/ext/yass/src/.DS_Store +0 -0
- data/ext/yass/src/declarations/.DS_Store +0 -0
- data/lib/yass/3.2/yass.so +0 -0
- data/lib/yass/3.3/yass.so +0 -0
- data/lib/yass/3.4/yass.so +0 -0
- data/lib/yass/4.0/yass.so +0 -0
- data/lib/yass/declarations.rb +7975 -0
- data/lib/yass/general.rb +31 -0
- data/lib/yass/node.rb +9 -0
- data/lib/yass/rules.rb +515 -0
- data/lib/yass/selectors.rb +325 -0
- data/lib/yass/stylesheet.rb +13 -0
- data/lib/yass/version.rb +1 -1
- data/lib/yass/visitor.rb +2463 -0
- data/lib/yass.rb +35 -0
- data/scripts/build_all.sh +44 -0
- data/scripts/detect.rb +39 -0
- data/scripts/wpt.rb +20 -0
- metadata +21 -3
- data/sig/yass.rbs +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9699731b9cb7b11899012d241ed4bddfdd6f1b62e0d374c443877622983e07ce
|
|
4
|
+
data.tar.gz: c8ab1dde56c4b4b62dcfd66c4d6da091140d2ef0bb5f4d3fb67e59e647d819ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 863baf9e4014165ecd489a219141a9a29837ce8096908486084c470421877ffb22ff697df5c83512d302ee07b6ba35ba5778c08d12e7b20f3a09e583b93af70d
|
|
7
|
+
data.tar.gz: f2923cba1fa13e211118f5ca0e55c8eba800a95da1212142da962c95cb3c2ac6b3093ae42159f7fa54d1e7b96dad5fc4ac069b96886932c63bc8d3b8a920ed7e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.3.0]
|
|
4
|
+
|
|
5
|
+
- Upgrade to rb_sys v0.9.126.
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2026-04-19
|
|
8
|
+
|
|
9
|
+
- Fully support all style declarations.
|
|
10
|
+
- Add support for `@media` rules.
|
|
11
|
+
- Add support for `@font-face` rules.
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-03-26
|
|
4
14
|
|
|
5
15
|
- Initial release
|
data/README.md
CHANGED
|
@@ -1,38 +1,134 @@
|
|
|
1
1
|
# Yass
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
## Rationale
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
22
|
+
gem install yass-css
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
## Usage
|
|
24
26
|
|
|
25
|
-
|
|
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 `
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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
|
@@ -7,8 +7,6 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
7
7
|
|
|
8
8
|
require "rb_sys/extensiontask"
|
|
9
9
|
|
|
10
|
-
task build: :compile
|
|
11
|
-
|
|
12
10
|
GEMSPEC = Gem::Specification.load("yass.gemspec")
|
|
13
11
|
|
|
14
12
|
RbSys::ExtensionTask.new("yass", GEMSPEC) do |ext|
|
|
@@ -25,6 +23,15 @@ end
|
|
|
25
23
|
|
|
26
24
|
task default: %i[compile spec]
|
|
27
25
|
|
|
26
|
+
namespace :spec do
|
|
27
|
+
desc 'Run full specs suit'
|
|
28
|
+
task full: [:full_spec_env, :compile, :spec]
|
|
29
|
+
|
|
30
|
+
task :full_spec_env do
|
|
31
|
+
ENV['FULL_SPEC'] = 'true'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
28
35
|
# For whatever reason, the :native task that comes with rb-sys doesn't work, so
|
|
29
36
|
# we define our own
|
|
30
37
|
task :native, [:platform] do |task, args|
|
|
@@ -45,3 +52,77 @@ task :native, [:platform] do |task, args|
|
|
|
45
52
|
|
|
46
53
|
sh(env, "bundle", "exec", "rb-sys-dock", "--platform", args[:platform], "--build")
|
|
47
54
|
end
|
|
55
|
+
|
|
56
|
+
task :codegen do
|
|
57
|
+
require "yass"
|
|
58
|
+
require_relative "codegen/rust_file_set"
|
|
59
|
+
|
|
60
|
+
general_files = Yass::Codegen::RustFileSet::new(
|
|
61
|
+
Dir.glob("ext/yass/src/general/**/*.rs")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
File.write("lib/yass/general.rb", <<~RUBY)
|
|
65
|
+
# frozen_string_literal: true
|
|
66
|
+
|
|
67
|
+
#{general_files.module_tree.to_ruby_classes}
|
|
68
|
+
RUBY
|
|
69
|
+
|
|
70
|
+
declaration_files = Yass::Codegen::RustFileSet.new(Dir.glob("ext/yass/src/declarations/**/*.rs"))
|
|
71
|
+
|
|
72
|
+
File.write("lib/yass/declarations.rb", <<~RUBY)
|
|
73
|
+
# frozen_string_literal: true
|
|
74
|
+
|
|
75
|
+
#{declaration_files.module_tree.to_ruby_classes}
|
|
76
|
+
RUBY
|
|
77
|
+
|
|
78
|
+
selector_files = Yass::Codegen::RustFileSet.new(Dir.glob("ext/yass/src/selectors/**/*.rs"))
|
|
79
|
+
|
|
80
|
+
File.write("lib/yass/selectors.rb", <<~RUBY)
|
|
81
|
+
# frozen_string_literal: true
|
|
82
|
+
|
|
83
|
+
#{selector_files.module_tree.to_ruby_classes}
|
|
84
|
+
RUBY
|
|
85
|
+
|
|
86
|
+
rule_files = Yass::Codegen::RustFileSet.new([
|
|
87
|
+
"ext/yass/src/rules/rule.rs",
|
|
88
|
+
"ext/yass/src/rules/style_rule.rs",
|
|
89
|
+
"ext/yass/src/rules/media_rule.rs",
|
|
90
|
+
"ext/yass/src/rules/font_face_rule.rs",
|
|
91
|
+
*Dir.glob("ext/yass/src/rules/fonts/**/*.rs"),
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
File.write("lib/yass/rules.rb", <<~RUBY)
|
|
95
|
+
# frozen_string_literal: true
|
|
96
|
+
|
|
97
|
+
#{rule_files.module_tree.to_ruby_classes}
|
|
98
|
+
RUBY
|
|
99
|
+
|
|
100
|
+
all_modules = general_files.module_tree
|
|
101
|
+
.merge(declaration_files.module_tree)
|
|
102
|
+
.merge(selector_files.module_tree)
|
|
103
|
+
.merge(rule_files.module_tree)
|
|
104
|
+
|
|
105
|
+
visitor_class = <<~RUBY
|
|
106
|
+
# frozen_string_literal: true
|
|
107
|
+
|
|
108
|
+
module Yass
|
|
109
|
+
class Visitor
|
|
110
|
+
def visit(node)
|
|
111
|
+
node.accept(self) if node
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def visit_list(nodes)
|
|
115
|
+
nodes.each { |node| visit(node) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def visit_stylesheet(node)
|
|
119
|
+
visit_list(node.rules)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
#{all_modules.to_visitor_methods(indent: " ").join("\n")}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
RUBY
|
|
126
|
+
|
|
127
|
+
File.write("lib/yass/visitor.rb", visitor_class)
|
|
128
|
+
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
|
|
@@ -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
|