herb 0.3.1-aarch64-linux-musl → 0.4.0-aarch64-linux-musl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4ffe2d37f81c4df238feb9a0e20ba157a109f05667519e6ee2be99793fffc2b
4
- data.tar.gz: 40971d2c61f119c5c2b0926b935cfbce112235d597219c7b63ca0721e14c4ba9
3
+ metadata.gz: 0fa254558ee95041b966d15220cafcf063babfa7512c880c1b15768067c7bb8a
4
+ data.tar.gz: 516183c55e2f06c945cbdc3b1e395bdea034de781be034d9e5606cc90d6cfa7d
5
5
  SHA512:
6
- metadata.gz: 8a7df30e14bad739f2f1170e95bac0e16983037057ccb411e3dbcf9fea55e877ed4857b9323d3a283746dce7818fb8f7a26e1321893b2f0b66a3975517852b1f
7
- data.tar.gz: dccac7fe7875a58ed3f7dc130f8bdef7c040449d1f5e76b1beaa7f0fbccbe6ca7344804a70eea58fb2ae994539a5a0f4158367810664599cb7e5b0a228bdc29d
6
+ metadata.gz: 45bc1334be559a2b7103e246fe0c9d13164d0456e1490af3dcf9cce8517e2934947e21c2a06db96cb7b564af89b77bba5de4709dea404eb9c696c83b40c4eda6
7
+ data.tar.gz: 041d1d2eef12610df2791c4d7144f65d0fb1f393ae24a1b69840ea646b75df83076b9516b5de8377472fd2dfedeb28e80df90a5b2332e0a23b3cae045bb94d28
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
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
- ## Contributing
12
-
13
- This project builds the Herb program and its associated unit tests using a Makefile for automation. The Makefile provides several useful commands for compiling, running tests, and cleaning the project.
14
-
15
- ### Requirements
16
-
17
- - [**Check**](https://libcheck.github.io/check/): For unit testing.
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
- ##### For Linux
19
+ <br/><br/><br/>
28
20
 
29
- ```bash
30
- xargs sudo apt-get install < Aptfile
31
- ```
32
- or:
21
+ ## What is Herb?
33
22
 
34
- ```bash
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
- ##### For macOS (using Homebrew)
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
- ```bash
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
- ```bash
46
- brew install check llvm@19 emscripten doxygen
47
- ```
29
+ ## What Herb Can Do for You
48
30
 
49
- ### Building
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
- #### Clone the Repo
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
- Clone the Git Repository:
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
- git clone https://github.com/marcoroth/herb && cd herb/
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
- #### Build Herb
42
+ You can use Herb programmatically in **Ruby**, as well as in **JavaScript** via Node.js, WebAssembly, or directly in browsers.
60
43
 
61
- We can now compile all source files in `src/` and generate the `herb` executable.
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
- ```bash
64
- make all
65
- ```
46
+ ## Motiviation
66
47
 
67
- > [!NOTE]
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
- ### Run
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
- The `herb` executable exposes a few commands for interacting with `.html.erb` files:
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
- Herb 🌿 Powerful and seamless HTML-aware ERB parsing and tooling.
56
+ Install the Herb gem via RubyGems:
79
57
 
80
- ./herb lex [file] - Lex a file
81
- ./herb lex_json [file] - Lex a file and return the result as json.
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
- Running the executable shows a pretty-printed output for the respective command and the time it took to execute:
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
- Finished lexing in:
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
- ### Building the Ruby extension
107
-
108
- We use `rake` and `rake-compiler` to compile the Ruby extension. Running rake will generate the needed templates, run make, build the needed artifacts, and run the Ruby tests.
77
+ Files with parse errors:
78
+ - app/views/contributions/index.html.erb
79
+ - index.html.erb
109
80
 
110
- ```bash
111
- rake
81
+ Results saved to 2025-06-29_12-16-23_erb_parsing_result_rubyevents.log
112
82
  ```
113
83
 
114
- If `rake` was successful you can use `bundle console` to interact with `Herb`:
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
- #### For the Ruby Tests
89
+ Usage:
90
+ bundle exec herb [command] [options]
137
91
 
138
- ```bash
139
- rake test
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
- ### Clean
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
- ```bash
147
- make clean
148
- ```
105
+ ## Background and Talk
149
106
 
150
- ### Local Integration Testing
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
- The `bin/integration` script allows for quick local iteration. On every run it cleans the directory, builds the source from scratch and runs all checks, including the C-Tests, Ruby Tests, Linters, and examples in succession.
109
+ ## Contributing
153
110
 
154
- ```bash
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
- The integration was successful if you see:
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
- Integration successful!
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 licensed under the MIT License - see the [LICENSE](LICENSE.txt) file for details.
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:0x00007ffffed652f8 @name="analyzed_ruby", @options={kind: nil}> */
418
+ /* #<Herb::Template::AnalyzedRubyField:0x00007ffffed652a8 @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.count == 1 ? item_name : "#{item_name}s"})"
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).parse!
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
 
@@ -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? || value.errors.any? # TODO: this should probably be recursive
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 + value.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
- File.open(output_file, "w") do |log|
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
- next
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
- puts "Parsing .html.erb files in: #{project_path}"
83
- puts "Total files to process: #{files.count}\n"
97
+ if interactive?
98
+ puts "Parsing .html.erb files in: #{project_path}"
99
+ puts "Total files to process: #{files.count}\n"
84
100
 
85
- relative_path = file_path.sub("#{project_path}/", "")
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 "Files that failed:"
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 timeout_files.any?
100
- puts
101
- puts "Files that timed out:"
102
- timeout_files.each { |file| puts " - #{file}" }
103
- puts
104
- end
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
- if error_files.any?
107
- puts
108
- puts "Files with parse errors:"
109
- error_files.each { |file| puts " - #{file}" }
110
- puts
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
- print "\e[1A\e[K"
218
- puts "Completed processing all files."
219
-
220
- print "\e[H\e[2J"
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
- puts "\nResults saved to #{output_file}"
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.winsize[1] - "[] 100% (#{total}/#{total})".length)
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
@@ -2,5 +2,5 @@
2
2
  # typed: true
3
3
 
4
4
  module Herb
5
- VERSION = "0.3.1"
5
+ VERSION = "0.4.0"
6
6
  end
@@ -43,6 +43,9 @@ module Herb
43
43
 
44
44
  # : () -> Array[Herb::AST::Node]
45
45
  def compact_child_nodes: () -> Array[Herb::AST::Node]
46
+
47
+ # : () -> Array[Herb::Errors::Error]
48
+ def recursive_errors: () -> Array[Herb::Errors::Error]
46
49
  end
47
50
  end
48
51
  end
@@ -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
- analyzed_ruby_T* analyzed = herb_analyze_ruby(erb_content_node->content->value);
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
- if (false) { pretty_print_analyed_ruby(analyzed, erb_content_node->content->value); }
56
+ if (false) { pretty_print_analyed_ruby(analyzed, erb_content_node->content->value); }
55
57
 
56
- erb_content_node->parsed = true;
57
- erb_content_node->valid = analyzed->valid;
58
- erb_content_node->analyzed_ruby = analyzed;
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, array_T* array, size_t index, array_T* children_array, analyze_ruby_context_T* context,
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, array_T* array, size_t index, AST_NODE_T** subsequent_out, analyze_ruby_context_T* context,
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, array_T* children, AST_NODE_T* subsequent, AST_ERB_END_NODE_T* end_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, array_T* array, size_t index, array_T* output_array, analyze_ruby_context_T* context,
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, array_T* array, size_t index, AST_NODE_T** subsequent_out, analyze_ruby_context_T* context,
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, array_T* array, size_t index, array_T* children_array, analyze_ruby_context_T* context,
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)) {
@@ -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
- analyzed->has_if_node = true;
93
- return true;
94
- } else {
95
- pm_visit_child_nodes(node, search_if_nodes, analyzed);
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) { skip_erb_content = true; }
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) { skip_erb_content = true; }
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, buffer_T* buffer, array_T* children, position_T* start
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, array_T* body, array_T* errors
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
 
@@ -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, const char* name, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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, const char* value, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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, const char* name, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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, const char* name, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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, const char* value, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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, bool value, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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, const char* name, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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, array_T* array, size_t indent, size_t relative_indent, bool last_property, buffer_T* buffer
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);
@@ -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, const AST_NODE_T* node, const char* source, pm_parser_t* parser
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);
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  #ifndef HERB_VERSION_H
2
2
  #define HERB_VERSION_H
3
3
 
4
- #define HERB_VERSION "0.3.1"
4
+ #define HERB_VERSION "0.4.0"
5
5
 
6
6
  #endif
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
- lexer_advance(lexer);
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, array_T* children, array_T* errors
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 (token_is_none_of(parser, TOKEN_QUOTE, TOKEN_EOF)) {
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, AST_HTML_OPEN_TAG_NODE_T* open_tag
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, AST_HTML_OPEN_TAG_NODE_T* open_tag
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, buffer_T* buffer, array_T* children, position_T* start
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, array_T* body, array_T* errors
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, const AST_HTML_CLOSE_TAG_NODE_T* close_tag, array_T* errors
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, const size_t indent, const size_t relative_indent, const bool last_property, buffer_T* buffer
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, const char* value, const size_t indent, const size_t relative_indent, const bool last_property,
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, bool value, const size_t indent, const size_t relative_indent, const bool last_property,
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, const char* value, const size_t indent, const size_t relative_indent, const bool last_property,
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, const char* name, const size_t indent, const size_t relative_indent, const bool last_property,
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, array_T* array, const size_t indent, const size_t relative_indent, const bool last_property,
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, const size_t indent, const size_t relative_indent, const bool last_property, buffer_T* buffer
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, const char* name, const size_t indent, const size_t relative_indent, const bool last_property,
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, const char* name, const size_t indent, const size_t relative_indent, const bool last_property,
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, const char* name, const size_t indent, const size_t relative_indent, const bool last_property,
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, const AST_NODE_T* node, const char* source, pm_parser_t* parser
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.3.1
4
+ version: 0.4.0
5
5
  platform: aarch64-linux-musl
6
6
  authors:
7
7
  - Marco Roth
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-23 00:00:00.000000000 Z
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: