xmp2assert 2 → 3

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
  SHA1:
3
- metadata.gz: 499b5b5dbee30515d03a50c57cebc85905904a2d
4
- data.tar.gz: 90b1f2cfb97885b646aec7568750020ff5f6399d
3
+ metadata.gz: 37e895dead7be33e2d883950ffc4472e4ed4c49b
4
+ data.tar.gz: 81805143514b795409f5258853471d67794f698a
5
5
  SHA512:
6
- metadata.gz: 4fad80ca13043b9d11e1048fb023c3c5327260526ba8d7fa9578b11d2d883799b83a8e0d15208681260998547f14934a5493dc35f330ff700b4c2f9cbfb78df5
7
- data.tar.gz: 335f6ec8854569273212213b4635b6aba37ef37b037371ae52dc90892dbf70a09ed822e8b6635da6f7ab3ca4fb7db3b4bbd355011ed9be11fa1850b92a43b18d
6
+ metadata.gz: 974409606e9bc0812d5d01ba91329b51f2a095ced6fc5cf64b765685a4d978bd260e4279274a0402b7ca6480777df07d095cd883b53bd269d70e2333464e5897
7
+ data.tar.gz: 32860b61ca5ebf164f81e4209b8c66bc8ea243bd48793d75eb0f8f6fc6ff87b09477cb76810a4f1b15f19efc7827ad78fb639ffbe2db8854464c8bd01f6a1ccf
data/.rubocop.yml CHANGED
@@ -37,12 +37,18 @@ Lint/EmptyWhen:
37
37
  Rails:
38
38
  Enabled: false
39
39
 
40
+ Style/Alias:
41
+ Enabled: false
42
+
40
43
  Style/AlignHash:
41
44
  Enabled: false
42
45
 
43
46
  Style/AlignParameters:
44
47
  Enabled: false
45
48
 
49
+ Style/AndOr:
50
+ Enabled: false
51
+
46
52
  Style/BracesAroundHashParameters:
47
53
  Enabled: false
48
54
 
@@ -52,6 +58,9 @@ Style/CaseEquality:
52
58
  Style/CaseIndentation:
53
59
  Enabled: false
54
60
 
61
+ Style/ClassAndModuleCamelCase:
62
+ Enabled: false
63
+
55
64
  Style/ClassAndModuleChildren:
56
65
  Enabled: false
57
66
 
@@ -82,6 +91,9 @@ Style/ExtraSpacing:
82
91
  Style/FormatString:
83
92
  Enabled: false
84
93
 
94
+ Style/GuardClause:
95
+ Enabled: false
96
+
85
97
  Style/IndentHash:
86
98
  Enabled: false
87
99
 
@@ -94,24 +106,45 @@ Style/IndentationWidth:
94
106
  Style/MethodDefParentheses:
95
107
  Enabled: false
96
108
 
109
+ Style/MethodName:
110
+ Enabled: false
111
+
97
112
  Style/MultilineIfThen:
98
113
  Enabled: false
99
114
 
115
+ Style/MultilineMethodCallIndentation:
116
+ Enabled: false
117
+
100
118
  Style/ParallelAssignment:
101
119
  Enabled: false
102
120
 
103
121
  Style/PercentLiteralDelimiters:
104
122
  Enabled: false
105
123
 
124
+ Style/PerlBackrefs:
125
+ Enabled: false
126
+
106
127
  Style/RedundantReturn:
107
128
  Enabled: false
108
129
 
130
+ Style/RedundantSelf:
131
+ Enabled: false
132
+
133
+ Style/RegexpLiteral:
134
+ Enabled: false
135
+
136
+ Style/Semicolon:
137
+ Enabled: false
138
+
109
139
  Style/SpaceAroundOperators:
110
140
  Enabled: false
111
141
 
112
142
  Style/SpaceInsideBrackets:
113
143
  Enabled: false
114
144
 
145
+ Style/SpecialGlobalVars:
146
+ Enabled: false
147
+
115
148
  Style/StringLiterals:
116
149
  Enabled: false
117
150
 
@@ -127,40 +160,45 @@ Style/TrailingCommaInLiteral:
127
160
  Style/TrailingUnderscoreVariable:
128
161
  Enabled: false
129
162
 
163
+ Style/VariableInterpolation:
164
+ Enabled: false
165
+
130
166
  Style/VariableName:
131
167
  Enabled: false
132
168
 
133
169
  Style/WordArray:
134
170
  Enabled: false
135
171
 
136
- Lint/UselessAssignment:
137
- Exclude:
138
- - 'test/**/*'
139
- - '*.gemspec'
172
+ Style/WhileUntilDo:
173
+ Enabled: false
140
174
 
141
- Metrics/AbcSize:
175
+ Style/WhileUntilModifier:
176
+ Enabled: false
177
+
178
+ Lint/UselessAccessModifier:
142
179
  Exclude:
143
- - 'test/**/*'
144
- - '*.gemspec'
180
+ # I think detecting this file is a rubocop bug.
181
+ - 'lib/xmp2assert/validator.rb'
145
182
 
146
- Metrics/BlockLength:
183
+ Lint/UselessAssignment:
147
184
  Exclude:
148
185
  - 'test/**/*'
149
186
  - '*.gemspec'
150
187
 
151
- Metrics/ClassLength:
188
+ Metrics/AbcSize:
152
189
  Exclude:
153
190
  - 'test/**/*'
154
191
  - '*.gemspec'
155
192
 
156
- Style/ClassAndModuleCamelCase:
193
+ Metrics/ClassLength:
157
194
  Exclude:
158
195
  - 'test/**/*'
159
196
  - '*.gemspec'
160
197
 
161
- Style/Semicolon:
162
- Exclude:
163
- - 'lib/*/version.rb' # for YARD
198
+ Metrics/BlockLength:
199
+ ExcludedMethods:
200
+ - new
201
+ - sub_test_case
164
202
 
165
203
  Metrics/LineLength:
166
204
  AllowURI: true
@@ -170,3 +208,14 @@ Metrics/MethodLength:
170
208
  CountComments: false
171
209
  Enabled: true
172
210
  Max: 30
211
+
212
+ Style/EmptyElse:
213
+ Enabled: true
214
+ EnforcedStyle: empty
215
+
216
+ Style/SpaceInsideBlockBraces:
217
+ Enabled: true
218
+ EnforcedStyle: space
219
+ SpaceBeforeBlockParameters: false
220
+ Exclude:
221
+ - '*.gemspec'
data/exe/xmpcheck.rb CHANGED
@@ -29,6 +29,7 @@ require 'test/unit/autorunner'
29
29
  require 'pathname'
30
30
  require 'xmp2assert'
31
31
 
32
+ # This class introduces the test auto-run.
32
33
  class TC_Main < Test::Unit::TestCase
33
34
  include XMP2Assert::Assertions
34
35
 
@@ -44,12 +45,8 @@ class TC_Main < Test::Unit::TestCase
44
45
  else
45
46
  test f.__FILE__ do
46
47
  t, o = XMP2Assert::Converter.convert f
47
- if k.include? :'=>' then
48
- t.eval binding
49
- end
50
- if k.include? :'>>' then
51
- assert_capture2e o, f
52
- end
48
+ t.eval binding if k.include? :'=>'
49
+ assert_capture2e o, f if k.include? :'>>'
53
50
  end
54
51
  end
55
52
  end
@@ -30,6 +30,8 @@ require 'erb'
30
30
  require 'test/unit'
31
31
  require 'test/unit/assertions'
32
32
  require 'test/unit/assertion-failed-error'
33
+ require_relative 'namespace'
34
+ require_relative 'quasifile'
33
35
  require_relative 'xmp2rexp'
34
36
 
35
37
  # Helper module that implements assertions.
@@ -52,7 +54,8 @@ module XMP2Assert::Assertions
52
54
  # As the method name implies the assertion is against both stdin and stderr
53
55
  # at once. This is for convenience.
54
56
  def assert_capture2e expected, script, message = nil, rubyopts: nil, **opts
55
- actual, _ = ruby script, rubyopts: rubyopts, **opts
57
+ qscript = XMP2Assert::Quasifile.new script
58
+ actual, _ = ruby qscript, rubyopts: rubyopts, **opts
56
59
  actual.force_encoding expected.encoding
57
60
  return assert_xmp_raw expected, actual, message
58
61
  end
@@ -74,20 +77,20 @@ module XMP2Assert::Assertions
74
77
 
75
78
  # :TODO: is it private?
76
79
  def assert_xmp_raw xmp, actual, message = nil
77
- msg = genmsg xmp, actual, message
78
80
  expected = xmp2rexp xmp
79
81
 
