neuroncheck 0.1.0

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