dead_end 2.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.
data/lib/dead_end.rb CHANGED
@@ -1,4 +1,159 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "dead_end/internals"
3
+ require_relative "dead_end/version"
4
+
5
+ require "tmpdir"
6
+ require "stringio"
7
+ require "pathname"
8
+ require "ripper"
9
+ require "timeout"
10
+
11
+ module DeadEnd
12
+ # Used to indicate a default value that cannot
13
+ # be confused with another input
14
+ DEFAULT_VALUE = Object.new.freeze
15
+
16
+ class Error < StandardError; end
17
+ TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
18
+
19
+ def self.handle_error(e)
20
+ filename = e.message.split(":").first
21
+ $stderr.sync = true
22
+
23
+ call(
24
+ source: Pathname(filename).read,
25
+ filename: filename
26
+ )
27
+
28
+ raise e
29
+ end
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
+
41
+ def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
42
+ search = nil
43
+ filename = nil if filename == DEFAULT_VALUE
44
+ Timeout.timeout(timeout) do
45
+ record_dir ||= ENV["DEBUG"] ? "tmp" : nil
46
+ search = CodeSearch.new(source, record_dir: record_dir).call
47
+ end
48
+
49
+ blocks = search.invalid_blocks
50
+ DisplayInvalidBlocks.new(
51
+ io: io,
52
+ blocks: blocks,
53
+ filename: filename,
54
+ terminal: terminal,
55
+ code_lines: search.code_lines
56
+ ).call
57
+ rescue Timeout::Error => e
58
+ io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
59
+ io.puts e.backtrace.first(3).join($/)
60
+ end
61
+
62
+ # Used for counting spaces
63
+ module SpaceCount
64
+ def self.indent(string)
65
+ string.split(/\S/).first&.length || 0
66
+ end
67
+ end
68
+
69
+ # This will tell you if the `code_lines` would be valid
70
+ # if you removed the `without_lines`. In short it's a
71
+ # way to detect if we've found the lines with syntax errors
72
+ # in our document yet.
73
+ #
74
+ # code_lines = [
75
+ # CodeLine.new(line: "def foo\n", index: 0)
76
+ # CodeLine.new(line: " def bar\n", index: 1)
77
+ # CodeLine.new(line: "end\n", index: 2)
78
+ # ]
79
+ #
80
+ # DeadEnd.valid_without?(
81
+ # without_lines: code_lines[1],
82
+ # code_lines: code_lines
83
+ # ) # => true
84
+ #
85
+ # DeadEnd.valid?(code_lines) # => false
86
+ def self.valid_without?(without_lines:, code_lines:)
87
+ lines = code_lines - Array(without_lines).flatten
88
+
89
+ if lines.empty?
90
+ true
91
+ else
92
+ valid?(lines)
93
+ end
94
+ end
95
+
96
+ def self.invalid?(source)
97
+ source = source.join if source.is_a?(Array)
98
+ source = source.to_s
99
+
100
+ Ripper.new(source).tap(&:parse).error?
101
+ end
102
+
103
+ # Returns truthy if a given input source is valid syntax
104
+ #
105
+ # DeadEnd.valid?(<<~EOM) # => true
106
+ # def foo
107
+ # end
108
+ # EOM
109
+ #
110
+ # DeadEnd.valid?(<<~EOM) # => false
111
+ # def foo
112
+ # def bar # Syntax error here
113
+ # end
114
+ # EOM
115
+ #
116
+ # You can also pass in an array of lines and they'll be
117
+ # joined before evaluating
118
+ #
119
+ # DeadEnd.valid?(
120
+ # [
121
+ # "def foo\n",
122
+ # "end\n"
123
+ # ]
124
+ # ) # => true
125
+ #
126
+ # DeadEnd.valid?(
127
+ # [
128
+ # "def foo\n",
129
+ # " def bar\n", # Syntax error here
130
+ # "end\n"
131
+ # ]
132
+ # ) # => false
133
+ #
134
+ # As an FYI the CodeLine class instances respond to `to_s`
135
+ # so passing a CodeLine in as an object or as an array
136
+ # will convert it to it's code representation.
137
+ def self.valid?(source)
138
+ !invalid?(source)
139
+ end
140
+ end
141
+
142
+ require_relative "dead_end/code_line"
143
+ require_relative "dead_end/code_block"
144
+ require_relative "dead_end/code_search"
145
+ require_relative "dead_end/code_frontier"
146
+ require_relative "dead_end/clean_document"
147
+
148
+ require_relative "dead_end/lex_all"
149
+ require_relative "dead_end/block_expand"
150
+ require_relative "dead_end/insertion_sort"
151
+ require_relative "dead_end/around_block_scan"
152
+ require_relative "dead_end/ripper_errors"
153
+ require_relative "dead_end/display_invalid_blocks"
154
+ require_relative "dead_end/parse_blocks_from_indent_line"
155
+
156
+ require_relative "dead_end/explain_syntax"
157
+
4
158
  require_relative "dead_end/auto"