80
82
  raise unless expected.match actual
81
83
  rescue
82
84
  # Regexp#match can raise. That should also be a failure.
83
- ix = Test::Unit::Assertions::AssertionMessage.convert xmp
84
- ia = Test::Unit::Assertions::AssertionMessage.convert actual
85
- ex = Test::Unit::AssertionFailedError.new(msg,
86
- expected: xmp,
87
- actual: actual,
88
- inspected_expected: ix,
89
- inspected_actual: ia,
90
- user_message: message)
85
+ msg = genmsg xmp, actual, message
86
+ ix = Test::Unit::Assertions::AssertionMessage.convert xmp
87
+ ia = Test::Unit::Assertions::AssertionMessage.convert actual
88
+ ex = Test::Unit::AssertionFailedError.new(msg,
89
+ expected: xmp,
90
+ actual: actual,
91
+ inspected_expected: ix,
92
+ inspected_actual: ia,
93
+ user_message: message)
91
94
  raise ex
92
95
  else
93
96
  return self # or...?
@@ -102,7 +105,10 @@ module XMP2Assert::Assertions
102
105
 
103
106
  def genmsg x, y, z = nil
104
107
  diff = Test::Unit::Assertions::AssertionMessage.delayed_diff x, y
105
- if try(x, :encoding) != try(y, :encoding) then
108
+ if try(x, :ascii_only?) && try(y, :ascii_only?) then
109
+ fmt = "<?> expected but was\n<?>.?"
110
+ argv = [x, y, diff]
111
+ elsif try(x, :encoding) != try(y, :encoding) then
106
112
  fmt = "<?>(?) expected but was\n<?>(?).?"
107
113
  argv = [x, x.encoding.name, y, y.encoding.name, diff]
108
114
  else
@@ -24,6 +24,8 @@
24
24
  # SOFTWARE.
25
25
 
26
26
  require 'ripper'
27
+ require_relative 'namespace'
28
+ require_relative 'parser'
27
29
 
28
30
  # Usually, you want to check LOTS of files that may or may not contain xmp
29
31
  # comments at once, maybe from inside of a CI process. That's OK but we want
@@ -37,35 +39,21 @@ require 'ripper'
37
39
  # XMP2Assert::Classifier.classify(f)
38
40
  # end
39
41
  # ```
40
- class XMP2Assert::Classifier < Ripper
41
- private_class_method :new
42
-
43
- # @param qfile [Quasifile] file-ish
44
- # @return [<Symbol>] either empty, :=>, :>>, or both.
45
- # @note syntax error results in empty return value.
46
- def self.classify qfile
47
- case qfile when XMP2Assert::Quasifile then
48
- this = new qfile.read, qfile.__FILE__, qfile.__LINE__
49
- return this.send :parse
50
- else
51
- q = XMP2Assert::Quasifile.new qfile
52
- return classify q
53
- end
54
- end
55
-
56
- private
57
-
58
- def parse
59
- @ret = []
60
- super
61
- return @ret
62
- end
63
-
64
- def on_comment tok
65
- case tok
66
- when /^# =>/ then @ret |= [:'=>']
67
- when /^# >>/ then @ret |= [:'>>']
68
- when /^# ~>/ then @ret |= [:'>>']
69
- end
42
+ module XMP2Assert::Classifier
43
+ # @param (see XMP2Assert::Parser.new)
44
+ # @return [<Symbol>] either empty, :=>, :>>, or both.
45
+ # @note syntax error results in empty return value.
46
+ def self.classify obj, file = nil, line = nil
47
+ parser = XMP2Assert::Parser.new obj, file, line
48
+ rescue SyntaxError
49
+ return []
50
+ else
51
+ return parser \
52
+ .tokens \
53
+ .map(&:to_sym) \
54
+ .sort \
55
+ .uniq \
56
+ .map {|i| case i when :'=>', :'>>' then i else nil end } \
57
+ .compact
70
58
  end
71
59
  end
@@ -25,89 +25,219 @@
25
25
 
26
26
  require 'ripper'
27
27
  require 'uuid'
28
+ require_relative 'namespace'
29
+ require_relative 'parser'
30
+ require_relative 'quasifile'
28
31
 
29
- class XMP2Assert::Converter < Ripper
32
+ # This class converts a Ruby script into assertions
33
+ class XMP2Assert::Converter
30
34
  private_class_method :new
