locomotivecms_mounter 1.0.0.alpha1

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 (71) hide show
  1. data/lib/locomotive/mounter/config.rb +21 -0
  2. data/lib/locomotive/mounter/engine_api.rb +40 -0
  3. data/lib/locomotive/mounter/exceptions.rb +38 -0
  4. data/lib/locomotive/mounter/extensions/compass.rb +36 -0
  5. data/lib/locomotive/mounter/extensions/httmultiparty.rb +22 -0
  6. data/lib/locomotive/mounter/extensions/tilt/css.rb +31 -0
  7. data/lib/locomotive/mounter/extensions/tilt/haml.rb +27 -0
  8. data/lib/locomotive/mounter/extensions/tilt/liquid.rb +23 -0
  9. data/lib/locomotive/mounter/extensions/tilt/template.rb +11 -0
  10. data/lib/locomotive/mounter/fields.rb +250 -0
  11. data/lib/locomotive/mounter/models/base.rb +41 -0
  12. data/lib/locomotive/mounter/models/content_asset.rb +84 -0
  13. data/lib/locomotive/mounter/models/content_entry.rb +290 -0
  14. data/lib/locomotive/mounter/models/content_field.rb +128 -0
  15. data/lib/locomotive/mounter/models/content_select_option.rb +29 -0
  16. data/lib/locomotive/mounter/models/content_type.rb +217 -0
  17. data/lib/locomotive/mounter/models/editable_element.rb +27 -0
  18. data/lib/locomotive/mounter/models/page.rb +377 -0
  19. data/lib/locomotive/mounter/models/site.rb +27 -0
  20. data/lib/locomotive/mounter/models/snippet.rb +55 -0
  21. data/lib/locomotive/mounter/models/theme_asset.rb +135 -0
  22. data/lib/locomotive/mounter/models/translation.rb +28 -0
  23. data/lib/locomotive/mounter/mounting_point.rb +49 -0
  24. data/lib/locomotive/mounter/reader/api/base.rb +49 -0
  25. data/lib/locomotive/mounter/reader/api/content_assets_reader.rb +40 -0
  26. data/lib/locomotive/mounter/reader/api/content_entries_reader.rb +141 -0
  27. data/lib/locomotive/mounter/reader/api/content_types_reader.rb +74 -0
  28. data/lib/locomotive/mounter/reader/api/pages_reader.rb +174 -0
  29. data/lib/locomotive/mounter/reader/api/site_reader.rb +37 -0
  30. data/lib/locomotive/mounter/reader/api/snippets_reader.rb +59 -0
  31. data/lib/locomotive/mounter/reader/api/theme_assets_reader.rb +42 -0
  32. data/lib/locomotive/mounter/reader/api/translations_reader.rb +28 -0
  33. data/lib/locomotive/mounter/reader/api.rb +49 -0
  34. data/lib/locomotive/mounter/reader/file_system/base.rb +65 -0
  35. data/lib/locomotive/mounter/reader/file_system/content_assets_reader.rb +88 -0
  36. data/lib/locomotive/mounter/reader/file_system/content_entries_reader.rb +101 -0
  37. data/lib/locomotive/mounter/reader/file_system/content_types_reader.rb +88 -0
  38. data/lib/locomotive/mounter/reader/file_system/pages_reader.rb +206 -0
  39. data/lib/locomotive/mounter/reader/file_system/site_reader.rb +24 -0
  40. data/lib/locomotive/mounter/reader/file_system/snippets_reader.rb +78 -0
  41. data/lib/locomotive/mounter/reader/file_system/theme_assets_reader.rb +78 -0
  42. data/lib/locomotive/mounter/reader/file_system/translations_reader.rb +36 -0
  43. data/lib/locomotive/mounter/reader/file_system.rb +42 -0
  44. data/lib/locomotive/mounter/reader/runner.rb +89 -0
  45. data/lib/locomotive/mounter/utils/hash.rb +31 -0
  46. data/lib/locomotive/mounter/utils/string.rb +17 -0
  47. data/lib/locomotive/mounter/utils/yaml.rb +125 -0
  48. data/lib/locomotive/mounter/version.rb +8 -0
  49. data/lib/locomotive/mounter/writer/api/base.rb +323 -0
  50. data/lib/locomotive/mounter/writer/api/content_assets_writer.rb +74 -0
  51. data/lib/locomotive/mounter/writer/api/content_entries_writer.rb +223 -0
  52. data/lib/locomotive/mounter/writer/api/content_types_writer.rb +151 -0
  53. data/lib/locomotive/mounter/writer/api/pages_writer.rb +225 -0
  54. data/lib/locomotive/mounter/writer/api/site_writer.rb +164 -0
  55. data/lib/locomotive/mounter/writer/api/snippets_writer.rb +111 -0
  56. data/lib/locomotive/mounter/writer/api/theme_assets_writer.rb +152 -0
  57. data/lib/locomotive/mounter/writer/api/translations_writer.rb +83 -0
  58. data/lib/locomotive/mounter/writer/api.rb +62 -0
  59. data/lib/locomotive/mounter/writer/file_system/base.rb +61 -0
  60. data/lib/locomotive/mounter/writer/file_system/content_assets_writer.rb +33 -0
  61. data/lib/locomotive/mounter/writer/file_system/content_entries_writer.rb +29 -0
  62. data/lib/locomotive/mounter/writer/file_system/content_types_writer.rb +27 -0
  63. data/lib/locomotive/mounter/writer/file_system/pages_writer.rb +73 -0
  64. data/lib/locomotive/mounter/writer/file_system/site_writer.rb +25 -0
  65. data/lib/locomotive/mounter/writer/file_system/snippets_writer.rb +54 -0
  66. data/lib/locomotive/mounter/writer/file_system/theme_assets_writer.rb +35 -0
  67. data/lib/locomotive/mounter/writer/file_system/translations_writer.rb +22 -0
  68. data/lib/locomotive/mounter/writer/file_system.rb +69 -0
  69. data/lib/locomotive/mounter/writer/runner.rb +68 -0
  70. data/lib/locomotive/mounter.rb +97 -0
  71. metadata +487 -0
