neuroncheck 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 48bfd036b69c0f6d509363762c39a3fda0dc97f4
4
+ data.tar.gz: 49471c3dd25296b6527572ec6fbf37aa6a896784
5
+ SHA512:
6
+ metadata.gz: 35c0916e4421893c8f1d175585f08586fd8e66f43d4b93a4ac10a99c65c4cee556c2c4ae49d030b1b23274eceab3a592b8e04182a857fc7405ec71a3b2495f5f
7
+ data.tar.gz: 7f07bda2153ba07a262fae2afd1b3c6e6e078ff197aab19df8a9bb8445c54043cb3713676c380c4d553c33e997956603781a188c3547e53bab5f97c728175037
@@ -0,0 +1,54 @@
1
+ require 'rake/testtask'
2
+ require "bundler/gem_tasks"
3
+ Rake.application.instance_variable_get('@tasks').delete('release')
4
+
5
+ desc 'Run test_unit based test'
6
+ task :test do
7
+ Dir.glob('test/**/test_*.rb') do |path|
8
+ cmd = "bundle exec ruby #{path}"
9
+ puts cmd
10
+ system cmd
11
+ end
12
+ end
13
+
14
+ %w(example example_dev).each do |base_dir_name|
15
+
16
+ example_dests = []
17
+ directory "#{base_dir_name}/"
18
+ Dir.glob("#{base_dir_name}_src/**/*.rb") do |src|
19
+ # サブディレクトリを持っているかどうかで処理を分岐
20
+ dest_dir = "#{base_dir_name}/"
21
+ if src =~ %r|/.+?/| then
22
+ dest_dir = File.dirname(src).sub(/^example_src/, 'example')
23
+ end
24
+
25
+ # 出力先を取得
26
+ dest = src.sub(/^#{base_dir_name}_src/, base_dir_name)
27
+ example_dests << dest
28
+
29
+ # タスク定義
30
+ directory dest_dir
31
+ desc '-'
32
+ file dest => [src, dest_dir, __FILE__] + FileList['lib/**/*.rb'] do
33
+ # NeuronCheckを使用してスクリプト実行
34
+ cmd = %Q|bundle exec ruby -I lib #{src}|
35
+ puts cmd
36
+ system cmd, [:out, :err] => ['_example_out.txt', 'w']
37
+
38
+ # スクリプトの実行の結果、出力された内容を、コメントとして後ろに結合したうえで書き込む
39
+ # ただしこの際、ファイルパスを置換するなどの加工もかける
40
+ output_lines = File.readlines('_example_out.txt')
41
+ output = output_lines.map{|x| "# #{x}"}.join.sub('# ', '#=>')
42
+ output.gsub!(Dir.pwd, '.')
43
+ output.gsub!(src, 'script.rb')
44
+
45
+ File.write(dest, File.read(src) + "\n" + output)
46
+ $stderr.puts "-> #{dest}"
47
+ end
48
+ end
49
+
50
+
51
+ desc 'make example code'
52
+ task base_dir_name => example_dests
53
+
54
+ end
@@ -0,0 +1 @@
1
+ require 'neuroncheck/kernel'
@@ -0,0 +1,165 @@
1
+ require 'neuroncheck/plugin'
2
+
3
+ # 組み込みキーワードの定義
4
+
5
+ # respondable: 指定した名前のメソッドが定義されている(メソッド呼び出しに応答可能である)ことを表す。Duck Typing用
6
+ NeuronCheckSystem::Plugin.add_keyword(:respondable) do
7
+ def on_call(*method_names)
8
+ @method_names = method_names
9
+ end
10
+
11
+ def match?(value)
12
+ @method_names.all?{|x| value.respond_to?(x)}
13
+ end
14
+
15
+ def expected_caption
16
+ in_cap = NeuronCheckSystem::Utils.string_join_using_or_conjunction(@method_names.map{|x| "##{x}"}) # 複数の文字列を結合し、orを使ったフレーズの形にするUtilityメソッド。 ['A', 'B', 'C'] => "A, B or C"
17
+ "respondable to #{in_cap}"
18
+ end
19
+
20
+ def expected_short_caption
21
+ 'respondable(' + @method_names.map{|x| x.inspect}.join(', ') + ')'
22
+ end
23
+
24
+ def get_params_as_json
25
+ {'expected' => @method_names.map(&:to_s)}
26
+ end
27
+
28
+ def self.builtin_keyword?
29
+ true
30
+ end
31
+ end
32
+
33
+ # respond_to, res: respondableのエイリアス
34
+ NeuronCheckSystem::Plugin.alias_keyword(:res, :respondable)
35
+
36
+ # any: すべての値を受け付ける
37
+ NeuronCheckSystem::Plugin.add_keyword(:any) do
38
+ def on_call
39
+ end
40
+
41
+ def match?(value)
42
+ true # 常にtrue
43
+ end
44
+
45
+ def expected_caption
46
+ "any value"
47
+ end
48
+
49
+ def expected_short_caption
50
+ "any"
51
+ end
52
+
53
+ def get_params_as_json
54
+ {}
55
+ end
56
+
57
+ def self.builtin_keyword?
58
+ true
59
+ end
60
+ end
61
+
62
+ # block: ブロック引数用の特殊なキーワード。[Proc, nil]と同じ
63
+ NeuronCheckSystem::Plugin.add_keyword(:block) do
64
+ def on_call
65
+ end
66
+
67
+ def match?(value)
68
+ @api.expected_value_match?(value, [Proc, nil])
69
+ end
70
+
71
+ def expected_caption
72
+ "block or nil"
73
+ end
74
+
75
+ def self.builtin_keyword?
76
+ true
77
+ end
78
+ end
79
+
80
+
81
+ # except: 指定した値以外を許可 (否定 / NOT)
82
+ NeuronCheckSystem::Plugin.add_keyword(:except) do
83
+ def on_call(target)
84
+ @target = target
85
+ end
86
+
87
+ def match?(value)
88
+ not @api.expected_value_match?(value, @target)
89
+ end
90
+
91
+ def expected_caption
92
+ "any value except #{@api.get_expected_value_caption(@target)}"
93
+ end
94
+
95
+ def expected_short_caption
96
+ "except(#{@api.get_expected_value_short_caption(@target)})"
97
+ end
98
+
99
+ def get_params_as_json
100
+ {'target' => @api.get_expected_value_meta_info_as_json(@target)}
101
+ end
102
+
103
+ def self.builtin_keyword?
104
+ true
105
+ end
106
+ end
107
+
108
+
109
+ # array_of: 指定した種類の値のみを格納した配列であることを表す
110
+ NeuronCheckSystem::Plugin.add_keyword(:array_of) do
111
+ def on_call(item_expected)
112
+ @item_expected = item_expected
113
+ end
114
+
115
+ def match?(value)
116
+ return false unless value.kind_of?(Array) # まずは配列であるかどうかチェック
117
+
118
+ # 配列であれば、1つ1つの値が型どおりであるかどうかをチェック
119
+ return value.all?{|x| @api.expected_value_match?(x, @item_expected)}
120
+ end
121
+
122
+ def expected_caption
123
+ "array of #{@api.get_expected_value_caption(@item_expected)}"
124
+ end
125
+
126
+ def get_params_as_json
127
+ {'item' => @api.get_expected_value_meta_info_as_json(@item_expected)}
128
+ end
129
+
130
+ def self.builtin_keyword?
131
+ true
132
+ end
133
+ end
134
+
135
+
136
+ # hash_of: 指定した種類のキーと値のみを格納した配列であることを表す
137
+ NeuronCheckSystem::Plugin.add_keyword(:hash_of) do
138
+ def on_call(key_expected, value_expected)
139
+ @key_expected = key_expected
140
+ @value_expected = value_expected
141
+ end
142
+
143
+ def match?(value)
144
+ return false unless value.kind_of?(Hash) # まずはHashであるかどうかチェック
145
+
146
+ # ハッシュであれば、1つ1つのキーと値をチェック
147
+ return value.all?{|k, v| @api.expected_value_match?(k, @key_expected) and @api.expected_value_match?(v, @value_expected)}
148
+ end
149
+
150
+ def expected_caption
151
+ "hash that has keys of #{@api.get_expected_value_caption(@key_expected)} and values of #{@api.get_expected_value_caption(@value_expected)}, #{expected_short_caption}"
152
+ end
153
+
154
+ def expected_short_caption
155
+ "{#{@api.get_expected_value_short_caption(@key_expected)} => #{api.get_expected_value_short_caption(@value_expected)}}"
156
+ end
157
+
158
+ def get_params_as_json
159
+ {'key' => @api.get_expected_value_meta_info_as_json(@key_expected), 'value' => @api.get_expected_value_meta_info_as_json(@value_expected)}
160
+ end
161
+
162
+ def self.builtin_keyword?
163
+ true
164
+ end
165
+ end
@@ -0,0 +1,42 @@
1
+ module NeuronCheckSystem
2
+ class CondBlockContext
3
+ def initialize(block_name, method_self, allow_instance_method)
4
+ @block_name = block_name
5
+ @method_self = method_self
6
+ @allow_instance_method = allow_instance_method
7
+ end
8
+
9
+ def method_missing(name, *args, &block)
10
+ if @method_self.respond_to?(name, true) then
11
+ if @allow_instance_method then
12
+ @method_self.send(name, *args, &block)
13
+ else
14
+ raise NeuronCheckSystem::DeclarationError, "instance method `#{name}' cannot be called in #{@block_name}, it is forbidden", (NeuronCheck.debug? ? caller : caller(1))
15
+ end
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def assert(*dummy)
22
+ unless block_given? then
23
+ raise NeuronCheckSystem::DeclarationError, "no block given for `assert' in #{@block_name}", (NeuronCheck.debug? ? caller : caller(1))
24
+ end
25
+
26
+ passed = yield
27
+
28
+ unless passed
29
+ locs = Utils.backtrace_locations_to_captions(caller(1, 1))
30
+
31
+ msg = <<MSG
32
+ #{@block_name} assertion failed
33
+ asserted at: #{locs.join("\n" + ' ' * 15)}
34
+
35
+ MSG
36
+
37
+ # エラーを発生させる
38
+ throw :neuron_check_error_tag, msg
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,295 @@
1
+ require 'neuroncheck/matcher'
2
+ require 'neuroncheck/plugin'
3
+ require 'neuroncheck/syntax'
4
+ require 'neuroncheck/builtin_keyword'
5
+
6
+ module NeuronCheckSystem
7
+ # 宣言用のメソッドやメソッド追加時の処理を定義したモジュール。NeuronCheckを行いたい対象のモジュールやクラスにextendすることで使用する
8
+ module DeclarationMethods
9
+ # 宣言を実行
10
+ def ndecl(*expecteds, &block)
11
+ # 未初期化の場合、NeuronCheck用の初期化を自動実行
12
+ unless @__neuron_check_initialized then
13
+ NeuronCheckSystem.initialize_module_for_neuron_check(self)
14
+ end
15
+
16
+ # メイン処理実行
17
+ __neuroncheck_ndecl_main(expecteds, block, caller(1, 1))
18
+ end
19
+
20
+ # ndeclのエイリアス
21
+ alias ncheck ndecl
22
+ alias ndeclare ndecl
23
+ alias nsig ndecl
24
+ alias ntypesig ndecl
25
+
26
+ # ndeclのメイン処理
27
+ def __neuroncheck_ndecl_main(expecteds, block, declared_caller_locations)
28
+ # 2回連続で宣言された場合はエラー
29
+ if @__neuron_check_last_declaration then
30
+ raise DeclarationError, "repeated declarations - Declaration block and method definition must correspond one-to-one"
31
+ end
32
+
33
+ # ブロックが渡されたかどうかで処理を分岐
34
+ if block then
35
+ # ブロックが渡された場合
36
+ __neuroncheck_ndecl_main_with_block(block, declared_caller_locations)
37
+ else
38
+ # 短縮記法はNeuronCheckSyntax使用可能時のみ
39
+ unless defined?(NeuronCheckSyntax) then
40
+ raise DeclarationError, "NeuronCheck shorthand syntax (without block) can be used only in Ruby 2.1 or later"
41
+ end
42
+
43
+ # ブロックが渡されていない場合 (短縮記法)
44
+ __neuroncheck_ndecl_main_without_block(expecteds, declared_caller_locations)
45
+ end
46
+ end
47
+
48
+ # ndeclの通常記法
49
+ def __neuroncheck_ndecl_main_with_block(block, declared_caller_locations)
50
+ # 宣言ブロック実行用のコンテキストを作成
51
+ context = NeuronCheckSystem::DeclarationContext.new
52
+
53
+ # 宣言ブロックの内容を実行
54
+ context.instance_eval(&block)
55
+
56
+ # 呼び出し場所を記憶
57
+ context.declaration.declared_caller_locations = declared_caller_locations
58
+
59
+ # 宣言の内容を「最後の宣言」として保持
60
+ @__neuron_check_last_declaration = context.declaration
61
+ end
62
+
63
+ # ndeclの短縮記法
64
+ def __neuroncheck_ndecl_main_without_block(expecteds, declared_caller_locations)
65
+ # 宣言ブロック実行用のコンテキストを作成
66
+ context = NeuronCheckSystem::DeclarationContext.new
67
+
68
+ # 引数の解釈
69
+ expected_args = nil
70
+ expected_return = nil
71
+ if expecteds.last.kind_of?(Hash) and expecteds.last.size == 1 then
72
+ # expectedsの最後が、値が1つだけ格納されたHashであれば、キーを最後の引数、値を戻り値と解釈する
73
+ # 例: String, String => Numeric
74
+ last_hash = expecteds.pop
75
+ expected_args = expecteds.concat([last_hash.keys.first])
76
+ expected_return = last_hash.values.first
77
+ else
78
+ # 上記以外の場合はすべて引数と見なす
79
+ expected_args = expecteds
80
+ end
81
+
82
+ # 引数1つで、かつ空配列が渡された場合は、「引数なし」と宣言されたとみなす
83
+ if expected_args[0].kind_of?(Array) and expected_args.size == 1 then
84
+ expected_args = []
85
+ end
86
+
87
+ # 簡易宣言を実行
88
+ context.instance_eval do
89
+ unless expected_args.empty? then
90
+ args *expected_args
91
+ end
92
+
93
+ if expected_return then
94
+ returns expected_return
95
+ end
96
+ end
97
+
98
+ # 短縮記法フラグON
99
+ context.declaration.shorthand = true
100
+
101
+ # 呼び出し場所を記憶
102
+ context.declaration.declared_caller_locations = declared_caller_locations
103
+ context.declaration.arg_matchers.each do |matcher|
104
+ matcher.declared_caller_locations = context.declaration.declared_caller_locations
105
+ end
106
+ if context.declaration.return_matcher then
107
+ context.declaration.return_matcher.declared_caller_locations = context.declaration.declared_caller_locations
108
+ end
109
+
110
+ # 宣言の内容を「最後の宣言」として保持 (通常のndeclと同じ)
111
+ @__neuron_check_last_declaration = context.declaration
112
+ end
113
+ end
114
+
115
+ class DeclarationContext
116
+ include Keywords
117
+ attr_reader :declaration
118
+
119
+ def initialize
120
+ @declaration = Declaration.new
121
+ end
122
+
123
+ def args(*expecteds)
124
+ declared_caller_locations = caller(1, 1)
125
+ @declaration.arg_matchers = expecteds.map{|x| NeuronCheckSystem.get_appropriate_matcher(x, declared_caller_locations)}
126
+ end
127
+
128
+ def returns(expected)
129
+ declared_caller_locations = caller(1, 1)
130
+ @declaration.return_matcher = NeuronCheckSystem.get_appropriate_matcher(expected, declared_caller_locations)
131
+ end
132
+
133
+ def precond(allow_instance_method: false, &cond_block)
134
+ @declaration.precond = cond_block
135
+ @declaration.precond_allow_instance_method = allow_instance_method
136
+ end
137
+
138
+ def postcond(allow_instance_method: false, &cond_block)
139
+ @declaration.postcond = cond_block
140
+ @declaration.postcond_allow_instance_method = allow_instance_method
141
+ end
142
+
143
+ def val(expected)
144
+ declared_caller_locations = caller(1, 1)
145
+ @declaration.attr_matcher = NeuronCheckSystem.get_appropriate_matcher(expected, declared_caller_locations)
146
+ end
147
+ alias must_be val
148
+ alias value val
149
+ end
150
+
151
+ class Declaration
152
+ attr_accessor :arg_matchers
153
+ attr_accessor :return_matcher
154
+ attr_accessor :attr_matcher
155
+
156
+ attr_accessor :precond
157
+ attr_accessor :precond_allow_instance_method
158
+ attr_accessor :postcond
159
+ attr_accessor :postcond_allow_instance_method
160
+
161
+ attr_accessor :assigned_class_or_module
162
+ attr_accessor :assigned_method
163
+ attr_accessor :assigned_singleton_original_class
164
+ attr_accessor :assigned_attribute_name
165
+ attr_accessor :shorthand
166
+ attr_accessor :declared_caller_locations
167
+
168
+
169
+ def initialize
170
+ @arg_matchers = []
171
+ @return_matcher = nil
172
+ @attr_matcher = nil
173
+ @precond = nil
174
+ @precond_allow_instance_method = false
175
+ @postcond = nil
176
+ @postcond_allow_instance_method = false
177
+
178
+ @assigned_class_or_module = nil
179
+ @assigned_method = nil
180
+ @assigned_singleton_original_class = nil
181
+ @assigned_attribute_name = nil
182
+
183
+ @shorthand = false
184
+ @declared_caller_locations = nil
185
+ end
186
+
187
+ def attribute?
188
+ (@assigned_attribute_name ? true : false)
189
+ end
190
+
191
+ def assinged_to_toplevel_method?
192
+ @assigned_class_or_module == Object
193
+ end
194
+ def assinged_to_singleton_method?
195
+ @assigned_singleton_original_class
196
+ end
197
+
198
+ # メソッド名/属性名の表記文字列を取得
199
+ def signature_caption_name_only
200
+ if @assigned_class_or_module and (@assigned_method or attribute?) then
201
+ ret = ""
202
+
203
+ # 属性、特異メソッド、インスタンスメソッドのそれぞれで処理を分岐
204
+ if attribute? then
205
+ if @assigned_class_or_module.name then
206
+ ret << @assigned_class_or_module.name
207
+ end
208
+
209
+ # 属性名出力
210
+ ret << "##{@assigned_attribute_name}"
211
+
212
+ elsif assinged_to_toplevel_method? then
213
+ # メソッド名出力
214
+ ret << "#{@assigned_method.name}"
215
+
216
+ elsif assinged_to_singleton_method? then
217
+ if @assigned_singleton_original_class.name then
218
+ ret << @assigned_singleton_original_class.name
219
+ end
220
+
221
+ # メソッド名出力
222
+ ret << ".#{@assigned_method.name}"
223
+ else
224
+ if @assigned_class_or_module.name then
225
+ ret << @assigned_class_or_module.name
226
+ end
227
+
228
+ # メソッド名出力
229
+ ret << "##{@assigned_method.name}"
230
+ end
231
+ else
232
+ nil
233
+ end
234
+ end
235
+
236
+ # メソッド名/属性名+引数+戻り値の表記文字列を取得
237
+ def signature_caption
238
+ ret = signature_caption_name_only
239
+ if ret then
240
+ if attribute? then
241
+
242
+ if @attr_matcher then
243
+ ret << " -> #{@attr_matcher.expected_short_caption}"
244
+ end
245
+ else
246
+
247
+ # 引数出力
248
+ unless @assigned_method.parameters.empty? then
249
+ ret << "("
250
+ @assigned_method.parameters.each_with_index do |param_info, i|
251
+ _, param_name = param_info
252
+ if i >= 1 then
253
+ ret << ", "
254
+ end
255
+
256
+ if (matcher = @arg_matchers[i]) then
257
+ ret << "#{param_name}:#{matcher.expected_short_caption}"
258
+ else
259
+ ret << "#{param_name}:any"
260
+ end
261
+ end
262
+ ret << ")"
263
+ end
264
+
265
+ if @return_matcher then
266
+ ret << " -> #{@return_matcher.expected_short_caption}"
267
+ end
268
+ end
269
+
270
+ return ret
271
+ else
272
+ nil
273
+ end
274
+ end
275
+
276
+ def meta_info_as_json
277
+ re = {}
278
+
279
+ if attribute? then
280
+ re['value'] = (@attr_matcher ? @attr_matcher.meta_info_as_json : nil)
281
+ re['signature_caption'] = signature_caption
282
+ re['signature_caption_name_only'] = signature_caption_name_only
283
+ else
284
+ re['args'] = @arg_matchers.map{|x| x.meta_info_as_json}
285
+ re['returns'] = (@return_matcher ? @return_matcher.meta_info_as_json : nil)
286
+ re['signature_caption'] = signature_caption
287
+ re['signature_caption_name_only'] = signature_caption_name_only
288
+ end
289
+ re['precond_source_location'] = (@precond ? @precond.source_location : nil)
290
+ re['postcond_source_location'] = (@postcond ? @postcond.source_location : nil)
291
+
292
+ re
293
+ end
294
+ end
295
+ end