mbrao 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 (73) hide show
  1. data/.gitignore +6 -0
  2. data/.travis-gemfile +13 -0
  3. data/.travis.yml +8 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +21 -0
  6. data/README.md +60 -0
  7. data/Rakefile +11 -0
  8. data/doc/ActionView/Template/Handlers/MbraoTemplate.html +568 -0
  9. data/doc/ActionView/Template/Handlers.html +125 -0
  10. data/doc/HTML/Pipeline/KramdownFilter.html +354 -0
  11. data/doc/HTML/Pipeline.html +140 -0
  12. data/doc/HTML.html +125 -0
  13. data/doc/Mbrao/Author.html +1402 -0
  14. data/doc/Mbrao/Content.html +4779 -0
  15. data/doc/Mbrao/ContentPublicInterface.html +658 -0
  16. data/doc/Mbrao/Exceptions/InvalidDate.html +133 -0
  17. data/doc/Mbrao/Exceptions/InvalidMetadata.html +133 -0
  18. data/doc/Mbrao/Exceptions/Parsing.html +133 -0
  19. data/doc/Mbrao/Exceptions/Rendering.html +133 -0
  20. data/doc/Mbrao/Exceptions/UnavailableLocalization.html +133 -0
  21. data/doc/Mbrao/Exceptions/Unimplemented.html +133 -0
  22. data/doc/Mbrao/Exceptions/UnknownEngine.html +133 -0
  23. data/doc/Mbrao/Exceptions.html +125 -0
  24. data/doc/Mbrao/Parser.html +418 -0
  25. data/doc/Mbrao/ParsingEngines/Base.html +658 -0
  26. data/doc/Mbrao/ParsingEngines/PlainText.html +571 -0
  27. data/doc/Mbrao/ParsingEngines.html +127 -0
  28. data/doc/Mbrao/PublicInterface/ClassMethods.html +1727 -0
  29. data/doc/Mbrao/PublicInterface.html +134 -0
  30. data/doc/Mbrao/RenderingEngines/Base.html +280 -0
  31. data/doc/Mbrao/RenderingEngines/HtmlPipeline.html +873 -0
  32. data/doc/Mbrao/RenderingEngines.html +127 -0
  33. data/doc/Mbrao/Validations/ClassMethods.html +692 -0
  34. data/doc/Mbrao/Validations.html +134 -0
  35. data/doc/Mbrao/Version.html +189 -0
  36. data/doc/Mbrao.html +130 -0
  37. data/doc/_index.html +395 -0
  38. data/doc/class_list.html +53 -0
  39. data/doc/css/common.css +1 -0
  40. data/doc/css/full_list.css +57 -0
  41. data/doc/css/style.css +338 -0
  42. data/doc/file.README.html +135 -0
  43. data/doc/file_list.html +55 -0
  44. data/doc/frames.html +28 -0
  45. data/doc/index.html +135 -0
  46. data/doc/js/app.js +214 -0
  47. data/doc/js/full_list.js +173 -0
  48. data/doc/js/jquery.js +4 -0
  49. data/doc/method_list.html +516 -0
  50. data/doc/top-level-namespace.html +112 -0
  51. data/lib/mbrao/author.rb +61 -0
  52. data/lib/mbrao/content.rb +300 -0
  53. data/lib/mbrao/exceptions.rb +38 -0
  54. data/lib/mbrao/integrations/rails.rb +51 -0
  55. data/lib/mbrao/parser.rb +276 -0
  56. data/lib/mbrao/parsing_engines/base.rb +51 -0
  57. data/lib/mbrao/parsing_engines/plain_text.rb +187 -0
  58. data/lib/mbrao/rendering_engines/base.rb +22 -0
  59. data/lib/mbrao/rendering_engines/html_pipeline.rb +147 -0
  60. data/lib/mbrao/version.rb +25 -0
  61. data/lib/mbrao.rb +24 -0
  62. data/mbrao.gemspec +31 -0
  63. data/spec/coverage_helper.rb +17 -0
  64. data/spec/mbrao/author_spec.rb +62 -0
  65. data/spec/mbrao/content_spec.rb +280 -0
  66. data/spec/mbrao/integrations/rails_spec.rb +92 -0
  67. data/spec/mbrao/parser_spec.rb +269 -0
  68. data/spec/mbrao/parsing_engines/base_spec.rb +52 -0
  69. data/spec/mbrao/parsing_engines/plain_text_spec.rb +141 -0
  70. data/spec/mbrao/rendering_engines/base_spec.rb +17 -0
  71. data/spec/mbrao/rendering_engines/html_pipeline_spec.rb +128 -0
  72. data/spec/spec_helper.rb +15 -0
  73. metadata +195 -0