31
35
 
32
- def self.convert qfile
33
- case qfile when XMP2Assert::Quasifile then
34
- this = new qfile.read, qfile.__FILE__, qfile.__LINE__
35
- s, o = this.send :convert
36
- r = XMP2Assert::Quasifile.new s, qfile.__FILE__, qfile.__LINE__
37
- return r, o
38
- else
39
- q = XMP2Assert::Quasifile.new qfile
40
- return convert q
41
- end
36
+ # Detects XMP and make them assertions. For example:
37
+ #
38
+ # ```ruby
39
+ # def foo; 2; end
40
+ # 1 + foo # => 3
41
+ # ```
42
+ #
43
+ # would become
44
+ #
45
+ # ```ruby
46
+ # def foo; 2; end
47
+ # (1 + foo).tap {|i| assert_xmp("3", i ) }
48
+ # ```
49
+ #
50
+ # @param (see #initialize)
51
+ # @return [(Quasifile,String)] tuple of generated file and its expected
52
+ # output.
53
+ def self.convert obj, file = nil, line = nil
54
+ this = new obj, file, line
55
+ return this.send :convert
42
56
  end
43
57
 
44
58
  private
45
59
 
46
- def pos
47
- return [filename, lineno, column]
60
+ # @param (see XMP2Assert::Parser.new)
61
+ def initialize obj, file = nil, line = nil
62
+ @program = XMP2Assert::Parser.new obj, file, line
48
63
  end
49
64
 
50
65
  def convert
51
- @ret = []
52
- @xmps = []
53
- @last_seen_xmp = nil
54
- @outputs = String.new
55
- parse
56
- postprocess
57
- return @ret.join, @outputs
66
+ @tokens = @program.tokens
67
+ outputs = aggregate
68
+ merge_xmp
69
+ render
70
+ ret = XMP2Assert::Quasifile.new @tokens.join, *@program.locations
71
+ return ret, outputs
58
72
  end
59
73
 
60
- def postprocess
61
- @ret.sort!
62
- @ret.map! do |(_, xmp, tok)|
63
- if xmp
64
- n = UUID.create_sha1 tok, Namespace
65
- n = n.to_uri
66
- n.gsub! %r/[:-]/, '_'
67
- sprintf ".tap {|%s| assert_xmp(%s, %s) }\n", n, tok.chomp.dump, n
68
- else
69
- tok
70
- end
71
- end
74
+ def gensym expr
75
+ n = UUID.create_sha1 expr, Namespace
76
+ n = n.to_uri
77
+ n.gsub! %r/[:-]/, '_'
78
+ return n
72
79
  end
73
80
 
74
81
  Namespace = UUID.create_random
75
82
  private_constant :Namespace
76
83
 
77
- def on_comment tok
78
- xmp = false
79
- case tok
80
- when /^\# [~>]> (.+\n)/ then
81
- @outputs << $1
82
- when /^\# => (.+\n)/ then
83
- if @last_seen_xmp
84
- @last_seen_xmp << $1
85
- tok = "#\n"
86
- else
87
- tok = @last_seen_xmp = $1
88
- xmp = true
84
+ def gen_tap xmp
85
+ n = gensym xmp
86
+ x = xmp.chomp.dump
87
+ return sprintf ".tap {|%s| assert_xmp(%s, %s) }", n, x, n
88
+ end
89
+
90
+ def end_of_expr? tok
91
+ case tok.to_sym
92
+ when :sp then return false
93
+ when :comma then return false
94
+ when :semicolon then return false
95
+ when :comment then return false
96
+ when :'=>' then return false
97
+ when :>> then return false
98
+ when :nl then return false
99
+ when :ignored_nl then return false
100
+ when :heredoc_end then return nil # give up. too complicated
101
+ when :kw then
102
+ case tok.to_s
103
+ when 'then' then return false
104
+ when 'end' then return nil # give up. too complicated
105
+ else return true
89
106
  end
