herb 0.1.0-x86_64-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 +7 -0
- data/License.txt +21 -0
- data/Makefile +121 -0
- data/README.md +166 -0
- data/Rakefile +184 -0
- data/exe/herb +5 -0
- data/ext/herb/error_helpers.c +302 -0
- data/ext/herb/error_helpers.h +15 -0
- data/ext/herb/extconf.rb +75 -0
- data/ext/herb/extension.c +110 -0
- data/ext/herb/extension.h +6 -0
- data/ext/herb/extension_helpers.c +117 -0
- data/ext/herb/extension_helpers.h +24 -0
- data/ext/herb/nodes.c +936 -0
- data/ext/herb/nodes.h +12 -0
- data/herb.gemspec +49 -0
- 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 +61 -0
- data/lib/herb/ast/nodes.rb +1542 -0
- data/lib/herb/ast.rb +6 -0
- data/lib/herb/cli.rb +164 -0
- data/lib/herb/errors.rb +352 -0
- data/lib/herb/lex_result.rb +20 -0
- data/lib/herb/libherb/array.rb +48 -0
- data/lib/herb/libherb/ast_node.rb +47 -0
- data/lib/herb/libherb/buffer.rb +53 -0
- data/lib/herb/libherb/extract_result.rb +17 -0
- data/lib/herb/libherb/lex_result.rb +29 -0
- data/lib/herb/libherb/libherb.rb +49 -0
- data/lib/herb/libherb/parse_result.rb +17 -0
- data/lib/herb/libherb/token.rb +43 -0
- data/lib/herb/libherb.rb +32 -0
- data/lib/herb/location.rb +42 -0
- data/lib/herb/parse_result.rb +26 -0
- data/lib/herb/position.rb +36 -0
- data/lib/herb/project.rb +361 -0
- data/lib/herb/range.rb +40 -0
- data/lib/herb/result.rb +21 -0
- data/lib/herb/token.rb +43 -0
- data/lib/herb/token_list.rb +11 -0
- data/lib/herb/version.rb +5 -0
- data/lib/herb.rb +32 -0
- data/src/analyze.c +989 -0
- data/src/analyze_helpers.c +241 -0
- data/src/analyzed_ruby.c +35 -0
- data/src/array.c +137 -0
- data/src/ast_node.c +81 -0
- data/src/ast_nodes.c +866 -0
- data/src/ast_pretty_print.c +588 -0
- data/src/buffer.c +199 -0
- data/src/errors.c +740 -0
- data/src/extract.c +110 -0
- data/src/herb.c +103 -0
- data/src/html_util.c +143 -0
- data/src/include/analyze.h +36 -0
- data/src/include/analyze_helpers.h +43 -0
- data/src/include/analyzed_ruby.h +33 -0
- data/src/include/array.h +33 -0
- data/src/include/ast_node.h +35 -0
- data/src/include/ast_nodes.h +303 -0
- data/src/include/ast_pretty_print.h +17 -0
- data/src/include/buffer.h +36 -0
- data/src/include/errors.h +125 -0
- data/src/include/extract.h +20 -0
- data/src/include/herb.h +32 -0
- data/src/include/html_util.h +13 -0
- data/src/include/io.h +9 -0
- data/src/include/json.h +28 -0
- data/src/include/lexer.h +13 -0
- data/src/include/lexer_peek_helpers.h +23 -0
- data/src/include/lexer_struct.h +32 -0
- data/src/include/location.h +25 -0
- data/src/include/macros.h +10 -0
- data/src/include/memory.h +12 -0
- data/src/include/parser.h +22 -0
- data/src/include/parser_helpers.h +33 -0
- data/src/include/position.h +22 -0
- data/src/include/pretty_print.h +53 -0
- data/src/include/prism_helpers.h +18 -0
- data/src/include/range.h +23 -0
- data/src/include/ruby_parser.h +6 -0
- data/src/include/token.h +25 -0
- data/src/include/token_matchers.h +21 -0
- data/src/include/token_struct.h +51 -0
- data/src/include/util.h +25 -0
- data/src/include/version.h +6 -0
- data/src/include/visitor.h +11 -0
- data/src/io.c +30 -0
- data/src/json.c +205 -0
- data/src/lexer.c +284 -0
- data/src/lexer_peek_helpers.c +59 -0
- data/src/location.c +41 -0
- data/src/main.c +162 -0
- data/src/memory.c +53 -0
- data/src/parser.c +704 -0
- data/src/parser_helpers.c +161 -0
- data/src/position.c +33 -0
- data/src/pretty_print.c +242 -0
- data/src/prism_helpers.c +50 -0
- data/src/range.c +38 -0
- data/src/ruby_parser.c +47 -0
- data/src/token.c +194 -0
- data/src/token_matchers.c +32 -0
- data/src/util.c +128 -0
- data/src/visitor.c +321 -0
- metadata +159 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Herb
|
6
|
+
class LexResult
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@array, :items, :size, :capacity
|
10
|
+
|
11
|
+
attr_accessor :array
|
12
|
+
|
13
|
+
def initialize(pointer)
|
14
|
+
@array = LibHerb::Array.new(pointer, LibHerb::Token)
|
15
|
+
end
|
16
|
+
|
17
|
+
def as_json
|
18
|
+
JSON.parse(to_json)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_json(*_args)
|
22
|
+
"[#{@array.items.map(&:to_json).join(", ")}]"
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
@array.items.map(&:inspect).join("\n")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "herb/libherb/ast_node"
|
4
|
+
require "herb/libherb/buffer"
|
5
|
+
require "herb/libherb/array"
|
6
|
+
require "herb/libherb/token"
|
7
|
+
|
8
|
+
require "herb/libherb/lex_result"
|
9
|
+
require "herb/libherb/parse_result"
|
10
|
+
|
11
|
+
module Herb
|
12
|
+
VERSION = LibHerb.herb_version.read_string
|
13
|
+
|
14
|
+
def self.parse(source)
|
15
|
+
ParseResult.new(
|
16
|
+
LibHerb.herb_parse(source)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.lex(source)
|
21
|
+
LexResult.new(
|
22
|
+
LibHerb.herb_lex(source)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.lex_to_json(source)
|
27
|
+
LibHerb::Buffer.with do |output|
|
28
|
+
LibHerb.herb_lex_json_to_buffer(source, output.pointer)
|
29
|
+
|
30
|
+
JSON.parse(output.read.force_encoding("utf-8"))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.extract_ruby(source)
|
35
|
+
LibHerb::Buffer.with do |output|
|
36
|
+
LibHerb.herb_extract_ruby_to_buffer(source, output.pointer)
|
37
|
+
|
38
|
+
output.read
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.extract_html(source)
|
43
|
+
LibHerb::Buffer.with do |output|
|
44
|
+
LibHerb.herb_extract_html_to_buffer(source, output.pointer)
|
45
|
+
|
46
|
+
output.read
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Herb
|
6
|
+
class ParseResult
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@root_node, :type, :child_count
|
10
|
+
|
11
|
+
attr_accessor :root_node
|
12
|
+
|
13
|
+
def initialize(pointer)
|
14
|
+
@root_node = LibHerb::ASTNode.new(pointer)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herb
|
4
|
+
module LibHerb
|
5
|
+
attach_function :token_to_string, [:pointer], :string
|
6
|
+
attach_function :token_to_json, [:pointer], :string
|
7
|
+
attach_function :token_type_to_string, [:int], :pointer
|
8
|
+
attach_function :token_value, [:pointer], :pointer
|
9
|
+
attach_function :token_type, [:pointer], :int
|
10
|
+
|
11
|
+
class Token
|
12
|
+
attr_reader :pointer
|
13
|
+
|
14
|
+
def initialize(pointer)
|
15
|
+
@pointer = pointer
|
16
|
+
end
|
17
|
+
|
18
|
+
def value
|
19
|
+
@value ||= LibHerb.token_value(pointer).read_string
|
20
|
+
end
|
21
|
+
|
22
|
+
def type
|
23
|
+
@type ||= LibHerb.token_type_to_string(type_int).read_string
|
24
|
+
end
|
25
|
+
|
26
|
+
def type_int
|
27
|
+
@type_int ||= LibHerb.token_type(pointer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
LibHerb.token_to_string(pointer).force_encoding("utf-8")
|
32
|
+
end
|
33
|
+
|
34
|
+
def as_json
|
35
|
+
JSON.parse(to_json)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_json(*_args)
|
39
|
+
LibHerb.token_to_json(pointer).force_encoding("utf-8")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/herb/libherb.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ffi"
|
4
|
+
require "rbconfig"
|
5
|
+
|
6
|
+
module Herb
|
7
|
+
module LibHerb
|
8
|
+
extend FFI::Library
|
9
|
+
|
10
|
+
def self.library_extension
|
11
|
+
RbConfig::CONFIG["DLEXT"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.library_name
|
15
|
+
"libherb.#{library_extension}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.library_path
|
19
|
+
File.expand_path("../../#{library_name}", __dir__)
|
20
|
+
end
|
21
|
+
|
22
|
+
ffi_lib(library_path)
|
23
|
+
|
24
|
+
attach_function :herb_lex_to_buffer, [:pointer, :pointer], :void
|
25
|
+
attach_function :herb_lex_json_to_buffer, [:pointer, :pointer], :void
|
26
|
+
attach_function :herb_lex, [:pointer], :pointer
|
27
|
+
attach_function :herb_parse, [:pointer], :pointer
|
28
|
+
attach_function :herb_extract_ruby_to_buffer, [:pointer, :pointer], :void
|
29
|
+
attach_function :herb_extract_html_to_buffer, [:pointer, :pointer], :void
|
30
|
+
attach_function :herb_version, [], :pointer
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herb
|
4
|
+
class Location
|
5
|
+
attr_reader :start, :end
|
6
|
+
|
7
|
+
def initialize(start_position, end_position)
|
8
|
+
@start = start_position
|
9
|
+
@end = end_position
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from(start_line, start_column, end_line, end_column)
|
13
|
+
new(
|
14
|
+
Position.new(start_line, start_column),
|
15
|
+
Position.new(end_line, end_column)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.[](...)
|
20
|
+
from(...)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
{
|
25
|
+
start: start,
|
26
|
+
end: self.end,
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_json(*args)
|
31
|
+
to_hash.to_json(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def tree_inspect
|
35
|
+
%((location: #{start.tree_inspect}-#{self.end.tree_inspect}))
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
%(#<Herb::Location #{tree_inspect}>)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Herb
|
6
|
+
class ParseResult < Result
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize(value, source, warnings, errors)
|
10
|
+
@value = value
|
11
|
+
super(source, warnings, errors)
|
12
|
+
end
|
13
|
+
|
14
|
+
def failed?
|
15
|
+
errors.any? || value.errors.any? # TODO: this should probably be recursive
|
16
|
+
end
|
17
|
+
|
18
|
+
def success?
|
19
|
+
!failed?
|
20
|
+
end
|
21
|
+
|
22
|
+
def pretty_errors
|
23
|
+
JSON.pretty_generate(errors + value.errors)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herb
|
4
|
+
class Position
|
5
|
+
attr_reader :line, :column
|
6
|
+
|
7
|
+
def initialize(line, column)
|
8
|
+
@line = line
|
9
|
+
@column = column
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.[](...)
|
13
|
+
new(...)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from(...)
|
17
|
+
new(...)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
{ line: line, column: column }
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_json(*args)
|
25
|
+
to_hash.to_json(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def tree_inspect
|
29
|
+
"(#{line}:#{column})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
%(#<Herb::Position #{tree_inspect}>)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/herb/project.rb
ADDED
@@ -0,0 +1,361 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "io/console"
|
4
|
+
require "timeout"
|
5
|
+
require "tempfile"
|
6
|
+
require "pathname"
|
7
|
+
require "English"
|
8
|
+
|
9
|
+
module Herb
|
10
|
+
class Project
|
11
|
+
attr_accessor :project_path, :output_file
|
12
|
+
|
13
|
+
def initialize(project_path, output_file: nil)
|
14
|
+
@project_path = Pathname.new(
|
15
|
+
project_path ? File.expand_path(".", project_path) : File.expand_path("../..", __dir__)
|
16
|
+
)
|
17
|
+
|
18
|
+
date = Time.now.strftime("%Y-%m-%d_%H-%M-%S")
|
19
|
+
@output_file = output_file || "#{date}_erb_parsing_result_#{@project_path.basename}.log"
|
20
|
+
end
|
21
|
+
|
22
|
+
def glob
|
23
|
+
"**/*.html.erb"
|
24
|
+
end
|
25
|
+
|
26
|
+
def full_path_glob
|
27
|
+
project_path + glob
|
28
|
+
end
|
29
|
+
|
30
|
+
def absolute_path
|
31
|
+
File.expand_path(@project_path, File.expand_path("../..", __dir__))
|
32
|
+
end
|
33
|
+
|
34
|
+
def files
|
35
|
+
@files ||= Dir[full_path_glob]
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse!
|
39
|
+
File.open(output_file, "w") do |log|
|
40
|
+
log.puts heading("METADATA")
|
41
|
+
log.puts "Herb Version: #{Herb.version}"
|
42
|
+
log.puts "Reported at: #{Time.now.strftime("%Y-%m-%dT%H:%M:%S")}\n\n"
|
43
|
+
|
44
|
+
log.puts heading("PROJECT")
|
45
|
+
log.puts "Path: #{absolute_path}"
|
46
|
+
log.puts "Glob: #{"#{absolute_path}#{glob}"}\n\n"
|
47
|
+
|
48
|
+
log.puts heading("PROCESSED FILES")
|
49
|
+
|
50
|
+
if files.empty?
|
51
|
+
message = "No .html.erb files found using #{full_path_glob}"
|
52
|
+
log.puts message
|
53
|
+
puts message
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
print "\e[H\e[2J"
|
58
|
+
|
59
|
+
successful_files = []
|
60
|
+
failed_files = []
|
61
|
+
timeout_files = []
|
62
|
+
error_files = []
|
63
|
+
error_outputs = {}
|
64
|
+
file_contents = {}
|
65
|
+
parse_errors = {}
|
66
|
+
|
67
|
+
files.each_with_index do |file_path, index|
|
68
|
+
total_failed = failed_files.count
|
69
|
+
total_timeout = timeout_files.count
|
70
|
+
total_errors = error_files.count
|
71
|
+
|
72
|
+
lines_to_clear = 6 + total_failed + total_timeout + total_errors
|
73
|
+
lines_to_clear += 3 if total_failed.positive?
|
74
|
+
lines_to_clear += 3 if total_timeout.positive?
|
75
|
+
lines_to_clear += 3 if total_errors.positive?
|
76
|
+
|
77
|
+
lines_to_clear.times { print "\e[1A\e[K" } if index.positive?
|
78
|
+
|
79
|
+
puts "Parsing .html.erb files in: #{project_path}"
|
80
|
+
puts "Total files to process: #{files.count}\n"
|
81
|
+
|
82
|
+
relative_path = file_path.sub("#{project_path}/", "")
|
83
|
+
|
84
|
+
puts
|
85
|
+
puts progress_bar(index + 1, files.count)
|
86
|
+
puts
|
87
|
+
puts "Processing [#{index + 1}/#{files.count}]: #{relative_path}"
|
88
|
+
|
89
|
+
if failed_files.any?
|
90
|
+
puts
|
91
|
+
puts "Files that failed:"
|
92
|
+
failed_files.each { |file| puts " - #{file}" }
|
93
|
+
puts
|
94
|
+
end
|
95
|
+
|
96
|
+
if timeout_files.any?
|
97
|
+
puts
|
98
|
+
puts "Files that timed out:"
|
99
|
+
timeout_files.each { |file| puts " - #{file}" }
|
100
|
+
puts
|
101
|
+
end
|
102
|
+
|
103
|
+
if error_files.any?
|
104
|
+
puts
|
105
|
+
puts "Files with parse errors:"
|
106
|
+
error_files.each { |file| puts " - #{file}" }
|
107
|
+
puts
|
108
|
+
end
|
109
|
+
|
110
|
+
begin
|
111
|
+
file_content = File.read(file_path)
|
112
|
+
|
113
|
+
stdout_file = Tempfile.new("stdout")
|
114
|
+
stderr_file = Tempfile.new("stderr")
|
115
|
+
ast_file = Tempfile.new("ast")
|
116
|
+
|
117
|
+
Timeout.timeout(1) do
|
118
|
+
pid = Process.fork do
|
119
|
+
$stdout.reopen(stdout_file.path, "w")
|
120
|
+
$stderr.reopen(stderr_file.path, "w")
|
121
|
+
|
122
|
+
begin
|
123
|
+
result = Herb.parse(file_content)
|
124
|
+
|
125
|
+
if result.failed?
|
126
|
+
File.open(ast_file.path, "w") do |f|
|
127
|
+
f.puts result.value.inspect
|
128
|
+
end
|
129
|
+
|
130
|
+
exit!(2)
|
131
|
+
end
|
132
|
+
|
133
|
+
exit!(0)
|
134
|
+
rescue StandardError => e
|
135
|
+
warn "Ruby exception: #{e.class}: #{e.message}"
|
136
|
+
warn e.backtrace.join("\n") if e.backtrace
|
137
|
+
exit!(1)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
Process.waitpid(pid)
|
142
|
+
|
143
|
+
stdout_file.rewind
|
144
|
+
stderr_file.rewind
|
145
|
+
stdout_content = stdout_file.read
|
146
|
+
stderr_content = stderr_file.read
|
147
|
+
ast = File.exist?(ast_file.path) ? File.read(ast_file.path) : ""
|
148
|
+
|
149
|
+
case $CHILD_STATUS.exitstatus
|
150
|
+
when 0
|
151
|
+
log.puts "✅ Parsed #{file_path} successfully"
|
152
|
+
successful_files << file_path
|
153
|
+
when 2
|
154
|
+
message = "⚠️ Parsing #{file_path} completed with errors"
|
155
|
+
log.puts message
|
156
|
+
|
157
|
+
parse_errors[file_path] = {
|
158
|
+
ast: ast,
|
159
|
+
stdout: stdout_content,
|
160
|
+
stderr: stderr_content,
|
161
|
+
}
|
162
|
+
|
163
|
+
file_contents[file_path] = file_content
|
164
|
+
|
165
|
+
error_files << file_path
|
166
|
+
else
|
167
|
+
message = "❌ Parsing #{file_path} failed"
|
168
|
+
log.puts message
|
169
|
+
|
170
|
+
error_outputs[file_path] = {
|
171
|
+
exit_code: $CHILD_STATUS.exitstatus,
|
172
|
+
stdout: stdout_content,
|
173
|
+
stderr: stderr_content,
|
174
|
+
}
|
175
|
+
|
176
|
+
file_contents[file_path] = file_content
|
177
|
+
|
178
|
+
failed_files << file_path
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
stdout_file.close
|
183
|
+
stdout_file.unlink
|
184
|
+
stderr_file.close
|
185
|
+
stderr_file.unlink
|
186
|
+
ast_file.close
|
187
|
+
ast_file.unlink
|
188
|
+
rescue Timeout::Error
|
189
|
+
message = "⏱️ Parsing #{file_path} timed out after 1 second"
|
190
|
+
log.puts message
|
191
|
+
|
192
|
+
begin
|
193
|
+
Process.kill("TERM", pid)
|
194
|
+
rescue StandardError
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
|
198
|
+
timeout_files << file_path
|
199
|
+
file_contents[file_path] = file_content
|
200
|
+
rescue StandardError => e
|
201
|
+
message = "⚠️ Error processing #{file_path}: #{e.message}"
|
202
|
+
log.puts message
|
203
|
+
|
204
|
+
failed_files << file_path
|
205
|
+
|
206
|
+
begin
|
207
|
+
file_contents[file_path] = File.read(file_path)
|
208
|
+
rescue StandardError => read_error
|
209
|
+
log.puts " Could not read file content: #{read_error.message}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
print "\e[1A\e[K"
|
215
|
+
puts "Completed processing all files."
|
216
|
+
|
217
|
+
print "\e[H\e[2J"
|
218
|
+
|
219
|
+
log.puts ""
|
220
|
+
|
221
|
+
summary = [
|
222
|
+
heading("Summary"),
|
223
|
+
"Total files: #{files.count}",
|
224
|
+
"✅ Successful: #{successful_files.count}",
|
225
|
+
"❌ Failed: #{failed_files.count}",
|
226
|
+
"⚠️ Parse errors: #{error_files.count}",
|
227
|
+
"⏱️ Timed out: #{timeout_files.count}"
|
228
|
+
]
|
229
|
+
|
230
|
+
summary.each do |line|
|
231
|
+
log.puts line
|
232
|
+
puts line
|
233
|
+
end
|
234
|
+
|
235
|
+
if failed_files.any?
|
236
|
+
log.puts "\n#{heading("Files that failed")}"
|
237
|
+
puts "\nFiles that failed:"
|
238
|
+
|
239
|
+
failed_files.each do |f|
|
240
|
+
log.puts "- #{f}"
|
241
|
+
puts " - #{f}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
if error_files.any?
|
246
|
+
log.puts "\n#{heading("Files with parse errors")}"
|
247
|
+
puts "\nFiles with parse errors:"
|
248
|
+
|
249
|
+
error_files.each do |f|
|
250
|
+
log.puts f
|
251
|
+
puts " - #{f}"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
if timeout_files.any?
|
256
|
+
log.puts "\n#{heading("Files that timed out")}"
|
257
|
+
puts "\nFiles that timed out:"
|
258
|
+
|
259
|
+
timeout_files.each do |f|
|
260
|
+
log.puts f
|
261
|
+
puts " - #{f}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
problem_files = failed_files + timeout_files + error_files
|
266
|
+
|
267
|
+
if problem_files.any?
|
268
|
+
log.puts "\n#{heading("FILE CONTENTS AND DETAILS")}"
|
269
|
+
|
270
|
+
problem_files.each do |file|
|
271
|
+
next unless file_contents[file]
|
272
|
+
|
273
|
+
divider = "=" * [80, file.length].max
|
274
|
+
|
275
|
+
log.puts
|
276
|
+
log.puts divider
|
277
|
+
log.puts file
|
278
|
+
log.puts divider
|
279
|
+
|
280
|
+
log.puts "\n#{heading("CONTENT")}"
|
281
|
+
log.puts "```erb"
|
282
|
+
log.puts file_contents[file]
|
283
|
+
log.puts "```"
|
284
|
+
|
285
|
+
if error_outputs[file]
|
286
|
+
if error_outputs[file][:exit_code]
|
287
|
+
log.puts "\n#{heading("EXIT CODE")}"
|
288
|
+
log.puts error_outputs[file][:exit_code]
|
289
|
+
end
|
290
|
+
|
291
|
+
if error_outputs[file][:stderr].strip.length.positive?
|
292
|
+
log.puts "\n#{heading("ERROR OUTPUT")}"
|
293
|
+
log.puts "```"
|
294
|
+
log.puts error_outputs[file][:stderr]
|
295
|
+
log.puts "```"
|
296
|
+
end
|
297
|
+
|
298
|
+
if error_outputs[file][:stdout].strip.length.positive?
|
299
|
+
log.puts "\n#{heading("STANDARD OUTPUT")}"
|
300
|
+
log.puts "```"
|
301
|
+
log.puts error_outputs[file][:stdout]
|
302
|
+
log.puts "```"
|
303
|
+
log.puts
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
next unless parse_errors[file]
|
308
|
+
|
309
|
+
if parse_errors[file][:stdout].strip.length.positive?
|
310
|
+
log.puts "\n#{heading("STANDARD OUTPUT")}"
|
311
|
+
log.puts "```"
|
312
|
+
log.puts parse_errors[file][:stdout]
|
313
|
+
log.puts "```"
|
314
|
+
end
|
315
|
+
|
316
|
+
if parse_errors[file][:stderr].strip.length.positive?
|
317
|
+
log.puts "\n#{heading("ERROR OUTPUT")}"
|
318
|
+
log.puts "```"
|
319
|
+
log.puts parse_errors[file][:stderr]
|
320
|
+
log.puts "```"
|
321
|
+
end
|
322
|
+
|
323
|
+
next unless parse_errors[file][:ast]
|
324
|
+
|
325
|
+
log.puts "\n#{heading("AST")}"
|
326
|
+
log.puts "```"
|
327
|
+
log.puts parse_errors[file][:ast]
|
328
|
+
log.puts "```"
|
329
|
+
log.puts
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
puts "\nResults saved to #{output_file}"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
def progress_bar(current, total, width = IO.console.winsize[1] - "[] 100% (#{total}/#{total})".length)
|
340
|
+
progress = current.to_f / total
|
341
|
+
completed_length = (progress * width).to_i
|
342
|
+
completed = "█" * completed_length
|
343
|
+
|
344
|
+
partial_index = ((progress * width) % 1 * 8).to_i
|
345
|
+
partial_chars = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
|
346
|
+
partial = partial_index.zero? ? "" : partial_chars[partial_index]
|
347
|
+
|
348
|
+
remaining = " " * (width - completed_length - (partial.empty? ? 0 : 1))
|
349
|
+
percentage = (progress * 100).to_i
|
350
|
+
|
351
|
+
# Format as [███████▋ ] 42% (123/292)
|
352
|
+
"[#{completed}#{partial}#{remaining}] #{percentage}% (#{current}/#{total})"
|
353
|
+
end
|
354
|
+
|
355
|
+
def heading(text)
|
356
|
+
prefix = "--- #{text.upcase} "
|
357
|
+
|
358
|
+
prefix + ("-" * (80 - prefix.length))
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
data/lib/herb/range.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herb
|
4
|
+
class Range
|
5
|
+
attr_reader :from, :to
|
6
|
+
|
7
|
+
def initialize(from, to)
|
8
|
+
@from = from
|
9
|
+
@to = to
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.[](...)
|
13
|
+
new(...)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from(...)
|
17
|
+
new(...)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_a
|
21
|
+
[from, to]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_json(*args)
|
25
|
+
to_a.to_json(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def tree_inspect
|
29
|
+
to_a.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
%(#<Herb::Range #{to_a}>)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
inspect
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/herb/result.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herb
|
4
|
+
class Result
|
5
|
+
attr_reader :source, :warnings, :errors
|
6
|
+
|
7
|
+
def initialize(source, warnings, errors)
|
8
|
+
@source = source
|
9
|
+
@warnings = warnings
|
10
|
+
@errors = errors
|
11
|
+
end
|
12
|
+
|
13
|
+
def success?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def failed?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|