159
+ require_relative "dead_end/cli"
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: 2.0.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-10-11 00:00:00.000000000 Z
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
@@ -41,19 +41,21 @@ files:
41
41
  - lib/dead_end/block_expand.rb
42
42
  - lib/dead_end/capture_code_context.rb
43
43
  - lib/dead_end/clean_document.rb
44
+ - lib/dead_end/cli.rb
44
45
  - lib/dead_end/code_block.rb
45
46
  - lib/dead_end/code_frontier.rb
46
47
  - lib/dead_end/code_line.rb
47
48
  - lib/dead_end/code_search.rb
48
49
  - lib/dead_end/display_code_with_line_numbers.rb
49
50
  - lib/dead_end/display_invalid_blocks.rb
50
- - lib/dead_end/fyi.rb
51
- - lib/dead_end/internals.rb
51
+ - lib/dead_end/explain_syntax.rb
52
+ - lib/dead_end/insertion_sort.rb
53
+ - lib/dead_end/left_right_lex_count.rb
52
54
  - lib/dead_end/lex_all.rb
53
55
  - lib/dead_end/lex_value.rb
54
56
  - lib/dead_end/parse_blocks_from_indent_line.rb
57
+ - lib/dead_end/ripper_errors.rb
55
58
  - lib/dead_end/version.rb
56
- - lib/dead_end/who_dis_syntax_error.rb
57
59
  homepage: https://github.com/zombocom/dead_end.git
58
60
  licenses:
59
61
  - MIT
