locomotivecms_mounter 1.0.0.alpha1

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