apipie-dsl 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +485 -0
  4. data/app/controllers/apipie_dsl/apipie_dsls_controller.rb +190 -0
  5. data/app/helpers/apipie_dsl_helper.rb +110 -0
  6. data/app/public/apipie_dsl/javascripts/apipie_dsl.js +6 -0
  7. data/app/public/apipie_dsl/javascripts/bundled/bootstrap-collapse.js +138 -0
  8. data/app/public/apipie_dsl/javascripts/bundled/bootstrap.js +1726 -0
  9. data/app/public/apipie_dsl/javascripts/bundled/jquery.js +5 -0
  10. data/app/public/apipie_dsl/javascripts/bundled/prettify.js +28 -0
  11. data/app/public/apipie_dsl/stylesheets/application.css +7 -0
  12. data/app/public/apipie_dsl/stylesheets/bundled/bootstrap-responsive.min.css +12 -0
  13. data/app/public/apipie_dsl/stylesheets/bundled/bootstrap.min.css +689 -0
  14. data/app/public/apipie_dsl/stylesheets/bundled/prettify.css +30 -0
  15. data/app/views/apipie_dsl/apipie_dsls/_index_class_meth.erb +11 -0
  16. data/app/views/apipie_dsl/apipie_dsls/_index_class_prop.erb +13 -0
  17. data/app/views/apipie_dsl/apipie_dsls/_languages.erb +6 -0
  18. data/app/views/apipie_dsl/apipie_dsls/_metadata.erb +1 -0
  19. data/app/views/apipie_dsl/apipie_dsls/_method.erb +34 -0
  20. data/app/views/apipie_dsl/apipie_dsls/_method_detail.erb +58 -0
  21. data/app/views/apipie_dsl/apipie_dsls/_params.html.erb +47 -0
  22. data/app/views/apipie_dsl/apipie_dsls/_params_plain.html.erb +19 -0
  23. data/app/views/apipie_dsl/apipie_dsls/_property.erb +24 -0
  24. data/app/views/apipie_dsl/apipie_dsls/_property_detail.erb +35 -0
  25. data/app/views/apipie_dsl/apipie_dsls/_raises.html.erb +23 -0
  26. data/app/views/apipie_dsl/apipie_dsls/_returns.html.erb +53 -0
  27. data/app/views/apipie_dsl/apipie_dsls/apipie_dsl_404.html.erb +17 -0
  28. data/app/views/apipie_dsl/apipie_dsls/class.html.erb +75 -0
  29. data/app/views/apipie_dsl/apipie_dsls/custom_help.html.erb +10 -0
  30. data/app/views/apipie_dsl/apipie_dsls/getting_started.html.erb +4 -0
  31. data/app/views/apipie_dsl/apipie_dsls/index.html.erb +72 -0
  32. data/app/views/apipie_dsl/apipie_dsls/method.html.erb +52 -0
  33. data/app/views/apipie_dsl/apipie_dsls/plain.html.erb +116 -0
  34. data/app/views/apipie_dsl/apipie_dsls/static.html.erb +158 -0
  35. data/app/views/layouts/apipie_dsl/apipie_dsl.html.erb +26 -0
  36. data/lib/apipie-dsl.rb +3 -0
  37. data/lib/apipie_dsl.rb +28 -0
  38. data/lib/apipie_dsl/Rakefile +6 -0
  39. data/lib/apipie_dsl/apipie_dsl_module.rb +51 -0
  40. data/lib/apipie_dsl/application.rb +321 -0
  41. data/lib/apipie_dsl/class_description.rb +107 -0
  42. data/lib/apipie_dsl/configuration.rb +87 -0
  43. data/lib/apipie_dsl/dsl.rb +596 -0
  44. data/lib/apipie_dsl/errors.rb +68 -0
  45. data/lib/apipie_dsl/exception_description.rb +39 -0
  46. data/lib/apipie_dsl/markup.rb +41 -0
  47. data/lib/apipie_dsl/method_description.rb +112 -0
  48. data/lib/apipie_dsl/parameter_description.rb +152 -0
  49. data/lib/apipie_dsl/railtie.rb +15 -0
  50. data/lib/apipie_dsl/return_description.rb +71 -0
  51. data/lib/apipie_dsl/routing.rb +17 -0
  52. data/lib/apipie_dsl/see_description.rb +35 -0
  53. data/lib/apipie_dsl/static_dispatcher.rb +70 -0
  54. data/lib/apipie_dsl/tag_list_description.rb +11 -0
  55. data/lib/apipie_dsl/tasks/apipie_dsl/cache.rake +43 -0
  56. data/lib/apipie_dsl/tasks/apipie_dsl/static.rake +43 -0
  57. data/lib/apipie_dsl/tasks/apipie_dsl/static_json.rake +29 -0
  58. data/lib/apipie_dsl/tasks_utils.rb +137 -0
  59. data/lib/apipie_dsl/utils.rb +83 -0
  60. data/lib/apipie_dsl/validator.rb +479 -0
  61. data/lib/apipie_dsl/version.rb +5 -0
  62. data/lib/generators/apipie_dsl/install/install_generator.rb +21 -0
  63. data/lib/generators/apipie_dsl/install/templates/initializer.rb.erb +9 -0
  64. data/lib/generators/apipie_dsl/views_generator.rb +14 -0
  65. data/test/test_helper.rb +6 -0
  66. metadata +220 -0
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApipieDSL
4
+ class Configuration
5
+ attr_accessor :app_name, :copyright, :markup, :doc_base_url, :layout,
6
+ :default_version, :debug, :version_in_url, :validate,
7
+ :doc_path, :languages, :link_extension, :translate, :locale,
8
+ :default_locale, :class_full_names, :autoload_methods,
9
+ :dsl_classes_matcher, :sections, :authenticate, :authorize,
10
+ :use_cache, :app_info, :help_layout
11
+ attr_writer :validate_value, :ignored, :reload_dsl, :default_section,
12
+ :dsl_classes_matchers, :cache_dir
13
+
14
+ alias_method :validate?, :validate
15
+ alias_method :class_full_names?, :class_full_names
16
+ alias_method :autoload_methods?, :autoload_methods
17
+ alias_method :use_cache?, :use_cache
18
+
19
+ def cache_dir
20
+ @cache_dir ||= File.join(Rails.root, 'public', 'apipie-dsl-cache')
21
+ end
22
+
23
+ def validate_value
24
+ (validate? && @validate_value)
25
+ end
26
+ alias_method :validate_value?, :validate_value
27
+
28
+ # array of class names (strings) (might include methods as well)
29
+ # to be ignored when generationg the documentation
30
+ # e.g. %w[DSL::MyClass DSL::IO#puts]
31
+ def ignored
32
+ @ignored ||= []
33
+ @ignored.map(&:to_s)
34
+ end
35
+
36
+ def app_info=(description)
37
+ version = ApipieDSL.configuration.default_version
38
+ @app_info[version] = description
39
+ end
40
+
41
+ def dsl_classes_matchers
42
+ unless @dsl_classes_matcher.empty?
43
+ @dsl_classes_matchers << @dsl_classes_matcher
44
+ end
45
+ @dsl_classes_matchers = @dsl_classes_matchers.uniq
46
+ end
47
+
48
+ def reload_dsl?
49
+ @reload_dsl = if defined? Rails
50
+ Rails.env.development?
51
+ else
52
+ @reload_dsl
53
+ end
54
+ @reload_dsl && !dsl_classes_matchers.empty?
55
+ end
56
+
57
+ def default_section
58
+ @default_section || @sections.first
59
+ end
60
+
61
+ def initialize
62
+ @markup = ApipieDSL::Markup::RDoc.new
63
+ @app_name = 'Another DOC'
64
+ @app_info = {}
65
+ @copyright = nil
66
+ @validate = :implicitly
67
+ @validate_value = true
68
+ @doc_base_url = '/apipie-dsl'
69
+ @layout = 'apipie_dsl/apipie_dsl'
70
+ @default_version = '1.0'
71
+ @debug = false
72
+ @version_in_url = true
73
+ @doc_path = 'doc'
74
+ @link_extension = '.html'
75
+ @languages = []
76
+ @default_locale = 'en'
77
+ @locale = lambda { |_locale| @default_locale }
78
+ @translate = lambda { |str, _locale| str }
79
+ @class_full_names = true
80
+ @autoload_methods = false
81
+ @dsl_classes_matcher = ''
82
+ @dsl_classes_matchers = []
83
+ @sections = ['all']
84
+ @default_section = nil
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,596 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApipieDSL
4
+ module Base
5
+ def apipie_eval_dsl(*args, &block)
6
+ raise ArgumentError, 'Block expected' unless block_given?
7
+
8
+ instance_exec(*args, &block)
9
+ dsl_data
10
+ ensure
11
+ dsl_data_clear
12
+ end
13
+
14
+ def dsl_data
15
+ @dsl_data ||= dsl_data_init
16
+ end
17
+
18
+ def dsl_data_clear
19
+ @dsl_data = nil
20
+ end
21
+
22
+ private
23
+
24
+ def dsl_data_init
25
+ @dsl_data =
26
+ {
27
+ name: nil,
28
+ short_description: nil,
29
+ description: nil,
30
+ dsl_versions: [],
31
+ deprecated: false,
32
+ meta: nil,
33
+ params: [],
34
+ properties: [],
35
+ raises: [],
36
+ returns: nil,
37
+ see: [],
38
+ show: true,
39
+ examples: [],
40
+ sections: ['all']
41
+ }
42
+ end
43
+ end
44
+
45
+ module Common
46
+ def dsl_versions(*versions)
47
+ dsl_data[:dsl_versions].concat(versions)
48
+ end
49
+ alias_method :dsl_version, :dsl_versions
50
+
51
+ def desc(description)
52
+ dsl_data[:description] = description
53
+ end
54
+ alias_method :description, :desc
55
+ alias_method :full_description, :desc
56
+
57
+ def short(short)
58
+ dsl_data[:short_description] = short
59
+ end
60
+ alias_method :short_description, :short
61
+
62
+ # Describe additional metadata
63
+ #
64
+ # meta :author => { :name => 'John', :surname => 'Doe' }
65
+ def meta(meta)
66
+ dsl_data[:meta] = meta
67
+ end
68
+
69
+ # Add tags to classes and methods group operations together.
70
+ def tags(*args)
71
+ tags = args.length == 1 ? args.first : args
72
+ dsl_data[:tag_list] += tags
73
+ end
74
+
75
+ def deprecated(value)
76
+ dsl_data[:deprecated] = value
77
+ end
78
+
79
+ # Determine if the method (class) should be included
80
+ # in the documentation
81
+ def show(show)
82
+ dsl_data[:show] = show
83
+ end
84
+ end
85
+
86
+ module Parameter
87
+ SUPPORTED_TYPES = %i[required optional keyword block].freeze
88
+ # Describe method's parameter
89
+ #
90
+ # Example:
91
+ # param :greeting, String, :desc => "arbitrary text", :type => :required
92
+ # def hello_world(greeting)
93
+ # puts greeting
94
+ # end
95
+ #
96
+ def param(name, validator, desc_or_options = nil, options = {}, &block)
97
+ dsl_data[:params] << [name,
98
+ validator,
99
+ desc_or_options,
100
+ options.merge(param_group: @current_param_group),
101
+ block]
102
+ end
103
+
104
+ def required(name, validator, desc_or_options = nil, options = {}, &block)
105
+ options[:type] = :required
106
+ param(name, validator, desc_or_options, options, &block)
107
+ end
108
+
109
+ def optional(name, validator, desc_or_options = nil, options = {}, &block)
110
+ options[:type] = :optional
111
+ param(name, validator, desc_or_options, options, &block)
112
+ end
113
+
114
+ def keyword(name, validator, desc_or_options = nil, options = {}, &block)
115
+ options[:type] = :keyword
116
+ param(name, validator, desc_or_options, options, &block)
117
+ end
118
+
119
+ def block(desc_or_options = nil, options = {}, &block)
120
+ options[:type] = :block
121
+ name = options[:name] || :block
122
+ param(name, Proc, desc_or_options, options)
123
+ end
124
+
125
+ def list(name, desc_or_options = nil, options = {})
126
+ options[:type] = :optional
127
+ options[:default] ||= 'empty list'
128
+ param(name, :rest, desc_or_options, options)
129
+ end
130
+ alias_method :splat, :list
131
+ alias_method :rest, :list
132
+
133
+ def define_param_group(name, &block)
134
+ ApipieDSL.define_param_group(class_scope, name, &block)
135
+ end
136
+
137
+ # Reuses param group for this method. The definition is looked up
138
+ # in scope of this class. If the group was defined in
139
+ # different class, the second param can be used to specify it.
140
+ def param_group(name, scope_or_options = nil, options = {})
141
+ if scope_or_options.is_a?(Hash)
142
+ options.merge!(scope_or_options)
143
+ scope = options[:scope]
144
+ else
145
+ scope = scope_or_options
146
+ end
147
+ scope ||= default_param_group_scope
148
+
149
+ @current_param_group = {
150
+ scope: scope,
151
+ name: name,
152
+ options: options
153
+ }
154
+ instance_exec(&ApipieDSL.get_param_group(scope, name))
155
+ ensure
156
+ @current_param_group = nil
157
+ end
158
+
159
+ # Where the group definition should be looked up when no scope
160
+ # given. This is expected to return a class.
161
+ def default_param_group_scope
162
+ class_scope
163
+ end
164
+ end
165
+
166
+ module Method
167
+ include ApipieDSL::Parameter
168
+
169
+ def method(name, desc = nil, _options = {})
170
+ dsl_data[:name] = name
171
+ dsl_data[:short_description] = desc
172
+ end
173
+
174
+ def aliases(*names)
175
+ dsl_data[:aliases] = names
176
+ end
177
+
178
+ # Describe possible errors
179
+ #
180
+ # Example:
181
+ # raises :desc => "wrong argument", :error => ArgumentError, :meta => [:some, :more, :data]
182
+ # raises ArgumentError, "wrong argument"
183
+ # def print_string(string)
184
+ # raise ArgumentError unless string.is_a?(String)
185
+ # puts string
186
+ # end
187
+ #
188
+ def raises(error_or_options, desc = nil, options = {})
189
+ dsl_data[:raises] << [error_or_options, desc, options]
190
+ end
191
+
192
+ def returns(retobj_or_options, desc_or_options = nil, options = {}, &block)
193
+ raise MultipleReturnsError unless dsl_data[:returns].nil?
194
+
195
+ if desc_or_options.is_a?(Hash)
196
+ options.merge!(desc_or_options)
197
+ elsif !desc_or_options.nil?
198
+ options[:desc] = desc_or_options
199
+ end
200
+
201
+ if retobj_or_options.is_a?(Hash)
202
+ options.merge!(retobj_or_options)
203
+ elsif retobj_or_options.is_a?(Symbol)
204
+ options[:param_group] = retobj_or_options
205
+ else
206
+ options[:object_of] ||= retobj_or_options
207
+ end
208
+
209
+ options[:scope] ||= default_param_group_scope
210
+
211
+ raise ArgumentError, 'Block can be specified for Hash return type only' if block && (options[:object_of] != Hash)
212
+
213
+ data = [options, block]
214
+ dsl_data[:returns] = data unless options[:property]
215
+ data
216
+ end
217
+
218
+ # Reference other similar method
219
+ #
220
+ # method :print
221
+ # see "MyIO#puts"
222
+ # def print; end
223
+ def see(method, options = {})
224
+ args = [method, options]
225
+ dsl_data[:see] << args
226
+ end
227
+
228
+ def example(example, desc_or_options = nil, options = {})
229
+ if desc_or_options.is_a?(Hash)
230
+ options.merge!(desc_or_options)
231
+ elsif !desc_or_options.nil?
232
+ options[:desc] = desc_or_options
233
+ end
234
+ dsl_data[:examples] << { example: example, desc: options[:desc], for: options[:for] }
235
+ end
236
+
237
+ def example_for(method_name, example, desc_or_options = nil, options = {})
238
+ if desc_or_options.is_a?(Hash)
239
+ options.merge!(desc_or_options)
240
+ elsif !desc_or_options.nil?
241
+ options[:desc] = desc_or_options
242
+ end
243
+ dsl_data[:examples] << { example: example, desc: options[:desc], for: method_name }
244
+ end
245
+ end
246
+
247
+ module Klass
248
+ def app_info(app_info)
249
+ dsl_data[:app_info] = app_info
250
+ end
251
+
252
+ def class_description(&block)
253
+ dsl_data = apipie_eval_dsl(&block)
254
+ dsl_data[:dsl_versions] = ApipieDSL.class_versions(class_scope) if dsl_data[:dsl_versions].empty?
255
+ versions = dsl_data[:dsl_versions]
256
+ versions.map do |version|
257
+ ApipieDSL.define_class_description(class_scope, version, dsl_data)
258
+ end
259
+ ApipieDSL.set_class_versions(class_scope, versions)
260
+ end
261
+
262
+ def name(new_name)
263
+ dsl_data[:class_name] = new_name
264
+ end
265
+ alias_method :label, :name
266
+
267
+ def refs(*class_names)
268
+ dsl_data[:refs] = class_names
269
+ end
270
+ alias_method :referenced_on, :refs
271
+
272
+ def sections(sec_or_options, options = {})
273
+ if sec_or_options.is_a?(Hash)
274
+ options.merge!(sec_or_options)
275
+ elsif !sec_or_options.nil?
276
+ options[:only] = sec_or_options
277
+ end
278
+ only = [options[:only]].flatten || ApipieDSL.configuration.sections
279
+ except = if options[:except]
280
+ [options[:except]].flatten
281
+ else
282
+ []
283
+ end
284
+ dsl_data[:sections] = only - except
285
+ end
286
+
287
+ def property(name, retobj_or_options, desc_or_options = nil, options = {}, &block)
288
+ if desc_or_options.is_a?(Hash)
289
+ options.merge!(desc_or_options)
290
+ elsif !desc_or_options.nil?
291
+ options[:desc] = desc_or_options
292
+ end
293
+
294
+ options[:property] = true
295
+ returns = returns(retobj_or_options, desc_or_options, options, &block)
296
+ prop_dsl_data = {
297
+ short_description: options[:desc],
298
+ returns: returns
299
+ }
300
+ dsl_data[:properties] << [name, prop_dsl_data]
301
+ end
302
+ alias_method :prop, :property
303
+
304
+ def define_prop_group(name, &block)
305
+ ApipieDSL.define_prop_group(class_scope, name, &block)
306
+ end
307
+
308
+ # Reuses param group for this method. The definition is looked up
309
+ # in scope of this class. If the group was defined in
310
+ # different class, the second param can be used to specify it.
311
+ def prop_group(name, scope_or_options = nil, options = {})
312
+ if scope_or_options.is_a?(Hash)
313
+ options.merge!(scope_or_options)
314
+ scope = options[:scope]
315
+ else
316
+ scope = scope_or_options
317
+ end
318
+ scope ||= default_prop_group_scope
319
+
320
+ @current_prop_group = {
321
+ scope: scope,
322
+ name: name,
323
+ options: options
324
+ }
325
+ @meta = (options[:meta] || {}).tap { |meta| meta[:class_scope] = class_scope }
326
+ instance_exec(&ApipieDSL.get_prop_group(scope, name))
327
+ ensure
328
+ @current_prop_group = nil
329
+ @meta = nil
330
+ end
331
+
332
+ # Where the group definition should be looked up when no scope
333
+ # given. This is expected to return a class.
334
+ def default_prop_group_scope
335
+ class_scope
336
+ end
337
+ end
338
+
339
+ module Delegatable
340
+ class Delegatee
341
+ include ApipieDSL::Base
342
+ include ApipieDSL::Common
343
+ include ApipieDSL::Klass
344
+ include ApipieDSL::Method
345
+
346
+ attr_reader :class_scope
347
+
348
+ def initialize(class_scope)
349
+ @class_scope = class_scope
350
+ end
351
+
352
+ def with(options = {}, &block)
353
+ @dsl_block = block if block_given?
354
+ @options = options
355
+ self
356
+ end
357
+
358
+ def eval_dsl_for(context)
359
+ case context
360
+ when :method
361
+ apipie_eval_dsl(&@dsl_block)
362
+ when :class
363
+ class_description(&@dsl_block)
364
+ when :param_group
365
+ define_param_group(@options[:name], &@dsl_block)
366
+ when :prop_group
367
+ define_prop_group(@options[:name], &@dsl_block)
368
+ end
369
+ end
370
+
371
+ def self.instance_for(class_scope)
372
+ @instance_for = new(class_scope)
373
+ end
374
+
375
+ def self.instance_reset
376
+ @instance_for = nil
377
+ end
378
+
379
+ def self.instance
380
+ @instance_for
381
+ end
382
+
383
+ def self.extension_data
384
+ @extension_data ||= []
385
+ end
386
+
387
+ # rubocop:disable Metrics/AbcSize
388
+ def self.define_validators(class_scope, method_desc)
389
+ return if method_desc.nil? || ![true, :implicitly, :explicitly].include?(ApipieDSL.configuration.validate)
390
+ return unless [true, :implicitly].include?(ApipieDSL.configuration.validate)
391
+
392
+ old_method = class_scope.instance_method(method_desc.name)
393
+ old_params = old_method.parameters.map { |param| param[1] }
394
+
395
+ class_scope.define_method(method_desc.name) do |*args|
396
+ # apipie validations start
397
+ if ApipieDSL.configuration.validate_value?
398
+ documented_params = ApipieDSL.get_method_description(ApipieDSL.get_class_name(self.class), __method__)
399
+ .param_descriptions
400
+ param_values = old_params.each_with_object({}) { |param, values| values[param] = args.shift }
401
+
402
+ documented_params.each do |param|
403
+ param.validate(param_values[param.name]) if param_values.key?(param.name)
404
+ end
405
+ end
406
+ # apipie validations end
407
+ old_method.bind(self).call(*args)
408
+ end
409
+ end
410
+
411
+ def self.update_method_desc(method_desc, dsl_data)
412
+ method_desc.full_description = dsl_data[:description] || method_desc.full_description
413
+ method_desc.short_description = dsl_data[:short_description] || method_desc.short_description
414
+ if dsl_data[:meta]&.is_a?(Hash)
415
+ method_desc.metadata&.merge!(dsl_data[:meta])
416
+ else
417
+ method_desc.metadata = dsl_data[:meta]
418
+ end
419
+ method_desc.show = dsl_data[:show]
420
+ method_desc.raises += dsl_data[:raises].map do |args|
421
+ ApipieDSL::ExceptionDescription.from_dsl_data(args)
422
+ end
423
+ # Update parameters
424
+ params = dsl_data[:params].map do |args|
425
+ ApipieDSL::ParameterDescription.from_dsl_data(method_desc, args)
426
+ end
427
+ ParameterDescription.merge(method_desc.plain_params, params)
428
+ end
429
+ # rubocop:enable Metrics/AbcSize
430
+ end
431
+ end
432
+
433
+ module Module
434
+ include ApipieDSL::Delegatable
435
+
436
+ def apipie_class(name, desc_or_options = nil, options = {}, &block)
437
+ delegatee = prepare_delegatee(self, desc_or_options, options, &block)
438
+ delegatee.name(name)
439
+ delegatee.with(options).eval_dsl_for(:class)
440
+
441
+ Delegatee.instance_reset
442
+ end
443
+
444
+ def apipie_method(name, desc_or_options = nil, options = {}, &block)
445
+ delegatee = prepare_delegatee(self, desc_or_options, options, &block)
446
+ dsl_data = delegatee.eval_dsl_for(:method)
447
+ class_scope = delegatee.class_scope
448
+ ApipieDSL.remove_method_description(class_scope, dsl_data[:dsl_versions], name)
449
+ ApipieDSL.define_method_description(class_scope, name, dsl_data)
450
+
451
+ Delegatee.instance_reset
452
+ end
453
+
454
+ def apipie(context = :method, desc_or_options = nil, options = {}, &block)
455
+ if desc_or_options.is_a?(Hash)
456
+ options = options.merge(desc_or_options)
457
+ elsif desc_or_options.is_a?(String)
458
+ options[:desc] = desc_or_options
459
+ end
460
+ options[:name] ||= context.to_s
461
+
462
+ block = proc {} unless block_given?
463
+
464
+ delegatee = Delegatee.instance_for(self).with(&block)
465
+ delegatee.short(options[:desc])
466
+ # Don't eval the block, since it will be evaluated after method is defined
467
+ return if context == :method
468
+
469
+ delegatee.with(options).eval_dsl_for(context)
470
+ Delegatee.instance_reset
471
+ end
472
+
473
+ def method_added(method_name)
474
+ super
475
+ if Delegatee.instance.nil?
476
+ # Don't autoload methods if validations are enabled but no apipie block
477
+ # was called
478
+ return if ApipieDSL.configuration.validate?
479
+ # If no apipie block was called but validations are disabled then
480
+ # it's possible to autoload methods
481
+ return unless ApipieDSL.configuration.autoload_methods?
482
+
483
+ apipie
484
+ end
485
+
486
+ instance = Delegatee.instance
487
+ class_scope = instance.class_scope
488
+ # Mainly for Rails in case of constant loading within apipie block.
489
+ # Prevents methods, that are being defined in other class than the class
490
+ # where apipie block was called, to be documented with current apipie block
491
+ return unless class_scope == self
492
+
493
+ dsl_data = instance.eval_dsl_for(:method)
494
+
495
+ ApipieDSL.remove_method_description(class_scope, dsl_data[:dsl_versions], method_name)
496
+ method_desc = ApipieDSL.define_method_description(class_scope, method_name, dsl_data)
497
+
498
+ Delegatee.instance_reset
499
+ Delegatee.define_validators(class_scope, method_desc)
500
+ ensure
501
+ # Reset if we finished method describing in the right class
502
+ Delegatee.instance_reset if class_scope == self
503
+ end
504
+
505
+ private
506
+
507
+ def prepare_delegatee(scope, desc_or_options, options, &block)
508
+ if desc_or_options.is_a?(Hash)
509
+ options = options.merge(desc_or_options)
510
+ elsif desc_or_options.is_a?(String)
511
+ options[:desc] = desc_or_options
512
+ end
513
+
514
+ block = proc {} unless block_given?
515
+ delegatee = Delegatee.instance_for(scope).with(&block)
516
+ delegatee.short(options[:desc])
517
+ delegatee
518
+ end
519
+ end
520
+
521
+ module Class
522
+ include Module
523
+ end
524
+
525
+ module Extension
526
+ include ApipieDSL::Delegatable
527
+
528
+ def apipie(context = :method, desc_or_options = nil, options = {}, &block)
529
+ if desc_or_options.is_a?(Hash)
530
+ options = options.merge(desc_or_options)
531
+ elsif desc_or_options.is_a?(String)
532
+ options[:desc] = desc_or_options
533
+ end
534
+ options[:name] ||= context.to_s
535
+
536
+ block = proc {} unless block_given?
537
+
538
+ delegatee = Delegatee.instance_for(self).with(&block)
539
+ delegatee.short(options[:desc])
540
+ # Don't eval the block, since it will be evaluated after method is defined
541
+ return if context == :method
542
+
543
+ # Currently method extensions are supported only
544
+ Delegatee.instance_reset
545
+ end
546
+
547
+ def apipie_update(context = :method, &block)
548
+ block = proc {} unless block_given?
549
+
550
+ delegatee = Delegatee.instance_for(self).with(&block)
551
+ delegatee.dsl_data[:update_only] = true
552
+ # Don't eval the block, since it will be evaluated after method is defined
553
+ return if context == :method
554
+
555
+ # Currently method extensions are supported only
556
+ Delegatee.instance_reset
557
+ end
558
+
559
+ def prepended(klass)
560
+ super
561
+ Delegatee.extension_data.each do |method_name, dsl_data|
562
+ class_scope = klass
563
+ if dsl_data[:update_only]
564
+ class_name = ApipieDSL.get_class_name(class_scope)
565
+ # Update the old method description
566
+ method_desc = ApipieDSL.get_method_description(class_name, method_name)
567
+ unless method_desc
568
+ raise StandardError, "Could not find method description for #{class_name}##{method_name}. Was the method defined?"
569
+ end
570
+
571
+ Delegatee.update_method_desc(method_desc, dsl_data)
572
+ # Define validators for the new method
573
+ class_scope = self
574
+ else
575
+ ApipieDSL.remove_method_description(class_scope, dsl_data[:dsl_versions], method_name)
576
+ method_desc = ApipieDSL.define_method_description(class_scope, method_name, dsl_data)
577
+ end
578
+ Delegatee.instance_reset
579
+ Delegatee.define_validators(class_scope, method_desc)
580
+ end
581
+ ensure
582
+ Delegatee.instance_reset
583
+ end
584
+
585
+ def method_added(method_name)
586
+ super
587
+ # Methods autoload is not supported for extension modules
588
+ return if Delegatee.instance.nil?
589
+
590
+ dsl_data = Delegatee.instance.eval_dsl_for(:method)
591
+ Delegatee.extension_data << [method_name, dsl_data]
592
+ ensure
593
+ Delegatee.instance_reset
594
+ end
595
+ end
596
+ end