107
+ else return true
108
+ end
109
+ end
110
+
111
+ def find_stop xmp
112
+ pos = @tokens.index xmp
113
+ (pos - 1).downto 0 do |i|
114
+ case end_of_expr? @tokens[i]
115
+ when TrueClass then return true, @tokens[i]
116
+ when FalseClass then next
117
+ when NilClass then return nil, @tokens[i + 1]
118
+ end
119
+ end
120
+ # reaching here indicates no stop; fatal.
121
+ @tokens[pos].raise
122
+ end
123
+
124
+ def valid? line
125
+ XMP2Assert::Parser.new line.join
126
+ rescue SyntaxError
127
+ return false
128
+ else
129
+ return true
130
+ end
131
+
132
+ def find_start stop
133
+ line = @program.same_line_as stop
134
+ line.sort!
135
+ line.select! {|i| i.__COLUMN__ <= stop.__COLUMN__ }
136
+ line = line.drop_while {|i| i.to_sym == :sp }
137
+ until valid? line do
138
+ line.shift
139
+ end
140
+ return line
141
+ end
142
+
143
+ def need_paren? list
144
+ start = list.first.to_sym.to_s
145
+ stop = list.last .to_sym.to_s
146
+
147
+ if %r/(.+)_beg$/ =~ start then
148
+ return %r/#{$1}_end$/ !~ stop
149
+ elsif %r/^l(paren|brace|bracket)$/ =~ start then
150
+ return %r/^r#{$1}$/ !~ stop
151
+ else
152
+ return true
153
+ end
154
+ end
155
+
156
+ # 1. take the line that has xmp.
157
+ # 2. if that line is syntactically valid, use it.
158
+ # 3. pop some tokens so that punctuations disappear, e.g. take `1` for
159
+ #
160
+ # ```ruby
161
+ # {
162
+ # one:
163
+ # 1, # => 1
164
+ # }
165
+ # ```
166
+ #
167
+ # 4. shift some tokens so that parens etc. disappear, e.g. take `1` for
168
+ #
169
+ # ```ruby
170
+ # {
171
+ # one: 1, # => 1
172
+ # }
173
+ # ```
174
+ #
175
+ # 5. if above 3 and 4 resulted in deleting all tokens, that means the
176
+ # expression started before that line. Give up and take the whole line to
177
+ # expect it peacefully terminates something. e.g. take `}` for
178
+ #
179
+ # ```ruby
180
+ # {
181
+ # one: 1,
182
+ # } # => {:one=>1}
183
+ # ```
184
+ def rev_lookup_expr xmp
185
+ needs_start, stop = find_stop xmp
186
+ return nil, stop unless needs_start
187
+ list = find_start stop
188
+
189
+ if list.empty? then
190
+ return nil, stop
191
+ elsif need_paren? list then
192
+ return list.first, stop
90
193
  else
91
- @last_seen_xmp = nil
194
+ return nil, stop
92
195
  end
93
- @ret << [pos, xmp, tok]
94
196
  end
95
197
 
96
- def on_sp tok
97
- # no reset @last_seen_xmp
98
- @ret << [pos, false, tok]
198
+ def aggregate
199
+ return @tokens.each_with_object String.new do |tok, r|
200
+ next unless tok.to_sym == :>>
201
+ str = tok.to_s
202
+ r << str
203
+ str.replace "\n"
204
+ end
99
205
  end
100
206
 
101
- def on_scanner_event tok
102
- @last_seen_xmp = nil
103
- @ret << [pos, false, tok]
207
+ def merge_xmp
208
+ xmp = nil
209
+ @tokens.each do |tok|
210
+ case tok.to_sym
211
+ when :sp then next
212
+ when :'=>' then
213
+ str = tok.to_s
214
+ if xmp then
215
+ xmp << str
216
+ str.replace "\n"
217
+ tok.yylex = :nl
218
+ else
219
+ xmp = str
220
+ end
221
+ else
222
+ xmp = nil
223
+ end
224
+ end
104
225
  end
105
226
 
106
- pim = private_instance_methods false
107
- SCANNER_EVENTS.each do |e|
108
- m = :"on_#{e}"
109
- unless pim.include? m then
110
- alias_method m, :on_scanner_event
227
+ def render
228
+ @tokens.each do |tok|
229
+ next unless tok.to_sym == :'=>'
230
+ xmp = tok.to_s
231
+ tap = gen_tap xmp
232
+ xmp.replace "\n"
233
+ x, y = rev_lookup_expr tok
234
+ if x and x != y then
235
+ x.to_s.sub! %r/^/, '('
236
+ y.to_s.sub! %r/$/, ')'
237
+ end
238
+ y.to_s.sub! %r/$/ do
239
+ tap # use block to prevent backslash substitution
240
+ end
111
241
  end
112
242
  end
113
243
  end
