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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deadfire
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/deadfire.rb CHANGED
@@ -1,28 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "deadfire/css_buffer"
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/parser"
7
- require_relative "deadfire/transformers/transformer"
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
- @configuration ||= Configuration.new
33
+ @config ||= Configuration.new
14
34
  end
15
35
 
16
36
  def reset
17
- @configuration = Configuration.new
37
+ @config = Configuration.new
18
38
  end
19
39
 
20
40
  def configure
21
- yield(@configuration)
41
+ yield(configuration)
22
42
  end
23
43
 
24
44
  def parse(content, options = {})
25
- Parser.parse(content, options)
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.2.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: 2022-10-25 00:00:00.000000000 Z
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/css_buffer.rb
43
+ - lib/deadfire/css_generator.rb
44
+ - lib/deadfire/error_reporter.rb
42
45
  - lib/deadfire/errors.rb
43
- - lib/deadfire/parser.rb
44
- - lib/deadfire/transformers/transformer.rb
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.3.7
85
+ rubygems_version: 3.5.6
69
86
  signing_key:
70
87
  specification_version: 4
71
88
  summary: Deadfire - lightweight css preprocessor
@@ -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