deadfire 0.2.0 → 0.4.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/.github/workflows/ci.yml +2 -2
- data/Gemfile.lock +1 -1
- data/README.md +55 -32
- data/benchmarks/basic_benchmark.rb +47 -12
- data/benchmarks/tailwind_parser.rb +23 -0
- data/bin/console +16 -3
- data/changelog.md +38 -1
- data/lib/deadfire/ast_printer.rb +58 -0
- data/lib/deadfire/configuration.rb +18 -5
- data/lib/deadfire/css_generator.rb +65 -0
- data/lib/deadfire/error_reporter.rb +30 -0
- data/lib/deadfire/errors.rb +1 -14
- data/lib/deadfire/filename_helper.rb +49 -0
- data/lib/deadfire/front_end/apply_node.rb +43 -0
- data/lib/deadfire/front_end/at_rule_node.rb +19 -0
- data/lib/deadfire/front_end/base_node.rb +11 -0
- data/lib/deadfire/front_end/block_node.rb +21 -0
- data/lib/deadfire/front_end/comment_node.rb +17 -0
- data/lib/deadfire/front_end/newline_node.rb +17 -0
- data/lib/deadfire/front_end/parser.rb +156 -0
- data/lib/deadfire/front_end/ruleset_node.rb +18 -0
- data/lib/deadfire/front_end/scanner.rb +280 -0
- data/lib/deadfire/front_end/selector_node.rb +52 -0
- data/lib/deadfire/front_end/stylesheet_node.rb +21 -0
- data/lib/deadfire/front_end/token.rb +20 -0
- data/lib/deadfire/interpreter.rb +84 -0
- data/lib/deadfire/parser_engine.rb +41 -0
- data/lib/deadfire/spec.rb +135 -0
- data/lib/deadfire/version.rb +1 -1
- data/lib/deadfire.rb +34 -7
- metadata +23 -6
- data/lib/deadfire/css_buffer.rb +0 -37
- data/lib/deadfire/parser.rb +0 -387
- data/lib/deadfire/transformers/transformer.rb +0 -17
@@ -0,0 +1,84 @@
|
|
1
|
+
# Frozen_string_literal: true
|
2
|
+
|
3
|
+
module Deadfire
|
4
|
+
class Interpreter # :nodoc:
|
5
|
+
singleton_class.attr_accessor :cached_apply_rules
|
6
|
+
self.cached_apply_rules = Hash.new { |h, k| h[k] = nil }
|
7
|
+
|
8
|
+
def initialize(error_reporter)
|
9
|
+
@error_reporter = error_reporter
|
10
|
+
end
|
11
|
+
|
12
|
+
def interpret(node)
|
13
|
+
node.accept(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_stylesheet_node(node)
|
17
|
+
node.statements.each { |child| child.accept(self) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_at_rule_node(node)
|
21
|
+
if node.block
|
22
|
+
visit_block_node(node.block, node)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_ruleset_node(node)
|
27
|
+
if node.block
|
28
|
+
visit_block_node(node.block, node)
|
29
|
+
|
30
|
+
unless Interpreter.cached_apply_rules[node.selector.selector]
|
31
|
+
Interpreter.cached_apply_rules[node.selector.selector] = node.block if node.selector.cacheable?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_block_node(node, parent)
|
37
|
+
node.declarations.each do |declaration|
|
38
|
+
case declaration
|
39
|
+
when ApplyNode
|
40
|
+
apply_mixin(declaration, node)
|
41
|
+
else
|
42
|
+
# we may not need to visit this as we don't process/transform/optimize
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_comment_node(node)
|
48
|
+
# node.accept(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_apply_node(node)
|
52
|
+
# do nothing for now
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_newline_node(node)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def apply_mixin(mixin, node)
|
61
|
+
updated_declarations = []
|
62
|
+
mixin.mixin_names.each do |mixin_name|
|
63
|
+
if Interpreter.cached_apply_rules[mixin_name]
|
64
|
+
cached_block = Interpreter.cached_apply_rules[mixin_name]
|
65
|
+
|
66
|
+
# NOTE: remove the left and right brace but we probably don't need to do this, how can this be simplified?
|
67
|
+
cached_block.declarations[1...-1].each do |cached_declaration|
|
68
|
+
updated_declarations << cached_declaration
|
69
|
+
end
|
70
|
+
updated_declarations.shift if updated_declarations.first.type == :newline
|
71
|
+
updated_declarations.pop if updated_declarations.last.type == :newline
|
72
|
+
else
|
73
|
+
@error_reporter.error(mixin.lineno, "Mixin #{mixin_name} not found") # TODO: we need the declarations lineno, not the block
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if updated_declarations.any?
|
78
|
+
index = node.declarations.index(mixin)
|
79
|
+
node.declarations.delete_at(index)
|
80
|
+
node.declarations.insert(index, *updated_declarations)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Deadfire
|
4
|
+
class ParserEngine # :nodoc:
|
5
|
+
attr_reader :error_reporter, :options, :current
|
6
|
+
|
7
|
+
def initialize(content, options = {})
|
8
|
+
@error_reporter = ErrorReporter.new
|
9
|
+
@options = {}
|
10
|
+
@scanner = FrontEnd::Scanner.new(content, error_reporter)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
ast = _parse
|
15
|
+
interpreter = Interpreter.new(error_reporter)
|
16
|
+
ast.statements.each do |node|
|
17
|
+
interpreter.interpret(node)
|
18
|
+
end
|
19
|
+
CssGenerator.new(ast).generate
|
20
|
+
end
|
21
|
+
|
22
|
+
def print_ast
|
23
|
+
ast = _parse
|
24
|
+
printer = AstPrinter.new
|
25
|
+
ast.statements.each do |node|
|
26
|
+
printer.print(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def errors?
|
31
|
+
@error_reporter.errors?
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def _parse
|
37
|
+
tokens = @scanner.tokenize
|
38
|
+
FrontEnd::Parser.new(tokens, error_reporter).parse
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Deadfire
|
2
|
+
class Spec # :nodoc:
|
3
|
+
# EBNF
|
4
|
+
# stylesheet = [ CDO | CDC | S | statement ]*;
|
5
|
+
# statement = ruleset | at-rule;
|
6
|
+
# at-rule = ATKEYWORD S* any-value* [ block | ';' S* ];
|
7
|
+
# block = '{' S* [ S* (declaration | at-rule) ]* '}' S*;
|
8
|
+
# ruleset = selector [ ',' S* selector ]* S* '{' S* declaration [ ';' S* declaration ]* '}' S*;
|
9
|
+
# selector = simple-selector [ combinator selector | S+ [ combinator? selector ]? ];
|
10
|
+
# simple-selector = element-name? [ '#' id-selector ]? [ '.' class-selector ]* [ '[' attrib-selector ']' ]*;
|
11
|
+
# id-selector = NAME;
|
12
|
+
# class-selector = '.' NAME;
|
13
|
+
# attrib-selector = NAME [ [ '=' | INCLUDES | DASHMATCH ] any-value ]?;
|
14
|
+
# combinator = '+' | '>' | '~' | S+;
|
15
|
+
# declaration = property ':' S* value;
|
16
|
+
# property = NAME;
|
17
|
+
# value = any-value [ ',' S* any-value ]*;
|
18
|
+
# any-value = IDENT | STRING | NUMBER | PERCENTAGE | DIMENSION | COLOR | URI | FUNCTION any-value* ')' | '(' any-value* ')' | '[' any-value* ']' | '{' any-value* '}' | ';';
|
19
|
+
|
20
|
+
CHARSET = "@charset"
|
21
|
+
IMPORT = "@import"
|
22
|
+
MEDIA = "@media"
|
23
|
+
PAGE = "@page"
|
24
|
+
FONT_FACE = "@font-face"
|
25
|
+
KEYFRAMES = "@keyframes"
|
26
|
+
SUPPORTS = "@supports"
|
27
|
+
NAMESPACE = "@namespace"
|
28
|
+
COUNTER_STYLE = "@counter-style"
|
29
|
+
VIEWPORT = "@viewport"
|
30
|
+
DOCUMENT = "@document"
|
31
|
+
APPLY = "@apply"
|
32
|
+
LAYER = "@layer"
|
33
|
+
|
34
|
+
CSS_AT_RULES = [
|
35
|
+
CHARSET,
|
36
|
+
IMPORT,
|
37
|
+
MEDIA,
|
38
|
+
PAGE,
|
39
|
+
FONT_FACE,
|
40
|
+
KEYFRAMES,
|
41
|
+
SUPPORTS,
|
42
|
+
NAMESPACE,
|
43
|
+
COUNTER_STYLE,
|
44
|
+
VIEWPORT,
|
45
|
+
DOCUMENT,
|
46
|
+
APPLY,
|
47
|
+
LAYER
|
48
|
+
]
|
49
|
+
|
50
|
+
CSS_SELECTORS = [
|
51
|
+
":root",
|
52
|
+
"::before",
|
53
|
+
"::after",
|
54
|
+
":hover",
|
55
|
+
":active",
|
56
|
+
":focus",
|
57
|
+
":first-child",
|
58
|
+
":last-child",
|
59
|
+
":nth-child(n)",
|
60
|
+
":nth-last-child(n)",
|
61
|
+
":only-child",
|
62
|
+
":only-of-type",
|
63
|
+
":first-of-type",
|
64
|
+
":last-of-type",
|
65
|
+
":nth-of-type(n)",
|
66
|
+
":nth-last-of-type(n)",
|
67
|
+
":checked",
|
68
|
+
":disabled",
|
69
|
+
":enabled",
|
70
|
+
":empty",
|
71
|
+
"::first-line",
|
72
|
+
"::first-letter",
|
73
|
+
"::selection",
|
74
|
+
"~",
|
75
|
+
"+",
|
76
|
+
">",
|
77
|
+
" ",
|
78
|
+
".class",
|
79
|
+
"#id",
|
80
|
+
"[attribute]",
|
81
|
+
"[attribute=value]",
|
82
|
+
"[attribute~=value]",
|
83
|
+
"[attribute|=value]",
|
84
|
+
"[attribute^=value]",
|
85
|
+
"[attribute$=value]",
|
86
|
+
"[attribute*=value]",
|
87
|
+
":not(selector)",
|
88
|
+
":matches(selector)",
|
89
|
+
":any(selector)",
|
90
|
+
":has(selector)",
|
91
|
+
"::placeholder"
|
92
|
+
]
|
93
|
+
|
94
|
+
CSS_POTENTIAL_VALUES_TYPES = [
|
95
|
+
:length_and_size, # e.g. 5px or 5em
|
96
|
+
:color, # e.g. hex codes, RGB and RGBA values, HSL and HSLA values, and named colors - #fff
|
97
|
+
:textual, # strings, keywords, and URLs - bold, url, etc.
|
98
|
+
:enumerated, # e.g. inline, block, list-item, etc.
|
99
|
+
:functional, # e.g. calc(), attr(), and var() - calc(100% - 10px)
|
100
|
+
:other # everything else like inherit, initial, unset
|
101
|
+
]
|
102
|
+
|
103
|
+
COMMON_CSS_PROPERTIES = [
|
104
|
+
"color",
|
105
|
+
"background-color",
|
106
|
+
"background-image",
|
107
|
+
"border",
|
108
|
+
"border-radius",
|
109
|
+
"box-shadow",
|
110
|
+
"font-family",
|
111
|
+
"font-size",
|
112
|
+
"font-weight",
|
113
|
+
"letter-spacing",
|
114
|
+
"line-height",
|
115
|
+
"margin",
|
116
|
+
"padding",
|
117
|
+
"text-align",
|
118
|
+
"text-decoration",
|
119
|
+
"text-transform",
|
120
|
+
"width",
|
121
|
+
"height",
|
122
|
+
"display",
|
123
|
+
"flex",
|
124
|
+
"flex-direction",
|
125
|
+
"justify-content",
|
126
|
+
"align-items",
|
127
|
+
"position",
|
128
|
+
"top",
|
129
|
+
"right",
|
130
|
+
"bottom",
|
131
|
+
"left",
|
132
|
+
"z-index"
|
133
|
+
]
|
134
|
+
end
|
135
|
+
end
|
data/lib/deadfire/version.rb
CHANGED
data/lib/deadfire.rb
CHANGED
@@ -1,28 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "deadfire/
|
3
|
+
require_relative "deadfire/ast_printer"
|
4
|
+
require_relative "deadfire/css_generator"
|
4
5
|
require_relative "deadfire/configuration"
|
5
6
|
require_relative "deadfire/errors"
|
6
|
-
require_relative "deadfire/
|
7
|
-
require_relative "deadfire/
|
7
|
+
require_relative "deadfire/error_reporter"
|
8
|
+
require_relative "deadfire/interpreter"
|
9
|
+
require_relative "deadfire/parser_engine"
|
10
|
+
require_relative "deadfire/spec"
|
11
|
+
require_relative "deadfire/filename_helper"
|
8
12
|
require_relative "deadfire/version"
|
13
|
+
require_relative "deadfire/front_end/scanner"
|
14
|
+
require_relative "deadfire/front_end/token"
|
15
|
+
require_relative "deadfire/front_end/parser"
|
16
|
+
require_relative "deadfire/front_end/base_node"
|
17
|
+
require_relative "deadfire/front_end/apply_node"
|
18
|
+
require_relative "deadfire/front_end/at_rule_node"
|
19
|
+
require_relative "deadfire/front_end/block_node"
|
20
|
+
require_relative "deadfire/front_end/comment_node"
|
21
|
+
require_relative "deadfire/front_end/newline_node"
|
22
|
+
require_relative "deadfire/front_end/ruleset_node"
|
23
|
+
require_relative "deadfire/front_end/selector_node"
|
24
|
+
require_relative "deadfire/front_end/stylesheet_node"
|
9
25
|
|
10
26
|
module Deadfire
|
27
|
+
PERMISSIBLE_FILE_EXTENSIONS = [".css", ".scss"].freeze
|
28
|
+
|
11
29
|
class << self
|
30
|
+
attr_reader :config
|
31
|
+
|
12
32
|
def configuration
|
13
|
-
@
|
33
|
+
@config ||= Configuration.new
|
14
34
|
end
|
15
35
|
|
16
36
|
def reset
|
17
|
-
@
|
37
|
+
@config = Configuration.new
|
18
38
|
end
|
19
39
|
|
20
40
|
def configure
|
21
|
-
yield(
|
41
|
+
yield(configuration)
|
22
42
|
end
|
23
43
|
|
24
44
|
def parse(content, options = {})
|
25
|
-
|
45
|
+
configure do |config|
|
46
|
+
config.root_path = options[:root_path]
|
47
|
+
config.compressed = options[:compressed]
|
48
|
+
end
|
49
|
+
|
50
|
+
parser = ParserEngine.new(content)
|
51
|
+
# TODO: hook into a logger and report the errors
|
52
|
+
parser.parse
|
26
53
|
end
|
27
54
|
end
|
28
55
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deadfire
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Haroon Ahmed
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -31,17 +31,34 @@ files:
|
|
31
31
|
- benchmarks/output.css.map
|
32
32
|
- benchmarks/profilng.rb
|
33
33
|
- benchmarks/tailwind.css
|
34
|
+
- benchmarks/tailwind_parser.rb
|
34
35
|
- bin/console
|
35
36
|
- bin/setup
|
36
37
|
- bin/test
|
37
38
|
- changelog.md
|
38
39
|
- deadfire.gemspec
|
39
40
|
- lib/deadfire.rb
|
41
|
+
- lib/deadfire/ast_printer.rb
|
40
42
|
- lib/deadfire/configuration.rb
|
41
|
-
- lib/deadfire/
|
43
|
+
- lib/deadfire/css_generator.rb
|
44
|
+
- lib/deadfire/error_reporter.rb
|
42
45
|
- lib/deadfire/errors.rb
|
43
|
-
- lib/deadfire/
|
44
|
-
- lib/deadfire/
|
46
|
+
- lib/deadfire/filename_helper.rb
|
47
|
+
- lib/deadfire/front_end/apply_node.rb
|
48
|
+
- lib/deadfire/front_end/at_rule_node.rb
|
49
|
+
- lib/deadfire/front_end/base_node.rb
|
50
|
+
- lib/deadfire/front_end/block_node.rb
|
51
|
+
- lib/deadfire/front_end/comment_node.rb
|
52
|
+
- lib/deadfire/front_end/newline_node.rb
|
53
|
+
- lib/deadfire/front_end/parser.rb
|
54
|
+
- lib/deadfire/front_end/ruleset_node.rb
|
55
|
+
- lib/deadfire/front_end/scanner.rb
|
56
|
+
- lib/deadfire/front_end/selector_node.rb
|
57
|
+
- lib/deadfire/front_end/stylesheet_node.rb
|
58
|
+
- lib/deadfire/front_end/token.rb
|
59
|
+
- lib/deadfire/interpreter.rb
|
60
|
+
- lib/deadfire/parser_engine.rb
|
61
|
+
- lib/deadfire/spec.rb
|
45
62
|
- lib/deadfire/version.rb
|
46
63
|
homepage: https://github.com/hahmed/deadfire
|
47
64
|
licenses:
|
@@ -65,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
82
|
- !ruby/object:Gem::Version
|
66
83
|
version: '0'
|
67
84
|
requirements: []
|
68
|
-
rubygems_version: 3.
|
85
|
+
rubygems_version: 3.5.6
|
69
86
|
signing_key:
|
70
87
|
specification_version: 4
|
71
88
|
summary: Deadfire - lightweight css preprocessor
|
data/lib/deadfire/css_buffer.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require "stringio"
|
3
|
-
|
4
|
-
module Deadfire
|
5
|
-
class CssBuffer
|
6
|
-
attr_reader :lineno, :buffer
|
7
|
-
|
8
|
-
def initialize(content)
|
9
|
-
@content = StringIO.new(content)
|
10
|
-
@buffer = []
|
11
|
-
@lineno = 0
|
12
|
-
end
|
13
|
-
|
14
|
-
def gets(skip_buffer: false)
|
15
|
-
output = content.gets
|
16
|
-
if output && !skip_buffer
|
17
|
-
buffer << output
|
18
|
-
end
|
19
|
-
@lineno += 1
|
20
|
-
output
|
21
|
-
end
|
22
|
-
|
23
|
-
def peek
|
24
|
-
output = content.gets
|
25
|
-
content.ungetc(output)
|
26
|
-
output
|
27
|
-
end
|
28
|
-
|
29
|
-
def eof?
|
30
|
-
content.eof?
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
attr_reader :content
|
36
|
-
end
|
37
|
-
end
|