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,97 @@
1
+ require 'active_support/inflector/inflections'
2
+ require 'brainstem/api_docs/abstract_collection'
3
+ require 'brainstem/api_docs/presenter'
4
+ require 'brainstem/concerns/formattable'
5
+
6
+ module Brainstem
7
+ module ApiDocs
8
+ class PresenterCollection < AbstractCollection
9
+ include Concerns::Formattable
10
+
11
+
12
+ def valid_options
13
+ super | [ :presenter_constant_lookup_method ]
14
+ end
15
+
16
+ attr_writer :presenter_constant_lookup_method
17
+
18
+
19
+ #
20
+ # Finds or creates a presenter with the given target class and appends it to the
21
+ # members list if it is new.
22
+ #
23
+ def find_or_create_from_target_class(target_class)
24
+ find_by_target_class(target_class) ||
25
+ create_from_target_class(target_class)
26
+ end
27
+ alias_method :find_or_create_by_target_class,
28
+ :find_or_create_from_target_class
29
+
30
+
31
+ #
32
+ # Finds a presenter for the given class
33
+ #
34
+ def find_by_target_class(target_class)
35
+ find { |p| p.target_class == target_class }
36
+ end
37
+
38
+
39
+ #
40
+ # Creates a new +Presenter+ wrapper and appends it to the collection. If
41
+ # the constant lookup for the actual presenter class fails, returns nil.
42
+ #
43
+ def create_from_target_class(target_class)
44
+ ::Brainstem::ApiDocs::Presenter.new(atlas,
45
+ target_class: target_class,
46
+ const: target_class_to_const(target_class)
47
+ ).tap { |p| self.<< p }
48
+ rescue KeyError
49
+ nil
50
+ end
51
+
52
+
53
+ def find_or_create_from_presenter_collection(target_class, const)
54
+ find_by_target_class(target_class) ||
55
+ create_from_presenter_collection(target_class, const)
56
+ end
57
+ alias_method :find_or_create_by_presenter_collection,
58
+ :find_or_create_from_presenter_collection
59
+
60
+
61
+ def create_from_presenter_collection(target_class, const)
62
+ ::Brainstem::ApiDocs::Presenter.new(atlas,
63
+ target_class: target_class,
64
+ const: const
65
+ ).tap { |p| self.<< p }
66
+ end
67
+
68
+
69
+ #########################################################################
70
+ private
71
+ #########################################################################
72
+
73
+ #
74
+ # Converts a target class into a presenter constant. Raises an error
75
+ # if not found.
76
+ #
77
+ def target_class_to_const(target_class)
78
+ presenter_constant_lookup_method.call(target_class.to_s)
79
+ end
80
+
81
+
82
+ #
83
+ # A callable method by which presenter constants can be looked up from
84
+ # their human name.
85
+ #
86
+ # In future, it might be worth unifying this method with `find_by_class`
87
+ # to reduce total surface.
88
+ #
89
+ def presenter_constant_lookup_method
90
+ @presenter_constant_lookup_method ||= \
91
+ Brainstem.presenter_collection.presenters.method(:fetch)
92
+ end
93
+
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,73 @@
1
+ require 'brainstem/concerns/optional'
2
+
3
+ #
4
+ # The Resolver is responsible for taking a non-ApiDocs class and turning it
5
+ # into an ApiDocs wrapper class (such as a Presenter, Controller, or Endpoint).
6
+ #
7
+ # It's useful for doing object lookups for associated objects where we only
8
+ # have the original class or constant to look up the relationship.
9
+ #
10
+ module Brainstem
11
+ module ApiDocs
12
+ class Resolver
13
+ include Brainstem::Concerns::Optional
14
+
15
+
16
+ def valid_options
17
+ [
18
+ :presenter_constant_lookup_method,
19
+ ]
20
+ end
21
+
22
+ def initialize(atlas, options = {})
23
+ self.atlas = atlas
24
+ super options
25
+ end
26
+
27
+
28
+ attr_accessor :atlas,
29
+ :presenter_constant_lookup_method
30
+
31
+
32
+ def find_by_class(klass)
33
+ if klass == :polymorphic
34
+ nil
35
+ elsif klass < ActiveRecord::Base
36
+ find_presenter_from_target_class(klass)
37
+ end
38
+ end
39
+
40
+
41
+ #########################################################################
42
+ private
43
+ #########################################################################
44
+
45
+ def find_presenter_from_target_class(klass)
46
+ const = presenter_target_class_to_const(klass)
47
+ atlas.presenters.find {|p| p.const == const }
48
+ rescue
49
+ nil
50
+ end
51
+
52
+
53
+ #
54
+ # Converts a class into a presenter constant. Raises an error
55
+ # if not found.
56
+ #
57
+ def presenter_target_class_to_const(target_class)
58
+ presenter_constant_lookup_method.call(target_class.to_s)
59
+ end
60
+
61
+
62
+ #
63
+ # A callable method by which presenter constants can be looked up from
64
+ # their human name.
65
+ #
66
+ def presenter_constant_lookup_method
67
+ @presenter_constant_lookup_method ||= \
68
+ Brainstem.presenter_collection.presenters.method(:fetch)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,37 @@
1
+ require 'brainstem/concerns/optional'
2
+
3
+ module Brainstem
4
+ module ApiDocs
5
+ module Sinks
6
+ class AbstractSink
7
+ include Concerns::Optional
8
+
9
+
10
+ #
11
+ # Primary method for putting the atlas into the sink.
12
+ #
13
+ # @param [Brainstem::ApiDocs::Atlas] the atlas
14
+ #
15
+ def <<(atlas)
16
+ raise NotImplementedError
17
+ end
18
+
19
+
20
+ #######################################################################$
21
+ private
22
+ ########################################################################
23
+
24
+
25
+ #
26
+ # Whitelist of options which can be set on an instance.
27
+ #
28
+ # @return [Array<Symbol>] valid options
29
+ #
30
+ def valid_options
31
+ []
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,93 @@
1
+ require 'brainstem/api_docs/sinks/abstract_sink'
2
+ require 'fileutils'
3
+ require 'forwardable'
4
+
5
+ module Brainstem
6
+ module ApiDocs
7
+ module Sinks
8
+ class ControllerPresenterMultifileSink < AbstractSink
9
+ extend Forwardable
10
+
11
+ delegate [:controllers, :presenters] => :atlas
12
+
13
+
14
+ def <<(atlas)
15
+ self.atlas = atlas
16
+
17
+ write_controller_files
18
+ write_presenter_files
19
+ end
20
+
21
+
22
+ def valid_options
23
+ super | [ :write_method, :format, :write_path ]
24
+ end
25
+
26
+
27
+ attr_writer :write_method,
28
+ :write_path
29
+
30
+ attr_accessor :atlas,
31
+ :format
32
+
33
+
34
+ #######################################################################
35
+ private
36
+ #######################################################################
37
+
38
+ #
39
+ # Dumps each formatted controller to a file.
40
+ #
41
+ def write_controller_files
42
+ controllers.each_formatted_with_filename(
43
+ format,
44
+ include_actions: true,
45
+ &method(:write_buffer_to_file)
46
+ )
47
+ end
48
+
49
+
50
+ #
51
+ # Dumps each formatted presenter to a file.
52
+ #
53
+ def write_presenter_files
54
+ presenters.each_formatted_with_filename(format, &method(:write_buffer_to_file))
55
+ end
56
+
57
+
58
+ #
59
+ # Writes a given bufer to a filename within the base path.
60
+ #
61
+ def write_buffer_to_file(buffer, filename)
62
+ abs_path = File.join(write_path, filename)
63
+ assert_directory_exists!(abs_path)
64
+ write_method.call(abs_path, buffer)
65
+ end
66
+
67
+
68
+ #
69
+ # Asserts that a directory exists, creating it if it does not.
70
+ #
71
+ def assert_directory_exists!(path)
72
+ dir = File.dirname(path)
73
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
74
+ end
75
+
76
+
77
+ #
78
+ # Defines how we write out the files.
79
+ #
80
+ def write_method
81
+ @write_method ||= Proc.new do |name, buff|
82
+ File.write(name, buff, mode: 'w')
83
+ end
84
+ end
85
+
86
+
87
+ def write_path
88
+ @write_path ||= ::Brainstem::ApiDocs.write_path
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,44 @@
1
+ require 'brainstem/api_docs/sinks/abstract_sink'
2
+
3
+ module Brainstem
4
+ module ApiDocs
5
+ module Sinks
6
+ class StdoutSink < AbstractSink
7
+
8
+ #
9
+ # Writes the output using stdout.puts.
10
+ #
11
+ def <<(output)
12
+ puts_method.call(output)
13
+ end
14
+
15
+
16
+ ########################################################################
17
+ private
18
+ ########################################################################
19
+
20
+
21
+ def valid_options
22
+ super | [ :puts_method ]
23
+ end
24
+
25
+
26
+ #
27
+ # Storage for holding the writing method.
28
+ #
29
+ attr_writer :puts_method
30
+
31
+
32
+ #
33
+ # Callable method for writing data to a buffer (by default stdout).
34
+ #
35
+ # @return [Proc] a method which writes data to a buffer when called.
36
+ #
37
+ def puts_method
38
+ @puts_method ||= $stdout.method(:puts)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,146 @@
1
+ # Require all CLI commands.
2
+ Dir.glob(File.expand_path('../cli/**/*.rb', __FILE__)).each { |f| require f }
3
+ require 'brainstem/concerns/optional'
4
+
5
+
6
+ #
7
+ # General manager for CLI requests. Takes incoming user input and routes to a
8
+ # subcommand.
9
+ module Brainstem
10
+ class Cli
11
+ include Concerns::Optional
12
+
13
+ EXECUTABLE_NAME = 'brainstem'
14
+
15
+ #
16
+ # Convenience for instantiating and calling the Cli object.
17
+ #
18
+ # @return [Brainstem::Cli] the created instance
19
+ #
20
+ def self.call(args, options = {})
21
+ new(args, options).call
22
+ end
23
+
24
+
25
+ #
26
+ # Creates a new instance of the Cli to respond to user input.
27
+ #
28
+ # Input is expected to be the name of the subcommand, followed by any
29
+ # additional arguments.
30
+ #
31
+ def initialize(args, options = {})
32
+ super options
33
+
34
+ self._args = args.dup.freeze
35
+ self.requested_command = args.shift
36
+ end
37
+
38
+
39
+ #
40
+ # Routes to an application endpoint depending on given options.
41
+ #
42
+ # @return [Brainstem::Cli] the instance
43
+ #
44
+ def call
45
+ if requested_command && commands.has_key?(requested_command)
46
+ self.command_method = commands[requested_command].method(:call)
47
+ end
48
+
49
+ command_method.call(_args.drop(1))
50
+
51
+ self
52
+ end
53
+
54
+
55
+ #
56
+ # Holds a copy of the initial given args for debugging purposes.
57
+ #
58
+ attr_accessor :_args
59
+
60
+
61
+ #
62
+ # Storage for the extracted command.
63
+ #
64
+ attr_accessor :requested_command
65
+
66
+
67
+ ################################################################################
68
+ private
69
+ ################################################################################
70
+
71
+
72
+ #
73
+ # A whitelist of valid options that can be applied to the instance.
74
+ #
75
+ # @returns [Array<String>]
76
+ #
77
+ def valid_options
78
+ super | [
79
+ :log_method
80
+ ]
81
+ end
82
+
83
+
84
+ #
85
+ # A basic routing table where the keys are the command to invoke, and where
86
+ # the value is a callable object or class that will be called with the
87
+ # +command_options+.
88
+ #
89
+ # @return [Hash] A hash of +'command' => Callable+
90
+ #
91
+ def commands
92
+ { 'generate' => Brainstem::CLI::GenerateApiDocsCommand }
93
+ end
94
+
95
+
96
+ #
97
+ # Retrieves the help text and subs any placeholder values.
98
+ #
99
+ def help_text
100
+ @help_text ||= File.read(File.expand_path('../help_text.txt', __FILE__))
101
+ .gsub('EXECUTABLE_NAME', EXECUTABLE_NAME)
102
+ end
103
+
104
+
105
+ #
106
+ # Stores the method we should call to run the user command.
107
+ #
108
+ attr_writer :command_method
109
+
110
+
111
+ #
112
+ # Reader for the method to invoke. By default, will output the help text
113
+ # when called.
114
+ #
115
+ # @return [Proc] An object responding to +#call+ that is used as the main
116
+ # point execution.
117
+ #
118
+ def command_method
119
+ @command_method ||= Proc.new do
120
+ # By default, serve help.
121
+ log_method.call(help_text)
122
+ end
123
+ end
124
+
125
+
126
+ #
127
+ # Stores the method we should use to log messages.
128
+ #
129
+ attr_writer :log_method
130
+
131
+
132
+ #
133
+ # Reader for the method to log. By default, will print to stdout when
134
+ # called.
135
+ #
136
+ # We return a proc here because it's much tricker to make assertions
137
+ # against stdout than it is to allow ourselves to inject a proc.
138
+ #
139
+ # @return [Proc] An object responding to +#call+ that is used to print
140
+ # information.
141
+ #
142
+ def log_method
143
+ @log_method ||= $stdout.method(:puts)
144
+ end
145
+ end
146
+ end