@@ -0,0 +1,34 @@
1
+ #! /your/favourite/path/to/ruby
2
+ # -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level 2 -*-
3
+ # -*- frozen_string_literal: true -*-
4
+ # -*- warn_indent: true -*-
5
+
6
+ # Copyright (c) 2017 Urabe, Shyouhei
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # SOFTWARE.
25
+ ;
26
+
27
+ # This is a namespace. Look at each classes under this module:
28
+ #
29
+ # - {XMP2Assert::Assertions} The assertion framework.
30
+ # - {XMP2Assert::Classifier} Check if the given file actually has the comment.
31
+ # - {XMP2Assert::Converter} Source code in-place editor using Ripper.
32
+ # - {XMP2Assert::Quasifile} IO/String abstraction layer
33
+ # - {XMP2Assert::PrettierInspect} Helper module to ease inspection.
34
+ module XMP2Assert; end
@@ -0,0 +1,136 @@
1
+ #! /your/favourite/path/to/ruby
2
+ # -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level 2 -*-
3
+ # -*- frozen_string_literal: true -*-
4
+ # -*- warn_indent: true -*-
5
+
6
+ # Copyright (c) 2017 Urabe, Shyouhei
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # SOFTWARE.
25
+
26
+ require 'ripper'
27
+ require_relative 'namespace'
28
+ require_relative 'quasifile'
29
+ require_relative 'token'
30
+
31
+ # This is a Ruby parser. Generates ASTs from the given program.
32
+ class XMP2Assert::Parser < Ripper
33
+ attr_reader :tokens # @return [Array<Token>] program, split into tokens.
34
+ attr_reader :sexp # @return [Array] constructed s-expression.
35
+
36
+ # @param (see XMP2Assert::Quasifile.new)
37
+ # @raise [SyntaxError] failed to parse the program.
38
+ def initialize obj, file = nil, line = nil
39
+ @qfile = XMP2Assert::Quasifile.new obj, file, line
40
+ super @qfile.read, *locations
41
+ @tokens = []
42
+ @sexp = parse
43
+ @tokens.sort!
44
+ end
45
+
46
+ # @return [String, Integer] the program's file name and line offset.
47
+ def locations
48
+ return @qfile.__FILE__, @qfile.__LINE__
49
+ end
50
+
51
+ # Find tokens that are in the same line as the argument.
52
+ #
53
+ # ```ruby
54
+ # [ 1, # => 1
55
+ # 2, # => 2
56
+ # ] # => [1, 2]
57
+ # ```
58
+ #
59
+ # It will return `[(:sp ' ') (:int 2) (:'=>' "2")]` for `(:'=>' "2")`.
60
+ #
61
+ # @param tok [Token] a token to look at.
62
+ # @return [Array] tokens of the same line as the argument.
63
+ def same_line_as tok
64
+ f, l = tok.__FILE__, tok.__LINE__
65
+ return @tokens.select {|i| i.__FILE__ == f }.select {|i| i.__LINE__ == l }
66
+ end
67
+
68
+ private
69
+
70
+ def on_error msg
71
+ raise SyntaxError, msg
72
+ end
73
+
74
+ def on_comment c
75
+ case c
76
+ when /^# => / then
77
+ yylex = :'=>'
78
+ yylval = $'
79
+ when /^# [~>]> / then
80
+ yylex = :>>
81
+ yylval = $'
82
+ else
83
+ yylex = :comment
84
+ yylval = c
85
+ end
86
+ yylloc = [filename, lineno, column]
87
+ tok = XMP2Assert::Token.new yylex, yylval, yylloc
88
+ @tokens << tok
89
+ return tok
90
+ end
91
+
92
+ def on_scanner_event yylval
93
+ yylex = __callee__.to_s.sub(/^on_/, '').intern
94
+ yylloc = [filename, lineno, column]
95
+ tok = XMP2Assert::Token.new yylex, yylval, yylloc
96
+ @tokens << tok
97
+ return tok
98
+ end
99
+
100
+ def on_parser_event *argv
101
+ nonterminal = __callee__.to_s.sub(/^on_/, '').intern
102
+ return [nonterminal, *argv]
103
+ end
104
+
105
+ def on_parser_list_new *argv
106
+ nonterminal = __callee__.to_s.sub(/^on_(.+)_new$/, '\\1').intern
107
+ raise [__callee__, argv].inspect unless argv.empty?
108
+ return [nonterminal]
109
+ end
110
+
111
+ def on_parser_list_append list, cdr
112
+ return list << cdr
113
+ end
114
+
115
+ pim = private_instance_methods false
116
+
117
+ SCANNER_EVENTS.each do |e|
118
+ m = :"on_#{e}"
119
+ next if pim.include? m
120
+ alias_method m, :on_scanner_event
121
+ end
122
+
123
+ PARSER_EVENTS.each do |e|
124
+ m = :"on_#{e}"
125
+ next if pim.include? m
126
+ case m
127
+ when :on_assoc_new then alias_method m, :on_parser_event
128
+ when /_new$/ then alias_method m, :on_parser_list_new
129
+ when /_add$/ then alias_method m, :on_parser_list_append
130
+ else alias_method m, :on_parser_event
131
+ end
132
+ end
133
+
134
+ alias on_parse_error on_error
135
+ alias compile_error on_error
136
+ end
@@ -24,6 +24,7 @@
24
24
  # SOFTWARE.
