locomotivecms_mounter_pull_19 1.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +7 -0
  6. data/Gemfile.lock +131 -0
  7. data/MIT-LICENSE +20 -0
  8. data/NOTES +4 -0
  9. data/README.md +26 -0
  10. data/Rakefile +37 -0
  11. data/TODO +82 -0
  12. data/init.rb +2 -0
  13. data/lib/locomotive/mounter.rb +106 -0
  14. data/lib/locomotive/mounter/config.rb +21 -0
  15. data/lib/locomotive/mounter/engine_api.rb +205 -0
  16. data/lib/locomotive/mounter/exceptions.rb +44 -0
  17. data/lib/locomotive/mounter/extensions/compass.rb +38 -0
  18. data/lib/locomotive/mounter/extensions/httmultiparty.rb +22 -0
  19. data/lib/locomotive/mounter/extensions/sprockets.rb +46 -0
  20. data/lib/locomotive/mounter/extensions/tilt/css.rb +31 -0
  21. data/lib/locomotive/mounter/fields.rb +254 -0
  22. data/lib/locomotive/mounter/models/base.rb +42 -0
  23. data/lib/locomotive/mounter/models/content_asset.rb +84 -0
  24. data/lib/locomotive/mounter/models/content_entry.rb +372 -0
  25. data/lib/locomotive/mounter/models/content_field.rb +190 -0
  26. data/lib/locomotive/mounter/models/content_select_option.rb +29 -0
  27. data/lib/locomotive/mounter/models/content_type.rb +274 -0
  28. data/lib/locomotive/mounter/models/editable_element.rb +27 -0
  29. data/lib/locomotive/mounter/models/page.rb +442 -0
  30. data/lib/locomotive/mounter/models/site.rb +28 -0
  31. data/lib/locomotive/mounter/models/snippet.rb +55 -0
  32. data/lib/locomotive/mounter/models/theme_asset.rb +148 -0
  33. data/lib/locomotive/mounter/models/translation.rb +28 -0
  34. data/lib/locomotive/mounter/mounting_point.rb +65 -0
  35. data/lib/locomotive/mounter/reader/api.rb +64 -0
  36. data/lib/locomotive/mounter/reader/api/base.rb +67 -0
  37. data/lib/locomotive/mounter/reader/api/content_assets_reader.rb +39 -0
  38. data/lib/locomotive/mounter/reader/api/content_entries_reader.rb +142 -0
  39. data/lib/locomotive/mounter/reader/api/content_types_reader.rb +76 -0
  40. data/lib/locomotive/mounter/reader/api/pages_reader.rb +192 -0
  41. data/lib/locomotive/mounter/reader/api/site_reader.rb +42 -0
  42. data/lib/locomotive/mounter/reader/api/snippets_reader.rb +61 -0
  43. data/lib/locomotive/mounter/reader/api/theme_assets_reader.rb +42 -0
  44. data/lib/locomotive/mounter/reader/api/translations_reader.rb +30 -0
  45. data/lib/locomotive/mounter/reader/file_system.rb +43 -0
  46. data/lib/locomotive/mounter/reader/file_system/base.rb +65 -0
  47. data/lib/locomotive/mounter/reader/file_system/content_assets_reader.rb +90 -0
  48. data/lib/locomotive/mounter/reader/file_system/content_entries_reader.rb +97 -0
  49. data/lib/locomotive/mounter/reader/file_system/content_types_reader.rb +88 -0
  50. data/lib/locomotive/mounter/reader/file_system/pages_reader.rb +211 -0
  51. data/lib/locomotive/mounter/reader/file_system/site_reader.rb +27 -0
  52. data/lib/locomotive/mounter/reader/file_system/snippets_reader.rb +115 -0
  53. data/lib/locomotive/mounter/reader/file_system/theme_assets_reader.rb +83 -0
  54. data/lib/locomotive/mounter/reader/file_system/translations_reader.rb +36 -0
  55. data/lib/locomotive/mounter/reader/runner.rb +89 -0
  56. data/lib/locomotive/mounter/utils/hash.rb +31 -0
  57. data/lib/locomotive/mounter/utils/output.rb +124 -0
  58. data/lib/locomotive/mounter/utils/string.rb +40 -0
  59. data/lib/locomotive/mounter/utils/yaml.rb +125 -0
  60. data/lib/locomotive/mounter/utils/yaml_front_matters_template.rb +45 -0
  61. data/lib/locomotive/mounter/version.rb +8 -0
  62. data/lib/locomotive/mounter/writer/api.rb +74 -0
  63. data/lib/locomotive/mounter/writer/api/base.rb +172 -0
  64. data/lib/locomotive/mounter/writer/api/content_assets_writer.rb +74 -0
  65. data/lib/locomotive/mounter/writer/api/content_entries_writer.rb +227 -0
  66. data/lib/locomotive/mounter/writer/api/content_types_writer.rb +151 -0
  67. data/lib/locomotive/mounter/writer/api/pages_writer.rb +250 -0
  68. data/lib/locomotive/mounter/writer/api/site_writer.rb +125 -0
  69. data/lib/locomotive/mounter/writer/api/snippets_writer.rb +111 -0
  70. data/lib/locomotive/mounter/writer/api/theme_assets_writer.rb +201 -0
  71. data/lib/locomotive/mounter/writer/api/translations_writer.rb +85 -0
  72. data/lib/locomotive/mounter/writer/file_system.rb +44 -0
  73. data/lib/locomotive/mounter/writer/file_system/base.rb +70 -0
  74. data/lib/locomotive/mounter/writer/file_system/content_assets_writer.rb +38 -0
  75. data/lib/locomotive/mounter/writer/file_system/content_entries_writer.rb +33 -0
  76. data/lib/locomotive/mounter/writer/file_system/content_types_writer.rb +32 -0
  77. data/lib/locomotive/mounter/writer/file_system/pages_writer.rb +93 -0
  78. data/lib/locomotive/mounter/writer/file_system/site_writer.rb +30 -0
  79. data/lib/locomotive/mounter/writer/file_system/snippets_writer.rb +59 -0
  80. data/lib/locomotive/mounter/writer/file_system/theme_assets_writer.rb +72 -0
  81. data/lib/locomotive/mounter/writer/file_system/translations_writer.rb +29 -0
  82. data/lib/locomotive/mounter/writer/runner.rb +73 -0
  83. data/locomotivecms_mounter.gemspec +64 -0
  84. metadata +539 -0
