herb 0.8.10-arm-linux-gnu → 0.9.0-arm-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/Makefile +11 -3
- data/README.md +64 -34
- data/Rakefile +48 -40
- data/config.yml +317 -34
- data/ext/herb/error_helpers.c +367 -140
- data/ext/herb/error_helpers.h +1 -0
- data/ext/herb/extconf.rb +67 -28
- data/ext/herb/extension.c +317 -51
- data/ext/herb/extension.h +1 -0
- data/ext/herb/extension_helpers.c +23 -14
- data/ext/herb/extension_helpers.h +2 -2
- data/ext/herb/nodes.c +537 -270
- data/ext/herb/nodes.h +1 -0
- data/herb.gemspec +3 -2
- 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/4.0/herb.so +0 -0
- data/lib/herb/ast/helpers.rb +3 -3
- data/lib/herb/ast/node.rb +15 -2
- data/lib/herb/ast/nodes.rb +1132 -157
- data/lib/herb/bootstrap.rb +87 -0
- data/lib/herb/cli.rb +341 -31
- data/lib/herb/configuration.rb +248 -0
- data/lib/herb/defaults.yml +32 -0
- data/lib/herb/engine/compiler.rb +78 -11
- data/lib/herb/engine/debug_visitor.rb +13 -3
- data/lib/herb/engine/error_formatter.rb +13 -9
- data/lib/herb/engine/parser_error_overlay.rb +10 -6
- data/lib/herb/engine/validator.rb +8 -3
- data/lib/herb/engine/validators/nesting_validator.rb +2 -2
- data/lib/herb/engine.rb +82 -35
- data/lib/herb/errors.rb +563 -88
- data/lib/herb/lex_result.rb +1 -0
- data/lib/herb/location.rb +7 -3
- data/lib/herb/parse_result.rb +12 -2
- data/lib/herb/parser_options.rb +57 -0
- data/lib/herb/position.rb +1 -0
- data/lib/herb/prism_inspect.rb +116 -0
- data/lib/herb/project.rb +923 -331
- data/lib/herb/range.rb +1 -0
- data/lib/herb/token.rb +7 -1
- data/lib/herb/version.rb +1 -1
- data/lib/herb/visitor.rb +37 -2
- data/lib/herb/warnings.rb +6 -1
- data/lib/herb.rb +35 -3
- data/sig/herb/ast/helpers.rbs +2 -2
- data/sig/herb/ast/node.rbs +12 -2
- data/sig/herb/ast/nodes.rbs +641 -128
- data/sig/herb/bootstrap.rbs +31 -0
- data/sig/herb/configuration.rbs +89 -0
- data/sig/herb/engine/compiler.rbs +9 -1
- data/sig/herb/engine/debug_visitor.rbs +2 -0
- data/sig/herb/engine/validator.rbs +5 -1
- data/sig/herb/engine.rbs +17 -3
- data/sig/herb/errors.rbs +258 -63
- data/sig/herb/location.rbs +4 -0
- data/sig/herb/parse_result.rbs +4 -2
- data/sig/herb/parser_options.rbs +42 -0
- data/sig/herb/position.rbs +1 -0
- data/sig/herb/prism_inspect.rbs +28 -0
- data/sig/herb/range.rbs +1 -0
- data/sig/herb/token.rbs +6 -0
- data/sig/herb/visitor.rbs +25 -4
- data/sig/herb/warnings.rbs +6 -1
- data/sig/herb.rbs +14 -0
- data/sig/herb_c_extension.rbs +5 -2
- data/sig/serialized_ast_errors.rbs +54 -6
- data/sig/serialized_ast_nodes.rbs +60 -6
- data/src/analyze/action_view/attribute_extraction_helpers.c +290 -0
- data/src/analyze/action_view/content_tag.c +70 -0
- data/src/analyze/action_view/link_to.c +143 -0
- data/src/analyze/action_view/registry.c +60 -0
- data/src/analyze/action_view/tag.c +64 -0
- data/src/analyze/action_view/tag_helper_node_builders.c +305 -0
- data/src/analyze/action_view/tag_helpers.c +748 -0
- data/src/analyze/action_view/turbo_frame_tag.c +88 -0
- data/src/analyze/analyze.c +882 -0
- data/src/{analyzed_ruby.c → analyze/analyzed_ruby.c} +13 -11
- data/src/analyze/builders.c +343 -0
- data/src/analyze/conditional_elements.c +594 -0
- data/src/analyze/conditional_open_tags.c +640 -0
- data/src/analyze/control_type.c +250 -0
- data/src/{analyze_helpers.c → analyze/helpers.c} +48 -23
- data/src/analyze/invalid_structures.c +193 -0
- data/src/{analyze_missing_end.c → analyze/missing_end.c} +33 -22
- data/src/analyze/parse_errors.c +84 -0
- data/src/analyze/prism_annotate.c +397 -0
- data/src/{analyze_transform.c → analyze/transform.c} +17 -3
- data/src/ast_node.c +17 -7
- data/src/ast_nodes.c +662 -387
- data/src/ast_pretty_print.c +190 -6
- data/src/errors.c +1076 -520
- data/src/extract.c +145 -49
- data/src/herb.c +52 -34
- data/src/html_util.c +241 -12
- data/src/include/analyze/action_view/attribute_extraction_helpers.h +36 -0
- data/src/include/analyze/action_view/tag_helper_handler.h +41 -0
- data/src/include/analyze/action_view/tag_helper_node_builders.h +70 -0
- data/src/include/analyze/action_view/tag_helpers.h +38 -0
- data/src/include/{analyze.h → analyze/analyze.h} +14 -4
- data/src/include/{analyzed_ruby.h → analyze/analyzed_ruby.h} +3 -3
- data/src/include/analyze/builders.h +27 -0
- data/src/include/analyze/conditional_elements.h +9 -0
- data/src/include/analyze/conditional_open_tags.h +9 -0
- data/src/include/analyze/control_type.h +14 -0
- data/src/include/{analyze_helpers.h → analyze/helpers.h} +4 -2
- data/src/include/analyze/invalid_structures.h +11 -0
- data/src/include/analyze/prism_annotate.h +16 -0
- data/src/include/ast_node.h +11 -5
- data/src/include/ast_nodes.h +117 -38
- data/src/include/ast_pretty_print.h +5 -0
- data/src/include/element_source.h +3 -8
- data/src/include/errors.h +148 -55
- data/src/include/extract.h +21 -5
- data/src/include/herb.h +18 -6
- data/src/include/herb_prism_node.h +13 -0
- data/src/include/html_util.h +7 -2
- data/src/include/io.h +3 -1
- data/src/include/lex_helpers.h +29 -0
- data/src/include/lexer.h +1 -1
- data/src/include/lexer_peek_helpers.h +87 -13
- data/src/include/lexer_struct.h +2 -0
- data/src/include/location.h +2 -1
- data/src/include/parser.h +27 -2
- data/src/include/parser_helpers.h +19 -3
- data/src/include/pretty_print.h +10 -5
- data/src/include/prism_context.h +45 -0
- data/src/include/prism_helpers.h +10 -7
- data/src/include/prism_serialized.h +12 -0
- data/src/include/token.h +16 -4
- data/src/include/token_struct.h +10 -3
- data/src/include/utf8.h +2 -1
- data/src/include/util/hb_allocator.h +78 -0
- data/src/include/util/hb_arena.h +6 -1
- data/src/include/util/hb_arena_debug.h +12 -1
- data/src/include/util/hb_array.h +7 -3
- data/src/include/util/hb_buffer.h +6 -4
- data/src/include/util/hb_foreach.h +79 -0
- data/src/include/util/hb_narray.h +8 -4
- data/src/include/util/hb_string.h +56 -9
- data/src/include/util.h +6 -3
- data/src/include/version.h +1 -1
- data/src/io.c +3 -2
- data/src/lexer.c +42 -30
- data/src/lexer_peek_helpers.c +12 -74
- data/src/location.c +2 -2
- data/src/main.c +53 -28
- data/src/parser.c +783 -247
- data/src/parser_helpers.c +110 -23
- data/src/parser_match_tags.c +109 -48
- data/src/pretty_print.c +29 -24
- data/src/prism_helpers.c +30 -27
- data/src/ruby_parser.c +2 -0
- data/src/token.c +151 -66
- data/src/token_matchers.c +0 -1
- data/src/utf8.c +7 -6
- data/src/util/hb_allocator.c +341 -0
- data/src/util/hb_arena.c +81 -56
- data/src/util/hb_arena_debug.c +32 -17
- data/src/util/hb_array.c +30 -15
- data/src/util/hb_buffer.c +17 -21
- data/src/util/hb_narray.c +22 -7
- data/src/util/hb_string.c +49 -35
- data/src/util.c +21 -11
- data/src/visitor.c +47 -0
- data/templates/ext/herb/error_helpers.c.erb +24 -11
- data/templates/ext/herb/error_helpers.h.erb +1 -0
- data/templates/ext/herb/nodes.c.erb +50 -16
- data/templates/ext/herb/nodes.h.erb +1 -0
- data/templates/java/error_helpers.c.erb +1 -1
- data/templates/java/nodes.c.erb +30 -8
- data/templates/java/org/herb/ast/Errors.java.erb +24 -1
- data/templates/java/org/herb/ast/Nodes.java.erb +80 -21
- data/templates/javascript/packages/core/src/errors.ts.erb +16 -3
- data/templates/javascript/packages/core/src/node-type-guards.ts.erb +3 -1
- data/templates/javascript/packages/core/src/nodes.ts.erb +109 -32
- data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +13 -4
- data/templates/javascript/packages/node/extension/nodes.cpp.erb +43 -4
- data/templates/lib/herb/ast/nodes.rb.erb +88 -31
- data/templates/lib/herb/errors.rb.erb +15 -3
- data/templates/lib/herb/visitor.rb.erb +2 -2
- data/templates/rust/src/ast/nodes.rs.erb +97 -44
- data/templates/rust/src/errors.rs.erb +2 -1
- data/templates/rust/src/nodes.rs.erb +167 -15
- data/templates/rust/src/union_types.rs.erb +60 -0
- data/templates/rust/src/visitor.rs.erb +81 -0
- data/templates/src/{analyze_missing_end.c.erb → analyze/missing_end.c.erb} +9 -6
- data/templates/src/{analyze_transform.c.erb → analyze/transform.c.erb} +2 -2
- data/templates/src/ast_nodes.c.erb +34 -26
- data/templates/src/ast_pretty_print.c.erb +24 -5
- data/templates/src/errors.c.erb +60 -54
- data/templates/src/include/ast_nodes.h.erb +6 -2
- data/templates/src/include/ast_pretty_print.h.erb +5 -0
- data/templates/src/include/errors.h.erb +15 -11
- data/templates/src/include/util/hb_foreach.h.erb +20 -0
- data/templates/src/parser_match_tags.c.erb +10 -4
- data/templates/src/visitor.c.erb +2 -2
- data/templates/template.rb +204 -29
- data/templates/wasm/error_helpers.cpp.erb +9 -5
- data/templates/wasm/nodes.cpp.erb +41 -4
- metadata +57 -16
- data/src/analyze.c +0 -1608
- data/src/element_source.c +0 -12
- data/src/include/util/hb_system.h +0 -9
- data/src/util/hb_system.c +0 -30
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: ignore
|
|
3
|
+
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Herb
|
|
7
|
+
module Bootstrap
|
|
8
|
+
ROOT_PATH = File.expand_path("../..", __dir__)
|
|
9
|
+
PRISM_VENDOR_DIR = File.join(ROOT_PATH, "vendor", "prism")
|
|
10
|
+
|
|
11
|
+
PRISM_ENTRIES = [
|
|
12
|
+
"config.yml",
|
|
13
|
+
"Rakefile",
|
|
14
|
+
"src/",
|
|
15
|
+
"include/",
|
|
16
|
+
"templates/"
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
def self.generate_templates
|
|
20
|
+
require "pathname"
|
|
21
|
+
require "set"
|
|
22
|
+
require_relative "../../templates/template"
|
|
23
|
+
|
|
24
|
+
Dir.chdir(ROOT_PATH) do
|
|
25
|
+
Dir.glob("#{ROOT_PATH}/templates/**/*.erb").each do |template|
|
|
26
|
+
Herb::Template.render(template)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.git_source?
|
|
32
|
+
File.directory?(File.join(ROOT_PATH, ".git"))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.templates_generated?
|
|
36
|
+
File.exist?(File.join(ROOT_PATH, "ext", "herb", "nodes.c"))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.vendor_prism(prism_gem_path:)
|
|
40
|
+
FileUtils.mkdir_p(PRISM_VENDOR_DIR)
|
|
41
|
+
|
|
42
|
+
PRISM_ENTRIES.each do |entry|
|
|
43
|
+
source = File.join(prism_gem_path, entry)
|
|
44
|
+
next unless File.exist?(source)
|
|
45
|
+
|
|
46
|
+
puts "Vendoring '#{entry}' Prism file to #{PRISM_VENDOR_DIR}/#{entry}"
|
|
47
|
+
FileUtils.cp_r(source, PRISM_VENDOR_DIR)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
generate_prism_templates unless prism_ast_header_exists?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.prism_vendored?
|
|
54
|
+
File.directory?(File.join(PRISM_VENDOR_DIR, "include"))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.prism_ast_header_exists?
|
|
58
|
+
File.exist?(File.join(PRISM_VENDOR_DIR, "include", "prism", "ast.h"))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.find_prism_gem_path
|
|
62
|
+
find_prism_as_bundler_sibling || find_prism_from_gem_spec
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.generate_prism_templates
|
|
66
|
+
puts "Generating Prism template files..."
|
|
67
|
+
system("ruby", "#{PRISM_VENDOR_DIR}/templates/template.rb", exception: true)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.find_prism_as_bundler_sibling
|
|
71
|
+
bundler_gems_dir = File.expand_path("..", ROOT_PATH)
|
|
72
|
+
candidates = Dir.glob(File.join(bundler_gems_dir, "prism-*"))
|
|
73
|
+
|
|
74
|
+
candidates.find { |path| File.directory?(File.join(path, "src")) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.find_prism_from_gem_spec
|
|
78
|
+
path = Gem::Specification.find_by_name("prism").full_gem_path
|
|
79
|
+
|
|
80
|
+
return path if File.directory?(File.join(path, "src"))
|
|
81
|
+
|
|
82
|
+
nil
|
|
83
|
+
rescue Gem::MissingSpecError
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
data/lib/herb/cli.rb
CHANGED
|
@@ -8,16 +8,16 @@ require "optparse"
|
|
|
8
8
|
class Herb::CLI
|
|
9
9
|
include Herb::Colors
|
|
10
10
|
|
|
11
|
-
attr_accessor :json, :silent, :
|
|
11
|
+
attr_accessor :json, :silent, :log_file, :no_timing, :local, :escape, :no_escape, :freeze, :debug, :tool, :strict, :analyze, :track_whitespace, :verbose, :isolate, :arena_stats, :leak_check
|
|
12
12
|
|
|
13
13
|
def initialize(args)
|
|
14
14
|
@args = args
|
|
15
15
|
@command = args[0]
|
|
16
|
-
@file = args[1]
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def call
|
|
20
19
|
options
|
|
20
|
+
@file = @args[1]
|
|
21
21
|
|
|
22
22
|
if silent
|
|
23
23
|
if result.failed?
|
|
@@ -61,43 +61,60 @@ class Herb::CLI
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def file_content
|
|
64
|
-
if @file && File.exist?(@file)
|
|
64
|
+
if @file && @file != "-" && File.exist?(@file)
|
|
65
65
|
File.read(@file)
|
|
66
|
-
elsif @file
|
|
66
|
+
elsif @file && @file != "-"
|
|
67
67
|
puts "File doesn't exist: #{@file}"
|
|
68
68
|
exit(1)
|
|
69
|
+
elsif @file == "-" || !$stdin.tty?
|
|
70
|
+
$stdin.read
|
|
69
71
|
else
|
|
70
72
|
puts "No file provided."
|
|
71
73
|
puts
|
|
72
74
|
puts "Usage:"
|
|
73
75
|
puts " bundle exec herb #{@command} [file] [options]"
|
|
76
|
+
puts
|
|
77
|
+
puts "You can also pipe content via stdin:"
|
|
78
|
+
puts " echo \"<div>Hello</div>\" | bundle exec herb #{@command}"
|
|
79
|
+
puts " cat file.html.erb | bundle exec herb #{@command}"
|
|
80
|
+
puts " bundle exec herb #{@command} -"
|
|
74
81
|
exit(1)
|
|
75
82
|
end
|
|
76
83
|
end
|
|
77
84
|
|
|
78
85
|
def help(exit_code = 0)
|
|
79
86
|
message = <<~HELP
|
|
80
|
-
|
|
81
|
-
▐▌ ▐▌▐▌ ▐▌ ▐▌▐▌ ▐▌
|
|
82
|
-
▐▛▀▜▌▐▛▀▀▘▐▛▀▚▖▐▛▀▚▖
|
|
83
|
-
▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌▐▙▄▞▘
|
|
84
|
-
|
|
85
|
-
Herb 🌿 Powerful and seamless HTML-aware ERB parsing and tooling.
|
|
87
|
+
Herb 🌿 Powerful and seamless HTML-aware ERB toolchain.
|
|
86
88
|
|
|
87
89
|
Usage:
|
|
88
90
|
bundle exec herb [command] [options]
|
|
89
91
|
|
|
90
92
|
Commands:
|
|
91
|
-
bundle exec herb lex [file]
|
|
92
|
-
bundle exec herb parse [file]
|
|
93
|
-
bundle exec herb compile [file]
|
|
94
|
-
bundle exec herb render [file]
|
|
95
|
-
bundle exec herb analyze [path]
|
|
96
|
-
bundle exec herb
|
|
97
|
-
bundle exec herb
|
|
98
|
-
bundle exec herb
|
|
99
|
-
bundle exec herb
|
|
100
|
-
bundle exec herb
|
|
93
|
+
bundle exec herb lex [file] Lex a file.
|
|
94
|
+
bundle exec herb parse [file] Parse a file.
|
|
95
|
+
bundle exec herb compile [file] Compile ERB template to Ruby code.
|
|
96
|
+
bundle exec herb render [file] Compile and render ERB template to final output.
|
|
97
|
+
bundle exec herb analyze [path] Analyze a project by passing a directory to the root of the project
|
|
98
|
+
bundle exec herb report [file] Generate a Markdown bug report for a file
|
|
99
|
+
bundle exec herb config [path] Show configuration and file patterns for a project
|
|
100
|
+
bundle exec herb ruby [file] Extract Ruby from a file.
|
|
101
|
+
bundle exec herb html [file] Extract HTML from a file.
|
|
102
|
+
bundle exec herb playground [file] Open the content of the source file in the playground
|
|
103
|
+
bundle exec herb version Prints the versions of the Herb gem and the libherb library.
|
|
104
|
+
|
|
105
|
+
bundle exec herb lint [patterns] Lint templates (delegates to @herb-tools/linter)
|
|
106
|
+
bundle exec herb format [patterns] Format templates (delegates to @herb-tools/formatter)
|
|
107
|
+
bundle exec herb highlight [file] Syntax highlight templates (delegates to @herb-tools/highlighter)
|
|
108
|
+
bundle exec herb print [file] Print AST (delegates to @herb-tools/printer)
|
|
109
|
+
bundle exec herb lsp Start the language server (delegates to @herb-tools/language-server)
|
|
110
|
+
|
|
111
|
+
stdin:
|
|
112
|
+
Commands that accept [file] also accept input via stdin:
|
|
113
|
+
echo "<div>Hello</div>" | bundle exec herb lex
|
|
114
|
+
cat file.html.erb | bundle exec herb parse
|
|
115
|
+
|
|
116
|
+
Use `-` to explicitly read from stdin:
|
|
117
|
+
bundle exec herb compile -
|
|
101
118
|
|
|
102
119
|
Options:
|
|
103
120
|
#{option_parser.to_s.strip.gsub(/^ /, " ")}
|
|
@@ -112,21 +129,44 @@ class Herb::CLI
|
|
|
112
129
|
def result
|
|
113
130
|
@result ||= case @command
|
|
114
131
|
when "analyze"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
path = @file || "."
|
|
133
|
+
|
|
134
|
+
if path != "-" && File.file?(path)
|
|
135
|
+
project = Herb::Project.new(File.dirname(path))
|
|
136
|
+
project.file_paths = [File.expand_path(path)]
|
|
137
|
+
else
|
|
138
|
+
unless File.directory?(path)
|
|
139
|
+
puts "Not a file or directory: '#{path}'."
|
|
140
|
+
exit(1)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
project = Herb::Project.new(path)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
project.no_log_file = log_file ? false : true
|
|
118
147
|
project.no_timing = no_timing
|
|
119
148
|
project.silent = silent
|
|
120
|
-
|
|
149
|
+
project.verbose = verbose || ci?
|
|
150
|
+
project.isolate = isolate
|
|
151
|
+
project.validate_ruby = true
|
|
152
|
+
project.arena_stats = arena_stats
|
|
153
|
+
project.leak_check = leak_check
|
|
154
|
+
has_issues = project.analyze!
|
|
121
155
|
exit(has_issues ? 1 : 0)
|
|
156
|
+
when "report"
|
|
157
|
+
generate_report
|
|
158
|
+
exit(0)
|
|
159
|
+
when "config"
|
|
160
|
+
show_config
|
|
161
|
+
exit(0)
|
|
122
162
|
when "parse"
|
|
123
|
-
Herb.parse(file_content)
|
|
163
|
+
Herb.parse(file_content, strict: strict.nil? || strict, analyze: analyze.nil? || analyze, track_whitespace: track_whitespace || false, arena_stats: arena_stats)
|
|
124
164
|
when "compile"
|
|
125
165
|
compile_template
|
|
126
166
|
when "render"
|
|
127
167
|
render_template
|
|
128
168
|
when "lex"
|
|
129
|
-
Herb.lex(file_content)
|
|
169
|
+
Herb.lex(file_content, arena_stats: arena_stats)
|
|
130
170
|
when "ruby"
|
|
131
171
|
puts Herb.extract_ruby(file_content)
|
|
132
172
|
exit(0)
|
|
@@ -134,7 +174,12 @@ class Herb::CLI
|
|
|
134
174
|
puts Herb.extract_html(file_content)
|
|
135
175
|
exit(0)
|
|
136
176
|
when "playground"
|
|
137
|
-
require "
|
|
177
|
+
require "bundler/inline"
|
|
178
|
+
|
|
179
|
+
gemfile do
|
|
180
|
+
source "https://rubygems.org"
|
|
181
|
+
gem "lz_string"
|
|
182
|
+
end
|
|
138
183
|
|
|
139
184
|
hash = LZString::UriSafe.compress(file_content)
|
|
140
185
|
local_url = "http://localhost:5173"
|
|
@@ -152,6 +197,16 @@ class Herb::CLI
|
|
|
152
197
|
system(%(open "#{url}##{hash}"))
|
|
153
198
|
exit(0)
|
|
154
199
|
end
|
|
200
|
+
when "lint"
|
|
201
|
+
run_node_tool("herb-lint", "@herb-tools/linter")
|
|
202
|
+
when "format"
|
|
203
|
+
run_node_tool("herb-format", "@herb-tools/formatter")
|
|
204
|
+
when "print"
|
|
205
|
+
run_node_tool("herb-print", "@herb-tools/printer")
|
|
206
|
+
when "highlight"
|
|
207
|
+
run_node_tool("herb-highlight", "@herb-tools/highlighter")
|
|
208
|
+
when "lsp"
|
|
209
|
+
run_node_tool("herb-language-server", "@herb-tools/language-server")
|
|
155
210
|
when "help"
|
|
156
211
|
help
|
|
157
212
|
when "version"
|
|
@@ -187,12 +242,16 @@ class Herb::CLI
|
|
|
187
242
|
self.silent = true
|
|
188
243
|
end
|
|
189
244
|
|
|
190
|
-
parser.on("
|
|
191
|
-
self.
|
|
245
|
+
parser.on("--verbose", "Show detailed per-file progress (default in CI)") do
|
|
246
|
+
self.verbose = true
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
parser.on("--isolate", "Fork each file into its own process for crash isolation (slower)") do
|
|
250
|
+
self.isolate = true
|
|
192
251
|
end
|
|
193
252
|
|
|
194
|
-
parser.on("--
|
|
195
|
-
self.
|
|
253
|
+
parser.on("--log-file", "Enable log file generation") do
|
|
254
|
+
self.log_file = true
|
|
196
255
|
end
|
|
197
256
|
|
|
198
257
|
parser.on("--no-timing", "Disable timing output") do
|
|
@@ -218,15 +277,100 @@ class Herb::CLI
|
|
|
218
277
|
parser.on("--debug", "Enable debug mode with ERB expression wrapping (for compile command)") do
|
|
219
278
|
self.debug = true
|
|
220
279
|
end
|
|
280
|
+
|
|
281
|
+
parser.on("--strict", "Enable strict mode - report errors for omitted closing tags (for parse/compile/render commands) (default: true)") do
|
|
282
|
+
self.strict = true
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
parser.on("--no-strict", "Disable strict mode (for parse/compile/render commands)") do
|
|
286
|
+
self.strict = false
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
parser.on("--analyze", "Enable analyze mode (for parse command) (default: true)") do
|
|
290
|
+
self.analyze = true
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
parser.on("--no-analyze", "Disable analyze mode (for parse command)") do
|
|
294
|
+
self.analyze = false
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
parser.on("--track-whitespace", "Enable whitespace tracking (for parse command) (default: false)") do
|
|
298
|
+
self.track_whitespace = true
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
parser.on("--tool TOOL", "Show config for specific tool: linter, formatter (for config command)") do |t|
|
|
302
|
+
self.tool = t.to_sym
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
parser.on("--arena-stats", "Print arena memory statistics (for lex/parse/analyze commands)") do
|
|
306
|
+
self.arena_stats = true
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
parser.on("--leak-check", "Check for memory leaks in lex/parse/extract operations (for analyze command)") do
|
|
310
|
+
self.leak_check = true
|
|
311
|
+
end
|
|
221
312
|
end
|
|
222
313
|
end
|
|
223
314
|
|
|
224
315
|
def options
|
|
316
|
+
return if ["lint", "format", "print", "highlight", "lsp"].include?(@command)
|
|
317
|
+
|
|
225
318
|
option_parser.parse!(@args)
|
|
226
319
|
end
|
|
227
320
|
|
|
228
321
|
private
|
|
229
322
|
|
|
323
|
+
def ci?
|
|
324
|
+
ENV["CI"] == "true" || ENV.key?("GITHUB_ACTIONS") || ENV.key?("BUILDKITE") || ENV.key?("JENKINS_URL") || ENV.key?("CIRCLECI") || ENV.key?("TRAVIS")
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def find_node_binary(name)
|
|
328
|
+
local_bin = File.join(Dir.pwd, "node_modules", ".bin", name)
|
|
329
|
+
return local_bin if File.executable?(local_bin)
|
|
330
|
+
|
|
331
|
+
path_result = `which #{name} 2>/dev/null`.strip
|
|
332
|
+
return path_result unless path_result.empty?
|
|
333
|
+
|
|
334
|
+
nil
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def node_available?
|
|
338
|
+
system("which node > /dev/null 2>&1")
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def run_node_tool(binary_name, package_name)
|
|
342
|
+
unless node_available?
|
|
343
|
+
warn "Error: Node.js is required to run 'herb #{@command}'."
|
|
344
|
+
warn ""
|
|
345
|
+
warn "Install the tool:"
|
|
346
|
+
warn " npm install #{package_name}"
|
|
347
|
+
warn " yarn add #{package_name}"
|
|
348
|
+
warn " pnpm add #{package_name}"
|
|
349
|
+
warn " bun add #{package_name}"
|
|
350
|
+
warn ""
|
|
351
|
+
warn "Or install Node.js from https://nodejs.org"
|
|
352
|
+
exit 1
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
remaining_args = @args[1..]
|
|
356
|
+
binary = find_node_binary(binary_name)
|
|
357
|
+
node_version = `node --version 2>/dev/null`.strip
|
|
358
|
+
|
|
359
|
+
command_parts = if binary
|
|
360
|
+
[binary, *remaining_args]
|
|
361
|
+
else
|
|
362
|
+
["npx", package_name, *remaining_args]
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
escaped_command = command_parts.map { |arg| arg.include?(" ") ? "\"#{arg}\"" : arg }.join(" ")
|
|
366
|
+
|
|
367
|
+
warn "Node.js: #{node_version}"
|
|
368
|
+
warn "Running: #{escaped_command}"
|
|
369
|
+
warn ""
|
|
370
|
+
|
|
371
|
+
exec(*command_parts)
|
|
372
|
+
end
|
|
373
|
+
|
|
230
374
|
def print_error_summary(errors)
|
|
231
375
|
puts
|
|
232
376
|
puts white("#{bold(red("Errors"))} #{dimmed("(#{errors.size} total)")}")
|
|
@@ -254,6 +398,35 @@ class Herb::CLI
|
|
|
254
398
|
end
|
|
255
399
|
end
|
|
256
400
|
|
|
401
|
+
def generate_report
|
|
402
|
+
unless @file
|
|
403
|
+
puts "Usage: herb report <file>"
|
|
404
|
+
exit(1)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
unless File.file?(@file)
|
|
408
|
+
puts "File not found: #{@file}"
|
|
409
|
+
exit(1)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
project = Herb::Project.new(File.dirname(@file))
|
|
413
|
+
project.file_paths = [File.expand_path(@file)]
|
|
414
|
+
project.no_log_file = true
|
|
415
|
+
project.no_timing = true
|
|
416
|
+
project.silent = true
|
|
417
|
+
project.validate_ruby = true
|
|
418
|
+
|
|
419
|
+
original_stdout = $stdout
|
|
420
|
+
$stdout = StringIO.new
|
|
421
|
+
begin
|
|
422
|
+
project.analyze!
|
|
423
|
+
ensure
|
|
424
|
+
$stdout = original_stdout
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
project.print_file_report(@file)
|
|
428
|
+
end
|
|
429
|
+
|
|
257
430
|
def compile_template
|
|
258
431
|
require_relative "engine"
|
|
259
432
|
|
|
@@ -262,12 +435,14 @@ class Herb::CLI
|
|
|
262
435
|
options[:filename] = @file if @file
|
|
263
436
|
options[:escape] = no_escape ? false : true
|
|
264
437
|
options[:freeze] = true if freeze
|
|
438
|
+
options[:strict] = strict.nil? || strict
|
|
265
439
|
|
|
266
440
|
if debug
|
|
267
441
|
options[:debug] = true
|
|
268
442
|
options[:debug_filename] = @file if @file
|
|
269
443
|
end
|
|
270
444
|
|
|
445
|
+
options[:validate_ruby] = true
|
|
271
446
|
engine = Herb::Engine.new(file_content, options)
|
|
272
447
|
|
|
273
448
|
if json
|
|
@@ -276,6 +451,7 @@ class Herb::CLI
|
|
|
276
451
|
source: engine.src,
|
|
277
452
|
filename: engine.filename,
|
|
278
453
|
bufvar: engine.bufvar,
|
|
454
|
+
strict: options[:strict],
|
|
279
455
|
}
|
|
280
456
|
|
|
281
457
|
puts result.to_json
|
|
@@ -286,6 +462,24 @@ class Herb::CLI
|
|
|
286
462
|
end
|
|
287
463
|
|
|
288
464
|
exit(0)
|
|
465
|
+
rescue Herb::Engine::InvalidRubyError => e
|
|
466
|
+
if json
|
|
467
|
+
result = {
|
|
468
|
+
success: false,
|
|
469
|
+
error: e.message,
|
|
470
|
+
source: e.compiled_source,
|
|
471
|
+
filename: @file,
|
|
472
|
+
}
|
|
473
|
+
puts result.to_json
|
|
474
|
+
elsif silent
|
|
475
|
+
puts "Failed"
|
|
476
|
+
else
|
|
477
|
+
puts e.compiled_source if e.compiled_source
|
|
478
|
+
puts
|
|
479
|
+
puts e.message
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
exit(1)
|
|
289
483
|
rescue Herb::Engine::CompilationError => e
|
|
290
484
|
if json
|
|
291
485
|
result = {
|
|
@@ -328,6 +522,7 @@ class Herb::CLI
|
|
|
328
522
|
options[:filename] = @file if @file
|
|
329
523
|
options[:escape] = no_escape ? false : true
|
|
330
524
|
options[:freeze] = true if freeze
|
|
525
|
+
options[:strict] = strict.nil? || strict
|
|
331
526
|
|
|
332
527
|
if debug
|
|
333
528
|
options[:debug] = true
|
|
@@ -344,6 +539,7 @@ class Herb::CLI
|
|
|
344
539
|
success: true,
|
|
345
540
|
output: rendered_output,
|
|
346
541
|
filename: engine.filename,
|
|
542
|
+
strict: options[:strict],
|
|
347
543
|
}
|
|
348
544
|
|
|
349
545
|
puts result.to_json
|
|
@@ -388,6 +584,120 @@ class Herb::CLI
|
|
|
388
584
|
end
|
|
389
585
|
end
|
|
390
586
|
|
|
587
|
+
def show_config
|
|
588
|
+
path = @file || "."
|
|
589
|
+
config = Herb::Configuration.load(path)
|
|
590
|
+
|
|
591
|
+
if tool
|
|
592
|
+
show_tool_config(config, path)
|
|
593
|
+
else
|
|
594
|
+
show_general_config(config, path)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def show_general_config(config, path)
|
|
599
|
+
puts bold("Herb Configuration")
|
|
600
|
+
puts
|
|
601
|
+
puts "#{bold("Project root:")} #{config.project_root || "(not found)"}"
|
|
602
|
+
puts "#{bold("Config file:")} #{config.config_path || "(using defaults)"}"
|
|
603
|
+
puts
|
|
604
|
+
|
|
605
|
+
puts bold("Include patterns:")
|
|
606
|
+
config.file_include_patterns.each { |p| puts " #{green("+")} #{p}" }
|
|
607
|
+
puts
|
|
608
|
+
|
|
609
|
+
puts bold("Exclude patterns:")
|
|
610
|
+
config.file_exclude_patterns.each { |p| puts " #{red("-")} #{p}" }
|
|
611
|
+
puts
|
|
612
|
+
|
|
613
|
+
all_matched = find_all_matching_files(path, config.file_include_patterns)
|
|
614
|
+
included_files = config.find_files(path)
|
|
615
|
+
excluded_files = all_matched - included_files
|
|
616
|
+
|
|
617
|
+
puts bold("Files (#{included_files.size} included, #{excluded_files.size} excluded):")
|
|
618
|
+
puts
|
|
619
|
+
|
|
620
|
+
show_file_lists(included_files, excluded_files, path, config.file_exclude_patterns)
|
|
621
|
+
|
|
622
|
+
puts
|
|
623
|
+
puts dimmed("Tip: Use --tool linter or --tool formatter to see tool-specific configuration")
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def show_tool_config(config, path)
|
|
627
|
+
unless [:linter, :formatter].include?(tool)
|
|
628
|
+
puts red("Unknown tool: #{tool}")
|
|
629
|
+
puts "Valid tools: linter, formatter"
|
|
630
|
+
exit(1)
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
tool_config = config.send(tool)
|
|
634
|
+
include_patterns = config.include_patterns_for(tool)
|
|
635
|
+
exclude_patterns = config.exclude_patterns_for(tool)
|
|
636
|
+
|
|
637
|
+
puts bold("Herb Configuration for #{tool.to_s.capitalize}")
|
|
638
|
+
puts
|
|
639
|
+
puts "#{bold("Project root:")} #{config.project_root || "(not found)"}"
|
|
640
|
+
puts "#{bold("Config file:")} #{config.config_path || "(using defaults)"}"
|
|
641
|
+
puts
|
|
642
|
+
|
|
643
|
+
if tool_config["enabled"] == false
|
|
644
|
+
puts yellow("⚠ #{tool.to_s.capitalize} is disabled in configuration")
|
|
645
|
+
puts
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
puts bold("Include patterns (files + #{tool}):")
|
|
649
|
+
include_patterns.each { |p| puts " #{green("+")} #{p}" }
|
|
650
|
+
puts
|
|
651
|
+
|
|
652
|
+
puts bold("Exclude patterns (files + #{tool}):")
|
|
653
|
+
exclude_patterns.each { |p| puts " #{red("-")} #{p}" }
|
|
654
|
+
puts
|
|
655
|
+
|
|
656
|
+
all_matched = find_all_matching_files(path, include_patterns)
|
|
657
|
+
included_files = config.find_files_for_tool(tool, path)
|
|
658
|
+
excluded_files = all_matched - included_files
|
|
659
|
+
|
|
660
|
+
puts bold("Files for #{tool} (#{included_files.size} included, #{excluded_files.size} excluded):")
|
|
661
|
+
puts
|
|
662
|
+
|
|
663
|
+
show_file_lists(included_files, excluded_files, path, exclude_patterns)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def show_file_lists(included_files, excluded_files, path, exclude_patterns)
|
|
667
|
+
expanded_path = File.expand_path(path)
|
|
668
|
+
|
|
669
|
+
if included_files.any?
|
|
670
|
+
puts " #{bold(green("Included:"))}"
|
|
671
|
+
included_files.each do |f|
|
|
672
|
+
relative = f.sub("#{expanded_path}/", "")
|
|
673
|
+
puts " #{green("✓")} #{relative}"
|
|
674
|
+
end
|
|
675
|
+
puts
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
return unless excluded_files.any?
|
|
679
|
+
|
|
680
|
+
puts " #{bold(red("Excluded:"))}"
|
|
681
|
+
excluded_files.each do |f|
|
|
682
|
+
relative = f.sub("#{expanded_path}/", "")
|
|
683
|
+
reason = find_exclude_reason(relative, exclude_patterns)
|
|
684
|
+
puts " #{red("✗")} #{relative} #{dimmed("(#{reason})")}"
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
def find_all_matching_files(path, include_patterns)
|
|
689
|
+
expanded_path = File.expand_path(path)
|
|
690
|
+
include_patterns.flat_map do |pattern|
|
|
691
|
+
Dir[File.join(expanded_path, pattern)]
|
|
692
|
+
end.uniq
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def find_exclude_reason(relative_path, exclude_patterns)
|
|
696
|
+
exclude_patterns.find do |pattern|
|
|
697
|
+
File.fnmatch?(pattern, relative_path, File::FNM_PATHNAME)
|
|
698
|
+
end || "excluded"
|
|
699
|
+
end
|
|
700
|
+
|
|
391
701
|
def print_version
|
|
392
702
|
puts Herb.version
|
|
393
703
|
exit(0)
|