brainstem 1.0.0.pre.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,116 @@
1
+ require 'forwardable'
2
+ require 'brainstem/concerns/optional'
3
+ require 'brainstem/concerns/formattable'
4
+
5
+ module Brainstem
6
+ module ApiDocs
7
+ class AbstractCollection
8
+ include Enumerable
9
+ extend Forwardable
10
+ include Concerns::Optional
11
+ include Concerns::Formattable
12
+
13
+ #
14
+ # Creates a new collection with all passed members. Very handy for
15
+ # reduce operations which should return a subset of members but retain
16
+ # the same utility.
17
+ #
18
+ def self.with_members(atlas, *members)
19
+ new(atlas).tap {|n| members.flatten.each { |m| n << m } }
20
+ end
21
+
22
+
23
+ def initialize(atlas, options = {})
24
+ self.atlas = atlas
25
+ self.members = []
26
+ super options
27
+ end
28
+
29
+
30
+ attr_accessor :atlas
31
+
32
+ delegate :find_by_class => :atlas
33
+
34
+
35
+ #
36
+ # Handy accessor for extracting the last member of the collection.
37
+ #
38
+ def last
39
+ members[-1]
40
+ end
41
+
42
+
43
+ #
44
+ # Appends a pre-existing object to the collection.
45
+ #
46
+ def <<(*objects)
47
+ members.push(*objects.flatten)
48
+ end
49
+
50
+
51
+ #
52
+ # Iterates over each controller in the collection.
53
+ #
54
+ def each(&block)
55
+ members.each(&block)
56
+ end
57
+
58
+
59
+ #
60
+ # Returns a map of each member formatted as specified.
61
+ #
62
+ def formatted(format, options = {})
63
+ map { |member| member.formatted_as(format, options) }
64
+ .reject(&:empty?)
65
+ end
66
+
67
+
68
+ #
69
+ # Returns a list of each member's filename.
70
+ #
71
+ # We internally refer to `formatted_with_filename` here because we don't
72
+ # want to include any filenames of empty files (i.e. nodoc).
73
+ #
74
+ def filenames(format)
75
+ formatted_with_filename(format).map { |arr| arr[1] }
76
+ end
77
+
78
+
79
+ #
80
+ # Returns a map of each formatted member and its suggested filename.
81
+ #
82
+ def formatted_with_filename(format, options = {})
83
+ map { |member| [
84
+ member.formatted_as(format, options),
85
+ member.suggested_filename(format)
86
+ ] }
87
+ .reject { |(buffer, _)| buffer.empty? }
88
+ end
89
+
90
+
91
+ def each_formatted_with_filename(format, options = {}, &block)
92
+ formatted_with_filename(format, options)
93
+ .each { |args| block.call(*args) }
94
+ end
95
+
96
+
97
+ def each_formatted(format, options = {}, &block)
98
+ formatted(format, options)
99
+ .each { |args| block.call(*args) }
100
+ end
101
+
102
+
103
+ def each_filename(format, &block)
104
+ filenames(format).each { |args| block.call(*args) }
105
+ end
106
+
107
+
108
+
109
+ #########################################################################
110
+ protected
111
+ #########################################################################
112
+
113
+ attr_accessor :members
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,158 @@
1
+ require 'forwardable'
2
+ require 'brainstem/api_docs/resolver'
3
+ require 'brainstem/api_docs/exceptions'
4
+ require 'brainstem/api_docs/endpoint_collection'
5
+ require 'brainstem/api_docs/controller_collection'
6
+ require 'brainstem/api_docs/presenter_collection'
7
+ require 'brainstem/concerns/optional'
8
+
9
+ #
10
+ #
11
+ # The Atlas is an object that makes the information from an introspector
12
+ # available in formatted and raw format.
13
+ #
14
+ module Brainstem
15
+ module ApiDocs
16
+ class Atlas
17
+ extend Forwardable
18
+ include Concerns::Optional
19
+
20
+
21
+ def initialize(introspector, options = {})
22
+ self.endpoints = EndpointCollection.new(self)
23
+ self.controllers = ControllerCollection.new(self)
24
+ self.presenters = ::Brainstem::ApiDocs::PresenterCollection.new(self)
25
+ self.resolver = Resolver.new(self)
26
+
27
+ self.controller_matches = []
28
+ self.introspector = introspector
29
+
30
+ super options
31
+
32
+ parse_routes!
33
+ extract_presenters!
34
+ validate!
35
+ end
36
+
37
+
38
+ attr_accessor :endpoints,
39
+ :controllers,
40
+ :presenters,
41
+ :resolver
42
+
43
+
44
+ delegate :find_by_class => :resolver
45
+
46
+
47
+ #########################################################################
48
+ private
49
+ #########################################################################
50
+
51
+
52
+ #
53
+ # Lists valid options that may be passed on instantiation.
54
+ #
55
+ def valid_options
56
+ super | [ :controller_matches ]
57
+ end
58
+
59
+
60
+ #
61
+ # Ensures the atlas is valid before allowing consumers to make requests
62
+ # of it.
63
+ #
64
+ def validate!
65
+ raise InvalidAtlasError, "Atlas is not valid." unless valid?
66
+ end
67
+
68
+
69
+ #
70
+ # Set and read the introspector.
71
+ #
72
+ attr_accessor :introspector
73
+
74
+
75
+ #
76
+ # Holds +Regexp+s which each controller name must match in order to be
77
+ # included in the list of endpoints.
78
+ #
79
+ attr_accessor :controller_matches
80
+
81
+
82
+ #
83
+ # Returns a list of all routes that pass the user's filtering.
84
+ #
85
+ def allowed_routes
86
+ introspector.routes.keep_if(&method(:allow_route?))
87
+ end
88
+
89
+
90
+ #
91
+ # Constructs +Endpoint+ and +Controller wrappers per route.
92
+ #
93
+ def parse_routes!
94
+ allowed_routes.each do |route|
95
+ if (endpoint = endpoints.find_from_route(route))
96
+ endpoint.merge_http_methods!(route[:http_methods])
97
+ else
98
+ controller = controllers.find_or_create_from_route(route)
99
+ endpoint = endpoints.create_from_route(route, controller)
100
+
101
+ controller.add_endpoint(endpoint)
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+ #
108
+ # Extracts declared presents for each endpoint and converts it into a
109
+ # Presenter wrapper object.
110
+ #
111
+ def extract_presenters!
112
+ valid_presenter_pairs.each do |target_class, const|
113
+ presenter = presenters.find_or_create_from_presenter_collection(target_class, const)
114
+
115
+ endpoints
116
+ .select do |ep|
117
+ declared_presented_class = ep.declared_presented_class
118
+ !declared_presented_class.nil? && declared_presented_class.to_s == target_class
119
+ end
120
+ .each {|ep| ep.presenter = presenter }
121
+ end
122
+ end
123
+
124
+
125
+ #
126
+ # Returns a list of valid +target_class_to_s => PresenterConst+ pairs,
127
+ # determining validity by whether they descend from the base presenter.
128
+ #
129
+ # @return [Hash{String => Class}] valid pairs
130
+ #
131
+ def valid_presenter_pairs
132
+ Brainstem.presenter_collection.presenters.select do |target_class, const|
133
+ introspector.presenters.include? const
134
+ end
135
+ end
136
+
137
+
138
+ #
139
+ # Whether this Atlas is valid (i.e. if it has at least one endpoint).
140
+ #
141
+ # @return [Boolean] if the atlas is valid
142
+ #
143
+ def valid?
144
+ endpoints.count > 0
145
+ end
146
+
147
+
148
+ #
149
+ # Returns whether a route's controller passes the limiting regexp passed to the
150
+ # generation command.
151
+ #
152
+ def allow_route?(route)
153
+ introspector.controllers.include?(route[:controller]) &&
154
+ controller_matches.all? { |regexp| route[:controller].to_s =~ regexp }
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,167 @@
1
+ require 'brainstem/api_docs/introspectors/rails_introspector'
2
+ require 'brainstem/concerns/optional'
3
+ require 'brainstem/api_docs/atlas'
4
+ require 'active_support/core_ext/hash/slice'
5
+
6
+ #
7
+ # This class describes the main API surface for generating API documentation.
8
+ # The command-line utility provided with Brainstem is basically just a thin
9
+ # veneer over top of this class.
10
+ #
11
+ # You can use this to programmatically generate the API docs, or to browse them
12
+ # while inside a REPL.
13
+ #
14
+ module Brainstem
15
+ module ApiDocs
16
+ class Builder
17
+ include Brainstem::Concerns::Optional
18
+
19
+
20
+ def valid_options
21
+ [
22
+ :introspector_method,
23
+ :atlas_method,
24
+
25
+ :args_for_introspector,
26
+ :args_for_atlas
27
+ ]
28
+ end
29
+
30
+
31
+ #
32
+ # @param [Hash] options
33
+ # @option options [Proc] :introspector_method Proc of arity one that
34
+ # returns an Introspector (an object that introspects into
35
+ # the host application, seeking its routes, controllers, and
36
+ # presenters).
37
+ # @option options [Hash] :args_for_introspector Additional arguments to
38
+ # be passed to the introspector on creation.
39
+ # @option options [Hash] :args_for_atlas Additional arguments to be passed
40
+ # to the atlas on creation.
41
+ # @option options [Proc,Object] :introspector_method A method that
42
+ # returns an introspector when called.
43
+ # @option options [Proc,Object] :atlas_method A method that returns an Atlas-like
44
+ # object when called.
45
+ #
46
+ # @see Brainstem::ApiDocs::Introspectors::AbstractIntrospector
47
+ # @see Brainstem::ApiDocs::Introspectors::RailsIntrospector
48
+ #
49
+ def initialize(options = {})
50
+ super
51
+
52
+ build_introspector!
53
+ build_atlas!
54
+ end
55
+
56
+
57
+ #
58
+ # Builds an introspector.
59
+ #
60
+ def build_introspector!
61
+ self.introspector = introspector_method.call(args_for_introspector)
62
+ end
63
+
64
+
65
+ #
66
+ # Builds an atlas.
67
+ #
68
+ def build_atlas!
69
+ self.atlas = atlas_method.call(introspector, args_for_atlas)
70
+ end
71
+
72
+
73
+ #
74
+ # Arguments to be passed to the introspector on creation.
75
+ #
76
+ # @see Brainstem::ApiDocs::Introspectors::AbstractIntrospector
77
+ # @see Brainstem::ApiDocs::Introspectors::RailsIntrospector
78
+ #
79
+ def args_for_introspector
80
+ @args_for_introspector ||= {}
81
+ end
82
+
83
+
84
+ #
85
+ # Allows passing args to the introspector if - for example - you are
86
+ # using a custom base controller class.
87
+ #
88
+ attr_writer :args_for_introspector
89
+
90
+
91
+ #
92
+ # Arguments to be passed to the atlas on creation.
93
+ #
94
+ # @see Brainstem::ApiDocs::Atlas
95
+ #
96
+ def args_for_atlas
97
+ @args_for_atlas ||= {}
98
+ end
99
+
100
+
101
+ #
102
+ # Allows passing args to the atlas if - for example - you are
103
+ # specifying match terms for the allowable controller set.
104
+ #
105
+ attr_writer :args_for_atlas
106
+
107
+
108
+ #
109
+ # A method which returns the introspector which extracts information
110
+ # about the Brainstem-powered API from the host application.
111
+ #
112
+ # Stored as a proc because it's impossible to inject an instantiated
113
+ # object and have it receive args from this class. This is less important
114
+ # in this specific circumstance but is kept for uniformity with
115
+ # +atlas_method+.
116
+ #
117
+ # @return [Proc] a proc of arity 1 which takes an options hash and
118
+ # returns an introspector
119
+ #
120
+ def introspector_method
121
+ @introspector_method ||=
122
+ Introspectors::RailsIntrospector.method(:with_loaded_environment)
123
+ end
124
+
125
+
126
+ #
127
+ # Allows setting the introspector_method if - for example - you are using
128
+ # Brainstem on a Sinatra app and you need to customize how lookups for
129
+ # presenters, controllers, and routes are performed.
130
+ #
131
+ attr_writer :introspector_method
132
+
133
+
134
+ #
135
+ # Holds a reference to the constructed introspector.
136
+ #
137
+ attr_accessor :introspector
138
+
139
+
140
+ #
141
+ # A proc of arity 1..2 which takes an introspector and optional options,
142
+ # and which returns a new Atlas.
143
+ #
144
+ # Passed an introspector.
145
+ #
146
+ # @return [Proc] a method to return an atlas
147
+ #
148
+ def atlas_method
149
+ @atlas_method ||= Atlas.method(:new)
150
+ end
151
+
152
+
153
+ #
154
+ # Allows setting the introspector_method if - for example - you are using
155
+ # an alternative formatter and the requisite information is not present
156
+ # in the +Endpoint+ objects.
157
+ #
158
+ attr_writer :atlas_method
159
+
160
+
161
+ #
162
+ # Holds a reference to the constructed atlas.
163
+ #
164
+ attr_accessor :atlas
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,122 @@
1
+ require 'brainstem/concerns/optional'
2
+ require 'brainstem/concerns/formattable'
3
+ require 'active_support/inflector'
4
+ require 'brainstem/api_docs/endpoint_collection'
5
+ require 'forwardable'
6
+
7
+ module Brainstem
8
+ module ApiDocs
9
+ class Controller
10
+ extend Forwardable
11
+ include Concerns::Optional
12
+ include Concerns::Formattable
13
+
14
+
15
+ def initialize(atlas, options = {})
16
+ self.atlas = atlas
17
+ self.endpoints = EndpointCollection.new(atlas)
18
+ super options
19
+ yield self if block_given?
20
+ end
21
+
22
+
23
+ attr_accessor :const,
24
+ :name,
25
+ :endpoints,
26
+ :filename_pattern,
27
+ :atlas
28
+
29
+
30
+ attr_writer :filename_pattern,
31
+ :filename_link_pattern
32
+
33
+
34
+ def valid_options
35
+ super | [
36
+ :const,
37
+ :name,
38
+ :formatters,
39
+ :filename_pattern,
40
+ :filename_link_pattern
41
+ ]
42
+ end
43
+
44
+
45
+ #
46
+ # Adds an existing endpoint to its endpoint collection.
47
+ #
48
+ def add_endpoint(endpoint)
49
+ self.endpoints << endpoint
50
+ end
51
+
52
+
53
+ def suggested_filename(format)
54
+ filename_pattern
55
+ .gsub('{{namespace}}', const.to_s.deconstantize.underscore)
56
+ .gsub('{{name}}', name.to_s.split("/").last)
57
+ .gsub('{{extension}}', extension)
58
+ end
59
+
60
+
61
+ def suggested_filename_link(format)
62
+ filename_link_pattern
63
+ .gsub('{{name}}', name.to_s)
64
+ .gsub('{{extension}}', extension)
65
+ end
66
+
67
+
68
+ def extension
69
+ @extension ||= Brainstem::ApiDocs.output_extension
70
+ end
71
+
72
+
73
+ def filename_pattern
74
+ @filename_pattern ||= Brainstem::ApiDocs.controller_filename_pattern
75
+ end
76
+
77
+
78
+ def filename_link_pattern
79
+ @filename_link_pattern ||= Brainstem::ApiDocs.controller_filename_link_pattern
80
+ end
81
+
82
+
83
+ delegate :configuration => :const
84
+ delegate :find_by_class => :atlas
85
+
86
+
87
+ def default_configuration
88
+ configuration[:_default]
89
+ end
90
+
91
+
92
+ def nodoc?
93
+ default_configuration[:nodoc]
94
+ end
95
+
96
+
97
+ def title
98
+ contextual_documentation(:title) || const.to_s.demodulize
99
+ end
100
+
101
+
102
+ def description
103
+ contextual_documentation(:description) || ""
104
+ end
105
+
106
+
107
+ #
108
+ # Returns a key if it exists and is documentable.
109
+ #
110
+ def contextual_documentation(key)
111
+ default_configuration.has_key?(key) &&
112
+ !default_configuration[key][:nodoc] &&
113
+ default_configuration[key][:info]
114
+ end
115
+
116
+
117
+ def valid_sorted_endpoints
118
+ endpoints.sorted_with_actions_in_controller(const)
119
+ end
120
+ end
121
+ end
122
+ end