25
25
 
26
26
  require 'pp'
27
+ require_relative 'namespace'
27
28
 
28
29
  # By including this module your class gets a {#inspect} method which uses
29
30
  # {::PP} methods to control outputs. You don't have to worry about redefining
@@ -35,7 +36,7 @@ module XMP2Assert::PrettierInspect
35
36
  #
36
37
  # Prettier inspection.
37
38
  def inspect
38
- str = PP.pp self, '', Float::INFINITY
39
+ str = PP.singleline_pp self, ''
39
40
  str.chomp!
40
41
  return str
41
42
  end
@@ -25,14 +25,14 @@
25
25
 
26
26
  require 'open-uri'
27
27
  require 'pathname'
28
+ require_relative 'namespace'
29
+ require_relative 'prettier_inspect'
28
30
 
29
31
  # XMP2Assert converts a ruby script into a test file but we want to hold
30
32
  # original path name / line number for diagnostic purposes. So this class.
31
33
  class XMP2Assert::Quasifile
32
34
  include XMP2Assert::PrettierInspect
33
35
 
34
- # @return [Quasifile] a new quasifile.
35
- #
36
36
  # @overload new(qfile)
37
37
  # Just return the given object (for possible recursive calls).
38
38
  #
@@ -84,6 +84,10 @@ class XMP2Assert::Quasifile
84
84
  # @param line [Integer] line offset.
85
85
  # @return [Quasifile] generated qiasifile.
86
86
  #
87
+ # @return [Quasifile] a new quasifile.
88
+ # @param obj [Quasifile, URI, Pathname, String, File, IO] a file-ish.
89
+ # @param file [String] path of the file (optional).
90
+ # @param line [Integer] line offset (optional).
87
91
  def self.new(obj, file = nil, line = nil)
88
92
  case
89
93
  when src = switch { obj.to_str } then # LIKELY
@@ -0,0 +1,88 @@
1
+ #! /your/favourite/path/to/ruby
2
+ # -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level 2 -*-
3
+ # -*- frozen_string_literal: true -*-
4
+ # -*- warn_indent: true -*-
5
+
6
+ # Copyright (c) 2017 Urabe, Shyouhei
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # SOFTWARE.
25
+
26
+ require_relative 'namespace'
27
+ require_relative 'prettier_inspect'
28
+
29
+ # Token is a tiny class that represents a token of a Ruby program.
30
+ #
31
+ # @!attribute [rw] yylex
32
+ # @return [Symbol] terminal symbol
33
+ # @!attribute [rw] yylval
34
+ # @return [String] terminal value
35
+ # @!attribute [rw] yylloc
36
+ # @return [Array] terminal location
37
+ XMP2Assert::Token = Struct.new :yylex, :yylval, :yylloc do
38
+ include Comparable
39
+
40
+ # Comparison of location in a file, to be used with sort.
41
+ # @param other [Token] token to compare
42
+ def <=> other
43
+ yylloc <=> other.yylloc
44
+ end
45
+
46
+ alias to_sym yylex
47
+ alias to_s yylval
48
+
49
+ # @!group Token locations
50
+
51
+ # @return [String] file name
52
+ def __FILE__
53
+ return yylloc[0]
54
+ end
55
+
56
+ # @return [String] line number (1 origin)
57
+ def __LINE__
58
+ return yylloc[1]
59
+ end
60
+
61
+ # @return [String] column in a line
62
+ def __COLUMN__
63
+ return yylloc[2]
64
+ end
65
+ # @!endgroup
66
+
67
+ # Considet this token being an error.
68
+ # @param klass [Exception] exception to raise
69
+ # @param msg [String] diagnostic message
70
+ def raise klass = SyntaxError, msg = ""
71
+ l = sprintf "%s:%s", self.__FILE__, self.__LINE__
72
+ m = sprintf 'syntax error near "%s" at line %d:%d %s',
73
+ to_s, self.__LINE__, self.__COLUMN__, msg
74
+ super klass, m, [l, *caller]
75
+ end
76
+
77
+ unless $DEBUG
78
+ include ::XMP2Assert::PrettierInspect
79
+
80
+ def pretty_print pp
81
+ pp.text "("
82
+ yylex.pretty_print pp
83
+ pp.breakable " "
84
+ yylval.pretty_print pp
85
+ pp.text ")"
86
+ end
87
+ end
88
+ end
@@ -24,4 +24,4 @@
24
24
  # SOFTWARE.