data/lib/dead_end/fyi.rb DELETED
@@ -1,8 +0,0 @@
1
- require_relative "../dead_end/internals"
2
-
3
- require_relative "auto"
4
-
5
- DeadEnd.send(:remove_const, :SEARCH_SOURCE_ON_ERROR_DEFAULT)
6
- DeadEnd::SEARCH_SOURCE_ON_ERROR_DEFAULT = false
7
-
8
- warn "DEPRECATED: calling `require 'dead_end/fyi'` is deprecated, `require 'dead_end'` instead"
@@ -1,154 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This is the top level file, but is moved to `internals`
4
- # so the top level require can instead enable the "automatic" behavior
5
-
6
- require_relative "version"
7
-
8
- require "tmpdir"
9
- require "stringio"
10
- require "pathname"
11
- require "ripper"
12
- require "timeout"
13
-
14
- module DeadEnd
15
- class Error < StandardError; end
16
- SEARCH_SOURCE_ON_ERROR_DEFAULT = true
17
- TIMEOUT_DEFAULT = ENV.fetch("DEAD_END_TIMEOUT", 1).to_i
18
-
19
- def self.handle_error(e, search_source_on_error: SEARCH_SOURCE_ON_ERROR_DEFAULT)
20
- raise e unless e.message.include?("end-of-input")
21
-
22
- filename = e.message.split(":").first
23
-
24
- $stderr.sync = true
25
- warn "Run `$ dead_end #{filename}` for more options\n"
26
-
27
- if search_source_on_error
28
- call(
29
- source: Pathname(filename).read,
30
- filename: filename,
31
- terminal: true
32
- )
33
- end
34
-
35
- raise e
36
- end
37
-
38
- def self.call(source:, filename:, terminal: false, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
39
- search = nil
40
- Timeout.timeout(timeout) do
41
- record_dir ||= ENV["DEBUG"] ? "tmp" : nil
42
- search = CodeSearch.new(source, record_dir: record_dir).call
43
- end
44
-
45
- blocks = search.invalid_blocks
46
- DisplayInvalidBlocks.new(
47
- blocks: blocks,
48
- filename: filename,
49
- terminal: terminal,
50
- code_lines: search.code_lines,
51
- invalid_obj: invalid_type(source),
52
- io: io
53
- ).call
54
- rescue Timeout::Error => e
55
- io.puts "Search timed out DEAD_END_TIMEOUT=#{timeout}, run with DEBUG=1 for more info"
56
- io.puts e.backtrace.first(3).join($/)
57
- end
58
-
59
- # Used for counting spaces
60
- module SpaceCount
61
- def self.indent(string)
62
- string.split(/\S/).first&.length || 0
63
- end
64
- end
65
-
66
- # This will tell you if the `code_lines` would be valid
67
- # if you removed the `without_lines`. In short it's a
68
- # way to detect if we've found the lines with syntax errors
69
- # in our document yet.
70
- #
71
- # code_lines = [
72
- # CodeLine.new(line: "def foo\n", index: 0)
73
- # CodeLine.new(line: " def bar\n", index: 1)
74
- # CodeLine.new(line: "end\n", index: 2)
75
- # ]
76
- #
77
- # DeadEnd.valid_without?(
78
- # without_lines: code_lines[1],
79
- # code_lines: code_lines
80
- # ) # => true
81
- #
82
- # DeadEnd.valid?(code_lines) # => false
83
- def self.valid_without?(without_lines:, code_lines:)
84
- lines = code_lines - Array(without_lines).flatten
85
-
86
- if lines.empty?
87
- true
88
- else
89
- valid?(lines)
90
- end
91
- end
92
-
93
- def self.invalid?(source)
94
- source = source.join if source.is_a?(Array)
95
- source = source.to_s
96
-
97
- Ripper.new(source).tap(&:parse).error?
98
- end
99
-
100
- # Returns truthy if a given input source is valid syntax
101
- #
102
- # DeadEnd.valid?(<<~EOM) # => true
103
- # def foo
104
- # end
105
- # EOM
106
- #
107
- # DeadEnd.valid?(<<~EOM) # => false
108
- # def foo
109
- # def bar # Syntax error here
110
- # end
111
- # EOM
112
- #
113
- # You can also pass in an array of lines and they'll be
114
- # joined before evaluating
115
- #
116
- # DeadEnd.valid?(
117
- # [
118
- # "def foo\n",
119
- # "end\n"
120
- # ]
121
- # ) # => true
122
- #
123
- # DeadEnd.valid?(
124
- # [
125
- # "def foo\n",
126
- # " def bar\n", # Syntax error here
127
- # "end\n"
128
- # ]
129
- # ) # => false
130
- #
131
- # As an FYI the CodeLine class instances respond to `to_s`
132
- # so passing a CodeLine in as an object or as an array
133
- # will convert it to it's code representation.
134
- def self.valid?(source)
135
- !invalid?(source)
136
- end
137
-
138
- def self.invalid_type(source)
139
- WhoDisSyntaxError.new(source).call
140
- end
141
- end
142
-
143
- require_relative "code_line"
144
- require_relative "code_block"
145
- require_relative "code_search"
146
- require_relative "code_frontier"
147
- require_relative "clean_document"
148
-
149
- require_relative "lex_all"
150
- require_relative "block_expand"
151
- require_relative "around_block_scan"
152
- require_relative "who_dis_syntax_error"
153
- require_relative "display_invalid_blocks"
154
- require_relative "parse_blocks_from_indent_line"
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeadEnd
4
- # Determines what type of syntax error that is in the source
5
- #
6
- # Example:
7
- #
8
- # puts WhoDisSyntaxError.new("def foo;").call.error_symbol
9
- # # => :missing_end
10
- class WhoDisSyntaxError < Ripper
11
- class Null
12
- def error_symbol
13
- :missing_end
14
- end
15
-
16
- def unmatched_symbol
17
- :end
18
- end
19
- end
20
- attr_reader :error, :run_once
21
-
22
- # Return options:
23
- # - :missing_end
24
- # - :unmatched_syntax
25
- # - :unknown
26
- def error_symbol
27
- call
28
- @error_symbol
29
- end
30
-
31
- # Return options:
32
- # - :end
33
- # - :|
34
- # - :}
35
- # - :unknown
36
- def unmatched_symbol
37
- call
38
- @unmatched_symbol
39
- end
40
-
41
- def call
42
- @run_once ||= begin
43
- parse
44
- true
45
- end
46
- self
47
- end
48
-
49
- def on_parse_error(msg)
50
- return if @error_symbol && @unmatched_symbol
51
-
52
- @error = msg
53
- @unmatched_symbol = :unknown
54
-
55
- case @error
56
- when /unexpected end-of-input/
57
- @error_symbol = :missing_end
58
- when /expecting end-of-input/
59
- @unmatched_symbol = :end
60
- @error_symbol = :unmatched_syntax
61
- when /unexpected .* expecting '(?<unmatched_symbol>.*)'/
62
- @unmatched_symbol = $1.to_sym if $1
63
- @error_symbol = :unmatched_syntax
64
- when /unexpected `end'/, # Ruby 2.7 and 3.0
65
- /unexpected end/, # Ruby 2.6
66
- /unexpected keyword_end/i # Ruby 2.5
67
-
68
- @error_symbol = :unmatched_syntax
69
- else
70
- @error_symbol = :unknown
71
- end
72
- end
73
- end
74
- end