dead_end 3.0.0 → 3.0.1
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/CHANGELOG.md +4 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +4 -2
- data/README.md +21 -1
- data/lib/dead_end/clean_document.rb +19 -28
- data/lib/dead_end/cli.rb +16 -5
- data/lib/dead_end/code_frontier.rb +11 -11
- data/lib/dead_end/code_search.rb +1 -2
- data/lib/dead_end/insertion_sort.rb +46 -0
- data/lib/dead_end/version.rb +1 -1
- data/lib/dead_end.rb +11 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c232ccfce002bd3a79d6a5508ea46b27684a9a0ae2bb5d6f843d49e5ba59c0ac
|
4
|
+
data.tar.gz: 745847314c5c3ff09cb85b525a5d583f89c1c597e1f5b785ed54e01299bb72c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c49f5b58a7a2f222606a01efc3256a2a0eceade66ff6e46f7c15172cf972a48aa4b6d55212eb714ad69dde1b6d80e91cf8589a9b7026ad2b3e9baafabb8c2e6c
|
7
|
+
data.tar.gz: f70fc9947a92d8de2d8f05e08ab803a791c9fc396f03a0bed812b0a88bdb74af33658b23ebdf148adbc716cc41870bee8fabe57c3ae62bd6c9e9b063717f0bbc
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## HEAD (unreleased)
|
2
2
|
|
3
|
+
## 3.0.1
|
4
|
+
|
5
|
+
- Fix CLI parsing when flags come before filename (https://github.com/zombocom/dead_end/pull/102)
|
6
|
+
|
3
7
|
## 3.0.0
|
4
8
|
|
5
9
|
- [Breaking] Remove previously deprecated `require "dead_end/fyi"` interface (https://github.com/zombocom/dead_end/pull/94)
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dead_end (3.0.
|
4
|
+
dead_end (3.0.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -42,6 +42,7 @@ GEM
|
|
42
42
|
rubocop-performance (1.11.5)
|
43
43
|
rubocop (>= 1.7.0, < 2.0)
|
44
44
|
rubocop-ast (>= 0.4.0)
|
45
|
+
ruby-prof (1.4.3)
|
45
46
|
ruby-progressbar (1.11.0)
|
46
47
|
stackprof (0.2.16)
|
47
48
|
standard (1.3.0)
|
@@ -56,8 +57,9 @@ DEPENDENCIES
|
|
56
57
|
dead_end!
|
57
58
|
rake (~> 12.0)
|
58
59
|
rspec (~> 3.0)
|
60
|
+
ruby-prof
|
59
61
|
stackprof
|
60
62
|
standard
|
61
63
|
|
62
64
|
BUNDLED WITH
|
63
|
-
2.2.
|
65
|
+
2.2.30
|
data/README.md
CHANGED
@@ -170,7 +170,27 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
170
170
|
You can see changes to output against a variety of invalid code by running specs and using the `DEBUG_DISPLAY=1` environment variable. For example:
|
171
171
|
|
172
172
|
```
|
173
|
-
$ DEBUG_DISPLAY=1
|
173
|
+
$ DEBUG_DISPLAY=1 bundle exec rspec spec/ --format=failures
|
174
|
+
```
|
175
|
+
|
176
|
+
### Run profiler
|
177
|
+
|
178
|
+
You can output profiler data to the `tmp` directory by running:
|
179
|
+
|
180
|
+
```
|
181
|
+
$ DEBUG_PERF=1 bundle exec rspec spec/integration/dead_end_spec.rb
|
182
|
+
```
|
183
|
+
|
184
|
+
Some outputs are in text format, some are html, the raw marshaled data is available in `raw.rb.marshal`. See https://ruby-prof.github.io/#reports for more info. One interesting one, is the "kcachegrind" interface. To view this on mac:
|
185
|
+
|
186
|
+
```
|
187
|
+
$ brew install qcachegrind
|
188
|
+
```
|
189
|
+
|
190
|
+
Open:
|
191
|
+
|
192
|
+
```
|
193
|
+
$ qcachegrind tmp/last/profile.callgrind.out.<numbers>
|
174
194
|
```
|
175
195
|
|
176
196
|
## Contributing
|
@@ -85,17 +85,16 @@ module DeadEnd
|
|
85
85
|
#
|
86
86
|
class CleanDocument
|
87
87
|
def initialize(source:)
|
88
|
-
@source = source
|
88
|
+
@source = clean_sweep(source: source)
|
89
89
|
@document = CodeLine.from_source(@source)
|
90
90
|
end
|
91
91
|
|
92
92
|
# Call all of the document "cleaners"
|
93
93
|
# and return self
|
94
94
|
def call
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
.join_heredoc!
|
95
|
+
join_trailing_slash!
|
96
|
+
join_consecutive!
|
97
|
+
join_heredoc!
|
99
98
|
|
100
99
|
self
|
101
100
|
end
|
@@ -122,17 +121,15 @@ module DeadEnd
|
|
122
121
|
# puts "world"
|
123
122
|
# EOM
|
124
123
|
#
|
125
|
-
# lines = CleanDocument.new(source: source).
|
124
|
+
# lines = CleanDocument.new(source: source).lines
|
126
125
|
# expect(lines[0].to_s).to eq("\n")
|
127
126
|
# expect(lines[1].to_s).to eq("puts "hello")
|
128
127
|
# expect(lines[2].to_s).to eq("\n")
|
129
128
|
# expect(lines[3].to_s).to eq("puts "world")
|
130
129
|
#
|
131
|
-
#
|
132
|
-
# If you run this after any of the "join" commands, they
|
133
|
-
# will be un-joined.
|
130
|
+
# Important: This must be done before lexing.
|
134
131
|
#
|
135
|
-
# After this change is made, we
|
132
|
+
# After this change is made, we lex the document because
|
136
133
|
# removing comments can change how the doc is parsed.
|
137
134
|
#
|
138
135
|
# For example:
|
@@ -142,7 +139,9 @@ module DeadEnd
|
|
142
139
|
# # comment
|
143
140
|
# where(name: 'schneems')
|
144
141
|
# EOM
|
145
|
-
# expect(
|
142
|
+
# expect(
|
143
|
+
# values.count {|v| v.type == :on_ignored_nl}
|
144
|
+
# ).to eq(1)
|
146
145
|
#
|
147
146
|
# After the comment is removed:
|
148
147
|
#
|
@@ -151,26 +150,18 @@ module DeadEnd
|
|
151
150
|
#
|
152
151
|
# where(name: 'schneems')
|
153
152
|
# EOM
|
154
|
-
# expect(
|
153
|
+
# expect(
|
154
|
+
# values.count {|v| v.type == :on_ignored_nl}
|
155
|
+
# ).to eq(2)
|
155
156
|
#
|
156
|
-
def clean_sweep
|
157
|
-
source
|
158
|
-
#
|
159
|
-
|
160
|
-
|
157
|
+
def clean_sweep(source:)
|
158
|
+
source.lines.map do |line|
|
159
|
+
if line.match?(/^\s*(#[^{].*)?$/) # https://rubular.com/r/LLE10D8HKMkJvs
|
160
|
+
$/
|
161
|
+
else
|
162
|
+
line
|
161
163
|
end
|
162
|
-
|
163
|
-
# Remove comments
|
164
|
-
if code_line.lex.detect { |lex| lex.type != :on_sp }&.type == :on_comment
|
165
|
-
next CodeLine.new(line: "\n", index: code_line.index, lex: [])
|
166
|
-
end
|
167
|
-
|
168
|
-
code_line
|
169
164
|
end.join
|
170
|
-
|
171
|
-
@source = source
|
172
|
-
@document = CodeLine.from_source(source)
|
173
|
-
self
|
174
165
|
end
|
175
166
|
|
176
167
|
# Smushes all heredoc lines into one line
|
data/lib/dead_end/cli.rb
CHANGED
@@ -12,7 +12,7 @@ module DeadEnd
|
|
12
12
|
# Cli.new(argv: ["<path/to/file>.rb", "--terminal"]).call
|
13
13
|
#
|
14
14
|
class Cli
|
15
|
-
attr_accessor :options
|
15
|
+
attr_accessor :options
|
16
16
|
|
17
17
|
# ARGV is Everything passed to the executable, does not include executable name
|
18
18
|
#
|
@@ -26,22 +26,33 @@ module DeadEnd
|
|
26
26
|
|
27
27
|
@io = io
|
28
28
|
@argv = argv
|
29
|
-
@file_name = argv[0]
|
30
29
|
@exit_obj = exit_obj
|
31
30
|
end
|
32
31
|
|
33
32
|
def call
|
34
|
-
if
|
33
|
+
if @argv.empty?
|
35
34
|
# Display help if raw command
|
36
35
|
parser.parse! %w[--help]
|
36
|
+
return
|
37
37
|
else
|
38
|
+
# Mutates @argv
|
38
39
|
parse
|
40
|
+
return if options[:exit]
|
39
41
|
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
+
file_name = @argv.first
|
44
|
+
if file_name.nil?
|
45
|
+
@io.puts "No file given"
|
46
|
+
@exit_obj.exit(1)
|
47
|
+
return
|
48
|
+
end
|
43
49
|
|
44
50
|
file = Pathname(file_name)
|
51
|
+
if !file.exist?
|
52
|
+
@io.puts "file not found: #{file.expand_path} "
|
53
|
+
@exit_obj.exit(1)
|
54
|
+
return
|
55
|
+
end
|
45
56
|
|
46
57
|
@io.puts "Record dir: #{options[:record_dir]}" if options[:record_dir]
|
47
58
|
|
@@ -52,14 +52,14 @@ module DeadEnd
|
|
52
52
|
class CodeFrontier
|
53
53
|
def initialize(code_lines:)
|
54
54
|
@code_lines = code_lines
|
55
|
-
@frontier =
|
55
|
+
@frontier = InsertionSort.new
|
56
56
|
@unvisited_lines = @code_lines.sort_by(&:indent_index)
|
57
57
|
@has_run = false
|
58
58
|
@check_next = true
|
59
59
|
end
|
60
60
|
|
61
61
|
def count
|
62
|
-
@frontier.
|
62
|
+
@frontier.to_a.length
|
63
63
|
end
|
64
64
|
|
65
65
|
# Performance optimization
|
@@ -89,7 +89,7 @@ module DeadEnd
|
|
89
89
|
def holds_all_syntax_errors?(block_array = @frontier, can_cache: true)
|
90
90
|
return false if can_cache && can_skip_check?
|
91
91
|
|
92
|
-
without_lines = block_array.flat_map do |block|
|
92
|
+
without_lines = block_array.to_a.flat_map do |block|
|
93
93
|
block.lines
|
94
94
|
end
|
95
95
|
|
@@ -101,7 +101,7 @@ module DeadEnd
|
|
101
101
|
|
102
102
|
# Returns a code block with the largest indentation possible
|
103
103
|
def pop
|
104
|
-
@frontier.pop
|
104
|
+
@frontier.to_a.pop
|
105
105
|
end
|
106
106
|
|
107
107
|
def next_indent_line
|
@@ -109,15 +109,15 @@ module DeadEnd
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def expand?
|
112
|
-
return false if @frontier.empty?
|
113
|
-
return true if @unvisited_lines.empty?
|
112
|
+
return false if @frontier.to_a.empty?
|
113
|
+
return true if @unvisited_lines.to_a.empty?
|
114
114
|
|
115
|
-
frontier_indent = @frontier.last.current_indent
|
115
|
+
frontier_indent = @frontier.to_a.last.current_indent
|
116
116
|
unvisited_indent = next_indent_line.indent
|
117
117
|
|
118
118
|
if ENV["DEBUG"]
|
119
119
|
puts "```"
|
120
|
-
puts @frontier.last.to_s
|
120
|
+
puts @frontier.to_a.last.to_s
|
121
121
|
puts "```"
|
122
122
|
puts " @frontier indent: #{frontier_indent}"
|
123
123
|
puts " @unvisited indent: #{unvisited_indent}"
|
@@ -141,13 +141,13 @@ module DeadEnd
|
|
141
141
|
register_indent_block(block)
|
142
142
|
|
143
143
|
# Make sure we don't double expand, if a code block fully engulfs another code block, keep the bigger one
|
144
|
-
@frontier.reject! { |b|
|
144
|
+
@frontier.to_a.reject! { |b|
|
145
145
|
b.starts_at >= block.starts_at && b.ends_at <= block.ends_at
|
146
146
|
}
|
147
147
|
|
148
148
|
@check_next = true if block.invalid?
|
149
149
|
@frontier << block
|
150
|
-
@frontier.sort!
|
150
|
+
# @frontier.sort!
|
151
151
|
|
152
152
|
self
|
153
153
|
end
|
@@ -167,7 +167,7 @@ module DeadEnd
|
|
167
167
|
# Given that we know our syntax error exists somewhere in our frontier, we want to find
|
168
168
|
# the smallest possible set of blocks that contain all the syntax errors
|
169
169
|
def detect_invalid_blocks
|
170
|
-
self.class.combination(@frontier.select(&:invalid?)).detect do |block_array|
|
170
|
+
self.class.combination(@frontier.to_a.select(&:invalid?)).detect do |block_array|
|
171
171
|
holds_all_syntax_errors?(block_array, can_cache: false)
|
172
172
|
end || []
|
173
173
|
end
|
data/lib/dead_end/code_search.rb
CHANGED
@@ -43,8 +43,7 @@ module DeadEnd
|
|
43
43
|
|
44
44
|
def initialize(source, record_dir: ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil)
|
45
45
|
if record_dir
|
46
|
-
@
|
47
|
-
@record_dir = Pathname(record_dir).join(@time).tap { |p| p.mkpath }
|
46
|
+
@record_dir = DeadEnd.record_dir(record_dir)
|
48
47
|
@write_count = 0
|
49
48
|
end
|
50
49
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeadEnd
|
4
|
+
# Sort elements on insert
|
5
|
+
#
|
6
|
+
# Instead of constantly calling `sort!`, put
|
7
|
+
# the element where it belongs the first time
|
8
|
+
# around
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# sorted = InsertionSort.new
|
13
|
+
# sorted << 33
|
14
|
+
# sorted << 44
|
15
|
+
# sorted << 1
|
16
|
+
# puts sorted.to_a
|
17
|
+
# # => [1, 44, 33]
|
18
|
+
#
|
19
|
+
class InsertionSort
|
20
|
+
def initialize
|
21
|
+
@array = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(value)
|
25
|
+
insert_in = @array.length
|
26
|
+
@array.each.with_index do |existing, index|
|
27
|
+
case value <=> existing
|
28
|
+
when -1
|
29
|
+
insert_in = index
|
30
|
+
break
|
31
|
+
when 0
|
32
|
+
insert_in = index
|
33
|
+
break
|
34
|
+
when 1
|
35
|
+
# Keep going
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@array.insert(insert_in, value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_a
|
43
|
+
@array
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/dead_end/version.rb
CHANGED
data/lib/dead_end.rb
CHANGED
@@ -28,6 +28,16 @@ module DeadEnd
|
|
28
28
|
raise e
|
29
29
|
end
|
30
30
|
|
31
|
+
def self.record_dir(dir)
|
32
|
+
time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
|
33
|
+
dir = Pathname(dir)
|
34
|
+
symlink = dir.join("last").tap { |path| path.delete if path.exist? }
|
35
|
+
dir.join(time).tap { |path|
|
36
|
+
path.mkpath
|
37
|
+
FileUtils.symlink(path.basename, symlink)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
31
41
|
def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
|
32
42
|
search = nil
|
33
43
|
filename = nil if filename == DEFAULT_VALUE
|
@@ -137,6 +147,7 @@ require_relative "dead_end/clean_document"
|
|
137
147
|
|
138
148
|
require_relative "dead_end/lex_all"
|
139
149
|
require_relative "dead_end/block_expand"
|
150
|
+
require_relative "dead_end/insertion_sort"
|
140
151
|
require_relative "dead_end/around_block_scan"
|
141
152
|
require_relative "dead_end/ripper_errors"
|
142
153
|
require_relative "dead_end/display_invalid_blocks"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dead_end
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-11-
|
11
|
+
date: 2021-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: When you get an "unexpected end" in your syntax this gem helps you find
|
14
14
|
it
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- lib/dead_end/display_code_with_line_numbers.rb
|
50
50
|
- lib/dead_end/display_invalid_blocks.rb
|
51
51
|
- lib/dead_end/explain_syntax.rb
|
52
|
+
- lib/dead_end/insertion_sort.rb
|
52
53
|
- lib/dead_end/left_right_lex_count.rb
|
53
54
|
- lib/dead_end/lex_all.rb
|
54
55
|
- lib/dead_end/lex_value.rb
|