25
25
  ;
26
26
 
27
- XMP2Assert::VERSION = 2
27
+ XMP2Assert::VERSION = 3
@@ -23,6 +23,8 @@
23
23
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  # SOFTWARE.
25
25
 
26
+ require_relative 'namespace'
27
+
26
28
  # An XMP comment normally looks like this:
27
29
  #
28
30
  # ```ruby
data/lib/xmp2assert.rb CHANGED
@@ -23,19 +23,10 @@
23
23
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  # SOFTWARE.
25
25
 
26
- # This is a namespace. Look at each classes under this module:
27
- #
28
- # - XMP2Assert::Assertions the assertion framework.
29
- # - XMP2Assert::Classifier check if the given file actually has the comment.
30
- # - XMP2Assert::Converter source code in-place editor using Ripper.
31
- # - XMP2Assert::Quasifile IO/String abstraction layer
32
- # - XMP2Assert::PrttierInpect helper module to ease inspection.
33
- module XMP2Assert
34
- # These files assume the namespace, hence required here inside.
35
- require_relative 'xmp2assert/version'
36
- require_relative 'xmp2assert/prettier_inspect'
37
- require_relative 'xmp2assert/quasifile'
38
- require_relative 'xmp2assert/converter'
39
- require_relative 'xmp2assert/classifier'
40
- require_relative 'xmp2assert/assertions'
41
- end
26
+ require_relative 'xmp2assert/namespace' # needs be first
27
+ require_relative 'xmp2assert/version'
28
+ require_relative 'xmp2assert/prettier_inspect'
29
+ require_relative 'xmp2assert/quasifile'
30
+ require_relative 'xmp2assert/converter'
31
+ require_relative 'xmp2assert/classifier'
32
+ require_relative 'xmp2assert/assertions'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xmp2assert
3
3
  version: !ruby/object:Gem::Version
4
- version: '2'
4
+ version: '3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Urabe, Shyouhei
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-04-04 00:00:00.000000000 Z
11
+ date: 2017-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -210,15 +210,18 @@ files:
210
210
  - LICENSE.txt
211
211
  - README.md
212
212
  - Rakefile
213
- - Stylegiude.md
213
+ - Styleguide.md
214
214
  - exe/xmpcheck.rb
215
215
  - lib/xmp2assert.rb
216
216
  - lib/xmp2assert/assertions.rb
217
217
  - lib/xmp2assert/classifier.rb
218
218
  - lib/xmp2assert/converter.rb
219
+ - lib/xmp2assert/namespace.rb
220
+ - lib/xmp2assert/parser.rb
219
221
  - lib/xmp2assert/prettier_inspect.rb
220
222
  - lib/xmp2assert/quasifile.rb
221
223
  - lib/xmp2assert/template.erb
224
+ - lib/xmp2assert/token.rb
222
225
  - lib/xmp2assert/version.rb
223
226
  - lib/xmp2assert/xmp2rexp.rb
224
227
  - xmp2assert.gemspec
@@ -242,7 +245,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
245
  version: '0'
243
246
  requirements: []
244
247
  rubyforge_project:
245
- rubygems_version: 2.6.11
248
+ rubygems_version: 2.6.12
246
249
  signing_key:
247
250
  specification_version: 4
248
251
  summary: auto-generate assertions from `# =>` comments
File without changes