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,73 @@
1
+ require 'brainstem/api_docs/formatters/abstract_formatter'
2
+ require 'brainstem/api_docs/formatters/markdown/helper'
3
+
4
+ #
5
+ # Responsible for formatting each endpoint.
6
+ #
7
+ module Brainstem
8
+ module ApiDocs
9
+ module Formatters
10
+ module Markdown
11
+ class EndpointCollectionFormatter < AbstractFormatter
12
+ include Helper
13
+
14
+ #####################################################################
15
+ # Public API
16
+ #####################################################################
17
+
18
+ def initialize(endpoint_collection, options = {})
19
+ self.endpoint_collection = endpoint_collection
20
+ self.output = ""
21
+ self.zero_text = "No endpoints were found."
22
+
23
+ super options
24
+ end
25
+
26
+
27
+ attr_accessor :endpoint_collection,
28
+ :zero_text,
29
+ :output
30
+
31
+
32
+ def valid_options
33
+ super | [ :zero_text ]
34
+ end
35
+
36
+
37
+ def call
38
+ format_endpoints!
39
+ format_zero_text! if output.empty?
40
+ output
41
+ end
42
+
43
+
44
+ #####################################################################
45
+ private
46
+ #####################################################################
47
+
48
+ def all_formatted_endpoints
49
+ endpoint_collection
50
+ .only_documentable
51
+ .formatted(:markdown)
52
+ .reject(&:empty?)
53
+ end
54
+
55
+
56
+ def format_endpoints!
57
+ output << all_formatted_endpoints.join(md_hr)
58
+ end
59
+
60
+
61
+ def format_zero_text!
62
+ output << zero_text
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+
71
+
72
+ Brainstem::ApiDocs::FORMATTERS[:endpoint_collection][:markdown] = \
73
+ Brainstem::ApiDocs::Formatters::Markdown::EndpointCollectionFormatter.method(:call)
@@ -0,0 +1,169 @@
1
+ require 'brainstem/api_docs/formatters/abstract_formatter'
2
+ require 'brainstem/api_docs/formatters/markdown/helper'
3
+ require 'active_support/core_ext/hash/except'
4
+ require 'forwardable'
5
+
6
+ #
7
+ # Responsible for formatting each endpoint.
8
+ #
9
+ module Brainstem
10
+ module ApiDocs
11
+ module Formatters
12
+ module Markdown
13
+ class EndpointFormatter < AbstractFormatter
14
+ include Helper
15
+ extend Forwardable
16
+
17
+
18
+ ################################################################################
19
+ # Public API
20
+ ################################################################################
21
+
22
+
23
+ def initialize(endpoint, options = {})
24
+ self.endpoint = endpoint
25
+ self.output = ""
26
+
27
+ super options
28
+ end
29
+
30
+
31
+ attr_accessor :endpoint,
32
+ :output
33
+
34
+
35
+ def call
36
+ return output if endpoint.nodoc?
37
+
38
+ format_title!
39
+ format_description!
40
+ format_endpoint!
41
+ format_params!
42
+ format_presents!
43
+
44
+ output
45
+ end
46
+
47
+
48
+ ################################################################################
49
+ private
50
+ ################################################################################
51
+
52
+ delegate :controller => :endpoint
53
+
54
+
55
+ #
56
+ # Formats the title as given, falling back to the humanized action
57
+ # name.
58
+ #
59
+ def format_title!
60
+ output << md_h4(endpoint.title)
61
+ end
62
+
63
+
64
+ #
65
+ # Formats the description if given.
66
+ #
67
+ def format_description!
68
+ output << md_p(endpoint.description) unless endpoint.description.empty?
69
+ end
70
+
71
+
72
+ #
73
+ # Formats the actual URI and stated HTTP methods.
74
+ #
75
+ def format_endpoint!
76
+ http_methods = endpoint.http_methods.map(&:upcase).join(" / ")
77
+ path = endpoint.path.gsub('(.:format)', '.json')
78
+ output << md_code("#{http_methods} #{path}")
79
+ end
80
+
81
+
82
+ #
83
+ # Formats each parameter.
84
+ #
85
+ def format_params!
86
+ return unless endpoint.root_param_keys.any?
87
+
88
+ output << md_h5("Valid Parameters")
89
+ output << md_ul do
90
+ endpoint.root_param_keys.inject("") do |buff, (root_param_name, child_keys)|
91
+ if child_keys.nil?
92
+ buff += parameter_with_indent_level(
93
+ root_param_name,
94
+ endpoint.valid_params[root_param_name],
95
+ 0
96
+ )
97
+ else
98
+ text = md_inline_code(root_param_name) + "\n"
99
+
100
+ child_keys.each do |param_name|
101
+ text += parameter_with_indent_level(
102
+ param_name,
103
+ endpoint.valid_params[param_name],
104
+ 1
105
+ )
106
+ end
107
+
108
+ buff << md_li(text)
109
+ end
110
+
111
+ buff
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+ #
118
+ # Formats a given parameter with a variable indent level. Useful for
119
+ # indifferently formatting root / nested parameters.
120
+ #
121
+ # @param [String] name the param name
122
+ # @param [Hash] options information pertinent to the param
123
+ # @option [Boolean] options :legacy
124
+ # @option [Boolean] options :recursive
125
+ # @option [String,Symbol] options :only Deprecated: use +actions+
126
+ # block instead
127
+ # @option [String] options :info the doc string for the param
128
+ # @param [Integer] indent how many levels the output should be
129
+ # indented from normal
130
+ #
131
+ def parameter_with_indent_level(title, options = {}, indent = 0)
132
+ options = options.dup
133
+ text = md_inline_code(title)
134
+
135
+ text += " - #{options.delete(:info)}" if options.has_key?(:info)
136
+
137
+ if options.keys.any?
138
+ text += "\n"
139
+ text += md_li("Legacy: #{options[:legacy].to_s}", indent + 1) if options.has_key?(:legacy)
140
+ text += md_li("Recursive: #{options[:recursive].to_s}", indent + 1) if options.has_key?(:recursive)
141
+ text.chomp!
142
+ end
143
+
144
+ md_li(text, indent)
145
+ end
146
+
147
+
148
+ #
149
+ # Formats the data model for the action.
150
+ #
151
+ def format_presents!
152
+ if endpoint.presenter
153
+ output << md_h5("Data Model")
154
+
155
+ link = md_a(endpoint.presenter_title, endpoint.relative_presenter_path_from_controller(:markdown))
156
+ output << md_ul do
157
+ md_li(link)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+
168
+ Brainstem::ApiDocs::FORMATTERS[:endpoint][:markdown] = \
169
+ Brainstem::ApiDocs::Formatters::Markdown::EndpointFormatter.method(:call)
@@ -0,0 +1,76 @@
1
+ # This is a very simple DSL that makes generating a markdown document a bit
2
+ # easier.
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ module Formatters
7
+ module Markdown
8
+ module Helper
9
+ def md_h1(text)
10
+ "# #{text}\n\n"
11
+ end
12
+
13
+
14
+ def md_h2(text)
15
+ "## #{text}\n\n"
16
+ end
17
+
18
+
19
+ def md_h3(text)
20
+ "### #{text}\n\n"
21
+ end
22
+
23
+
24
+ def md_h4(text)
25
+ "#### #{text}\n\n"
26
+ end
27
+
28
+
29
+ def md_h5(text)
30
+ "##### #{text}\n\n"
31
+ end
32
+
33
+
34
+ def md_strong(text)
35
+ "**#{text}**"
36
+ end
37
+
38
+
39
+ def md_hr
40
+ "-----\n\n"
41
+ end
42
+
43
+
44
+ def md_p(text)
45
+ text + "\n\n"
46
+ end
47
+
48
+
49
+ def md_code(text, lang = "")
50
+ "```#{lang}\n#{text}\n```\n\n"
51
+ end
52
+
53
+
54
+ def md_inline_code(text)
55
+ "`#{text}`"
56
+ end
57
+
58
+
59
+ def md_ul(&block)
60
+ (instance_eval(&block) || "") + "\n\n"
61
+ end
62
+
63
+
64
+ def md_li(text, indent_level = 0)
65
+ "#{' ' * (indent_level * 4)}- #{text}\n"
66
+ end
67
+
68
+
69
+ def md_a(text, link)
70
+ "[#{text}](#{link})"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,200 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require 'brainstem/api_docs/formatters/abstract_formatter'
3
+ require 'brainstem/api_docs/formatters/markdown/helper'
4
+
5
+ module Brainstem
6
+ module ApiDocs
7
+ module Formatters
8
+ module Markdown
9
+ class PresenterFormatter < AbstractFormatter
10
+ include Helper
11
+
12
+
13
+ def initialize(presenter, options = {})
14
+ self.presenter = presenter
15
+ self.output = ""
16
+ super options
17
+ end
18
+
19
+
20
+ attr_accessor :presenter,
21
+ :output
22
+
23
+
24
+ def call
25
+ return output if presenter.nodoc?
26
+
27
+ format_title!
28
+ format_brainstem_keys!
29
+ format_description!
30
+ format_fields!
31
+ format_filters!
32
+ format_sort_orders!
33
+ format_associations!
34
+
35
+ output
36
+ end
37
+
38
+
39
+ #####################################################################
40
+ private
41
+ #####################################################################
42
+
43
+ def format_title!
44
+ output << md_h4(presenter.title)
45
+ end
46
+
47
+
48
+ def format_brainstem_keys!
49
+ text = "Top-level key: "
50
+ text << presenter.brainstem_keys
51
+ .map(&method(:md_inline_code))
52
+ .join(" / ")
53
+
54
+ output << md_p(text)
55
+ end
56
+
57
+
58
+ def format_description!
59
+ output << md_p(presenter.description) unless presenter.description.empty?
60
+ end
61
+
62
+
63
+ def format_field_leaf(field, indent_level)
64
+ text = md_inline_code(field.name.to_s)
65
+ text << " (#{md_inline_code(field.type.to_s.capitalize)})"
66
+
67
+ text << "\n"
68
+ text << md_li(field.description, indent_level + 1) if field.description
69
+
70
+ if field.options[:if]
71
+ conditions = field.options[:if]
72
+ .reject { |cond| presenter.conditionals[cond].options[:nodoc] }
73
+ .map {|cond| presenter.conditionals[cond].description || "" }
74
+ .delete_if(&:empty?)
75
+ .join(" and ")
76
+
77
+ text << md_li("visible when #{conditions}", indent_level + 1) unless conditions.empty?
78
+ end
79
+
80
+ text << md_li("only returned when requested through the #{md_inline_code("optional_fields")} param") if field.optional?
81
+ text.chomp!
82
+ end
83
+
84
+
85
+ def format_field_branch(branch, indent_level = 0)
86
+ branch.inject("") do |buffer, (name, field)|
87
+ if nested_field?(field)
88
+ sub_fields = md_inline_code(name.to_s) + "\n"
89
+ sub_fields << format_field_branch(field.to_h, indent_level + 1)
90
+ buffer += md_li(sub_fields, indent_level)
91
+ else
92
+ buffer += md_li(format_field_leaf(field, indent_level), indent_level)
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+ def nested_field?(field)
99
+ !field.respond_to?(:options)
100
+ end
101
+
102
+
103
+ def format_fields!
104
+ output << md_h5("Fields")
105
+
106
+ if presenter.valid_fields.any?
107
+
108
+ output << md_ul do
109
+ format_field_branch(presenter.valid_fields)
110
+ end
111
+ else
112
+ output << md_p("No fields were listed.")
113
+ end
114
+ end
115
+
116
+
117
+ def format_filters!
118
+ if presenter.valid_filters.any?
119
+ output << md_h5("Filters")
120
+ output << md_ul do
121
+ presenter.valid_filters.inject("") do |buffer, (name, opts)|
122
+ text = md_inline_code(name)
123
+
124
+ if opts[:info]
125
+ text << "\n"
126
+ text << md_li(opts[:info], 1)
127
+ text.chomp!
128
+ end
129
+
130
+ buffer += md_li(text)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+
137
+ def format_sort_orders!
138
+ if presenter.valid_sort_orders.any?
139
+ output << md_h5("Sort Orders")
140
+ output << md_ul do
141
+ sorted_orders = presenter.valid_sort_orders.sort_by { |name, _| name.to_s }
142
+
143
+ # Shift the default sort_order to the top
144
+ sorted_orders.unshift sorted_orders.delete_at(sorted_orders.index { |name, _| name.to_s == presenter.default_sort_field })
145
+
146
+ sorted_orders.inject("") do |buffer, (name, opts)|
147
+ text = "#{md_inline_code(name.to_s)}"
148
+
149
+ if presenter.default_sort_field == name.to_s
150
+ text += " - #{md_strong("default")} (#{presenter.default_sort_direction})"
151
+ end
152
+
153
+ if opts[:info]
154
+ text += "\n" + md_li(opts[:info], 1)
155
+ text.chomp!
156
+ end
157
+
158
+ buffer += md_li(text)
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+
165
+ def format_associations!
166
+ if presenter.valid_associations.any?
167
+ output << md_h5("Associations")
168
+
169
+ output << "Association Name | Associated Class | Description\n"
170
+ output << " -------------- | -------------- | ----------\n"
171
+
172
+ output << presenter.valid_associations.inject("") do |buffer, (_, association)|
173
+ link = presenter.link_for_association(association)
174
+ if link
175
+ link = md_a(association.target_class, link)
176
+ else
177
+ link = association.target_class.to_s
178
+ end
179
+
180
+ desc = association.description.to_s
181
+ if association.options && association.options[:restrict_to_only]
182
+ desc += "." unless desc =~ /\.\s*\z/
183
+ desc += " Restricted to queries using the #{md_inline_code("only")} parameter."
184
+ desc.strip!
185
+ end
186
+
187
+ buffer << md_inline_code(association.name) + " | " + link + " | " + desc + "\n"
188
+ end
189
+
190
+ output << "\n"
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ Brainstem::ApiDocs::FORMATTERS[:presenter][:markdown] = \
200
+ Brainstem::ApiDocs::Formatters::Markdown::PresenterFormatter.method(:call)