brainstem 1.0.0.pre.1 → 1.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +383 -32
  5. data/bin/brainstem +6 -0
  6. data/brainstem.gemspec +2 -0
  7. data/docs/api_doc_generator.markdown +175 -0
  8. data/docs/brainstem_executable.markdown +32 -0
  9. data/docs/docgen.png +0 -0
  10. data/docs/docgen_ascii.txt +63 -0
  11. data/docs/executable.png +0 -0
  12. data/docs/executable_ascii.txt +10 -0
  13. data/lib/brainstem/api_docs.rb +146 -0
  14. data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
  15. data/lib/brainstem/api_docs/atlas.rb +158 -0
  16. data/lib/brainstem/api_docs/builder.rb +167 -0
  17. data/lib/brainstem/api_docs/controller.rb +122 -0
  18. data/lib/brainstem/api_docs/controller_collection.rb +40 -0
  19. data/lib/brainstem/api_docs/endpoint.rb +234 -0
  20. data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
  21. data/lib/brainstem/api_docs/exceptions.rb +8 -0
  22. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
  23. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
  24. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
  25. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
  26. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
  27. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
  28. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
  29. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
  30. data/lib/brainstem/api_docs/presenter.rb +225 -0
  31. data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
  32. data/lib/brainstem/api_docs/resolver.rb +73 -0
  33. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
  34. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
  35. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
  36. data/lib/brainstem/cli.rb +146 -0
  37. data/lib/brainstem/cli/abstract_command.rb +97 -0
  38. data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
  39. data/lib/brainstem/concerns/controller_dsl.rb +300 -0
  40. data/lib/brainstem/concerns/controller_param_management.rb +30 -9
  41. data/lib/brainstem/concerns/formattable.rb +38 -0
  42. data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
  43. data/lib/brainstem/concerns/optional.rb +43 -0
  44. data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
  45. data/lib/brainstem/controller_methods.rb +6 -3
  46. data/lib/brainstem/dsl/association.rb +6 -3
  47. data/lib/brainstem/dsl/associations_block.rb +6 -3
  48. data/lib/brainstem/dsl/base_block.rb +2 -4
  49. data/lib/brainstem/dsl/conditional.rb +7 -3
  50. data/lib/brainstem/dsl/conditionals_block.rb +4 -4
  51. data/lib/brainstem/dsl/configuration.rb +184 -8
  52. data/lib/brainstem/dsl/field.rb +6 -3
  53. data/lib/brainstem/dsl/fields_block.rb +2 -3
  54. data/lib/brainstem/help_text.txt +8 -0
  55. data/lib/brainstem/presenter.rb +27 -6
  56. data/lib/brainstem/presenter_validator.rb +5 -2
  57. data/lib/brainstem/time_classes.rb +1 -1
  58. data/lib/brainstem/version.rb +1 -1
  59. data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
  60. data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
  61. data/spec/brainstem/api_docs/builder_spec.rb +100 -0
  62. data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
  63. data/spec/brainstem/api_docs/controller_spec.rb +225 -0
  64. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
  65. data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
  66. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
  67. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
  68. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
  69. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
  70. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
  71. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
  72. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
  73. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
  74. data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
  75. data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
  76. data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
  77. data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
  78. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
  79. data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
  80. data/spec/brainstem/api_docs_spec.rb +58 -0
  81. data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
  82. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
  83. data/spec/brainstem/cli_spec.rb +67 -0
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
  85. data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
  86. data/spec/brainstem/concerns/formattable_spec.rb +30 -0
  87. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
  88. data/spec/brainstem/concerns/optional_spec.rb +48 -0
  89. data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
  90. data/spec/brainstem/dsl/association_spec.rb +18 -2
  91. data/spec/brainstem/dsl/conditional_spec.rb +25 -2
  92. data/spec/brainstem/dsl/configuration_spec.rb +1 -1
  93. data/spec/brainstem/dsl/field_spec.rb +18 -2
  94. data/spec/brainstem/presenter_collection_spec.rb +10 -2
  95. data/spec/brainstem/presenter_spec.rb +32 -0
  96. data/spec/brainstem/presenter_validator_spec.rb +12 -7
  97. data/spec/dummy/rails.rb +49 -0
  98. data/spec/shared/atlas_taker.rb +18 -0
  99. data/spec/shared/formattable.rb +14 -0
  100. data/spec/spec_helper.rb +2 -0
  101. data/spec/spec_helpers/db.rb +1 -1
  102. data/spec/spec_helpers/presenters.rb +20 -14
  103. metadata +106 -6