@@ -0,0 +1,21 @@
1
+ module Locomotive
2
+ module Mounter
3
+
4
+ class Config < Hash
5
+
6
+ def self.instance
7
+ @@instance ||= self.new
8
+ end
9
+
10
+ def self.register(attributes)
11
+ self.instance.merge!(attributes)
12
+ end
13
+
14
+ def self.[](key)
15
+ self.instance[key]
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ module Locomotive
2
+ module Mounter
3
+
4
+ class EngineApi
5
+
6
+ include HTTMultiParty
7
+
8
+ format :json
9
+
10
+ # Get a new token from the Engine API and set it for
11
+ # this class. It raises an exception if the operation fails.
12
+ # Example of the base uri: locomotivecms.com, nocoffee.fr.
13
+ #
14
+ # @param [ Hash / Array ] *args A hash or parameters in that order: uri, email, password
15
+ #
16
+ # @return [ String ] The new token
17
+ #
18
+ def self.set_token(*args)
19
+ uri, email, password = (if args.first.is_a?(Hash)
20
+ [args.first[:uri], args.first[:email], args.first[:password]]
21
+ else
22
+ [*args]
23
+ end)
24
+
25
+ self.base_uri uri
26
+
27
+ response = post('/tokens.json', body: { email: email, password: password })
28
+
29
+ if response.code < 400
30
+ self.default_params auth_token: response['token']
31
+ response['token']
32
+ else
33
+ raise WrongCredentials.new("#{response['message']} (#{response.code})")
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,38 @@
1
+ module Locomotive
2
+ module Mounter
3
+
4
+ class DefaultException < ::Exception
5
+
6
+ def initialize(message = nil)
7
+ Locomotive::Mounter.logger.error message
8
+ super
9
+ end
10
+
11
+ end
12
+
13
+ class ReaderException < DefaultException
14
+ end
15
+
16
+ class WriterException < DefaultException
17
+ end
18
+
19
+ class ImplementationIsMissingException < DefaultException
20
+ end
21
+
22
+ class FieldDoesNotExistException < DefaultException
23
+ end
24
+
25
+ class UnknownContentTypeException < DefaultException
26
+ end
27
+
28
+ class DuplicateContentEntryException < DefaultException
29
+ end
30
+
31
+ class UnknownTemplateException < DefaultException
32
+ end
33
+
34
+ class WrongCredentials < DefaultException
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,36 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Extensions
4
+
5
+ module Compass
6
+
7
+ # Configure Compass for the current site
8
+ #
9
+ # @param [ String ] site_path The root directory of the site
10
+ #
11
+ def self.configure(site_path)
12
+ ::Compass.configuration do |config|
13
+ config.project_path = File.join(site_path, 'public')
14
+ config.http_path = '/'
15
+ config.css_dir = '../tmp/stylesheets'
16
+ config.sass_dir = 'stylesheets'
17
+ config.images_dir = 'images'
18
+ config.javascripts_dir = 'javascripts'
19
+ config.project_type = :stand_alone
20
+ config.output_style = :nested
21
+ config.line_comments = false
22
+ end
23
+ end
24
+
25
+ # # Return the Compass options
26
+ # #
27
+ # # @return [ Hash ] The Compass options
28
+ # def self.options
29
+ # ::Compass.configuration.to_sass_engine_options
30
+ # end
31
+
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # Patch for https://github.com/jwagener/httmultiparty/issues/11
2
+
3
+ module HTTMultiParty
4
+ module ClassMethods
5
+
6
+ private
7
+
8
+ def perform_request(http_method, path, options, &block) #:nodoc:
9
+ options = default_options.dup.merge(options)
10
+
11
+ # FIXME: default_params are not handled for some unknown reasons, so move them to the body instead
12
+ if http_method == MultipartPost || http_method == MultipartPut
13
+ default_params = options.delete(:default_params)
14
+ options[:body].merge!(default_params) if options[:body] && default_params
15
+ end
16
+
17
+ process_cookies(options)
18
+ HTTParty::Request.new(http_method, path, options).perform(&block)
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ module Tilt
2
+
3
+ # Sass with Compass features
4
+ class SassWithCompassTemplate < SassTemplate
5
+
6
+ private
7
+
8
+ def sass_options
9
+ super.merge(::Compass.configuration.to_sass_engine_options)
10
+ end
11
+
12
+ end
13
+
14
+ Tilt.register 'sass', SassWithCompassTemplate
15
+ Tilt.prefer SassWithCompassTemplate
16
+
17
+ # Scss with Compass features
18
+ class ScssWithCompassTemplate < ScssTemplate
19
+
20
+ private
21
+
22
+ def sass_options
23
+ super.merge(::Compass.configuration.to_sass_engine_options)
24
+ end
25
+
26
+ end
27
+
28
+ Tilt.register 'scss', ScssWithCompassTemplate
29
+ Tilt.prefer ScssWithCompassTemplate
30
+
31
+ end
@@ -0,0 +1,27 @@
1
+ module Tilt
2
+
3
+ # YAML Front-matters for HAML templates
4
+ class YamlFrontMattersHamlTemplate < HamlTemplate
5
+
6
+ # Attributes from YAML Front-matters header
7
+ attr_reader :attributes
8
+
9
+ def need_for_prerendering?
10
+ true
11
+ end
12
+
13
+ def prepare
14
+ if data =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)(.*)/m
15
+ @attributes = YAML.load($1)
16
+ @data = $3
17
+ end
18
+ @data = @data.force_encoding('utf-8')
19
+ super
20
+ end
21
+
22
+ end
23
+
24
+ Tilt.register 'haml', YamlFrontMattersHamlTemplate
25
+ Tilt.prefer YamlFrontMattersHamlTemplate
26
+
27
+ end
@@ -0,0 +1,23 @@
1
+ module Tilt
2
+
3
+ # YAML Front-matters for Liquid templates
4
+ class YamlFrontMattersLiquidTemplate < Template
5
+
6
+ # Attributes from YAML Front-matters header
7
+ attr_reader :attributes
8
+
9
+ def prepare
10
+ if data =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)(.*)/m
11
+ @attributes = YAML.load($1)
12
+ @data = $3
13
+ end
14
+ @data = @data.force_encoding('utf-8')
15
+ # Note: do not call 'super' because we are going to use a different parse mechanism
16
+ end
17
+
18
+ end
19
+
20
+ Tilt.register 'liquid', YamlFrontMattersLiquidTemplate
21
+ Tilt.prefer YamlFrontMattersLiquidTemplate
22
+
23
+ end
@@ -0,0 +1,11 @@
1
+ module Tilt
2
+
3
+ class Template
4
+
5
+ def need_for_prerendering?
6
+ false
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,250 @@
1
+ module Locomotive
2
+ module Mounter
3
+
4
+ module Fields
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include ActiveSupport::Callbacks
10
+
11
+ define_callbacks :initialize
12
+
13
+ class << self; attr_accessor :_fields end
14
+
15
+ attr_accessor :_locales
16
+ end
17
+
18
+ # Default constructor
19
+ #
20
+ # @param [ Hash ] attributes The new attributes
21
+ #
22
+ def initialize(attributes = {})
23
+ run_callbacks :initialize do
24
+ _attributes = attributes.symbolize_keys
25
+
26
+ # set default values
27
+ self.class._fields.each do |name, options|
28
+ next if !options.key?(:default) || _attributes.key?(name)
29
+
30
+ _attributes[name] = options[:default]
31
+ end
32
+
33
+ # set default translation
34
+ self.add_locale(Locomotive::Mounter.locale)
35
+
36
+ self.write_attributes(_attributes)
37
+ end
38
+ end
39
+
40
+ # Set or replace the attributes of the current instance by the ones
41
+ # passed as parameter.
42
+ # It raises an exception if one of the keys is not included in the list of fields.
43
+ #
44
+ # @param [ Hash ] attributes The new attributes
45
+ #
46
+ def write_attributes(attributes)
47
+ return if attributes.blank?
48
+
49
+ attributes.each do |name, value|
50
+ unless self.class._fields.key?(name.to_sym) || self.respond_to?(:"#{name}=")
51
+ raise FieldDoesNotExistException.new "[#{self.class.inspect}] setting an unknown attribute '#{name}' with the value '#{value.inspect}'"
52
+ end
53
+
54
+ if self.localized?(name) && value.is_a?(Hash)
55
+ self.send(:"#{name}_translations=", value)
56
+ else
57
+ self.send(:"#{name}=", value)
58
+ end
59
+ end
60
+ end
61
+
62
+ alias :attributes= :write_attributes
63
+
64
+ # Return the fields with their values
65
+ #
66
+ # @return [ Hash ] The attributes
67
+ #
68
+ def attributes
69
+ {}.tap do |_attributes|
70
+ self.class._fields.each do |name, options|
71
+ _attributes[name] = self.send(name.to_sym)
72
+ end
73
+ end
74
+ end
75
+
76
+ # Return the fields with their values and their translations
77
+ #
78
+ # @return [ Hash ] The attributes
79
+ #
80
+ def attributes_with_translations
81
+ {}.tap do |_attributes|
82
+ self.class._fields.each do |name, options|
83
+ next if options[:association]
84
+
85
+ if options[:localized]
86
+ value = self.send(:"#{name}_translations")
87
+
88
+ value = value.values.first if value.size == 1
89
+
90
+ value = nil if value.empty?
91
+
92
+ _attributes[name] = value
93
+ else
94
+ _attributes[name] = self.send(name.to_sym)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # Check if the field specified by the argument is localized
101
+ #
102
+ # @param [ String ] name Name of the field
103
+ #
104
+ # @return [ Boolean ] True if the field is localized
105
+ #
106
+ def localized?(name)
107
+ method_name = :"#{name}_localized?"
108
+ self.respond_to?(method_name) && self.send(method_name)
109
+ end
110
+
111
+ # List all the translations done on that model
112
+ #
113
+ # @return [ List ] List of locales
114
+ #
115
+ def translated_in
116
+ self._locales.map(&:to_sym)
117
+ end
118
+
119
+ # Tell if the object has been translated in the locale
120
+ # passed in parameter.
121
+ #
122
+ # @param [ String/Symbol ] locale
123
+ #
124
+ # @return [ Boolean ] True if one of the fields has been translated.
125
+ #
126
+ def translated_in?(locale)
127
+ self.translated_in.include?(locale.to_sym)
128
+ end
129
+
130
+ # Return a Hash of all the non blank attributes of the object.
131
+ # It also performs a couple of modifications: stringify keys and
132
+ # convert Symbol to String.
133
+ #
134
+ # @param [ Boolean ] translation Flag (by default true) to get the translations too.
135
+ #
136
+ # @return [ Hash ] The non blank attributes
137
+ #
138
+ def to_hash(translations = true)
139
+ hash = translations ? self.attributes_with_translations : self.attributes
140
+
141
+ hash.delete_if { |k, v| v.blank? }
142
+
143
+ hash.each { |k, v| hash[k] = v.to_s if v.is_a?(Symbol) }
144
+
145
+ hash.deep_stringify_keys
146
+ end
147
+
148
+ # Provide a better output of the default to_yaml method
149
+ #
150
+ # @return [ String ] The YAML version of the object
151
+ #
152
+ def to_yaml
153
+ # get the attributes with their translations and get rid of all the symbols
154
+ object = self.to_hash
155
+
156
+ object.each do |key, value|
157
+ if value.is_a?(Array)
158
+ object[key] = if value.first.is_a?(String)
159
+ StyledYAML.inline(value) # inline array
160
+ else
161
+ value.map(&:to_hash)
162
+ end
163
+ end
164
+ end
165
+
166
+ StyledYAML.dump object
167
+ end
168
+
169
+ protected
170
+
171
+ def getter(name, options = {})
172
+ value = self.instance_variable_get(:"@#{name}")
173
+ if options[:localized]
174
+ (value || {})[Locomotive::Mounter.locale]
175
+ else
176
+ value
177
+ end
178
+ end
179
+
180
+ def setter(name, value, options = {})
181
+ if options[:localized]
182
+ # keep track of the current locale
183
+ self.add_locale(Locomotive::Mounter.locale)
184
+
185
+ translations = self.instance_variable_get(:"@#{name}") || {}
186
+ translations[Locomotive::Mounter.locale] = value
187
+ value = translations
188
+ end
189
+
190
+ if options[:type] == :array
191
+ klass = options[:class_name].constantize
192
+ value = value.map { |object| object.is_a?(Hash) ? klass.new(object) : object }
193
+ end
194
+
195
+ self.instance_variable_set(:"@#{name}", value)
196
+ end
197
+
198
+ def add_locale(locale)
199
+ self._locales ||= []
200
+ self._locales << locale.to_sym unless self._locales.include?(locale.to_sym)
201
+ end
202
+
203
+ module ClassMethods
204
+
205
+ # Add a field to the current instance. It creates getter/setter methods related to that field.
206
+ # A field can have translations if the option named localized is set to true.
207
+ #
208
+ # @param [ String ] name The name of the field
209
+ # @param [ Hash ] options The options related to the field.
210
+ #
211
+ def field(name, options = {})
212
+ options = { localized: false }.merge(options)
213
+
214
+ @_fields ||= {} # initialize the list of fields if nil
215
+
216
+ self._fields[name.to_sym] = options
217
+
218
+ class_eval <<-EOV
219
+ def #{name}
220
+ self.getter '#{name}', self.class._fields[:#{name}]
221
+ end
222
+
223
+ def #{name}=(value)
224
+ self.setter '#{name}', value, self.class._fields[:#{name}] #, #{options[:localized]}
225
+ end
226
+
227
+ def #{name}_localized?
228
+ #{options[:localized]}
229
+ end
230
+ EOV
231
+
232
+ if options[:localized]
233
+ class_eval <<-EOV
234
+ def #{name}_translations
235
+ @#{name} || {}
236
+ end
237
+
238
+ def #{name}_translations=(translations)
239
+ translations.each { |locale, value| self.add_locale(locale) }
240
+ @#{name} = translations.symbolize_keys
241
+ end
242
+ EOV
243
+ end
244
+ end
245
+
246
+ end
247
+
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,41 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Models
4
+
5
+ class Base
6
+
7
+ include Locomotive::Mounter::Fields
8
+
9
+ attr_accessor :_id, :mounting_point
10
+
11
+ ## methods ##
12
+
13
+ def initialize(attributes = {})
14
+ self.mounting_point = attributes.delete(:mounting_point)
15
+ super
16
+ end
17
+
18
+ def persisted?
19
+ !self._id.blank?
20
+ end
21
+
22
+ protected
23
+
24
+ # Take a list of field names and return a hash with
25
+ # their values only if they are not nil.
26
+ #
27
+ # @param [ Array ] fields List of field names (string)
28
+ #
29
+ # @return [ Hash ] A hash with symbolize keys
30
+ #
31
+ def filter_attributes(fields)
32
+ self.attributes.clone.delete_if do |k, v|
33
+ !fields.include?(k.to_s) || (!v.is_a?(FalseClass) && v.blank?)
34
+ end.deep_symbolize_keys
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,84 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Models
4
+
5
+ class ContentAsset < Base
6
+
7
+ ## fields ##
8
+ field :source
9
+
10
+ ## other accessors ##
11
+ attr_accessor :folder, :filepath, :uri
12
+
13
+ ## methods ##
14
+
15
+ # Name of the file
16
+ #
17
+ # @return [ String ] Name of the file
18
+ #
19
+ def filename
20
+ return @filename if @filename
21
+
22
+ if self.uri
23
+ @filename = File.basename(self.uri.path)
24
+ else
25
+ @filename = File.basename(self.filepath)
26
+ end
27
+ end
28
+
29
+ # Content of the asset.
30
+ #
31
+ # @return [ String ] The content of the asset
32
+ #
33
+ def content
34
+ return @raw if @raw
35
+
36
+ if self.uri
37
+ @raw = Net::HTTP.get(self.uri)
38
+ else
39
+ @raw = File.read(self.filepath)
40
+ end
41
+ end
42
+
43
+ def size
44
+ if self.uri
45
+ Net::HTTP.get(self.uri).body.size
46
+ else
47
+ File.size(self.filepath)
48
+ end
49
+ end
50
+
51
+ # Return true if the uri or the file exists.
52
+ #
53
+ # @return [ Boolean ] True if it exists
54
+ #
55
+ def exists?
56
+ self.size
57
+ true
58
+ rescue
59
+ false
60
+ end
61
+
62
+ def local_filepath
63
+ File.join('/', self.folder, self.filename)
64
+ end
65
+
66
+ # Return the params used for the API.
67
+ #
68
+ # @return [ Hash ] The params
69
+ #
70
+ def to_params
71
+ return {} if self.uri
72
+
73
+ { source: File.new(self.filepath) }
74
+ end
75
+
76
+ def to_s
77
+ self.uri ? self.uri.path : self.filename
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
84
+ end