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,111 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Writer
4
+ module Api
5
+
6
+ # Push snippets to a remote LocomotiveCMS engine.
7
+ #
8
+ # The force option is not used.
9
+ #
10
+ class SnippetsWriter < Base
11
+
12
+ def prepare
13
+ super
14
+
15
+ # set the unique identifier to each local snippet
16
+ self.get(:snippets, nil, true).each do |attributes|
17
+ snippet = self.snippets[attributes['slug']]
18
+
19
+ snippet._id = attributes['id'] if snippet
20
+ end
21
+ end
22
+
23
+ # Write all the snippets to the remote destination
24
+ def write
25
+ self.each_locale do |locale|
26
+ self.output_locale
27
+
28
+ self.snippets.values.each { |snippet| self.write_snippet(snippet) }
29
+ end
30
+ end
31
+
32
+ protected
33
+
34
+ # Write a snippet by calling the API.
35
+ #
36
+ # @param [ Object ] snippet The snippet
37
+ #
38
+ def write_snippet(snippet)
39
+ locale = Locomotive::Mounter.locale
40
+
41
+ return unless snippet.translated_in?(locale)
42
+
43
+ self.output_resource_op snippet
44
+
45
+ success = snippet.persisted? ? self.update_snippet(snippet) : self.create_snippet(snippet)
46
+
47
+ self.output_resource_op_status snippet, success ? :success : :error
48
+ self.flush_log_buffer
49
+ end
50
+
51
+ # Persist a snippet by calling the API. The returned id
52
+ # is then set to the snippet itself.
53
+ #
54
+ # @param [ Object ] snippet The snippet to create
55
+ #
56
+ # @return [ Boolean ] True if the call to the API succeeded
57
+ #
58
+ def create_snippet(snippet)
59
+ params = self.buffer_log { snippet_to_params(snippet) }
60
+
61
+ # make a call to the API to create the snippet, no need to set
62
+ # the locale since it first happens for the default locale.
63
+ response = self.post :snippets, params, nil, true
64
+
65
+ snippet._id = response['id'] if response
66
+
67
+ !response.nil?
68
+ end
69
+
70
+ # Update a snippet by calling the API.
71
+ #
72
+ # @param [ Object ] snippet The snippet to persist
73
+ #
74
+ # @return [ Boolean ] True if the call to the API succeeded
75
+ #
76
+ def update_snippet(snippet)
77
+ params = self.buffer_log { snippet_to_params(snippet) }
78
+
79
+ locale = Locomotive::Mounter.locale
80
+
81
+ # make a call to the API for the update
82
+ response = self.put :snippets, snippet._id, params, locale
83
+
84
+ !response.nil?
85
+ end
86
+
87
+ # Shortcut to get all the local snippets.
88
+ #
89
+ # @return [ Hash ] The hash whose key is the slug and the value is the snippet itself
90
+ #
91
+ def snippets
92
+ self.mounting_point.snippets
93
+ end
94
+
95
+ # Return the parameters of a snippet sent by the API.
96
+ #
97
+ # @param [ Object ] snippet The snippet
98
+ #
99
+ # @return [ Hash ] The parameters of the page
100
+ #
101
+ def snippet_to_params(snippet)
102
+ snippet.to_params.tap do |params|
103
+ params[:template] = self.replace_content_assets!(params[:template])
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,201 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Writer
4
+ module Api
5
+
6
+ # Push theme assets to a remote LocomotiveCMS engine.
7
+ #
8
+ # New assets are automatically pushed.
9
+ # Existing ones are not pushed unless the :force option is
10
+ # passed OR if the size of the asset (if not a javascript or stylesheet) has changed.
11
+ #
12
+ class ThemeAssetsWriter < Base
13
+
14
+ # Other local attributes
15
+ attr_accessor :tmp_folder
16
+
17
+ # store checksums of remote assets. needed to check if an asset has to be updated or not
18
+ attr_accessor :checksums
19
+
20
+ # the assets stored in the engine have the same base url
21
+ attr_accessor :remote_base_url
22
+
23
+ # cache the compiled theme assets to avoid to perform compilation more than once
24
+ attr_accessor :cached_compiled_assets
25
+
26
+ def prepare
27
+ super
28
+
29
+ self.checksums = {}
30
+
31
+ self.cached_compiled_assets = {}
32
+
33
+ # prepare the place where the assets will be stored temporarily.
34
+ self.create_tmp_folder
35
+
36
+ # assign an _id to a local content type if possible
37
+ self.get(:theme_assets, nil, true).each do |attributes|
38
+ remote_path = File.join(attributes['folder'], File.basename(attributes['local_path']))
39
+
40
+ if theme_asset = self.theme_assets[remote_path]
41
+ theme_asset._id = attributes['id']
42
+ self.checksums[theme_asset._id] = attributes['checksum']
43
+ end
44
+
45
+ if remote_base_url.nil?
46
+ attributes['url'] =~ /(.*\/sites\/[0-9a-f]+\/theme)/
47
+ self.remote_base_url = $1
48
+ end
49
+ end
50
+ end
51
+
52
+ def write
53
+ self.theme_assets_by_priority.each do |theme_asset|
54
+ # track it in the logs
55
+ self.output_resource_op theme_asset
56
+
57
+ status = :skipped
58
+ errors = []
59
+ file = self.build_temp_file(theme_asset)
60
+ params = theme_asset.to_params.merge(source: file, performing_plain_text: false)
61
+
62
+ begin
63
+ if theme_asset.persisted?
64
+ # we only update it if the size has changed or if the force option has been set.
65
+ if self.force? || self.theme_asset_changed?(theme_asset)
66
+ response = self.put :theme_assets, theme_asset._id, params
67
+ status = self.response_to_status(response)
68
+ else
69
+ status = :same
70
+ end
71
+ else
72
+ response = self.post :theme_assets, params, nil, true
73
+ status = self.response_to_status(response)
74
+ end
75
+ rescue Exception => e
76
+ if self.force?
77
+ status, errors = :error, e.message
78
+ else
79
+ raise e
80
+ end
81
+ end
82
+
83
+ # very important. we do not want a huge number of non-closed file descriptor.
84
+ file.close
85
+
86
+ # track the status
87
+ self.output_resource_op_status theme_asset, status, errors
88
+ end
89
+
90
+ # make the stuff like they were before
91
+ self.remove_tmp_folder
92
+ end
93
+
94
+ protected
95
+
96
+ # Create the folder to store temporarily the files.
97
+ #
98
+ def create_tmp_folder
99
+ self.tmp_folder = self.runner.parameters[:tmp_dir] || File.join(Dir.getwd, '.push-tmp')
100
+
101
+ FileUtils.mkdir_p(self.tmp_folder)
102
+ end
103
+
104
+ # Clean the folder which had stored temporarily the files.
105
+ #
106
+ def remove_tmp_folder
107
+ FileUtils.rm_rf(self.tmp_folder) if self.tmp_folder
108
+ end
109
+
110
+ # Build a temp file from a theme asset.
111
+ #
112
+ # @param [ Object ] theme_asset The theme asset
113
+ #
114
+ # @return [ File ] The file descriptor
115
+ #
116
+ def build_temp_file(theme_asset)
117
+ path = File.join(self.tmp_folder, theme_asset.path)
118
+
119
+ FileUtils.mkdir_p(File.dirname(path))
120
+
121
+ File.open(path, 'w') do |file|
122
+ file.write(self.content_of(theme_asset))
123
+ end
124
+
125
+ File.new(path)
126
+ end
127
+
128
+ # Shortcut to get all the theme assets.
129
+ #
130
+ # @return [ Hash ] The hash whose key is the slug and the value is the snippet itself
131
+ #
132
+ def theme_assets
133
+ return @theme_assets if @theme_assets
134
+
135
+ @theme_assets = {}.tap do |hash|
136
+ self.mounting_point.theme_assets.each do |theme_asset|
137
+ hash[theme_asset.path] = theme_asset
138
+ end
139
+ end
140
+ end
141
+
142
+ # List of theme assets sorted by their priority.
143
+ #
144
+ # @return [ Array ] Sorted list of the theme assets
145
+ #
146
+ def theme_assets_by_priority
147
+ self.theme_assets.values.sort { |a, b| a.priority <=> b.priority }
148
+ end
149
+
150
+ # Tell if the theme_asset has been changed in order to update it
151
+ # if so or simply skip it.
152
+ #
153
+ # @param [ Object ] theme_asset The theme asset
154
+ #
155
+ # @return [ Boolean ] True if the checksums of the local and remote files are different.
156
+ #
157
+ def theme_asset_changed?(theme_asset)
158
+ content = self.content_of(theme_asset)
159
+
160
+ if theme_asset.stylesheet_or_javascript?
161
+ # we need to compare compiled contents (sass, coffeescript) with the right urls inside
162
+ content = content.gsub(/[("'](\/(stylesheets|javascripts|fonts|images|media|others)\/(([^;.]+)\/)*([a-zA-Z_\-0-9]+)\.[a-z]{2,3})[)"']/) do |path|
163
+ sanitized_path = path.gsub(/[("')]/, '').gsub(/^\//, '')
164
+ sanitized_path = File.join(self.remote_base_url, sanitized_path)
165
+
166
+ "#{path.first}#{sanitized_path}#{path.last}"
167
+ end
168
+ end
169
+
170
+ # compare local checksum with the remote one
171
+ Digest::MD5.hexdigest(content) != self.checksums[theme_asset._id]
172
+ end
173
+
174
+ # Return the content of a theme asset.
175
+ # If the theme asset is either a stylesheet or javascript file,
176
+ # it uses Sprockets to compile it.
177
+ # Otherwise, it returns the raw content of the asset.
178
+ #
179
+ # @return [ String ] The content of the theme asset
180
+ #
181
+ def content_of(theme_asset)
182
+ if theme_asset.stylesheet_or_javascript?
183
+ if self.cached_compiled_assets[theme_asset.path].nil?
184
+ self.cached_compiled_assets[theme_asset.path] = self.sprockets[theme_asset.short_path].to_s
185
+ end
186
+
187
+ self.cached_compiled_assets[theme_asset.path]
188
+ else
189
+ theme_asset.content
190
+ end
191
+ end
192
+
193
+ def sprockets
194
+ Locomotive::Mounter::Extensions::Sprockets.environment(self.mounting_point.path, true)
195
+ end
196
+
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,85 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Writer
4
+ module Api
5
+
6
+ # Push translations to a remote LocomotiveCMS engine.
7
+ #
8
+ class TranslationsWriter < Base
9
+
10
+ def prepare
11
+ super
12
+
13
+ # set the unique identifier to each local translation
14
+ (self.get(:translations, nil, true) || []).each do |attributes|
15
+ translation = self.translations[attributes['key']]
16
+
17
+ translation._id = attributes['id'] if translation
18
+ end
19
+ end
20
+
21
+ # Write all the translations to the remote destination
22
+ def write
23
+ self.translations.each do |key, translation|
24
+ self.output_resource_op translation
25
+
26
+ status = translation.persisted? ? self.update_translation(translation) : self.create_translation(translation)
27
+
28
+ self.output_resource_op_status translation, status
29
+ self.flush_log_buffer
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ # Persist a translation by calling the API. The returned id
36
+ # is then set to the translation itself.
37
+ #
38
+ # @param [ Object ] translation The translation to create
39
+ #
40
+ # @return [ Boolean ] True if the call to the API succeeded
41
+ #
42
+ def create_translation(translation)
43
+ params = self.buffer_log { translation.to_params }
44
+
45
+ # make a call to the API to create the translation, no need to set
46
+ # the locale since it first happens for the default locale.
47
+ response = self.post :translations, params, nil, true
48
+
49
+ translation._id = response['id'] if response
50
+
51
+ !response.nil? ? :success : :error
52
+ end
53
+
54
+ # Update a translation by calling the API.
55
+ #
56
+ # @param [ Object ] translation The translation to persist
57
+ #
58
+ # @return [ Boolean ] True if the call to the API succeeded
59
+ #
60
+ def update_translation(translation)
61
+ params = self.buffer_log { translation.to_params }
62
+
63
+ if self.data?
64
+ # make a call to the API for the update
65
+ response = self.put :translations, translation._id, params
66
+
67
+ !response.nil? ? :success : :error
68
+ else
69
+ :skipped
70
+ end
71
+ end
72
+
73
+ # Shortcut to get all the local translations.
74
+ #
75
+ # @return [ Hash ] The hash whose key is the tr key and the value is translation itself
76
+ #
77
+ def translations
78
+ self.mounting_point.translations
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,44 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Writer
4
+
5
+ module FileSystem
6
+
7
+ # Build a singleton instance of the Runner class.
8
+ #
9
+ # @return [ Object ] A singleton instance of the Runner class
10
+ #
11
+ def self.instance
12
+ @@instance ||= Runner.new(:file_system)
13
+ end
14
+
15
+ class Runner < Locomotive::Mounter::Writer::Runner
16
+
17
+ attr_accessor :target_path
18
+
19
+ # Check the existence of the target_path parameter
20
+ #
21
+ def prepare
22
+ self.target_path = parameters[:target_path]
23
+
24
+ if self.target_path.blank?
25
+ raise Locomotive::Mounter::WriterException.new('target_path is required')
26
+ end
27
+ end
28
+
29
+ # List of all the writers
30
+ #
31
+ # @return [ Array ] List of the writer classes
32
+ #
33
+ def writers
34
+ [SiteWriter, SnippetsWriter, ContentTypesWriter, ContentEntriesWriter, PagesWriter, ThemeAssetsWriter, ContentAssetsWriter, TranslationsWriter]
35
+ # [SiteWriter, PagesWriter, SnippetsWriter, ContentTypesWriter, ContentEntriesWriter, ContentAssetsWriter, ThemeAssetsWriter, TranslationsWriter]
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,70 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Writer
4
+ module FileSystem
5
+
6
+ class Base
7
+
8
+ include Locomotive::Mounter::Utils::Output
9
+
10
+ attr_accessor :mounting_point, :runner
11
+
12
+ def initialize(mounting_point, runner)
13
+ self.mounting_point = mounting_point
14
+ self.runner = runner
15
+ end
16
+
17
+ # It should always be called before executing the write method.
18
+ # Writers inheriting from this class can overide it
19
+ #
20
+ def prepare
21
+ self.output_title(:writing)
22
+ end
23
+
24
+ # Writers inheriting from this class *must* overide it
25
+ def write
26
+ raise 'The write method has to be overridden'
27
+ end
28
+
29
+ # Helper method to create a folder from a relative path
30
+ #
31
+ # @param [ String ] path The relative path
32
+ #
33
+ def create_folder(path)
34
+ fullpath = File.join(self.target_path, path)
35
+ unless File.exists?(fullpath)
36
+ FileUtils.mkdir_p(fullpath)
37
+ end
38
+ end
39
+
40
+ # Open a file described by the relative path. The file will be closed after the execution of the block.
41
+ #
42
+ # @param [ String ] path The relative path
43
+ # @param [ String ] mode The file mode ('w' by default)
44
+ # @param [ Lambda ] &block The block passed to the File.open method
45
+ #
46
+ def open_file(path, mode = 'w', &block)
47
+ # make sure the target folder exists
48
+ self.create_folder(File.dirname(path))
49
+
50
+ fullpath = File.join(self.target_path, path)
51
+
52
+ File.open(fullpath, mode, &block)
53
+ end
54
+
55
+ def target_path
56
+ self.runner.target_path
57
+ end
58
+
59
+ protected
60
+
61
+ def resource_message(resource)
62
+ " writing #{truncate(resource.to_s)}"
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end