@@ -0,0 +1,40 @@
1
+ require 'brainstem/api_docs/abstract_collection'
2
+ require 'brainstem/api_docs/controller'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ class ControllerCollection < AbstractCollection
7
+
8
+
9
+ #
10
+ # Creates a new controller from a route object and appends it to the
11
+ # collection.
12
+ def create_from_route(route)
13
+ Controller.new(atlas,
14
+ const: route[:controller],
15
+ name: route[:controller_name].split("/").last
16
+ ).tap { |controller| self.<< controller }
17
+ end
18
+
19
+
20
+ #
21
+ # Finds a controller from a route object.
22
+ #
23
+ def find_by_route(route)
24
+ find do |controller|
25
+ controller.const == route[:controller]
26
+ end
27
+ end
28
+
29
+
30
+ #
31
+ # Finds a controller from a route object or creates one if it does not
32
+ # exist.
33
+ #
34
+ def find_or_create_from_route(route)
35
+ find_by_route(route) || create_from_route(route)
36
+ end
37
+ alias_method :find_or_create_by_route, :find_or_create_from_route
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,234 @@
1
+ require 'brainstem/concerns/optional'
2
+ require 'active_support/core_ext/string/inflections'
3
+ require 'brainstem/concerns/formattable'
4
+ require 'forwardable'
5
+ require 'pathname'
6
+
7
+ #
8
+ # Per-endpoint holder for presenter and controller information.
9
+ #
10
+ module Brainstem
11
+ module ApiDocs
12
+ class Endpoint
13
+ extend Forwardable
14
+ include Concerns::Optional
15
+ include Concerns::Formattable
16
+
17
+ ACTION_ORDER = %w(index show create update delete)
18
+
19
+ def valid_options
20
+ super | [
21
+ :path,
22
+ :http_methods,
23
+ :controller,
24
+ :controller_name,
25
+ :action,
26
+ :presenter
27
+ ]
28
+ end
29
+
30
+
31
+ def initialize(atlas, options = {})
32
+ self.atlas = atlas
33
+ super options
34
+ yield self if block_given?
35
+ end
36
+
37
+
38
+ attr_accessor :path,
39
+ :http_methods,
40
+ :controller,
41
+ :controller_name,
42
+ :action,
43
+ :atlas
44
+
45
+
46
+ #
47
+ # Pretty prints each endpoint.
48
+ #
49
+ def to_s
50
+ "#{http_methods.join(" / ")} #{path}"
51
+ end
52
+
53
+
54
+ #
55
+ # Merges http methods (for de-duping Rails' routes).
56
+ #
57
+ def merge_http_methods!(methods)
58
+ self.http_methods |= methods
59
+ end
60
+
61
+
62
+ #
63
+ # Sorts this endpoint in comparison to other endpoints.
64
+ #
65
+ # Follows a manually defined order of precedence (+ACTION_ORDER+). The
66
+ # earlier an action name appears on the list, the earlier it is sorted.
67
+ #
68
+ # In the event that an action is not on the list, it is sorted after any
69
+ # listed routes, and then sorted alphabetically among the remainder.
70
+ #
71
+ def <=>(other)
72
+
73
+ # Any unordered routes are assigned an index of +ACTION_ORDER.count+.
74
+ ordered_actions_count = ACTION_ORDER.count
75
+ own_action_priority = ACTION_ORDER.index(action.to_s) || ordered_actions_count
76
+ other_action_priority = ACTION_ORDER.index(other.action.to_s) || ordered_actions_count
77
+
78
+ # If the priorities are unequal (i.e. one or both are named; duplicates
79
+ # should not exist for named routes):
80
+ if own_action_priority != other_action_priority
81
+
82
+ # Flip order if this action's priority is greater than the other.
83
+ # other_action_priority <=> own_action_priority
84
+ own_action_priority <=> other_action_priority
85
+
86
+ # If the priorities are equal, i.e. both not in the list:
87
+ else
88
+
89
+ # Flip order if this action's name is alphabetically later.
90
+ action.to_s <=> other.action.to_s
91
+ end
92
+ end
93
+
94
+
95
+ ################################################################################
96
+ # Derived fields
97
+ ################################################################################
98
+
99
+ #
100
+ # Is the entire endpoint undocumentable?
101
+ #
102
+ def nodoc?
103
+ action_configuration[:nodoc]
104
+ end
105
+
106
+
107
+ def title
108
+ @title ||= contextual_documentation(:title) || action.to_s.humanize
109
+ end
110
+
111
+
112
+ def description
113
+ @description ||= contextual_documentation(:description) || ""
114
+ end
115
+
116
+
117
+ def valid_params
118
+ @valid_params ||= key_with_default_fallback(:valid_params)
119
+ end
120
+
121
+
122
+ #
123
+ # Returns a hash of all root-level params with values of an array of
124
+ # the parameters nested underneath, or +nil+ in the event they are
125
+ # root-level non-nested params.
126
+ #
127
+ # @return [Hash{Symbol => Array,NilClass}] root keys and the keys
128
+ # nested under them, or nil if not a nested param.
129
+ #
130
+ def root_param_keys
131
+ @root_param_keys ||= begin
132
+ valid_params.to_h
133
+ .inject({}) do |hsh, (field_name, data)|
134
+ next hsh if data[:nodoc]
135
+
136
+ if data.has_key?(:root)
137
+ key = data[:root].respond_to?(:call) ? data[:root].call(controller.const) : data[:root]
138
+ (hsh[key] ||= []) << field_name
139
+ else
140
+ hsh[field_name] = nil
141
+ end
142
+
143
+ hsh
144
+ end
145
+ end
146
+ end
147
+
148
+
149
+ #
150
+ # Retrieves the +presents+ settings.
151
+ #
152
+ def valid_presents
153
+ key_with_default_fallback(:presents) || {}
154
+ end
155
+
156
+
157
+ #
158
+ # Used to retrieve this endpoint's presenter constant.
159
+ #
160
+ def declared_presented_class
161
+ valid_presents.has_key?(:target_class) &&
162
+ !valid_presents[:nodoc] &&
163
+ valid_presents[:target_class]
164
+ end
165
+
166
+
167
+ #
168
+ # Stores the +ApiDocs::Presenter+ object associated with this endpoint.
169
+ #
170
+ attr_accessor :presenter
171
+
172
+
173
+ ################################################################################
174
+ # Configuration Helpers
175
+ #################################################################################
176
+
177
+ #
178
+ # Helper for retrieving configuration from its controller.
179
+ #
180
+ delegate :configuration => :controller
181
+ delegate :find_by_class => :atlas
182
+
183
+
184
+ #
185
+ # Helper for retrieving action-specific configuration from the controller.
186
+ #
187
+ def action_configuration
188
+ configuration[action] || {}
189
+ end
190
+
191
+
192
+ #
193
+ # Retrieves default action context from the controller.
194
+ #
195
+ def default_configuration
196
+ configuration[:_default] || {}
197
+ end
198
+
199
+
200
+ #
201
+ # Returns a key if it exists and is documentable
202
+ #
203
+ def contextual_documentation(key)
204
+ action_configuration.has_key?(key) &&
205
+ !action_configuration[key][:nodoc] &&
206
+ action_configuration[key][:info]
207
+ end
208
+
209
+
210
+ def key_with_default_fallback(key)
211
+ action_configuration[key] || default_configuration[key]
212
+ end
213
+
214
+
215
+ def presenter_title
216
+ presenter && presenter.title
217
+ end
218
+
219
+
220
+ #
221
+ # Returns the relative path from this endpoint's controller to this
222
+ # endpoint's declared presenter.
223
+ #
224
+ def relative_presenter_path_from_controller(format)
225
+ if presenter && controller
226
+ controller_path = Pathname.new(File.dirname(controller.suggested_filename_link(format)))
227
+ presenter_path = Pathname.new(presenter.suggested_filename_link(format))
228
+
229
+ presenter_path.relative_path_from(controller_path).to_s
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,58 @@
1
+ require 'brainstem/api_docs/abstract_collection'
2
+ require 'brainstem/api_docs/endpoint'
3
+ require 'brainstem/concerns/formattable'
4
+
5
+ module Brainstem
6
+ module ApiDocs
7
+ class EndpointCollection < AbstractCollection
8
+ include Concerns::Formattable
9
+
10
+
11
+ def find_from_route(route)
12
+ find do |endpoint|
13
+ endpoint.path == route[:path] &&
14
+ endpoint.controller.const == route[:controller] &&
15
+ endpoint.action == route[:action]
16
+ end
17
+ end
18
+
19
+ alias_method :find_by_route, :find_from_route
20
+
21
+
22
+ def create_from_route(route, controller)
23
+ Endpoint.new(atlas) do |ep|
24
+ ep.path = route[:path]
25
+ ep.http_methods = route[:http_methods]
26
+ ep.controller = controller
27
+ ep.controller_name = route[:controller_name]
28
+ ep.action = route[:action]
29
+ end.tap { |endpoint| self.<< endpoint }
30
+ end
31
+
32
+
33
+ def only_documentable
34
+ self.class.with_members(atlas, reject(&:nodoc?))
35
+ end
36
+
37
+
38
+ def with_declared_presented_class
39
+ self.class.with_members(atlas, reject { |m| m.declared_presented_class.nil? })
40
+ end
41
+
42
+
43
+ def sorted
44
+ self.class.with_members(atlas, sort)
45
+ end
46
+
47
+
48
+ def with_actions_in_controller(const)
49
+ self.class.with_members(atlas, reject { |m| !const.method_defined?(m.action) })
50
+ end
51
+
52
+
53
+ def sorted_with_actions_in_controller(const)
54
+ with_actions_in_controller(const).sorted
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,8 @@
1
+ module Brainstem
2
+ module ApiDocs
3
+ class IncorrectIntrospectorForAppException < StandardError; end
4
+ class InvalidIntrospectorError < StandardError; end
5
+ class InvalidAtlasError < StandardError; end
6
+ class NoSinkSpecifiedException < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,64 @@
1
+ require 'brainstem/api_docs'
2
+ require 'brainstem/api_docs/exceptions'
3
+ require 'brainstem/concerns/optional'
4
+
5
+ #
6
+ # A formatter is fundamentally just a function that, when +#call+ed, accepts an
7
+ # atlas and returns the whole or portion of its data in a readable format.
8
+ #
9
+ # There are (informally) several types of formatters, and you should select
10
+ # among them appropriately when developing your own:
11
+ #
12
+ # 1. Entity
13
+ #
14
+ # Turns a single data entity into its formatted version. For example, if
15
+ # formatting a Post object, call this formatter a
16
+ # +<Format>PostFormatter+.
17
+ #
18
+ # 2. Collection
19
+ #
20
+ # Turns a collection of formatted data entities into a single unit.
21
+ # Usually this will involve concatenating and/or rejecting inappropriate
22
+ # output. For example, if you have a collection of Markdown strings from
23
+ # the MarkdownPostFormatter, you might want to generate the whole section
24
+ # of documentation by using a +MarkdownPostCollectionFormatter+.
25
+ #
26
+ # 3. Aggregate
27
+ #
28
+ # A combination of entity and collection formatters. Knows how to turn a
29
+ # collection of unformatted entities into a single formatted document.
30
+ # Not as composable as the two used seperately, but far simpler. This
31
+ # should be named +MarkdownAggregatePostFormatter+.
32
+ #
33
+ # At the time of writing, there is no actual requirement to use these naming
34
+ # conventions, but it is highly recommended for clarity of purpose.
35
+ #
36
+ module Brainstem
37
+ module ApiDocs
38
+ module Formatters
39
+ class AbstractFormatter
40
+ include Concerns::Optional
41
+
42
+ #
43
+ # Convenience class method for instantiating and calling.
44
+ #
45
+ def self.call(*args)
46
+ new(*args).call
47
+ end
48
+
49
+
50
+ def initialize(*args)
51
+ super args.last || {}
52
+ end
53
+
54
+
55
+ #
56
+ # Override to transform atlas data into serialized format.
57
+ #
58
+ def call
59
+ raise NotImplementedError
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,76 @@
1
+ require 'brainstem/api_docs/formatters/abstract_formatter'
2
+ require 'brainstem/api_docs/formatters/markdown/helper'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ module Formatters
7
+ module Markdown
8
+ class ControllerFormatter < AbstractFormatter
9
+ include Helper
10
+
11
+ #
12
+ # Declares the options that are permissable to set on this instance.
13
+ #
14
+ def valid_options
15
+ super | [
16
+ :include_actions
17
+ ]
18
+ end
19
+
20
+
21
+ attr_accessor :controller,
22
+ :include_actions,
23
+ :output
24
+
25
+ alias_method :include_actions?, :include_actions
26
+
27
+
28
+ def initialize(controller, options = {})
29
+ self.controller = controller
30
+ self.output = ""
31
+ self.include_actions = false
32
+ super options
33
+ end
34
+
35
+
36
+ def call
37
+ return output if controller.nodoc?
38
+ format_title!
39
+ format_description!
40
+ format_actions!
41
+ end
42
+
43
+
44
+ #####################################################################
45
+ private
46
+ #####################################################################
47
+
48
+
49
+ def format_title!
50
+ output << md_h2(controller.title)
51
+ end
52
+
53
+
54
+ def format_description!
55
+ output << md_p(controller.description) unless controller.description.empty?
56
+ end
57
+
58
+
59
+ def format_actions!
60
+ return unless include_actions?
61
+
62
+ output << md_h3("Endpoints")
63
+
64
+ output << controller.valid_sorted_endpoints
65
+ .formatted_as(:markdown, zero_text: "No endpoints were found.")
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+
75
+ Brainstem::ApiDocs::FORMATTERS[:controller][:markdown] = \
76
+ Brainstem::ApiDocs::Formatters::Markdown::ControllerFormatter.method(:call)