@@ -0,0 +1,39 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module Api
5
+
6
+ class ContentAssetsReader < Base
7
+
8
+ # Build the list of content assets from the public folder with eager loading.
9
+ #
10
+ # @return [ Array ] The cached list of theme assets
11
+ #
12
+ def read
13
+ super
14
+
15
+ self.get(:content_assets).each do |attributes|
16
+ url = attributes.delete('url')
17
+
18
+ attributes['folder'] = 'samples/assets'
19
+ attributes['uri'] = URI(url =~ /^https?:\/\// ? url : "#{self.base_uri_with_scheme}#{url}")
20
+
21
+ self.items[url] = Locomotive::Mounter::Models::ContentAsset.new(attributes)
22
+ end
23
+
24
+ self.items
25
+ end
26
+
27
+ protected
28
+
29
+ def safe_attributes
30
+ %w(_id url created_at updated_at)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,142 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module Api
5
+
6
+ class ContentEntriesReader < Base
7
+
8
+ attr_accessor :ids, :relationships
9
+
10
+ def initialize(runner)
11
+ self.ids, self.relationships = {}, []
12
+ super
13
+ end
14
+
15
+ # Build the list of content types from the folder on the file system.
16
+ #
17
+ # @return [ Array ] The un-ordered list of content types
18
+ #
19
+ def read
20
+ super
21
+
22
+ self.fetch
23
+
24
+ self.items
25
+ end
26
+
27
+ protected
28
+
29
+ def fetch
30
+ self.mounting_point.content_types.each do |slug, content_type|
31
+ entries = self.get("content_types/#{slug}/entries", nil, true)
32
+
33
+ entries.each do |attributes|
34
+ locales = attributes.delete('translated_in') || []
35
+
36
+ entry = self.add(content_type, attributes)
37
+
38
+ # get all the translated versions
39
+ locales.each do |locale|
40
+ _attributes = self.get("content_types/#{slug}/entries/#{entry._id}", locale, true)
41
+
42
+ Locomotive::Mounter.with_locale(locale) do
43
+ self.filter_attributes(content_type, _attributes).each do |key, value|
44
+ entry.send(:"#{key}=", value)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # Add a content entry for a content type.
53
+ #
54
+ # @param [ Object ] content_type The content type
55
+ # @param [ Hash ] attributes The attributes of the content entry
56
+ #
57
+ # @return [ Object] The newly created content entry
58
+ #
59
+ def add(content_type, attributes)
60
+ _attributes = self.filter_attributes(content_type, attributes)
61
+
62
+ entry = content_type.build_entry(_attributes)
63
+
64
+ key = File.join(content_type.slug, entry._slug)
65
+
66
+ self.items[key] = self.ids[entry._id] = entry
67
+ end
68
+
69
+ # Filter the attributes coming directly from an API call.
70
+ #
71
+ # @param [ Object ] content_type The content type
72
+ # @param [ Hash ] attributes The attributes of the content entry
73
+ #
74
+ # @return [ Object] The attributes understandable by the content entry
75
+ #
76
+ def filter_attributes(content_type, original_attributes)
77
+ attributes = original_attributes.clone.keep_if { |k, v| %w(_id _slug seo_title meta_keywords meta_description _position _visible created_at updated_at).include?(k) }
78
+
79
+ content_type.fields.each do |field|
80
+ value = (case field.type
81
+ when
82
+ original_attributes[field.name]
83
+ when :text
84
+ replace_urls_by_content_assets(original_attributes[field.name])
85
+ when :select
86
+ field.find_select_option(original_attributes[field.name]).try(:name)
87
+ when :date
88
+ original_attributes["formatted_#{field.name}"]
89
+ when :file
90
+ retrieve_file_path(content_type, field, original_attributes)
91
+ when :has_many
92
+ nil
93
+ else
94
+ # :string, :boolean, :email, :integer, :float, :tags
95
+ original_attributes[field.name]
96
+ end)
97
+
98
+ attributes[field.name] = value unless value.nil?
99
+ end
100
+
101
+ attributes
102
+ end
103
+
104
+ # For a given content, parse it and replace all the urls from content assets
105
+ # by their corresponding locale ones.
106
+ #
107
+ # @param [ String ] content The content to parse
108
+ #
109
+ # @return [ String ] The content with local urls
110
+ #
111
+ def replace_urls_by_content_assets(content)
112
+ return "" unless content
113
+ self.mounting_point.content_assets.each do |path, asset|
114
+ content.gsub!(path, asset.local_filepath)
115
+ end
116
+ content
117
+ end
118
+
119
+ def retrieve_file_path(content_type, field, attributes)
120
+ value = attributes[field.name]
121
+
122
+ return nil if value.blank?
123
+
124
+ base_folder = File.join('/', 'samples', content_type.slug, attributes['_slug'])
125
+
126
+ if value.is_a?(Hash)
127
+ {}.tap do |translations|
128
+ value.each do |locale, url|
129
+ translations[locale] = self.add_content_asset(url, File.join(base_folder, locale))
130
+ end
131
+ end
132
+ else
133
+ self.add_content_asset(value, base_folder)
134
+ end
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,76 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module Api
5
+
6
+ class ContentTypesReader < Base
7
+
8
+ # Build the list of content types from the folder in the file system.
9
+ #
10
+ # @return [ Array ] The un-ordered list of content types
11
+ #
12
+ def read
13
+ super
14
+
15
+ self.fetch
16
+
17
+ self.enable_relationships
18
+
19
+ self.items
20
+ end
21
+
22
+ protected
23
+
24
+ def fetch
25
+ self.get(:content_types).each do |attributes|
26
+ self.add(attributes)
27
+ end
28
+ end
29
+
30
+ # Add a new content type in the global hash of content types.
31
+ # If the content type exists, it returns it.
32
+ #
33
+ # @param [ Hash ] attributes The attributes of the content type
34
+ #
35
+ # @return [ Object ] A newly created content type or the existing one
36
+ #
37
+ def add(attributes)
38
+ slug = attributes['slug']
39
+
40
+ attributes.delete('entries_custom_fields').each do |_attributes|
41
+ _attributes = _attributes.delete_if { |k, v| v.blank? || %w(id updated_at created_at).include?(k) }
42
+
43
+ # TODO: select options
44
+
45
+ (attributes['fields'] ||= []) << _attributes
46
+ end
47
+
48
+ unless self.items.key?(slug)
49
+ self.items[slug] = Locomotive::Mounter::Models::ContentType.new(attributes)
50
+ end
51
+
52
+ self.items[slug]
53
+ end
54
+
55
+ # Make sure that each "relationship" field of a content type is
56
+ # correctly connected to the target content type.
57
+ def enable_relationships
58
+ self.items.each do |_, content_type|
59
+ content_type.fields.find_all(&:is_relationship?).each do |field|
60
+ # look for the target content type from its slug
61
+ field.class_name = field.class_slug
62
+ field.klass = self.items[field.class_slug]
63
+ end
64
+ end
65
+ end
66
+
67
+ def safe_attributes
68
+ %w(name slug description order_by order_direction label_field_name group_by_field_name public_submission_accounts entries_custom_fields klass_name created_at updated_at)
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,192 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module Api
5
+
6
+ class PagesReader < Base
7
+
8
+ attr_accessor :pages
9
+
10
+ def initialize(runner)
11
+ self.pages = {}
12
+ super
13
+ end
14
+
15
+ # Build the tree of pages based on the filesystem structure
16
+ #
17
+ # @return [ Hash ] The pages organized as a Hash (using the fullpath as the key)
18
+ #
19
+ def read
20
+ super
21
+
22
+ self.fetch
23
+
24
+ index = self.pages['index']
25
+
26
+ self.build_relationships(index, self.pages_to_list)
27
+
28
+ # Locomotive::Mounter.with_locale(:en) { self.to_s } # DEBUG
29
+
30
+ # self.to_s
31
+
32
+ self.pages
33
+ end
34
+
35
+ protected
36
+
37
+ # Create a ordered list of pages from the Hash
38
+ #
39
+ # @return [ Array ] An ordered list of pages
40
+ #
41
+ def pages_to_list
42
+ # sort by fullpath first
43
+ list = self.pages.values.sort { |a, b| a.fullpath <=> b.fullpath }
44
+ # sort finally by depth
45
+ list.sort { |a, b| a.depth <=> b.depth }
46
+ end
47
+
48
+ def build_relationships(parent, list)
49
+ list.dup.each do |page|
50
+ next unless self.is_subpage_of?(page, parent)
51
+
52
+ # attach the page to the parent (order by position), also set the parent
53
+ parent.add_child(page)
54
+
55
+ # localize the fullpath in all the locales
56
+ page.localize_fullpath
57
+
58
+ # remove the page from the list
59
+ list.delete(page)
60
+
61
+ # go under
62
+ self.build_relationships(page, list)
63
+ end
64
+ end
65
+
66
+ # Record pages found in file system
67
+ def fetch
68
+ self.get(:pages).each do |attributes|
69
+ page = self.add(attributes['fullpath'], attributes)
70
+
71
+ self.mounting_point.locales[1..-1].each do |locale|
72
+ # if not translated, no need to make an api call for that locale
73
+ next unless page.translated_in?(locale)
74
+
75
+ Locomotive::Mounter.with_locale(locale) do
76
+ localized_attributes = self.get("pages/#{page._id}", locale)
77
+
78
+ # remove useless non localized attributes
79
+ localized_attributes.delete('target_klass_slug')
80
+
81
+ # isolate the editable elements
82
+ editable_elements = self.filter_editable_elements(localized_attributes.delete('editable_elements'))
83
+
84
+ page.attributes = localized_attributes
85
+
86
+ page.set_editable_elements(editable_elements)
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ # Add a new page in the global hash of pages.
93
+ # If the page exists, then do nothing.
94
+ #
95
+ # @param [ String ] fullpath The fullpath used as the key for the hash
96
+ # @param [ Hash ] attributes The attributes of the new page
97
+ #
98
+ # @return [ Object ] A newly created page or the existing one
99
+ #
100
+ def add(fullpath, attributes = {})
101
+ unless self.pages.key?(fullpath)
102
+ # editable elements
103
+ editable_elements = self.filter_editable_elements(attributes.delete('editable_elements'))
104
+
105
+ # content type
106
+ if content_type_slug = attributes.delete('target_klass_slug')
107
+ attributes['content_type'] = self.mounting_point.content_types[content_type_slug] #.values.find { |ct| ct._id == content_type_id }
108
+ end
109
+
110
+ self.pages[fullpath] = Locomotive::Mounter::Models::Page.new(attributes)
111
+
112
+ self.pages[fullpath].set_editable_elements(editable_elements)
113
+ end
114
+
115
+ self.pages[fullpath]
116
+ end
117
+
118
+ # Tell is a page described is a sub page of a parent page
119
+ #
120
+ # @param [ Object ] page The full path of the page to test
121
+ # @param [ Object ] parent The full path of the parent page
122
+ #
123
+ # @return [ Boolean] True if the page is a sub page of the parent one
124
+ #
125
+ def is_subpage_of?(page, parent)
126
+ return false if page.index_or_404?
127
+
128
+ if page.parent_id # only in the new version of the engine
129
+ return page.parent_id == parent._id
130
+ end
131
+
132
+ if parent.fullpath == 'index' && page.fullpath.split('/').size == 1
133
+ return true
134
+ end
135
+
136
+ File.dirname(page.fullpath.dasherize) == parent.fullpath.dasherize
137
+ end
138
+
139
+ # Only keep the minimal attributes from a list of
140
+ # editable elements hashes. It also replaces the url to
141
+ # content assets by their corresponding local ones.
142
+ #
143
+ # @param [ Array ] list The list of the editable elements with all the attributes
144
+ #
145
+ # @return [ Array ] The list of editable elements with the right attributes
146
+ #
147
+ def filter_editable_elements(list)
148
+ list.map do |attributes|
149
+ type = attributes['type']
150
+ attributes.keep_if { |k, _| %w(_id block slug content).include?(k) }.tap do |hash|
151
+ unless hash['content'].blank?
152
+ if type == 'EditableFile'
153
+ hash['content'] = self.add_content_asset(hash['content'], '/samples/pages')
154
+ else
155
+ self.mounting_point.content_assets.each do |path, asset|
156
+ hash['content'].gsub!(/(http:\/\/[^\/]*)?#{path}/, asset.local_filepath)
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def safe_attributes
165
+ %w(_id title slug handle fullpath translated_in
166
+ parent_id target_klass_slug
167
+ published listed templatized editable_elements
168
+ redirect_url cache_strategy response_type position
169
+ seo_title meta_keywords meta_description raw_template
170
+ created_at updated_at is_layout allow_layout)
171
+ end
172
+
173
+ # Output simply the tree structure of the pages.
174
+ #
175
+ # Note: only for debug purpose
176
+ #
177
+ def to_s(page = nil)
178
+ page ||= self.pages['index']
179
+
180
+ return unless page.translated_in?(Locomotive::Mounter.locale)
181
+
182
+ puts "#{" " * (page.try(:depth) + 1)} #{page.fullpath.inspect} (#{page.title}, position=#{page.position})"
183
+
184
+ (page.children || []).each { |child| self.to_s(child) }
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+ end
191
+ end
192
+ end