@@ -0,0 +1,300 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the mbrao gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ module Mbrao
8
+ # Setter methods for the {Content Content} class.
9
+ module ContentPublicInterface
10
+ # Checks if the content is available for at least one of the provided locales.
11
+ #
12
+ # @param locales [Array] The desired locales. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
13
+ # @return [Boolean] `true` if the content is available for at least one of the desired locales, `false` otherwise.
14
+ def enabled_for_locales?(*locales)
15
+ locales = Mbrao::Parser.sanitized_array(locales).collect(&:strip).reject {|l| l == "*" }
16
+ @locales.blank? || locales.blank? || (@locales & locales).present?
17
+ end
18
+
19
+ # Gets the title of the content in the desired locales.
20
+ #
21
+ # @param locales [String|Array] The desired locales. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
22
+ # @return [String|HashWithIndifferentAccess] Return the title of the content in the desired locales. If only one locale is required, then a `String` is returned, else a `HashWithIndifferentAccess` with locales as keys.
23
+ def get_title(locales = [])
24
+ filter_attribute_for_locales(@title, locales)
25
+ end
26
+
27
+ # Gets the body returning only the portion which are available for the given locales.
28
+ #
29
+ # @param locales [String|Array] The desired locales. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
30
+ # @param engine [String|Symbol|Object] The engine to use to filter contents.
31
+ # @return [String|HashWithIndifferentAccess] Return the body of the content in the desired locales. If only one locale is required, then a `String` is returned, else a `HashWithIndifferentAccess` with locales as keys.
32
+ def get_body(locales = [], engine = :plain_text)
33
+ Mbrao::Parser.create_engine(engine).filter_content(self, locales)
34
+ end
35
+
36
+ # Gets the tags of the content in the desired locales.
37
+ #
38
+ # @param locales [String|Array] The desired locales. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
39
+ # @return [Array|HashWithIndifferentAccess] Return the title of the content in the desired locales. If only one locale is required, then a `Array` is returned, else a `HashWithIndifferentAccess` with locales as keys.
40
+ def get_tags(locales = [])
41
+ filter_attribute_for_locales(@tags, locales)
42
+ end
43
+
44
+ # Gets the "more link" text of the content in the desired locales.
45
+ #
46
+ # @param locales [String|Array] The desired locales. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
47
+ # @return [String|HashWithIndifferentAccess] Return the label of the "more link" of the content in the desired locales. If only one locale is required, then a `String` is returned, else a `HashWithIndifferentAccess` with locales as keys.
48
+ def get_more(locales = [])
49
+ filter_attribute_for_locales(@more, locales)
50
+ end
51
+
52
+ private
53
+ # Filters an attribute basing a set of locales.
54
+ #
55
+ # @param attribute [Object|HashWithIndifferentAccess] The desired attribute.
56
+ # @param locales [String|Array] The desired locales. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
57
+ # @return [String|HashWithIndifferentAccess] Return the object for desired locales. If only one locale is available, then only a object is returned, else a `HashWithIndifferentAccess` with locales as keys.
58
+ def filter_attribute_for_locales(attribute, locales)
59
+ locales = ::Mbrao::Content.validate_locales(locales, self)
60
+
61
+ if attribute.is_a?(HashWithIndifferentAccess) then
62
+ rv = attribute.inject(HashWithIndifferentAccess.new) { |hash, pair| append_value_for_locale(hash, pair[0], locales, pair[1]) }
63
+ rv.keys.length == 1 ? rv.values.first : rv
64
+ else
65
+ attribute
66
+ end
67
+ end
68
+
69
+ # Adds an value on a hash if enable for requested locales.
70
+ #
71
+ # @param hash [Hash] The hash to handle.
72
+ # @param locale [String] The list of locale (separated by commas) for which the value is available.
73
+ # @param locales [Array] The list of locale for which this value is requested. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
74
+ # @param value [Object] The value to add.
75
+ # @return [Hash] Return the original hash.
76
+ def append_value_for_locale(hash, locale, locales, value)
77
+ locale.split(/\s*,\s*/).collect(&:strip).each do |l|
78
+ hash[l] = value if locales.include?("*") || locales.include?(l)
79
+ end
80
+
81
+ hash
82
+ end
83
+ end
84
+
85
+ # Represents a parsed content, with its metadata.
86
+ #
87
+ # @attribute uid
88
+ # @return [String] A unique ID for this post. This is only for client uses.
89
+ # @attribute locales
90
+ # @return [Array] A list of locales for this content should be visible. An empty list means that there are no limitations.
91
+ # @attribute title
92
+ # @return [String|HashWithIndifferentAccess] The content's title. Can be a `String` or an `HashWithIndifferentAccess`, if multiple title are specified for multiple locales.
93
+ # @attribute body
94
+ # @return [String|HashWithIndifferentAccess] The content's body. Can be a `String` or an `HashWithIndifferentAccess`, if multiple contents are specified for multiple locales.
95
+ # @attribute tags
96
+ # @return [String|HashWithIndifferentAccess] The content's "more link" label. Can be a `String` or an `HashWithIndifferentAccess`, if multiple labels are specified for multiple locales.
97
+ # @attribute tags
98
+ # @return [Array|HashWithIndifferentAccess] The tags associated with the content. Can be an `Array` or an `HashWithIndifferentAccess`, if multiple tags set are specified for multiple locales.
99
+ # @attribute author
100
+ # @return [Author] The post author.
101
+ # @attribute created_at
102
+ # @return [DateTime] The post creation date and time. The timezone is always UTC.
103
+ # @attribute updated_at
104
+ # @return [DateTime] The post creation date and time. Defaults to the creation date. The timezone is always UTC.
105
+ # @attribute metadata
106
+ # @return [Hash] The full list of metadata of this content.
107
+ class Content
108
+ include Mbrao::ContentPublicInterface
109
+
110
+ attr_accessor :uid
111
+ attr_accessor :locales
112
+ attr_accessor :title
113
+ attr_accessor :body
114
+ attr_accessor :tags
115
+ attr_accessor :more
116
+ attr_accessor :author
117
+ attr_accessor :created_at
118
+ attr_accessor :updated_at
119
+ attr_accessor :metadata
120
+
121
+ # Creates a new content.
122
+ #
123
+ # @param uid [String] The UID for this content.
124
+ def initialize(uid = nil)
125
+ @uid = uid
126
+ end
127
+
128
+ # Sets the `locales` attribute.
129
+ #
130
+ # @param value [Array] The new value for the attribute. If an Hash, keys must be a string with one or locale separated by commas. A empty or "*" will be the default value.
131
+ def locales=(value)
132
+ @locales = Mbrao::Parser.sanitized_array(value)
133
+ end
134
+
135
+ # Sets the `title` attribute.
136
+ #
137
+ # @param value [String|Hash] The new value for the attribute. If an Hash, keys must be a string with one or locale separated by commas. A empty or "*" will be the default value.
138
+ def title=(value)
139
+ @title = value.is_a?(Hash) ? Mbrao::Parser.sanitized_hash(value, :ensure_string) : value.ensure_string
140
+ end
141
+
142
+ # Sets the `body` attribute.
143
+ #
144
+ # @param value [String] The new value for the attribute. Can contain locales restriction (like !en), which will must be interpreted using #get_body.
145
+ def body=(value)
146
+ @body = value.ensure_string
147
+ end
148
+
149
+ # Sets the `tags` attribute.
150
+ #
151
+ # @param value [Array|Hash] The new value for the attribute. If an Hash, keys must be a string with one or locale separated by commas. A empty or "*" will be the default value.
152
+ def tags=(value)
153
+ @tags = if value.is_a?(Hash) then
154
+ values = Mbrao::Parser.sanitized_hash(value)
155
+ values.each {|k, v| values[k] = Mbrao::Parser.sanitized_array(v, true, true) }
156
+ @tags = values
157
+ else
158
+ Mbrao::Parser.sanitized_array(value, true, true)
159
+ end
160
+ end
161
+
162
+ # Sets the `more` attribute.
163
+ #
164
+ # @param value [String|Hash] The new value for the attribute. If an Hash, keys must be a string with one or locale separated by commas. A empty or "*" will be the default value.
165
+ def more=(value)
166
+ @more = value.is_a?(Hash) ? Mbrao::Parser.sanitized_hash(value, :ensure_string) : value.ensure_string
167
+ end
168
+
169
+ # Sets the `author` attribute.
170
+ #
171
+ # @param value [Author|Hash|Object] The new value for the attribute.
172
+ def author=(value)
173
+ if value.is_a?(Mbrao::Author) then
174
+ @author = value
175
+ elsif value.is_a?(Hash) then
176
+ value = Mbrao::Parser.sanitized_hash(value, nil)
177
+ @author = Mbrao::Author.new(value["name"], value["email"], value["website"], value["image"], value["metadata"], value["uid"])
178
+ else
179
+ @author = Mbrao::Author.new(value.ensure_string)
180
+ end
181
+ end
182
+
183
+ # Sets the `created_at` attribute.
184
+ #
185
+ # @param value [String|DateTime|Fixnum] The new value for the attribute.
186
+ def created_at=(value)
187
+ @created_at = extract_datetime(value)
188
+ end
189
+
190
+ # Sets the `updated_at` attribute.
191
+ #
192
+ # @param value [String|DateTime|Fixnum] The new value for the attribute.
193
+ def updated_at=(value)
194
+ @updated_at = extract_datetime(value)
195
+ @updated_at = @created_at if !@updated_at
196
+ end
197
+
198
+ # Gets metadata attribute.
199
+ #
200
+ # @return The metadata attribute.
201
+ def metadata
202
+ @metadata || {}
203
+ end
204
+
205
+ # Sets the `metadata` attribute.
206
+ #
207
+ # @param value [Hash] The new value for the attribute.
208
+ def metadata=(value)
209
+ if value.is_a?(Hash) then
210
+ @metadata = Mbrao::Parser.sanitized_hash(value)
211
+ else
212
+ @metadata = HashWithIndifferentAccess.new({raw: value})
213
+ end
214
+ end
215
+
216
+ # Validates locales for attribute retrieval.
217
+ #
218
+ # @param locales [Array] A list of desired locales for an attribute. Can include `*` to match all. If none are specified, the default mbrao locale will be used.
219
+ # @param content [Content|nil] An optional content to check for availability
220
+ # @return [Array] The validated list of locales.
221
+ def self.validate_locales(locales, content = nil)
222
+ locales = Mbrao::Parser.sanitized_array(locales).collect(&:strip)
223
+ locales = (locales.empty? ? [Mbrao::Parser.locale] : locales)
224
+ raise Mbrao::Exceptions::UnavailableLocalization.new if content && !content.enabled_for_locales?(locales)
225
+ locales
226
+ end
227
+
228
+ # Creates a content with metadata and body.
229
+ #
230
+ # @param metadata [Hash] The metadata.
231
+ # @param body [String] The body of the content.
232
+ # @return [Content] A new content.
233
+ def self.create(metadata, body)
234
+ rv = Mbrao::Content.new
235
+ rv.body = body.ensure_string.strip
236
+ assign_metadata(rv, metadata) if metadata.is_a?(Hash)
237
+ rv
238
+ end
239
+
240
+ private
241
+ # Assigns metadata to a content
242
+ #
243
+ # @param content [Content] The content to manipulate.
244
+ # @param metadata [Hash] The metadata to assign.
245
+ # @return [Content] The content with metadata.
246
+ def self.assign_metadata(content, metadata)
247
+ metadata = metadata.symbolize_keys!
248
+
249
+ content.uid = metadata.delete(:uid)
250
+ content.title = metadata.delete(:title)
251
+ content.author = Mbrao::Author.create(metadata.delete(:author))
252
+ content.tags = metadata.delete(:tags)
253
+ content.more = metadata.delete(:more)
254
+ content.created_at = metadata.delete(:created_at)
255
+ content.updated_at = metadata.delete(:updated_at)
256
+ content.locales = extract_locales(metadata)
257
+ content.metadata = metadata
258
+
259
+ content
260
+ end
261
+
262
+ # Extract locales from metadata.
263
+ #
264
+ # @param metadata [Hash] The metadata that contains the locales.
265
+ # @return [Array] The locales.
266
+ def self.extract_locales(metadata)
267
+ locales = metadata.delete(:locales)
268
+ locales = locales.ensure_string.split(/\s*,\s*/) if !locales.is_a?(::Array)
269
+ normalize_locales(locales)
270
+ end
271
+
272
+ # Normalize locales for further usage.
273
+ #
274
+ # @param locales [Array] The locales to normalize.
275
+ # @return [Array] The normalized locales.
276
+ def self.normalize_locales(locales)
277
+ locales.flatten.collect(&:ensure_string).collect(&:strip).uniq
278
+ end
279
+
280
+ # Extracts a date and time from a value.
281
+ #
282
+ # @param value [String|DateTime|Fixnum] The value to parse.
283
+ # @return [DateTime] The extract values.
284
+ def extract_datetime(value)
285
+ begin
286
+ if value.is_a?(Date) || value.is_a?(Time) then
287
+ value = value.to_datetime
288
+ elsif value.to_float > 0 then
289
+ value = Time.at(value.to_float).to_datetime
290
+ elsif value && !value.is_a?(DateTime) then
291
+ value = DateTime.strptime(value.ensure_string, "%Y%m%dT%H%M%S%z")
292
+ end
293
+
294
+ value ? value.utc : nil
295
+ rescue ArgumentError => _
296
+ raise Mbrao::Exceptions::InvalidDate.new
297
+ end
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the mbrao gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ module Mbrao
8
+ # The exceptions raised by mbrao.
9
+ module Exceptions
10
+ # Exception raised when metadata are not valid.
11
+ class InvalidMetadata < ::Exception
12
+ end
13
+
14
+ # Exception raised when a date is valid.
15
+ class InvalidDate < ::Exception
16
+ end
17
+
18
+ # Exception raised when there is a parsing error.
19
+ class Parsing < ::Exception
20
+ end
21
+
22
+ # Exception raised when there is a rendering error.
23
+ class Rendering < ::Exception
24
+ end
25
+
26
+ # Exception raised when a requested object is not available in any of the desired locales.
27
+ class UnavailableLocalization < ::Exception
28
+ end
29
+
30
+ # Exception raised when a invalid parsing or rendering engine is requested.
31
+ class UnknownEngine < ::Exception
32
+ end
33
+
34
+ # Exception raised when a requested method must be overridden by a subclass.
35
+ class Unimplemented < ::Exception
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the mbrao gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ # Generic interface to multiple Ruby template engines.
8
+ module ActionView::Template::Handlers
9
+ # Class for rendering mbrao contents in Rails.
10
+ class MbraoTemplate
11
+ # Returns a unique (singleton) instance of the template handler.
12
+ #
13
+ # @param force [Boolean] If to force recreation of the instance.
14
+ # @return [MbraoTemplate] The unique (singleton) instance of the template handler.
15
+ def self.instance(force = false)
16
+ @instance = nil if force
17
+ @instance ||= ActionView::Template::Handlers::MbraoTemplate.new
18
+ end
19
+
20
+ # Renders a template into a renderer context.
21
+ #
22
+ # @param renderer [Object] The renderer context.
23
+ # @param template [String] The template to render.
24
+ # @return [String] The rendered template.
25
+ def render(renderer, template)
26
+ content = ::Mbrao::Parser.parse(template)
27
+ renderer.controller.instance_variable_set(:@mbrao_content, content)
28
+ renderer.controller.define_singleton_method(:mbrao_content) { @mbrao_content }
29
+ renderer.controller.send(:helper_method, :mbrao_content)
30
+
31
+ ::Mbrao::Parser.render(content, {engine: content.metadata[:engine], locale: renderer.controller.params[:locale]})
32
+ end
33
+
34
+ # Declares support for streaming.
35
+ #
36
+ # @return [TrueClass] Declares support for streaming.
37
+ def supports_streaming?
38
+ true
39
+ end
40
+
41
+ # Method called to render a template.
42
+ #
43
+ # @param template [ActionView::Template] The template to render.
44
+ # @return [String] A Ruby snippet to execute to render the template.
45
+ def call(template)
46
+ "ActionView::Template::Handlers::MbraoTemplate.instance.render(self, #{template.source.to_json})"
47
+ end
48
+ end
49
+ end
50
+
51
+ ActionView::Template.register_template_handler "emt", ActionView::Template::Handlers::MbraoTemplate.instance if defined?(ActionView) && defined?(Rails) && Rails.version =~ /^[34]/
@@ -0,0 +1,276 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the mbrao gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ # A content parser and renderer with embedded metadata support.
8
+ module Mbrao
9
+ # Methods to allow class level access.
10
+ module PublicInterface
11
+ extend ActiveSupport::Concern
12
+
13
+ # Class methods.
14
+ #
15
+ # @attribute default_locale
16
+ # @return [String] The mbrao default locale.
17
+ # @attribute parsing_engine
18
+ # @return [String] The default parsing engine.
19
+ # @attribute rendering_engine
20
+ # @return [String] The default rendering engine.
21
+ module ClassMethods
22
+ attr_accessor :locale
23
+ attr_accessor :parsing_engine
24
+ attr_accessor :rendering_engine
25
+
26
+ # Gets the default locale for mbrao.
27
+ #
28
+ # @return [String] The default locale.
29
+ def locale
30
+ attribute_or_default(@locale, "en")
31
+ end
32
+
33
+ # Gets the default parsing engine.
34
+ #
35
+ # @return [String] The default parsing engine.
36
+ def parsing_engine
37
+ attribute_or_default(@parsing_engine, :plain_text, :to_sym)
38
+ end
39
+
40
+ # Gets the default rendering engine.
41
+ #
42
+ # @return [String] The default rendering engine.
43
+ def rendering_engine
44
+ attribute_or_default(@rendering_engine, :html_pipeline, :to_sym)
45
+ end
46
+
47
+ # Parses a source text.
48
+ #
49
+ # @param content [Object] The content to parse.
50
+ # @param options [Hash] A list of options for parsing.
51
+ # @return [Content] The parsed data.
52
+ def parse(content, options = {})
53
+ self.instance.parse(content, options)
54
+ end
55
+
56
+ # Renders a content.
57
+ #
58
+ # @param content [Content] The content to parse.
59
+ # @param options [Hash] A list of options for renderer.
60
+ # @param context [Hash] A context for rendering.
61
+ # @return [String] The rendered content.
62
+ def render(content, options = {}, context = {})
63
+ self.instance.render(content, options, context)
64
+ end
65
+
66
+ # Instantiates a new engine for rendering or parsing.
67
+ #
68
+ # @param cls [String|Symbol|Object] If a `String` or a `Symbol`, then it will be the class of the engine.
69
+ # @param type [Symbol] The type or engine. Can be `:parsing` or `:rendering`.
70
+ # @return [Object] A new engine.
71
+ def create_engine(cls, type = :parsing)
72
+ begin
73
+ type = :parsing if type != :rendering
74
+ ::Mbrao::Parser.find_class(cls, "::Mbrao::#{type.to_s.classify}Engines::%CLASS%").new
75
+ rescue Mbrao::Exceptions::Unimplemented
76
+ raise Mbrao::Exceptions::UnknownEngine.new
77
+ end
78
+ end
79
+
80
+ # Finds a class to instantiate.
81
+ #
82
+ # @param cls [Symbol|String|Object] If a `String` or a `Symbol`, then it will be the class to instantiate. Otherwise the class of the object will returned.
83
+ # @param scope [String] An additional scope to find the class. `%CLASS%` will be substituted with the class name.
84
+ # @param only_in_scope [Boolean] If only try to instantiate the class in the scope.
85
+ # @return [Class] The found class.
86
+ def find_class(cls, scope = "::%CLASS%", only_in_scope = false)
87
+ if cls.is_a?(String) || cls.is_a?(Symbol) then
88
+ rv = nil
89
+
90
+ cls = cls.to_s.camelize
91
+ if only_in_scope then
92
+ cls.gsub!(/^::/, "")
93
+ else
94
+ rv = (cls.constantize rescue nil)
95
+ end
96
+
97
+ rv = (scope.ensure_string.gsub("%CLASS%", cls).constantize rescue nil) if !rv && cls !~ /^::/ && scope.present?
98
+ rv || raise(Mbrao::Exceptions::Unimplemented.new(cls))
99
+ else
100
+ cls.class
101
+ end
102
+ end
103
+
104
+ # Returns a unique (singleton) instance of the parser.
105
+ #
106
+ # @param force [Boolean] If to force recreation of the instance.
107
+ # @return [Parser] The unique (singleton) instance of the parser.
108
+ def instance(force = false)
109
+ @instance = nil if force
110
+ @instance ||= Mbrao::Parser.new
111
+ end
112
+
113
+ private
114
+ # Returns an attribute or a default value.
115
+ #
116
+ # @param attr [Object ]The attribute to return.
117
+ # @param default_value [Object] The value to return if `attr` is blank.
118
+ # @param sanitizer [Symbol] An optional method to sanitize the returned value.
119
+ def attribute_or_default(attr, default_value = nil, sanitizer = :ensure_string)
120
+ rv = attr.present? ? attr : default_value
121
+ rv = rv.send(sanitizer) if sanitizer
122
+ rv
123
+ end
124
+ end
125
+ end
126
+
127
+ # Methods to perform validations.
128
+ module Validations
129
+ extend ActiveSupport::Concern
130
+
131
+ # Class methods.
132
+ module ClassMethods
133
+ # Checks if the text is a valid email.
134
+ #
135
+ # @param text [String] The text to check.
136
+ # @return [Boolean] `true` if the string is valid email, `false` otherwise.
137
+ def is_email?(text)
138
+ regex = /^([a-z0-9_\.\-\+]+)@([\da-z\.\-]+)\.([a-z\.]{2,6})$/i
139
+ text.ensure_string.strip =~ regex
140
+ end
141
+
142
+ # Checks if the text is a valid URL.
143
+ #
144
+ # @param text [String] The text to check.
145
+ # @return [Boolean] `true` if the string is valid URL, `false` otherwise.
146
+ def is_url?(text)
147
+ regex = /
148
+ ^(
149
+ ([a-z0-9\-]+:\/\/) #PROTOCOL
150
+ (([\w-]+\.)?) # LOWEST TLD
151
+ ([\w-]+) # 2nd LEVEL TLD
152
+ (\.[a-z]+) # TOP TLD
153
+ ((:\d+)?) # PORT
154
+ ([\S|\?]*) # PATH, QUERYSTRING AND FRAGMENT
155
+ )$
156
+ /ix
157
+
158
+ text.ensure_string.strip =~ regex
159
+ end
160
+
161
+ # Converts an object making sure that every `Hash` is converted to a `HashWithIndifferentAccess`.
162
+ #
163
+ # @param object [Object] The object to convert. If the object is not an Hash or doesn't respond to `collect` then the original object is returned.
164
+ # @param sanitize_method [Symbol|nil] If not `nil`, the method to use to sanitize entries. Ignored if a block is present.
165
+ # @param block [Proc] A block to sanitize entries. It must accept the value as unique argument.
166
+ # @return [Object] The converted object.
167
+ def sanitized_hash(object, sanitize_method = nil, &block)
168
+ if object.is_a?(Hash) || object.is_a?(HashWithIndifferentAccess) then
169
+ object.inject(HashWithIndifferentAccess.new) do |hash, pair|
170
+ hash[pair[0]] = Mbrao::Parser.sanitized_hash(pair[1], sanitize_method, &block)
171
+ hash
172
+ end
173
+ elsif object.respond_to?(:collect) then
174
+ object.collect {|item| Mbrao::Parser.sanitized_hash(item, sanitize_method, &block) }
175
+ else
176
+ sanitized_hash_entry(object, sanitize_method, &block)
177
+ end
178
+ end
179
+
180
+ # Converts an object to a a flatten array with all values sanitized.
181
+ #
182
+ # @param object [Object] The object to convert.
183
+ # @param uniq [Boolean] If to remove duplicates from the array before sanitizing.
184
+ # @param compact [Boolean] If to compact the array before sanitizing.
185
+ # @param sanitize_method [Symbol|nil] If not `nil`, the method to use to sanitize entries. Ignored if a block is present.
186
+ # @param block [Proc] A block to sanitize entries. It must accept the value as unique argument.
187
+ # @return [Array] An flattened array whose all values are strings.
188
+ def sanitized_array(object, uniq = true, compact = false, sanitize_method = :ensure_string, &block)
189
+ rv = object.ensure_array.flatten
190
+ rv.uniq! if uniq
191
+ rv.compact! if compact
192
+
193
+ if block then
194
+ rv = rv.collect(&block)
195
+ elsif sanitize_method then
196
+ rv = rv.collect(&sanitize_method)
197
+ end
198
+
199
+ rv.uniq! if uniq
200
+ rv.compact! if compact
201
+ rv
202
+ end
203
+
204
+ private
205
+ # Sanitizes an value for an hash.
206
+ #
207
+ # @param value [Object] The value to sanitize.
208
+ # @param sanitize_method [Symbol|nil] If not `nil`, the method to use to sanitize the value. Ignored if a block is present.
209
+ # @param block [Proc] A block to sanitize the value. It must accept the value as unique argument.
210
+ def sanitized_hash_entry(value, sanitize_method = :ensure_string, &block)
211
+ if block
212
+ block.call(value)
213
+ elsif sanitize_method
214
+ value.send(sanitize_method)
215
+ else
216
+ value
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ # A parser to handle pipelined content.
223
+ #
224
+ class Parser
225
+ include Mbrao::PublicInterface
226
+ include Mbrao::Validations
227
+
228
+ # Parses a source text.
229
+ #
230
+ # @param content [Object] The content to parse.
231
+ # @param options [Hash] A list of options for parsing.
232
+ # @return [Content] The parsed data.
233
+ def parse(content, options = {})
234
+ options = sanitize_parsing_options(options)
235
+ ::Mbrao::Parser.create_engine(options[:engine]).parse(content, options)
236
+ end
237
+
238
+ # Renders a content.
239
+ #
240
+ # @param content [Content] The content to parse.
241
+ # @param options [Hash] A list of options for renderer.
242
+ # @param context [Hash] A context for rendering.
243
+ # @return [String] The rendered content.
244
+ def render(content, options = {}, context = {})
245
+ options = sanitize_rendering_options(options)
246
+ ::Mbrao::Parser.create_engine(options[:engine], :rendering).render(content, options, context)
247
+ end
248
+
249
+ private
250
+ # Sanitizes options for parsing.
251
+ #
252
+ # @param options [Hash] The options to sanitize.
253
+ # @return [HashWithIndifferentAccess] The sanitized options.
254
+ def sanitize_parsing_options(options)
255
+ options = (options.is_a?(Hash) ? options : {}).symbolize_keys
256
+
257
+ options[:engine] ||= Mbrao::Parser.parsing_engine
258
+ options[:metadata] = options.fetch(:metadata, true).to_boolean
259
+ options[:content] = options.fetch(:content, true).to_boolean
260
+
261
+ HashWithIndifferentAccess.new(options)
262
+ end
263
+
264
+ # Sanitizes options for rendering.
265
+ #
266
+ # @param options [Hash] The options to sanitize.
267
+ # @return [HashWithIndifferentAccess] The sanitized options.
268
+ def sanitize_rendering_options(options)
269
+ options = (options.is_a?(Hash) ? options : {}).symbolize_keys
270
+
271
+ options[:engine] ||= Mbrao::Parser.rendering_engine
272
+
273
+ HashWithIndifferentAccess.new(options)
274
+ end
275
+ end
276
+ end