apipie-dsl 2.0.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.
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