xmp2assert 2 → 3

Sign up to get free protection for your applications and to get access to all the features.
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