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,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)