herb 0.3.0-x86-linux-gnu → 0.4.0-x86-linux-gnu
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/LICENSE.txt +2 -2
- data/README.md +81 -116
- data/ext/herb/nodes.c +1 -1
- data/lib/herb/3.0/herb.so +0 -0
- data/lib/herb/3.1/herb.so +0 -0
- data/lib/herb/3.2/herb.so +0 -0
- data/lib/herb/3.3/herb.so +0 -0
- data/lib/herb/3.4/herb.so +0 -0
- data/lib/herb/ast/node.rb +6 -1
- data/lib/herb/cli.rb +18 -2
- data/lib/herb/parse_result.rb +7 -2
- data/lib/herb/project.rb +79 -33
- data/lib/herb/version.rb +1 -1
- data/sig/herb/ast/node.rbs +3 -0
- data/sig/herb/parse_result.rbs +3 -0
- data/src/analyze.c +41 -11
- data/src/analyze_helpers.c +9 -4
- data/src/extract.c +6 -2
- data/src/include/parser_helpers.h +7 -2
- data/src/include/pretty_print.h +48 -8
- data/src/include/prism_helpers.h +4 -1
- data/src/include/token_struct.h +1 -1
- data/src/include/version.h +1 -1
- data/src/lexer.c +11 -2
- data/src/parser.c +13 -4
- data/src/parser_helpers.c +10 -3
- data/src/pretty_print.c +50 -10
- data/src/prism_helpers.c +4 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c26d594096c8c31de428e3e4ac60cfa041bd7aaf8dfc7ed519629a9069375b0a
|
4
|
+
data.tar.gz: 7fe196d58c58db1833bcd714b042ae9c2fd4419e65e6a4c987015035fd8c3941
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ef8bf7b14d9318f71c466153292bb0b447a941d57199fbafb6ad2426be5896d95d1eb7b39a999910d606dac74c97ca9ca55e40ace7d037ae740e7a27367ffe9
|
7
|
+
data.tar.gz: d7b82f499e55db467f480305d28c116c0a8d0937f3c0609a1bdc5cbd8c6a5a9f03f1bb070488a7c389aa3c4e5cb9be3744c72d50cfb8aca7ce64c29c5db93050
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2024 Marco Roth
|
3
|
+
Copyright (c) 2024-2025 Marco Roth
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
THE SOFTWARE.
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,170 +1,135 @@
|
|
1
1
|
<div align="center">
|
2
|
-
<img alt="Herb HTML+ERB parser" height="256px" src="https://github.com/user-attachments/assets/d0714ee1-ca33-4aa4-aaa9-d632ba79d54a">
|
2
|
+
<img alt="Herb HTML+ERB parser" style="height: 256px" height="256px" src="https://github.com/user-attachments/assets/d0714ee1-ca33-4aa4-aaa9-d632ba79d54a">
|
3
3
|
</div>
|
4
4
|
|
5
5
|
<h2 align="center">Herb</h2>
|
6
6
|
|
7
|
-
<h4 align="center">HTML
|
7
|
+
<h4 align="center">HTML+ERB (HTML + Embedded Ruby)</h4>
|
8
8
|
|
9
|
-
<div align="center">Powerful and seamless HTML-aware ERB parsing and tooling.</div
|
9
|
+
<div align="center">Powerful and seamless HTML-aware ERB parsing and tooling.</div><br/>
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
- [**Clang 19**](https://clang.llvm.org): The compiler used to build this project.
|
19
|
-
- [**Clang Format 19**](https://clang.llvm.org/docs/ClangFormat.html): For formatting the project.
|
20
|
-
- [**Clang Tidy 19**](https://clang.llvm.org/extra/clang-tidy/): For linting the project.
|
21
|
-
- [**Prism Ruby Parser v1.4.0**](https://github.com/ruby/prism/releases/tag/v1.4.0): We use Prism for Parsing the Ruby Source Code in the HTML+ERB files.
|
22
|
-
- [**Ruby**](https://www.ruby-lang.org/en/): We need Ruby as a dependency for `bundler`.
|
23
|
-
- [**Bundler**](https://bundler.io): We are using `bundler` to build [`prism`](https://github.com/ruby/prism) from source so we can build `herb` against it.
|
24
|
-
- [**Emscripten**](https://emscripten.org): For the WebAssembly build of `libherb` so it can be used in the browser using the [`@herb-tools/browser`](https://github.com/marcoroth/herb/blob/main/javascript/packages/browser) package.
|
25
|
-
- [**Doxygen**](https://www.doxygen.nl): For building the C-Reference documentation pages.
|
11
|
+
<p align="center">
|
12
|
+
<a href="https://rubygems.org/gems/herb"><img alt="Gem Version" src="https://img.shields.io/gem/v/herb"></a>
|
13
|
+
<a href="https://herb-tools.dev"><img alt="Documentation" src="https://img.shields.io/badge/documentation-available-green"></a>
|
14
|
+
<a href="https://herb-tools.dev/playground"><img alt="playground" src="https://img.shields.io/badge/playground-Try_it_in_the_browser!-green"></a>
|
15
|
+
<a href="https://github.com/marcoroth/herb/blob/main/LICENSE.txt"><img alt="License" src="https://img.shields.io/github/license/marcoroth/herb"></a>
|
16
|
+
<a href="https://github.com/marcoroth/herb/issues"><img alt="Issues" src="https://img.shields.io/github/issues/marcoroth/herb"></a>
|
17
|
+
</p>
|
26
18
|
|
27
|
-
|
19
|
+
<br/><br/><br/>
|
28
20
|
|
29
|
-
|
30
|
-
xargs sudo apt-get install < Aptfile
|
31
|
-
```
|
32
|
-
or:
|
21
|
+
## What is Herb?
|
33
22
|
|
34
|
-
|
35
|
-
sudo apt-get install check clang-19 clang-tidy-19 clang-format-19 emscripten doxygen
|
36
|
-
```
|
23
|
+
**Herb** is an ecosystem of developer tooling built specifically around **HTML+ERB** (`.html.erb`) files. It is designed to simplify and enhance the experience of working with HTML+ERB templates through precise, accurate tooling.
|
37
24
|
|
38
|
-
|
25
|
+
At the core of Herb is the **Herb Parser**, a fast, portable, and HTML-aware ERB parser written in C. The parser generates a detailed, accurate syntax tree that serves as the foundation for reliable code analysis, transformations, and developer tooling.
|
39
26
|
|
40
|
-
|
41
|
-
brew bundle
|
42
|
-
```
|
43
|
-
or:
|
27
|
+
The Herb ecosystem includes **developer tools** (CLI, language server, formatter, linter), **language bindings** (for Ruby, Node.js, and the Browser using WebAssembly), and **utility libraries** (language service, highlighter, minifier, printer). All these components leverage the Herb Parser's syntax tree to provide consistent, accurate, and helpful tooling experiences.
|
44
28
|
|
45
|
-
|
46
|
-
brew install check llvm@19 emscripten doxygen
|
47
|
-
```
|
29
|
+
## What Herb Can Do for You
|
48
30
|
|
49
|
-
|
31
|
+
Herb provides a complete ecosystem of HTML+ERB tooling, designed to simplify and enhance your daily workflow. Built on the **Herb Parser**, it offers multiple tools that integrate seamlessly into editors, developer environments, and CI pipelines:
|
50
32
|
|
51
|
-
|
33
|
+
- **Herb Language Server** ([available now](https://herb-tools.dev/projects/language-server)):
|
34
|
+
Rich integration for editors like VS Code, Zed, Neovim, and more. It provides diagnostics and real-time feedback to keep your templates error-free.
|
52
35
|
|
53
|
-
|
36
|
+
- **Herb Formatter** ([coming soon](https://herb-tools.dev/projects/formatter)):
|
37
|
+
Automatic, consistent formatting for HTML+ERB files, reducing manual styling and enforcing a standard across projects.
|
54
38
|
|
55
|
-
|
56
|
-
|
57
|
-
```
|
39
|
+
- **Herb Linter** ([coming soon](https://herb-tools.dev/projects/linter)):
|
40
|
+
Static analysis for your HTML+ERB templates to enforce best practices and quickly identify common mistakes.
|
58
41
|
|
59
|
-
|
42
|
+
You can use Herb programmatically in **Ruby**, as well as in **JavaScript** via Node.js, WebAssembly, or directly in browsers.
|
60
43
|
|
61
|
-
|
44
|
+
For a complete overview of all available tools, libraries, and integrations, visit the [**Projects page**](https://herb-tools.dev/projects) on our documentation site.
|
62
45
|
|
63
|
-
|
64
|
-
make all
|
65
|
-
```
|
46
|
+
## Motiviation
|
66
47
|
|
67
|
-
|
68
|
-
For any consecutive builds you can just run `make`/`make all`.
|
48
|
+
HTML+ERB templates never really had good, accurate, and reliable tooling. While developer tooling for Ruby code improved significantly in the last few years (especially with the introduction of the new Prism parser), HTML+ERB files remained underserved, lacking fundamental support like syntax checking, auto-formatting, linting, and structural understanding.
|
69
49
|
|
70
|
-
|
50
|
+
At the same time, with the rise of tools like [Hotwire](https://hotwired.dev), [Stimulus](https://stimulus.hotwired.dev), [Turbo](https://turbo.hotwired.dev), [HTMX](https://htmx.org), [Unploy](https://unpoly.com), and [Alpine.js](https://alpinejs.dev), advanced HTML templating became increasingly relevant (again). Developers expect modern, reliable, and precise tooling, especially given the robust ecosystem available to JavaScript frameworks and libraries.
|
71
51
|
|
72
|
-
|
52
|
+
Herb was built to close this tooling gap, providing proper tooling for HTML+ERB that matches what modern developers expect in the age of language servers, LLMs, and AI-driven workflows.
|
73
53
|
|
74
|
-
|
75
|
-
❯ ./herb
|
76
|
-
./herb [command] [options]
|
54
|
+
## Command-Line Usage
|
77
55
|
|
78
|
-
|
56
|
+
Install the Herb gem via RubyGems:
|
79
57
|
|
80
|
-
|
81
|
-
|
82
|
-
./herb parse [file] - Parse a file
|
83
|
-
./herb ruby [file] - Extract Ruby from a file
|
84
|
-
./herb html [file] - Extract HTML from a file
|
85
|
-
./herb prism [file] - Extract Ruby from a file and parse the Ruby source with Prism
|
58
|
+
```sh
|
59
|
+
gem install herb
|
86
60
|
```
|
87
61
|
|
88
|
-
|
62
|
+
Basic usage to analyze all HTML+ERB files in your project:
|
89
63
|
|
64
|
+
```sh
|
65
|
+
herb analyze .
|
90
66
|
```
|
91
|
-
❯ ./herb lex examples/simple_erb.html.erb
|
92
|
-
|
93
|
-
#<Herb::Token type="TOKEN_ERB_START" value="<%" range=[0, 2] start=(1:0) end=(1:2)>
|
94
|
-
#<Herb::Token type="TOKEN_ERB_CONTENT" value=" title " range=[2, 9] start=(1:2) end=(1:9)>
|
95
|
-
#<Herb::Token type="TOKEN_ERB_END" value="%>" range=[9, 11] start=(1:9) end=(1:11)>
|
96
|
-
#<Herb::Token type="TOKEN_NEWLINE" value="\n" range=[11, 12] start=(1:0) end=(2:1)>
|
97
|
-
#<Herb::Token type="TOKEN_EOF" value="" range=[12, 12] start=(2:1) end=(2:1)>
|
98
67
|
|
99
|
-
|
100
|
-
|
101
|
-
12 µs
|
102
|
-
0.012 ms
|
103
|
-
0.000012 s
|
68
|
+
This will give you an overview of how the Herb Parser sees your project:
|
104
69
|
```
|
70
|
+
--- SUMMARY --------------------------------------------------------------------
|
71
|
+
Total files: 145
|
72
|
+
✅ Successful: 143 (98.6%)
|
73
|
+
❌ Failed: 0 (0.0%)
|
74
|
+
⚠️ Parse errors: 2 (1.4%)
|
75
|
+
⏱️ Timed out: 0 (0.0%)
|
105
76
|
|
106
|
-
|
107
|
-
|
108
|
-
|
77
|
+
Files with parse errors:
|
78
|
+
- app/views/contributions/index.html.erb
|
79
|
+
- index.html.erb
|
109
80
|
|
110
|
-
|
111
|
-
rake
|
81
|
+
Results saved to 2025-06-29_12-16-23_erb_parsing_result_rubyevents.log
|
112
82
|
```
|
113
83
|
|
114
|
-
|
84
|
+
Herb also comes with other useful commands:
|
115
85
|
|
116
|
-
```bash
|
117
|
-
bundle console
|
118
|
-
```
|
119
|
-
|
120
|
-
```
|
121
|
-
irb(main):001> Herb.parse("<div></div>")
|
122
|
-
|
123
|
-
# => #<Herb::ParseResult:0x0000000 ... >
|
124
|
-
```
|
125
|
-
|
126
|
-
### Test
|
127
|
-
|
128
|
-
Builds the test suite from files in `test/` and creates the `run_herb_tests` executable to run the tests:
|
129
|
-
|
130
|
-
#### For the C Tests
|
131
|
-
|
132
|
-
```bash
|
133
|
-
make test && ./run_herb_tests
|
134
86
|
```
|
87
|
+
Herb 🌿 Powerful and seamless HTML-aware ERB parsing and tooling.
|
135
88
|
|
136
|
-
|
89
|
+
Usage:
|
90
|
+
bundle exec herb [command] [options]
|
137
91
|
|
138
|
-
|
139
|
-
|
92
|
+
Commands:
|
93
|
+
bundle exec herb lex [file] Lex a file.
|
94
|
+
bundle exec herb parse [file] Parse a file.
|
95
|
+
bundle exec herb analyze [path] Analyze a project by passing a directory to the root of the project
|
96
|
+
bundle exec herb ruby [file] Extract Ruby from a file.
|
97
|
+
bundle exec herb html [file] Extract HTML from a file.
|
98
|
+
bundle exec herb playground [file] Open the content of the source file in the playground
|
99
|
+
bundle exec herb version Prints the versions of the Herb gem and the libherb library.
|
140
100
|
```
|
141
101
|
|
142
|
-
|
102
|
+
For detailed information, like how you can use Herb progamatiacally in Ruby and JavaScript, visit the [documentation site](https://herb-tools.dev/bindings/ruby/reference).
|
143
103
|
|
144
|
-
Removes the `herb`, `run_herb_tests`, `prism` installation, and all `.o` files.
|
145
104
|
|
146
|
-
|
147
|
-
make clean
|
148
|
-
```
|
105
|
+
## Background and Talk
|
149
106
|
|
150
|
-
|
107
|
+
**Herb** was first introduced at [**RubyKaigi 2025**](https://rubykaigi.org/2025/presentations/marcoroth.html) in April 2025 with the talk [*Empowering Developers with HTML-Aware ERB Tooling*](https://www.rubyevents.org/talks/empowering-developers-with-html-aware-erb-tooling-rubykaigi-2025).
|
151
108
|
|
152
|
-
|
109
|
+
## Contributing
|
153
110
|
|
154
|
-
|
155
|
-
bin/integration
|
156
|
-
```
|
111
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/marcoroth/herb). Please see the [CONTRIBUTING.md](https://github.com/marcoroth/herb/blob/main/CONTRIBUTING.md) document for guidelines on how to set up Herb for local development and how to contribute to **Herb**.
|
157
112
|
|
158
|
-
|
113
|
+
## Prior Art & Inspiration
|
159
114
|
|
160
|
-
|
161
|
-
❯ bin/integration
|
115
|
+
While Herb brings a fresh approach to HTML+ERB tooling, it builds upon and learns from several existing tools and approaches in the ecosystem:
|
162
116
|
|
163
|
-
[
|
117
|
+
- [**Tree-sitter**](https://tree-sitter.github.io/tree-sitter/)
|
118
|
+
- [**tree-sitter-embedded-template**](https://github.com/tree-sitter/tree-sitter-embedded-template)
|
119
|
+
- [**Prism Ruby Parser**](https://github.com/ruby/prism)
|
120
|
+
- [**Ruby LSP**](https://github.com/Shopify/ruby-lsp)
|
121
|
+
- [**better-html**](https://github.com/Shopify/better-html)
|
122
|
+
- [**erb_lint**](https://github.com/Shopify/erb_lint)
|
123
|
+
- [**erb-formatter**](https://github.com/nebulab/erb-formatter)
|
124
|
+
- [**erb-formatter-vscode**](https://github.com/nebulab/erb-formatter-vscode)
|
125
|
+
- [**deface**](https://github.com/spree/deface)
|
126
|
+
- [**html_press**](https://github.com/stereobooster/html_press)
|
127
|
+
- [**htmlbeautifier**](https://github.com/threedaymonk/htmlbeautifier)
|
128
|
+
- [**vscode-erb-beautify**](https://github.com/aliariff/vscode-erb-beautify)
|
129
|
+
- [**vscode-erb-linter**](https://github.com/manuelpuyol/vscode-erb-linter)
|
164
130
|
|
165
|
-
|
166
|
-
```
|
131
|
+
Herb differentiates itself by being HTML-aware from the ground up, providing a unified parsing approach that understands both HTML and ERB as first-class citizens, rather than treating one as embedded within the other.
|
167
132
|
|
168
133
|
## License
|
169
134
|
|
170
|
-
This project is
|
135
|
+
This project is available as open source under the terms of the [MIT License](https://github.com/marcoroth/herb/blob/main/LICENSE.txt).
|
data/ext/herb/nodes.c
CHANGED
@@ -415,7 +415,7 @@ static VALUE rb_erb_content_node_from_c_struct(AST_ERB_CONTENT_NODE_T* erb_conte
|
|
415
415
|
VALUE erb_content_node_tag_opening = rb_token_from_c_struct(erb_content_node->tag_opening);
|
416
416
|
VALUE erb_content_node_content = rb_token_from_c_struct(erb_content_node->content);
|
417
417
|
VALUE erb_content_node_tag_closing = rb_token_from_c_struct(erb_content_node->tag_closing);
|
418
|
-
/* #<Herb::Template::AnalyzedRubyField:
|
418
|
+
/* #<Herb::Template::AnalyzedRubyField:0x00007ffffed61888 @name="analyzed_ruby", @options={kind: nil}> */
|
419
419
|
VALUE erb_content_node_analyzed_ruby = Qnil;
|
420
420
|
VALUE erb_content_node_parsed = (erb_content_node->parsed) ? Qtrue : Qfalse;
|
421
421
|
VALUE erb_content_node_valid = (erb_content_node->valid) ? Qtrue : Qfalse;
|
data/lib/herb/3.0/herb.so
CHANGED
Binary file
|
data/lib/herb/3.1/herb.so
CHANGED
Binary file
|
data/lib/herb/3.2/herb.so
CHANGED
Binary file
|
data/lib/herb/3.3/herb.so
CHANGED
Binary file
|
data/lib/herb/3.4/herb.so
CHANGED
Binary file
|
data/lib/herb/ast/node.rb
CHANGED
@@ -51,7 +51,7 @@ module Herb
|
|
51
51
|
output = +""
|
52
52
|
|
53
53
|
if array.any?
|
54
|
-
output += "(#{array.count} #{array.
|
54
|
+
output += "(#{array.count} #{array.one? ? item_name : "#{item_name}s"})"
|
55
55
|
output += "\n"
|
56
56
|
|
57
57
|
items = array.map { |item|
|
@@ -92,6 +92,11 @@ module Herb
|
|
92
92
|
def compact_child_nodes
|
93
93
|
child_nodes.compact
|
94
94
|
end
|
95
|
+
|
96
|
+
#: () -> Array[Herb::Errors::Error]
|
97
|
+
def recursive_errors
|
98
|
+
errors + compact_child_nodes.flat_map(&:recursive_errors)
|
99
|
+
end
|
95
100
|
end
|
96
101
|
end
|
97
102
|
end
|
data/lib/herb/cli.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
require "optparse"
|
7
7
|
|
8
8
|
class Herb::CLI
|
9
|
-
attr_accessor :json, :silent
|
9
|
+
attr_accessor :json, :silent, :no_interactive, :no_log_file, :no_timing
|
10
10
|
|
11
11
|
def initialize(args)
|
12
12
|
@args = args
|
@@ -106,7 +106,11 @@ class Herb::CLI
|
|
106
106
|
def result
|
107
107
|
@result ||= case @command
|
108
108
|
when "analyze"
|
109
|
-
Herb::Project.new(directory)
|
109
|
+
project = Herb::Project.new(directory)
|
110
|
+
project.no_interactive = no_interactive
|
111
|
+
project.no_log_file = no_log_file
|
112
|
+
project.no_timing = no_timing
|
113
|
+
project.parse!
|
110
114
|
exit(0)
|
111
115
|
when "parse"
|
112
116
|
Herb.parse(file_content)
|
@@ -162,6 +166,18 @@ class Herb::CLI
|
|
162
166
|
parser.on("-s", "--silent", "Log no result to stdout") do
|
163
167
|
self.silent = true
|
164
168
|
end
|
169
|
+
|
170
|
+
parser.on("-n", "--non-interactive", "Disable interactive output (progress bars, terminal clearing)") do
|
171
|
+
self.no_interactive = true
|
172
|
+
end
|
173
|
+
|
174
|
+
parser.on("--no-log-file", "Disable log file generation") do
|
175
|
+
self.no_log_file = true
|
176
|
+
end
|
177
|
+
|
178
|
+
parser.on("--no-timing", "Disable timing output") do
|
179
|
+
self.no_timing = true
|
180
|
+
end
|
165
181
|
end
|
166
182
|
end
|
167
183
|
|
data/lib/herb/parse_result.rb
CHANGED
@@ -12,9 +12,14 @@ module Herb
|
|
12
12
|
super(source, warnings, errors)
|
13
13
|
end
|
14
14
|
|
15
|
+
#: () -> Array[Herb::Errors::Error]
|
16
|
+
def errors
|
17
|
+
super + value.recursive_errors
|
18
|
+
end
|
19
|
+
|
15
20
|
#: () -> bool
|
16
21
|
def failed?
|
17
|
-
errors.any?
|
22
|
+
errors.any?
|
18
23
|
end
|
19
24
|
|
20
25
|
#: () -> bool
|
@@ -24,7 +29,7 @@ module Herb
|
|
24
29
|
|
25
30
|
#: () -> String
|
26
31
|
def pretty_errors
|
27
|
-
JSON.pretty_generate(errors
|
32
|
+
JSON.pretty_generate(errors)
|
28
33
|
end
|
29
34
|
|
30
35
|
#: (Visitor) -> void
|
data/lib/herb/project.rb
CHANGED
@@ -8,10 +8,17 @@ require "timeout"
|
|
8
8
|
require "tempfile"
|
9
9
|
require "pathname"
|
10
10
|
require "English"
|
11
|
+
require "stringio"
|
11
12
|
|
12
13
|
module Herb
|
13
14
|
class Project
|
14
|
-
attr_accessor :project_path, :output_file
|
15
|
+
attr_accessor :project_path, :output_file, :no_interactive, :no_log_file, :no_timing
|
16
|
+
|
17
|
+
def interactive?
|
18
|
+
return false if no_interactive
|
19
|
+
|
20
|
+
!IO.console.nil?
|
21
|
+
end
|
15
22
|
|
16
23
|
def initialize(project_path, output_file: nil)
|
17
24
|
@project_path = Pathname.new(
|
@@ -39,7 +46,15 @@ module Herb
|
|
39
46
|
end
|
40
47
|
|
41
48
|
def parse!
|
42
|
-
|
49
|
+
start_time = Time.now unless no_timing
|
50
|
+
|
51
|
+
log = if no_log_file
|
52
|
+
StringIO.new
|
53
|
+
else
|
54
|
+
File.open(output_file, "w")
|
55
|
+
end
|
56
|
+
|
57
|
+
begin
|
43
58
|
log.puts heading("METADATA")
|
44
59
|
log.puts "Herb Version: #{Herb.version}"
|
45
60
|
log.puts "Reported at: #{Time.now.strftime("%Y-%m-%dT%H:%M:%S")}\n\n"
|
@@ -54,10 +69,10 @@ module Herb
|
|
54
69
|
message = "No .html.erb files found using #{full_path_glob}"
|
55
70
|
log.puts message
|
56
71
|
puts message
|
57
|
-
|
72
|
+
return
|
58
73
|
end
|
59
74
|
|
60
|
-
print "\e[H\e[2J"
|
75
|
+
print "\e[H\e[2J" if interactive?
|
61
76
|
|
62
77
|
successful_files = []
|
63
78
|
failed_files = []
|
@@ -77,37 +92,43 @@ module Herb
|
|
77
92
|
lines_to_clear += 3 if total_timeout.positive?
|
78
93
|
lines_to_clear += 3 if total_errors.positive?
|
79
94
|
|
80
|
-
lines_to_clear.times { print "\e[1A\e[K" } if index.positive?
|
95
|
+
lines_to_clear.times { print "\e[1A\e[K" } if index.positive? && interactive?
|
81
96
|
|
82
|
-
|
83
|
-
|
97
|
+
if interactive?
|
98
|
+
puts "Parsing .html.erb files in: #{project_path}"
|
99
|
+
puts "Total files to process: #{files.count}\n"
|
84
100
|
|
85
|
-
|
101
|
+
relative_path = file_path.sub("#{project_path}/", "")
|
86
102
|
|
87
|
-
puts
|
88
|
-
puts progress_bar(index + 1, files.count)
|
89
|
-
puts
|
90
|
-
puts "Processing [#{index + 1}/#{files.count}]: #{relative_path}"
|
91
|
-
|
92
|
-
if failed_files.any?
|
93
103
|
puts
|
94
|
-
puts
|
95
|
-
failed_files.each { |file| puts " - #{file}" }
|
104
|
+
puts progress_bar(index + 1, files.count)
|
96
105
|
puts
|
106
|
+
else
|
107
|
+
relative_path = file_path.sub("#{project_path}/", "")
|
97
108
|
end
|
109
|
+
puts "Processing [#{index + 1}/#{files.count}]: #{relative_path}"
|
98
110
|
|
99
|
-
if
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
111
|
+
if interactive?
|
112
|
+
if failed_files.any?
|
113
|
+
puts
|
114
|
+
puts "Files that failed:"
|
115
|
+
failed_files.each { |file| puts " - #{file}" }
|
116
|
+
puts
|
117
|
+
end
|
105
118
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
119
|
+
if timeout_files.any?
|
120
|
+
puts
|
121
|
+
puts "Files that timed out:"
|
122
|
+
timeout_files.each { |file| puts " - #{file}" }
|
123
|
+
puts
|
124
|
+
end
|
125
|
+
|
126
|
+
if error_files.any?
|
127
|
+
puts
|
128
|
+
puts "Files with parse errors:"
|
129
|
+
error_files.each { |file| puts " - #{file}" }
|
130
|
+
puts
|
131
|
+
end
|
111
132
|
end
|
112
133
|
|
113
134
|
begin
|
@@ -214,10 +235,13 @@ module Herb
|
|
214
235
|
end
|
215
236
|
end
|
216
237
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
238
|
+
if interactive?
|
239
|
+
print "\e[1A\e[K"
|
240
|
+
puts "Completed processing all files."
|
241
|
+
print "\e[H\e[2J"
|
242
|
+
else
|
243
|
+
puts "Completed processing all files."
|
244
|
+
end
|
221
245
|
|
222
246
|
log.puts ""
|
223
247
|
|
@@ -333,13 +357,23 @@ module Herb
|
|
333
357
|
end
|
334
358
|
end
|
335
359
|
|
336
|
-
|
360
|
+
unless no_timing
|
361
|
+
end_time = Time.now
|
362
|
+
duration = end_time - start_time
|
363
|
+
timing_message = "\n⏱️ Total time: #{format_duration(duration)}"
|
364
|
+
log.puts timing_message
|
365
|
+
puts timing_message
|
366
|
+
end
|
367
|
+
|
368
|
+
puts "\nResults saved to #{output_file}" unless no_log_file
|
369
|
+
ensure
|
370
|
+
log.close unless no_log_file
|
337
371
|
end
|
338
372
|
end
|
339
373
|
|
340
374
|
private
|
341
375
|
|
342
|
-
def progress_bar(current, total, width = IO.console
|
376
|
+
def progress_bar(current, total, width = (IO.console&.winsize&.[](1) || 80) - "[] 100% (#{total}/#{total})".length)
|
343
377
|
progress = current.to_f / total
|
344
378
|
completed_length = (progress * width).to_i
|
345
379
|
completed = "█" * completed_length
|
@@ -366,5 +400,17 @@ module Herb
|
|
366
400
|
|
367
401
|
prefix + ("-" * (80 - prefix.length))
|
368
402
|
end
|
403
|
+
|
404
|
+
def format_duration(seconds)
|
405
|
+
if seconds < 1
|
406
|
+
"#{(seconds * 1000).round(2)}ms"
|
407
|
+
elsif seconds < 60
|
408
|
+
"#{seconds.round(2)}s"
|
409
|
+
else
|
410
|
+
minutes = (seconds / 60).to_i
|
411
|
+
remaining_seconds = seconds % 60
|
412
|
+
"#{minutes}m #{remaining_seconds.round(2)}s"
|
413
|
+
end
|
414
|
+
end
|
369
415
|
end
|
370
416
|
end
|
data/lib/herb/version.rb
CHANGED
data/sig/herb/ast/node.rbs
CHANGED
data/sig/herb/parse_result.rbs
CHANGED
@@ -7,6 +7,9 @@ module Herb
|
|
7
7
|
# : (Herb::AST::DocumentNode, String, Array[Herb::Warnings::Warning], Array[Herb::Errors::Error]) -> void
|
8
8
|
def initialize: (Herb::AST::DocumentNode, String, Array[Herb::Warnings::Warning], Array[Herb::Errors::Error]) -> void
|
9
9
|
|
10
|
+
# : () -> Array[Herb::Errors::Error]
|
11
|
+
def errors: () -> Array[Herb::Errors::Error]
|
12
|
+
|
10
13
|
# : () -> bool
|
11
14
|
def failed?: () -> bool
|
12
15
|
|
data/src/analyze.c
CHANGED
@@ -49,13 +49,20 @@ static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
|
|
49
49
|
if (node->type == AST_ERB_CONTENT_NODE) {
|
50
50
|
AST_ERB_CONTENT_NODE_T* erb_content_node = (AST_ERB_CONTENT_NODE_T*) node;
|
51
51
|
|
52
|
-
|
52
|
+
const char* opening = erb_content_node->tag_opening->value;
|
53
|
+
if (strcmp(opening, "<%%") != 0 && strcmp(opening, "<%%=") != 0) {
|
54
|
+
analyzed_ruby_T* analyzed = herb_analyze_ruby(erb_content_node->content->value);
|
53
55
|
|
54
|
-
|
56
|
+
if (false) { pretty_print_analyed_ruby(analyzed, erb_content_node->content->value); }
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
58
|
+
erb_content_node->parsed = true;
|
59
|
+
erb_content_node->valid = analyzed->valid;
|
60
|
+
erb_content_node->analyzed_ruby = analyzed;
|
61
|
+
} else {
|
62
|
+
erb_content_node->parsed = false;
|
63
|
+
erb_content_node->valid = true;
|
64
|
+
erb_content_node->analyzed_ruby = NULL;
|
65
|
+
}
|
59
66
|
}
|
60
67
|
|
61
68
|
herb_visit_child_nodes(node, analyze_erb_content, data);
|
@@ -64,12 +71,20 @@ static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
|
|
64
71
|
}
|
65
72
|
|
66
73
|
static size_t process_block_children(
|
67
|
-
AST_NODE_T* node,
|
74
|
+
AST_NODE_T* node,
|
75
|
+
array_T* array,
|
76
|
+
size_t index,
|
77
|
+
array_T* children_array,
|
78
|
+
analyze_ruby_context_T* context,
|
68
79
|
control_type_t parent_type
|
69
80
|
);
|
70
81
|
|
71
82
|
static size_t process_subsequent_block(
|
72
|
-
AST_NODE_T* node,
|
83
|
+
AST_NODE_T* node,
|
84
|
+
array_T* array,
|
85
|
+
size_t index,
|
86
|
+
AST_NODE_T** subsequent_out,
|
87
|
+
analyze_ruby_context_T* context,
|
73
88
|
control_type_t parent_type
|
74
89
|
);
|
75
90
|
|
@@ -132,7 +147,10 @@ static bool is_terminator_type(control_type_t parent_type, control_type_t child_
|
|
132
147
|
}
|
133
148
|
|
134
149
|
static AST_NODE_T* create_control_node(
|
135
|
-
AST_ERB_CONTENT_NODE_T* erb_node,
|
150
|
+
AST_ERB_CONTENT_NODE_T* erb_node,
|
151
|
+
array_T* children,
|
152
|
+
AST_NODE_T* subsequent,
|
153
|
+
AST_ERB_END_NODE_T* end_node,
|
136
154
|
control_type_t control_type
|
137
155
|
) {
|
138
156
|
array_T* errors = array_init(8);
|
@@ -362,7 +380,11 @@ static AST_NODE_T* create_control_node(
|
|
362
380
|
}
|
363
381
|
|
364
382
|
static size_t process_control_structure(
|
365
|
-
AST_NODE_T* node,
|
383
|
+
AST_NODE_T* node,
|
384
|
+
array_T* array,
|
385
|
+
size_t index,
|
386
|
+
array_T* output_array,
|
387
|
+
analyze_ruby_context_T* context,
|
366
388
|
control_type_t initial_type
|
367
389
|
) {
|
368
390
|
AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) array_get(array, index);
|
@@ -858,7 +880,11 @@ static size_t process_control_structure(
|
|
858
880
|
}
|
859
881
|
|
860
882
|
static size_t process_subsequent_block(
|
861
|
-
AST_NODE_T* node,
|
883
|
+
AST_NODE_T* node,
|
884
|
+
array_T* array,
|
885
|
+
size_t index,
|
886
|
+
AST_NODE_T** subsequent_out,
|
887
|
+
analyze_ruby_context_T* context,
|
862
888
|
control_type_t parent_type
|
863
889
|
) {
|
864
890
|
AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) array_get(array, index);
|
@@ -922,7 +948,11 @@ static size_t process_subsequent_block(
|
|
922
948
|
}
|
923
949
|
|
924
950
|
static size_t process_block_children(
|
925
|
-
AST_NODE_T* node,
|
951
|
+
AST_NODE_T* node,
|
952
|
+
array_T* array,
|
953
|
+
size_t index,
|
954
|
+
array_T* children_array,
|
955
|
+
analyze_ruby_context_T* context,
|
926
956
|
control_type_t parent_type
|
927
957
|
) {
|
928
958
|
while (index < array_size(array)) {
|
data/src/analyze_helpers.c
CHANGED
@@ -89,12 +89,17 @@ bool search_if_nodes(const pm_node_t* node, void* data) {
|
|
89
89
|
analyzed_ruby_T* analyzed = (analyzed_ruby_T*) data;
|
90
90
|
|
91
91
|
if (node->type == PM_IF_NODE) {
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
const pm_if_node_t* if_node = (const pm_if_node_t*) node;
|
93
|
+
|
94
|
+
// Handle ternary
|
95
|
+
if (if_node->if_keyword_loc.start != NULL && if_node->if_keyword_loc.end != NULL) {
|
96
|
+
analyzed->has_if_node = true;
|
97
|
+
return true;
|
98
|
+
}
|
96
99
|
}
|
97
100
|
|
101
|
+
pm_visit_child_nodes(node, search_if_nodes, analyzed);
|
102
|
+
|
98
103
|
return false;
|
99
104
|
}
|
100
105
|
|
data/src/extract.c
CHANGED
@@ -20,7 +20,9 @@ void herb_extract_ruby_to_buffer_with_semicolons(const char* source, buffer_T* o
|
|
20
20
|
}
|
21
21
|
|
22
22
|
case TOKEN_ERB_START: {
|
23
|
-
if (strcmp(token->value, "<%#") == 0)
|
23
|
+
if (strcmp(token->value, "<%#") == 0 || strcmp(token->value, "<%%") == 0 || strcmp(token->value, "<%%=") == 0) {
|
24
|
+
skip_erb_content = true;
|
25
|
+
}
|
24
26
|
|
25
27
|
buffer_append_whitespace(output, range_length(token->range));
|
26
28
|
break;
|
@@ -67,7 +69,9 @@ void herb_extract_ruby_to_buffer(const char* source, buffer_T* output) {
|
|
67
69
|
}
|
68
70
|
|
69
71
|
case TOKEN_ERB_START: {
|
70
|
-
if (strcmp(token->value, "<%#") == 0)
|
72
|
+
if (strcmp(token->value, "<%#") == 0 || strcmp(token->value, "<%%") == 0 || strcmp(token->value, "<%%=") == 0) {
|
73
|
+
skip_erb_content = true;
|
74
|
+
}
|
71
75
|
|
72
76
|
buffer_append_whitespace(output, range_length(token->range));
|
73
77
|
break;
|
@@ -16,7 +16,10 @@ void parser_append_unexpected_error(parser_T* parser, const char* description, c
|
|
16
16
|
void parser_append_unexpected_token_error(parser_T* parser, token_type_T expected_type, array_T* errors);
|
17
17
|
|
18
18
|
void parser_append_literal_node_from_buffer(
|
19
|
-
const parser_T* parser,
|
19
|
+
const parser_T* parser,
|
20
|
+
buffer_T* buffer,
|
21
|
+
array_T* children,
|
22
|
+
position_T* start
|
20
23
|
);
|
21
24
|
|
22
25
|
bool parser_in_svg_context(const parser_T* parser);
|
@@ -26,7 +29,9 @@ token_T* parser_consume_if_present(parser_T* parser, token_type_T type);
|
|
26
29
|
token_T* parser_consume_expected(parser_T* parser, token_type_T type, array_T* array);
|
27
30
|
|
28
31
|
AST_HTML_ELEMENT_NODE_T* parser_handle_missing_close_tag(
|
29
|
-
AST_HTML_OPEN_TAG_NODE_T* open_tag,
|
32
|
+
AST_HTML_OPEN_TAG_NODE_T* open_tag,
|
33
|
+
array_T* body,
|
34
|
+
array_T* errors
|
30
35
|
);
|
31
36
|
void parser_handle_mismatched_tags(const parser_T* parser, const AST_HTML_CLOSE_TAG_NODE_T* close_tag, array_T* errors);
|
32
37
|
|
data/src/include/pretty_print.h
CHANGED
@@ -13,37 +13,77 @@ void pretty_print_newline(size_t indent, size_t relative_indent, buffer_T* buffe
|
|
13
13
|
void pretty_print_label(const char* name, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer);
|
14
14
|
|
15
15
|
void pretty_print_position_property(
|
16
|
-
position_T* position,
|
16
|
+
position_T* position,
|
17
|
+
const char* name,
|
18
|
+
size_t indent,
|
19
|
+
size_t relative_indent,
|
20
|
+
bool last_property,
|
21
|
+
buffer_T* buffer
|
17
22
|
);
|
18
23
|
|
19
24
|
void pretty_print_location(location_T* location, buffer_T* buffer);
|
20
25
|
|
21
26
|
void pretty_print_property(
|
22
|
-
const char* name,
|
27
|
+
const char* name,
|
28
|
+
const char* value,
|
29
|
+
size_t indent,
|
30
|
+
size_t relative_indent,
|
31
|
+
bool last_property,
|
32
|
+
buffer_T* buffer
|
23
33
|
);
|
24
34
|
|
25
35
|
void pretty_print_size_t_property(
|
26
|
-
size_t value,
|
36
|
+
size_t value,
|
37
|
+
const char* name,
|
38
|
+
size_t indent,
|
39
|
+
size_t relative_indent,
|
40
|
+
bool last_property,
|
41
|
+
buffer_T* buffer
|
27
42
|
);
|
28
43
|
|
29
44
|
void pretty_print_string_property(
|
30
|
-
const char* string,
|
45
|
+
const char* string,
|
46
|
+
const char* name,
|
47
|
+
size_t indent,
|
48
|
+
size_t relative_indent,
|
49
|
+
bool last_property,
|
50
|
+
buffer_T* buffer
|
31
51
|
);
|
32
52
|
|
33
53
|
void pretty_print_quoted_property(
|
34
|
-
const char* name,
|
54
|
+
const char* name,
|
55
|
+
const char* value,
|
56
|
+
size_t indent,
|
57
|
+
size_t relative_indent,
|
58
|
+
bool last_property,
|
59
|
+
buffer_T* buffer
|
35
60
|
);
|
36
61
|
|
37
62
|
void pretty_print_boolean_property(
|
38
|
-
const char* name,
|
63
|
+
const char* name,
|
64
|
+
bool value,
|
65
|
+
size_t indent,
|
66
|
+
size_t relative_indent,
|
67
|
+
bool last_property,
|
68
|
+
buffer_T* buffer
|
39
69
|
);
|
40
70
|
|
41
71
|
void pretty_print_token_property(
|
42
|
-
token_T* token,
|
72
|
+
token_T* token,
|
73
|
+
const char* name,
|
74
|
+
size_t indent,
|
75
|
+
size_t relative_indent,
|
76
|
+
bool last_property,
|
77
|
+
buffer_T* buffer
|
43
78
|
);
|
44
79
|
|
45
80
|
void pretty_print_array(
|
46
|
-
const char* name,
|
81
|
+
const char* name,
|
82
|
+
array_T* array,
|
83
|
+
size_t indent,
|
84
|
+
size_t relative_indent,
|
85
|
+
bool last_property,
|
86
|
+
buffer_T* buffer
|
47
87
|
);
|
48
88
|
|
49
89
|
void pretty_print_errors(AST_NODE_T* node, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer);
|
data/src/include/prism_helpers.h
CHANGED
@@ -10,7 +10,10 @@
|
|
10
10
|
const char* pm_error_level_to_string(pm_error_level_t level);
|
11
11
|
|
12
12
|
RUBY_PARSE_ERROR_T* ruby_parse_error_from_prism_error(
|
13
|
-
const pm_diagnostic_t* error,
|
13
|
+
const pm_diagnostic_t* error,
|
14
|
+
const AST_NODE_T* node,
|
15
|
+
const char* source,
|
16
|
+
pm_parser_t* parser
|
14
17
|
);
|
15
18
|
|
16
19
|
position_T* position_from_source_with_offset(const char* source, size_t offset);
|
data/src/include/token_struct.h
CHANGED
@@ -20,7 +20,7 @@ typedef enum {
|
|
20
20
|
TOKEN_HTML_COMMENT_START, // <!--
|
21
21
|
TOKEN_HTML_COMMENT_END, // -->
|
22
22
|
|
23
|
-
TOKEN_ERB_START, // <%, <%=, <%#, <%-, <%==, <%%
|
23
|
+
TOKEN_ERB_START, // <%, <%=, <%%=, <%#, <%-, <%==, <%%
|
24
24
|
TOKEN_ERB_CONTENT, // Ruby Code
|
25
25
|
TOKEN_ERB_END, // %>, -%>, %%>
|
26
26
|
|
data/src/include/version.h
CHANGED
data/src/lexer.c
CHANGED
@@ -163,7 +163,7 @@ static token_T* lexer_parse_identifier(lexer_T* lexer) {
|
|
163
163
|
// ===== ERB Parsing
|
164
164
|
|
165
165
|
static token_T* lexer_parse_erb_open(lexer_T* lexer) {
|
166
|
-
const char* erb_patterns[] = { "<%==", "<%=", "<%#", "<%-", "<%%", "<%" };
|
166
|
+
const char* erb_patterns[] = { "<%==", "<%%=", "<%=", "<%#", "<%-", "<%%", "<%" };
|
167
167
|
|
168
168
|
lexer->state = STATE_ERB_CONTENT;
|
169
169
|
|
@@ -184,7 +184,16 @@ static token_T* lexer_parse_erb_content(lexer_T* lexer) {
|
|
184
184
|
}
|
185
185
|
|
186
186
|
buffer_append_char(&buffer, lexer->current_character);
|
187
|
-
|
187
|
+
|
188
|
+
if (is_newline(lexer->current_character)) {
|
189
|
+
lexer->current_line++;
|
190
|
+
lexer->current_column = 0;
|
191
|
+
} else {
|
192
|
+
lexer->current_column++;
|
193
|
+
}
|
194
|
+
|
195
|
+
lexer->current_position++;
|
196
|
+
lexer->current_character = lexer->source[lexer->current_position];
|
188
197
|
}
|
189
198
|
|
190
199
|
lexer->state = STATE_ERB_CLOSE;
|
data/src/parser.c
CHANGED
@@ -197,13 +197,19 @@ static AST_HTML_ATTRIBUTE_NAME_NODE_T* parser_parse_html_attribute_name(parser_T
|
|
197
197
|
}
|
198
198
|
|
199
199
|
static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value(
|
200
|
-
parser_T* parser,
|
200
|
+
parser_T* parser,
|
201
|
+
array_T* children,
|
202
|
+
array_T* errors
|
201
203
|
) {
|
202
204
|
buffer_T buffer = buffer_new();
|
203
205
|
token_T* opening_quote = parser_consume_expected(parser, TOKEN_QUOTE, errors);
|
204
206
|
position_T* start = position_copy(parser->current_token->location->start);
|
205
207
|
|
206
|
-
while (
|
208
|
+
while (!token_is(parser, TOKEN_EOF)
|
209
|
+
&& !(
|
210
|
+
token_is(parser, TOKEN_QUOTE) && opening_quote != NULL
|
211
|
+
&& strcmp(parser->current_token->value, opening_quote->value) == 0
|
212
|
+
)) {
|
207
213
|
if (token_is(parser, TOKEN_ERB_START)) {
|
208
214
|
parser_append_literal_node_from_buffer(parser, &buffer, children, start);
|
209
215
|
|
@@ -472,7 +478,8 @@ static AST_HTML_CLOSE_TAG_NODE_T* parser_parse_html_close_tag(parser_T* parser)
|
|
472
478
|
|
473
479
|
// TODO: this should probably be AST_HTML_ELEMENT_NODE_T with a AST_HTML_SELF_CLOSING_TAG_NODE_T
|
474
480
|
static AST_HTML_ELEMENT_NODE_T* parser_parse_html_self_closing_element(
|
475
|
-
const parser_T* parser,
|
481
|
+
const parser_T* parser,
|
482
|
+
AST_HTML_OPEN_TAG_NODE_T* open_tag
|
476
483
|
) {
|
477
484
|
return ast_html_element_node_init(
|
478
485
|
open_tag,
|
@@ -487,7 +494,8 @@ static AST_HTML_ELEMENT_NODE_T* parser_parse_html_self_closing_element(
|
|
487
494
|
}
|
488
495
|
|
489
496
|
static AST_HTML_ELEMENT_NODE_T* parser_parse_html_regular_element(
|
490
|
-
parser_T* parser,
|
497
|
+
parser_T* parser,
|
498
|
+
AST_HTML_OPEN_TAG_NODE_T* open_tag
|
491
499
|
) {
|
492
500
|
array_T* errors = array_init(8);
|
493
501
|
array_T* body = array_init(8);
|
@@ -618,6 +626,7 @@ static void parser_parse_in_data_state(parser_T* parser, array_T* children, arra
|
|
618
626
|
TOKEN_IDENTIFIER,
|
619
627
|
TOKEN_NEWLINE,
|
620
628
|
TOKEN_PERCENT,
|
629
|
+
TOKEN_QUOTE,
|
621
630
|
TOKEN_SEMICOLON,
|
622
631
|
TOKEN_SLASH,
|
623
632
|
TOKEN_UNDERSCORE,
|
data/src/parser_helpers.c
CHANGED
@@ -80,7 +80,10 @@ void parser_append_unexpected_token_error(parser_T* parser, token_type_T expecte
|
|
80
80
|
}
|
81
81
|
|
82
82
|
void parser_append_literal_node_from_buffer(
|
83
|
-
const parser_T* parser,
|
83
|
+
const parser_T* parser,
|
84
|
+
buffer_T* buffer,
|
85
|
+
array_T* children,
|
86
|
+
position_T* start
|
84
87
|
) {
|
85
88
|
if (buffer_length(buffer) == 0) { return; }
|
86
89
|
|
@@ -115,7 +118,9 @@ token_T* parser_consume_expected(parser_T* parser, const token_type_T expected_t
|
|
115
118
|
}
|
116
119
|
|
117
120
|
AST_HTML_ELEMENT_NODE_T* parser_handle_missing_close_tag(
|
118
|
-
AST_HTML_OPEN_TAG_NODE_T* open_tag,
|
121
|
+
AST_HTML_OPEN_TAG_NODE_T* open_tag,
|
122
|
+
array_T* body,
|
123
|
+
array_T* errors
|
119
124
|
) {
|
120
125
|
append_missing_closing_tag_error(
|
121
126
|
open_tag->tag_name,
|
@@ -137,7 +142,9 @@ AST_HTML_ELEMENT_NODE_T* parser_handle_missing_close_tag(
|
|
137
142
|
}
|
138
143
|
|
139
144
|
void parser_handle_mismatched_tags(
|
140
|
-
const parser_T* parser,
|
145
|
+
const parser_T* parser,
|
146
|
+
const AST_HTML_CLOSE_TAG_NODE_T* close_tag,
|
147
|
+
array_T* errors
|
141
148
|
) {
|
142
149
|
if (array_size(parser->open_tags_stack) > 0) {
|
143
150
|
token_T* expected_tag = array_last(parser->open_tags_stack);
|
data/src/pretty_print.c
CHANGED
@@ -25,7 +25,11 @@ void pretty_print_newline(const size_t indent, const size_t relative_indent, buf
|
|
25
25
|
}
|
26
26
|
|
27
27
|
void pretty_print_label(
|
28
|
-
const char* name,
|
28
|
+
const char* name,
|
29
|
+
const size_t indent,
|
30
|
+
const size_t relative_indent,
|
31
|
+
const bool last_property,
|
32
|
+
buffer_T* buffer
|
29
33
|
) {
|
30
34
|
pretty_print_indent(buffer, indent);
|
31
35
|
pretty_print_indent(buffer, relative_indent);
|
@@ -41,7 +45,11 @@ void pretty_print_label(
|
|
41
45
|
}
|
42
46
|
|
43
47
|
void pretty_print_quoted_property(
|
44
|
-
const char* name,
|
48
|
+
const char* name,
|
49
|
+
const char* value,
|
50
|
+
const size_t indent,
|
51
|
+
const size_t relative_indent,
|
52
|
+
const bool last_property,
|
45
53
|
buffer_T* buffer
|
46
54
|
) {
|
47
55
|
char* quoted = quoted_string(value);
|
@@ -50,14 +58,22 @@ void pretty_print_quoted_property(
|
|
50
58
|
}
|
51
59
|
|
52
60
|
void pretty_print_boolean_property(
|
53
|
-
const char* name,
|
61
|
+
const char* name,
|
62
|
+
bool value,
|
63
|
+
const size_t indent,
|
64
|
+
const size_t relative_indent,
|
65
|
+
const bool last_property,
|
54
66
|
buffer_T* buffer
|
55
67
|
) {
|
56
68
|
pretty_print_property(name, value ? "true" : "false", indent, relative_indent, last_property, buffer);
|
57
69
|
}
|
58
70
|
|
59
71
|
void pretty_print_property(
|
60
|
-
const char* name,
|
72
|
+
const char* name,
|
73
|
+
const char* value,
|
74
|
+
const size_t indent,
|
75
|
+
const size_t relative_indent,
|
76
|
+
const bool last_property,
|
61
77
|
buffer_T* buffer
|
62
78
|
) {
|
63
79
|
pretty_print_label(name, indent, relative_indent, last_property, buffer);
|
@@ -66,7 +82,11 @@ void pretty_print_property(
|
|
66
82
|
}
|
67
83
|
|
68
84
|
void pretty_print_size_t_property(
|
69
|
-
size_t value,
|
85
|
+
size_t value,
|
86
|
+
const char* name,
|
87
|
+
const size_t indent,
|
88
|
+
const size_t relative_indent,
|
89
|
+
const bool last_property,
|
70
90
|
buffer_T* buffer
|
71
91
|
) {
|
72
92
|
pretty_print_label(name, indent, relative_indent, last_property, buffer);
|
@@ -77,7 +97,11 @@ void pretty_print_size_t_property(
|
|
77
97
|
}
|
78
98
|
|
79
99
|
void pretty_print_array(
|
80
|
-
const char* name,
|
100
|
+
const char* name,
|
101
|
+
array_T* array,
|
102
|
+
const size_t indent,
|
103
|
+
const size_t relative_indent,
|
104
|
+
const bool last_property,
|
81
105
|
buffer_T* buffer
|
82
106
|
) {
|
83
107
|
if (array == NULL) {
|
@@ -122,7 +146,11 @@ void pretty_print_array(
|
|
122
146
|
}
|
123
147
|
|
124
148
|
void pretty_print_errors(
|
125
|
-
AST_NODE_T* node,
|
149
|
+
AST_NODE_T* node,
|
150
|
+
const size_t indent,
|
151
|
+
const size_t relative_indent,
|
152
|
+
const bool last_property,
|
153
|
+
buffer_T* buffer
|
126
154
|
) {
|
127
155
|
if (node->errors != NULL && array_size(node->errors) > 0) {
|
128
156
|
error_pretty_print_array("errors", node->errors, indent, relative_indent, last_property, buffer);
|
@@ -146,7 +174,11 @@ void pretty_print_location(location_T* location, buffer_T* buffer) {
|
|
146
174
|
}
|
147
175
|
|
148
176
|
void pretty_print_position_property(
|
149
|
-
position_T* position,
|
177
|
+
position_T* position,
|
178
|
+
const char* name,
|
179
|
+
const size_t indent,
|
180
|
+
const size_t relative_indent,
|
181
|
+
const bool last_property,
|
150
182
|
buffer_T* buffer
|
151
183
|
) {
|
152
184
|
pretty_print_label(name, indent, relative_indent, last_property, buffer);
|
@@ -173,7 +205,11 @@ void pretty_print_position_property(
|
|
173
205
|
}
|
174
206
|
|
175
207
|
void pretty_print_token_property(
|
176
|
-
token_T* token,
|
208
|
+
token_T* token,
|
209
|
+
const char* name,
|
210
|
+
const size_t indent,
|
211
|
+
const size_t relative_indent,
|
212
|
+
const bool last_property,
|
177
213
|
buffer_T* buffer
|
178
214
|
) {
|
179
215
|
pretty_print_label(name, indent, relative_indent, last_property, buffer);
|
@@ -193,7 +229,11 @@ void pretty_print_token_property(
|
|
193
229
|
}
|
194
230
|
|
195
231
|
void pretty_print_string_property(
|
196
|
-
const char* string,
|
232
|
+
const char* string,
|
233
|
+
const char* name,
|
234
|
+
const size_t indent,
|
235
|
+
const size_t relative_indent,
|
236
|
+
const bool last_property,
|
197
237
|
buffer_T* buffer
|
198
238
|
) {
|
199
239
|
const char* value = "∅";
|
data/src/prism_helpers.c
CHANGED
@@ -32,7 +32,10 @@ position_T* position_from_source_with_offset(const char* source, size_t offset)
|
|
32
32
|
}
|
33
33
|
|
34
34
|
RUBY_PARSE_ERROR_T* ruby_parse_error_from_prism_error(
|
35
|
-
const pm_diagnostic_t* error,
|
35
|
+
const pm_diagnostic_t* error,
|
36
|
+
const AST_NODE_T* node,
|
37
|
+
const char* source,
|
38
|
+
pm_parser_t* parser
|
36
39
|
) {
|
37
40
|
size_t start_offset = (size_t) (error->location.start - parser->start);
|
38
41
|
size_t end_offset = (size_t) (error->location.end - parser->start);
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: herb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: x86-linux-gnu
|
6
6
|
authors:
|
7
7
|
- Marco Roth
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-07-10 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: Powerful and seamless HTML-aware ERB parsing and tooling.
|
13
13
|
email:
|