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,100 @@
1
+ require 'brainstem/concerns/optional'
2
+
3
+ module Brainstem
4
+ module ApiDocs
5
+ module Introspectors
6
+ class AbstractIntrospector
7
+ include Brainstem::Concerns::Optional
8
+
9
+ def valid_options
10
+ [ ]
11
+ end
12
+
13
+ # Returns a new instance of the introspector with the environment
14
+ # loaded, ready for introspection.
15
+ #
16
+ # @param [Hash] options arguments to pass on to the instance
17
+ # @return [AbstractIntrospector] the loaded instance
18
+ def self.with_loaded_environment(options = {})
19
+ new(options).tap(&:load_environment!)
20
+ end
21
+
22
+
23
+ # Override to return a collection of all controller classes.
24
+ #
25
+ # @return [Array<Class>] all controller classes to document
26
+ def controllers
27
+ raise NotImplementedError
28
+ end
29
+
30
+
31
+ # Override to return a collection of all presenter classes.
32
+ #
33
+ # @return [Array<Class>] all presenter classes to document
34
+ def presenters
35
+ raise NotImplementedError
36
+ end
37
+
38
+
39
+ # Override to return a collection of hashes with the minimum following
40
+ # keys:
41
+ #
42
+ # +:path+ - the relative path (i.e. the endpoint)
43
+ # +:controller+ - the managing controller
44
+ # +:action+ - the managing action
45
+ # +:http_method+ - an array of the HTTP methods this route is available on.
46
+ #
47
+ def routes
48
+ raise NotImplementedError
49
+ end
50
+
51
+
52
+ # Provides both a sanity check to ensure that output confirms to
53
+ # interface and also confirms that there is actually something to
54
+ # generate docs for.
55
+ #
56
+ # @return [Boolean] Whether the Introspector is valid
57
+ def valid?
58
+ valid_controllers? && valid_presenters? && valid_routes?
59
+ end
60
+
61
+
62
+ #######################################################################
63
+ private
64
+ #######################################################################
65
+
66
+ # Don't allow instantiation through 'new'. We want to ensure that
67
+ # instantiation happens through +with_loaded_environment.
68
+ private_class_method :new
69
+
70
+
71
+ # Loads the host application environment.
72
+ # @api private
73
+ def load_environment!
74
+ raise NotImplementedError
75
+ end
76
+
77
+
78
+ def valid_controllers?
79
+ controllers.is_a?(Array) &&
80
+ controllers.count > 0 &&
81
+ controllers.all? {|c| c.class == Class }
82
+ end
83
+
84
+ def valid_presenters?
85
+ presenters.is_a?(Array) &&
86
+ presenters.all? {|p| p.class == Class }
87
+ end
88
+
89
+ def valid_routes?
90
+ routes.is_a?(Array) &&
91
+ routes.count > 0 &&
92
+ routes.all? do |r|
93
+ r.is_a?(Hash) &&
94
+ ([:path, :controller, :action, :http_methods] - r.keys).empty?
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,232 @@
1
+ require 'brainstem/api_docs/introspectors/abstract_introspector'
2
+ require 'brainstem/api_docs/exceptions'
3
+
4
+ # For 'constantize'
5
+ require 'active_support/inflector/methods'
6
+
7
+ module Brainstem
8
+ module ApiDocs
9
+ module Introspectors
10
+ class RailsIntrospector < AbstractIntrospector
11
+
12
+ #
13
+ # Loads ./config/environment.rb (by default) and eager loads all
14
+ # classes (otherwise +#descendants+ returns an empty set).
15
+ #
16
+ def load_environment!
17
+ load rails_environment_file unless env_already_loaded?
18
+ ::Rails.application.eager_load!
19
+
20
+ validate!
21
+ rescue LoadError => e
22
+ raise IncorrectIntrospectorForAppException,
23
+ "Hosting app does not appear to be a Rails app." +
24
+ "You may have to manually specify an Introspector (#{e.message})."
25
+ end
26
+
27
+
28
+ #
29
+ # Returns a list of presenters that descend from the base presenter
30
+ # class.
31
+ #
32
+ # @return [Array<Class>] an array of descendant classes
33
+ #
34
+ def presenters
35
+ base_presenter_class.constantize.descendants
36
+ end
37
+
38
+
39
+ #
40
+ # Returns a list of controllers that descend from the base controller
41
+ # class.
42
+ #
43
+ # @return [Array<Class>] an array of descendant classes
44
+ #
45
+ def controllers
46
+ base_controller_class.constantize.descendants
47
+ end
48
+
49
+
50
+ #
51
+ # Returns an array of hashes describing the endpoints of the
52
+ # application. See +routes_method+ for the keys of those hashes.
53
+ #
54
+ # @see #routes_method
55
+ #
56
+ # @return [Array<Hash>] each route defined on the hosting app
57
+ def routes
58
+ routes_method.call
59
+ end
60
+
61
+
62
+ #######################################################################
63
+ private
64
+ #######################################################################
65
+
66
+
67
+ def valid_options
68
+ super | [
69
+ :routes_method,
70
+ :rails_environment_file,
71
+ :base_presenter_class,
72
+ :base_controller_class,
73
+ ]
74
+ end
75
+
76
+
77
+ #
78
+ # Used to short-circuit loading if Rails is already loaded, which
79
+ # reduces start-up time substantially.
80
+ #
81
+ # @return [Boolean] whether Rails has already been loaded.
82
+ def env_already_loaded?
83
+ defined? Rails
84
+ end
85
+
86
+
87
+ # Returns the path of the Rails +config/environment.rb+ file - by
88
+ # default, +#{Dir.pwd}/config/environment.rb+.
89
+ #
90
+ # @return [String] the absolute path of the config/environment.rb file.
91
+ #
92
+ def rails_environment_file
93
+ @rails_environment_file ||= File.expand_path(
94
+ File.join(Dir.pwd, 'config', 'environment.rb')
95
+ )
96
+ end
97
+
98
+
99
+ #
100
+ # Allows a custom location to be set for the environment file if - for
101
+ # example - the command were to be called from a cron task that cannot
102
+ # change directory.
103
+ #
104
+ attr_writer :rails_environment_file
105
+
106
+
107
+ #
108
+ # Returns the name of the base presenter class.
109
+ #
110
+ # Because the initializer that contains configuration data is unlikely
111
+ # to have been loaded, this may also return a Proc, which will be called
112
+ # after the environment is loaded.
113
+ #
114
+ # @return [String,Proc] the base presenter class or a proc that returns
115
+ # the same
116
+ #
117
+ def base_presenter_class
118
+ proc_or_string = (@base_presenter_class ||= "::Brainstem::Presenter")
119
+ proc_or_string.respond_to?(:call) ? proc_or_string.call : proc_or_string
120
+ end
121
+
122
+
123
+ #
124
+ # Allows for the specification for an alternate base presenter class
125
+ # if - for example - only documentation of children of +MyBasePresenter+
126
+ # is desired.
127
+ #
128
+ # This argument accepts a string because most classes will not be
129
+ # defined at the time of passing, and will only be defined after
130
+ # environment load.
131
+ #
132
+ # Because the initializer that contains configuration data is unlikely
133
+ # to have been loaded, this may also return a Proc, which will be called
134
+ # after the environment is loaded.
135
+ #
136
+ # @param [String,Proc] base_presenter_class the class name to use as the
137
+ # base presenter, or a proc which returns the same.
138
+ #
139
+ attr_writer :base_presenter_class
140
+
141
+
142
+ #
143
+ # Returns the name of the base controller class.
144
+ #
145
+ # Because the initializer that contains configuration data is unlikely
146
+ # to have been loaded, this may also return a Proc, which will be called
147
+ # after the environment is loaded.
148
+ #
149
+ # @return [String,Proc] the base controller class or a proc that returns
150
+ # the same
151
+ #
152
+ def base_controller_class
153
+ proc_or_string = (@base_controller_class ||= "::ApplicationController")
154
+ proc_or_string.respond_to?(:call) ? proc_or_string.call : proc_or_string
155
+ end
156
+
157
+
158
+ #
159
+ # Allows for the specification for an alternate base controller class
160
+ # if - for example - only documentation of children of ApiController
161
+ # is desired. Best used through passing an argument to
162
+ # +with_loaded_environment+.
163
+ #
164
+ # This argument accepts a string because most classes will not be
165
+ # defined at the time of passing, and will only be defined after
166
+ # environment load.
167
+ #
168
+ # Because the initializer that contains configuration data is unlikely
169
+ # to have been loaded, this may also return a Proc, which will be called
170
+ # after the environment is loaded.
171
+ #
172
+ # @param [String,Proc] klass the class to use as the base controller, or
173
+ # a a method which returns the same.
174
+ #
175
+ attr_writer :base_controller_class
176
+
177
+
178
+ #
179
+ # Returns the proc that is called to format and retrieve routes.
180
+ # The proc's return must be an array of hashes that contains the
181
+ # following keys:
182
+ #
183
+ # +:path+ - the relative path
184
+ # +:controller+ - the managing controller as a constant
185
+ # +:controller_name+ - the internal underscored name of the controller
186
+ # +:action+ - the managing action
187
+ # +:http_method+ - an array of the HTTP methods this route is available on.
188
+ #
189
+ def routes_method
190
+ @routes_method ||= Proc.new do
191
+ Rails.application.routes.routes.map do |route|
192
+ next unless route.defaults.has_key?(:controller) &&
193
+ controller_const = "#{route.defaults[:controller]}_controller"
194
+ .classify
195
+ .constantize rescue nil
196
+
197
+ {
198
+ alias: route.name,
199
+ path: route.path.spec.to_s,
200
+ controller_name: route.defaults[:controller],
201
+ controller: controller_const,
202
+ action: route.defaults[:action],
203
+ http_methods: route.constraints
204
+ .fetch(:request_method, nil)
205
+ .inspect
206
+ .gsub(/[\/\$\^]/, '')
207
+ .split("|")
208
+ }
209
+ end.compact
210
+ end
211
+ end
212
+
213
+
214
+ #
215
+ # Allows setting the routes method used to retrieve the routes if - for
216
+ # example - your application needs to retrieve additional data or if it
217
+ # uses an explicit routing table to define documentable endpoints.
218
+ #
219
+ attr_writer :routes_method
220
+
221
+
222
+ #
223
+ # Throws an error if the introspector did not produce valid results.
224
+ #
225
+ def validate!
226
+ raise InvalidIntrospectorError, "Introspector is not valid." \
227
+ unless valid?
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,225 @@
1
+ require 'brainstem/api_docs'
2
+ require 'brainstem/concerns/optional'
3
+ require 'brainstem/concerns/formattable'
4
+ require 'forwardable'
5
+ require 'active_support/inflector'
6
+
7
+ #
8
+ # Wrapper for common presenter information lookups.
9
+ #
10
+ module Brainstem
11
+ module ApiDocs
12
+ class Presenter
13
+ extend Forwardable
14
+ include Concerns::Optional
15
+ include Concerns::Formattable
16
+
17
+
18
+ def valid_options
19
+ super | [
20
+ :const,
21
+ :target_class,
22
+ :filename_pattern,
23
+ :filename_link_pattern,
24
+ :document_empty_associations,
25
+ :document_empty_filters
26
+ ]
27
+ end
28
+
29
+ attr_accessor :const,
30
+ :target_class,
31
+ :document_empty_associations,
32
+ :document_empty_filters
33
+
34
+ attr_writer :filename_pattern,
35
+ :filename_link_pattern
36
+
37
+ alias_method :document_empty_associations?, :document_empty_associations
38
+ alias_method :document_empty_filters?, :document_empty_filters
39
+
40
+
41
+ def initialize(atlas, options = {})
42
+ self.atlas = atlas
43
+ self.document_empty_associations = Brainstem::ApiDocs.document_empty_presenter_associations
44
+ self.document_empty_filters = Brainstem::ApiDocs.document_empty_presenter_filters
45
+
46
+ super options
47
+ yield self if block_given?
48
+ end
49
+
50
+
51
+ def suggested_filename(format)
52
+ filename_pattern
53
+ .gsub('{{name}}', target_class.to_s.underscore)
54
+ .gsub('{{extension}}', extension)
55
+ end
56
+
57
+
58
+ def suggested_filename_link(format)
59
+ filename_link_pattern
60
+ .gsub('{{name}}', target_class.to_s.underscore)
61
+ .gsub('{{extension}}', extension)
62
+ end
63
+
64
+
65
+ attr_accessor :atlas
66
+
67
+
68
+ def extension
69
+ @extension ||= Brainstem::ApiDocs.output_extension
70
+ end
71
+
72
+
73
+ def filename_pattern
74
+ @filename_pattern ||= Brainstem::ApiDocs.presenter_filename_pattern
75
+ end
76
+
77
+
78
+ def filename_link_pattern
79
+ @filename_link_pattern ||= Brainstem::ApiDocs.presenter_filename_link_pattern
80
+ end
81
+
82
+
83
+ delegate :configuration => :const
84
+ delegate :find_by_class => :atlas
85
+
86
+
87
+ def nodoc?
88
+ configuration[:nodoc]
89
+ end
90
+
91
+
92
+ def title
93
+ contextual_documentation(:title) || const.to_s.demodulize
94
+ end
95
+
96
+
97
+ def brainstem_keys
98
+ const.possible_brainstem_keys.to_a.sort
99
+ end
100
+
101
+
102
+ def description
103
+ contextual_documentation(:description) || ""
104
+ end
105
+
106
+
107
+ def valid_fields(fields = configuration[:fields])
108
+ fields.to_h.reject do |k, v|
109
+ if nested_field?(v)
110
+ valid_fields_in(v).none?
111
+ else
112
+ invalid_field?(v)
113
+ end
114
+ end
115
+ end
116
+ alias_method :valid_fields_in, :valid_fields
117
+
118
+
119
+ def invalid_field?(field)
120
+ field.options[:nodoc]
121
+ end
122
+
123
+
124
+ def nested_field?(field)
125
+ !field.respond_to?(:options)
126
+ end
127
+
128
+
129
+ def valid_filters
130
+ configuration[:filters]
131
+ .to_h
132
+ .keep_if(&method(:documentable_filter?))
133
+ end
134
+
135
+
136
+ def documentable_filter?(_, filter)
137
+ !filter[:nodoc] &&
138
+ (
139
+ document_empty_filters? || # document empty filters or
140
+ !(filter[:info] || "").empty? # has info string
141
+ )
142
+ end
143
+
144
+
145
+ def valid_sort_orders
146
+ configuration[:sort_orders].to_h.reject {|k, v| v[:nodoc] }
147
+ end
148
+
149
+
150
+ def valid_associations
151
+ configuration[:associations]
152
+ .to_h
153
+ .keep_if(&method(:documentable_association?))
154
+ end
155
+
156
+
157
+
158
+ def link_for_association(association)
159
+ if (associated_presenter = find_by_class(association.target_class)) &&
160
+ !associated_presenter.nodoc?
161
+ relative_path_to_presenter(associated_presenter, :markdown)
162
+ else
163
+ nil
164
+ end
165
+ end
166
+
167
+
168
+ #
169
+ # Returns whether this association should be documented based on nodoc
170
+ # and empty description.
171
+ #
172
+ # @return [Bool] document this association?
173
+ #
174
+ def documentable_association?(_, association)
175
+ !association.options[:nodoc] && # not marked nodoc and
176
+ (
177
+ document_empty_associations? || # document empty associations or
178
+ !(association.description.nil? || association.description.empty?) # has description
179
+ )
180
+ end
181
+
182
+
183
+ def conditionals
184
+ configuration[:conditionals]
185
+ end
186
+
187
+
188
+ def default_sort_order
189
+ configuration[:default_sort_order] || ""
190
+ end
191
+
192
+
193
+ def default_sort_field
194
+ @default_sort_field ||= (default_sort_order.split(":")[0] || nil)
195
+ end
196
+
197
+
198
+ def default_sort_direction
199
+ @default_sort_direction ||= (default_sort_order.split(":")[1] || nil)
200
+ end
201
+
202
+
203
+ #
204
+ # Returns a key if it exists and is documentable.
205
+ #
206
+ def contextual_documentation(key)
207
+ configuration.has_key?(key) &&
208
+ !configuration[key][:nodoc] &&
209
+ configuration[key][:info]
210
+ end
211
+
212
+
213
+ #
214
+ # Returns the relative path between this presenter and another given
215
+ # presenter.
216
+ #
217
+ def relative_path_to_presenter(presenter, format)
218
+ my_path = Pathname.new(File.dirname(suggested_filename_link(format)))
219
+ presenter_path = Pathname.new(presenter.suggested_filename_link(format))
220
+
221
+ presenter_path.relative_path_from(my_path).to_s
222
+ end
223
+ end
224
+ end
225
+ end