mbrao 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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