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,88 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module FileSystem
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
+ self.fetch_from_filesystem
14
+
15
+ self.items
16
+ end
17
+
18
+ protected
19
+
20
+ def fetch_from_filesystem
21
+ Dir.glob(File.join(self.root_dir, '*.yml')).each do |filepath|
22
+ attributes = self.read_yaml(filepath)
23
+
24
+ self.add(attributes)
25
+ end
26
+ end
27
+
28
+ # Add a new content type in the global hash of content types.
29
+ # If the content type exists, it returns it.
30
+ #
31
+ # @param [ Hash ] attributes The attributes of the content type
32
+ #
33
+ # @return [ Object ] A newly created content type or the existing one
34
+ #
35
+ def add(attributes)
36
+ slug = attributes['slug']
37
+
38
+ # TODO: raise an error if no fields
39
+
40
+ attributes.delete('fields').each_with_index do |_attributes, index|
41
+ hash = { name: _attributes.keys.first, position: index }.merge(_attributes.values.first)
42
+
43
+ if options = hash.delete('select_options')
44
+ hash['select_options'] = self.sanitize_select_options(options)
45
+ end
46
+
47
+ (attributes['fields'] ||= []) << hash
48
+ end
49
+
50
+ attributes[:mounting_point] = self.mounting_point
51
+
52
+ unless self.items.key?(slug)
53
+ self.items[slug] = Locomotive::Mounter::Models::ContentType.new(attributes)
54
+ end
55
+
56
+ self.items[slug]
57
+ end
58
+
59
+ # Take the list of options described in the YAML file
60
+ # and convert it into a nice array of hashes
61
+ #
62
+ # @params [ Array ] options The list of raw options
63
+ #
64
+ # @return [ Array ] The sanitized list of options
65
+ #
66
+ def sanitize_select_options(options)
67
+ [].tap do |array|
68
+ options.each_with_index do |object, position|
69
+ array << { name: object, position: position }
70
+ end
71
+ end
72
+ end
73
+
74
+ # Return the directory where all the definition of
75
+ # the content types are stored.
76
+ #
77
+ # @return [ String ] The content types directory
78
+ #
79
+ def root_dir
80
+ File.join(self.runner.path, 'app', 'content_types')
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,211 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module FileSystem
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
+ self.fetch
21
+
22
+ index, not_found = self.pages['index'], self.pages['404']
23
+
24
+ # localize the fullpath for the 2 core pages: index and 404
25
+ [index, not_found].each { |p| p.localize_fullpath(self.locales) }
26
+
27
+ self.build_relationships(index, self.pages_to_list)
28
+
29
+ # Locomotive::Mounter.with_locale(:en) { self.to_s } # DEBUG
30
+
31
+ # Locomotive::Mounter.with_locale(:fr) { self.to_s } # DEBUG
32
+
33
+ self.pages
34
+ end
35
+
36
+ protected
37
+
38
+ # Create a ordered list of pages from the Hash
39
+ #
40
+ # @return [ Array ] An ordered list of pages
41
+ #
42
+ def pages_to_list
43
+ # sort by fullpath first
44
+ list = self.pages.values.sort { |a, b| a.fullpath <=> b.fullpath }
45
+ # sort finally by depth
46
+ list.sort { |a, b| a.depth <=> b.depth }
47
+ end
48
+
49
+ def build_relationships(parent, list)
50
+ # do not use an empty template for other locales than the default one
51
+ parent.set_default_template_for_each_locale(self.default_locale)
52
+
53
+ list.dup.each do |page|
54
+ next unless self.is_subpage_of?(page.fullpath, parent.fullpath)
55
+
56
+ # attach the page to the parent (order by position), also set the parent
57
+ parent.add_child(page)
58
+
59
+ # localize the fullpath in all the locales
60
+ page.localize_fullpath
61
+
62
+ # remove the page from the list
63
+ list.delete(page)
64
+
65
+ # go under
66
+ self.build_relationships(page, list)
67
+ end
68
+ end
69
+
70
+ # Record pages found in file system
71
+ def fetch
72
+ position, last_dirname = nil, nil
73
+
74
+ Dir.glob(File.join(self.root_dir, '**/*')).sort.each do |filepath|
75
+ next unless File.directory?(filepath) || filepath =~ /\.(#{Locomotive::Mounter::TEMPLATE_EXTENSIONS.join('|')})$/
76
+
77
+ if last_dirname != File.dirname(filepath)
78
+ position, last_dirname = 100, File.dirname(filepath)
79
+ end
80
+
81
+ page = self.add(filepath, position: position)
82
+
83
+ next if File.directory?(filepath) || page.nil?
84
+
85
+ if locale = self.filepath_locale(filepath)
86
+ Locomotive::Mounter.with_locale(locale) do
87
+ self.set_attributes_from_header(page, filepath)
88
+ end
89
+ else
90
+ Locomotive::Mounter.logger.warn "Unknown locale in the '#{File.basename(filepath)}' file."
91
+ end
92
+
93
+ position += 1
94
+ end
95
+ end
96
+
97
+ # Add a new page in the global hash of pages.
98
+ # If the page exists, override it.
99
+ #
100
+ # @param [ String ] filepath The path of the template
101
+ # @param [ Hash ] attributes The attributes of the new page
102
+ #
103
+ # @return [ Object ] A newly created page or the existing one
104
+ #
105
+ def add(filepath, attributes = {})
106
+ fullpath = self.filepath_to_fullpath(filepath)
107
+
108
+ unless self.pages.key?(fullpath)
109
+ attributes[:title] = File.basename(fullpath).humanize
110
+ attributes[:fullpath] = fullpath
111
+
112
+ page = Locomotive::Mounter::Models::Page.new(attributes)
113
+ page.mounting_point = self.mounting_point
114
+ page.filepath = File.expand_path(filepath)
115
+
116
+ page.template = OpenStruct.new(raw_source: '') if File.directory?(filepath)
117
+
118
+ self.pages[fullpath] = page
119
+ end
120
+
121
+ self.pages[fullpath]
122
+ end
123
+
124
+ # Set attributes of a page from the information
125
+ # stored in the header of the template (YAML matters).
126
+ # It also stores the template.
127
+ #
128
+ # @param [ Object ] page The page
129
+ # @param [ String ] filepath The path of the template
130
+ #
131
+ def set_attributes_from_header(page, filepath)
132
+ template = Locomotive::Mounter::Utils::YAMLFrontMattersTemplate.new(filepath)
133
+
134
+ if template.attributes
135
+ attributes = template.attributes.clone
136
+
137
+ # set the editable elements
138
+ page.set_editable_elements(attributes.delete('editable_elements'))
139
+
140
+ # set the content type
141
+ if content_type_slug = attributes.delete('content_type')
142
+ attributes['templatized'] = true
143
+ attributes['content_type'] = self.mounting_point.content_types.values.find { |ct| ct.slug == content_type_slug }
144
+ end
145
+
146
+ page.attributes = attributes
147
+ end
148
+
149
+ page.template = template
150
+ end
151
+
152
+ # Return the directory where all the templates of
153
+ # pages are stored in the filesystem.
154
+ #
155
+ # @return [ String ] The root directory
156
+ #
157
+ def root_dir
158
+ File.join(self.runner.path, 'app', 'views', 'pages')
159
+ end
160
+
161
+ # Take the path to a file on the filesystem
162
+ # and return its matching value for a Page.
163
+ #
164
+ # @param [ String ] filepath The path to the file
165
+ #
166
+ # @return [ String ] The fullpath of the page
167
+ #
168
+ def filepath_to_fullpath(filepath)
169
+ fullpath = filepath.gsub(File.join(self.root_dir, '/'), '')
170
+
171
+ fullpath.gsub!(/^\.\//, '')
172
+
173
+ fullpath.split('.').first.dasherize
174
+ end
175
+
176
+ # Tell is a page described by its fullpath is a sub page of a parent page
177
+ # also described by its fullpath
178
+ #
179
+ # @param [ String ] fullpath The full path of the page to test
180
+ # @param [ String ] parent_fullpath The full path of the parent page
181
+ #
182
+ # @return [ Boolean] True if the page is a sub page of the parent one
183
+ #
184
+ def is_subpage_of?(fullpath, parent_fullpath)
185
+ return false if %w(index 404).include?(fullpath)
186
+
187
+ if parent_fullpath == 'index' && fullpath.split('/').size == 1
188
+ return true
189
+ end
190
+
191
+ File.dirname(fullpath.dasherize) == parent_fullpath.dasherize
192
+ end
193
+
194
+ # Output simply the tree structure of the pages.
195
+ #
196
+ # Note: only for debug purpose
197
+ #
198
+ def to_s(page = nil)
199
+ page ||= self.pages['index']
200
+
201
+ puts "#{" " * (page.try(:depth) + 1)} #{page.fullpath.inspect} (#{page.title}, position=#{page.position}, template=#{page.template_translations.keys.inspect})"
202
+
203
+ (page.children || []).each { |child| self.to_s(child) }
204
+ end
205
+
206
+ end
207
+
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,27 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module FileSystem
5
+
6
+ class SiteReader < Base
7
+
8
+ def read
9
+ config_path = File.join(self.runner.path, 'config', 'site.yml')
10
+
11
+ site = self.read_yaml(config_path)
12
+
13
+ # set the default locale first
14
+ Locomotive::Mounter.locale = site['locales'].first.to_sym rescue Locomotive::Mounter.locale
15
+
16
+ Locomotive::Mounter::Models::Site.new(site).tap do |_site|
17
+ # set the time zone for the next Time operations (UTC by default)
18
+ Time.zone = ActiveSupport::TimeZone.new(_site.timezone || 'UTC')
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,115 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module FileSystem
5
+
6
+ class SnippetsReader < Base
7
+
8
+ # Build the list of snippets from the folder on the file system.
9
+ #
10
+ # @return [ Array ] The un-ordered list of snippets
11
+ #
12
+ def read
13
+ self.fetch_from_filesystem
14
+
15
+ self.set_default_template_for_each_locale
16
+
17
+ self.items
18
+ end
19
+
20
+ protected
21
+
22
+ # Record snippets found in file system
23
+ def fetch_from_filesystem
24
+ Dir.glob(File.join(self.root_dir, "*.{#{Locomotive::Mounter::TEMPLATE_EXTENSIONS.join(',')}}")).each do |filepath|
25
+ fullpath = File.basename(filepath)
26
+
27
+ snippet = self.add(filepath)
28
+
29
+ Locomotive::Mounter.with_locale(self.filepath_locale(filepath)) do
30
+ snippet.template = self.fetch_template(filepath)
31
+ end
32
+ end
33
+ end
34
+
35
+ # Set a default template (coming from the default locale)
36
+ # for each snippet which does not have a translated version
37
+ # of the template in each locale.
38
+ #
39
+ def set_default_template_for_each_locale
40
+ self.items.values.each do |snippet|
41
+ default_template = snippet.template
42
+
43
+ next if default_template.blank?
44
+
45
+ self.locales.map(&:to_sym).each do |locale|
46
+ next if locale == self.default_locale
47
+
48
+ _template = snippet.template_translations[locale]
49
+
50
+ if !_template.is_a?(Exception) && _template.blank?
51
+ snippet.template_translations[locale] = default_template
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # Return the directory where all the templates of
58
+ # snippets are stored in the filesystem.
59
+ #
60
+ # @return [ String ] The snippets directory
61
+ #
62
+ def root_dir
63
+ File.join(self.runner.path, 'app', 'views', 'snippets')
64
+ end
65
+
66
+ # Add a new snippet in the global hash of snippets.
67
+ # If the snippet exists, it returns it.
68
+ #
69
+ # @param [ String ] filepath The path to the file
70
+ #
71
+ # @return [ Object ] A newly created snippet or the existing one
72
+ #
73
+ def add(filepath)
74
+ slug = self.filepath_to_slug(filepath)
75
+
76
+ unless self.items.key?(slug)
77
+ self.items[slug] = Locomotive::Mounter::Models::Snippet.new({
78
+ name: slug.humanize,
79
+ slug: slug,
80
+ template: self.fetch_template(filepath)
81
+ })
82
+ end
83
+
84
+ self.items[slug]
85
+ end
86
+
87
+ # Convert a filepath to a slug
88
+ #
89
+ # @param [ String ] filepath The path to the file
90
+ #
91
+ # @return [ String ] The slug
92
+ #
93
+ def filepath_to_slug(filepath)
94
+ File.basename(filepath).split('.').first.permalink
95
+ end
96
+
97
+ # From a filepath, parse the template inside.
98
+ # and return the related Tilt instance.
99
+ # It may return the exception if the template is invalid
100
+ # (only for HAML templates).
101
+ #
102
+ # @param [ String ] filepath The path to the file
103
+ #
104
+ # @return [ Object ] The Tilt template or the exception itself if the template is invalid
105
+ #
106
+ def fetch_template(filepath)
107
+ Locomotive::Mounter::Utils::YAMLFrontMattersTemplate.new(filepath)
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,83 @@
1
+ module Locomotive
2
+ module Mounter
3
+ module Reader
4
+ module FileSystem
5
+
6
+ class ThemeAssetsReader < Base
7
+
8
+ # Build the list of theme assets from the public folder with eager loading.
9
+ #
10
+ # @return [ Array ] The cached list of theme assets
11
+ #
12
+ def read
13
+ ThemeAssetsArray.new(self.root_dir)
14
+ end
15
+
16
+ protected
17
+
18
+ # Return the directory where all the theme assets
19
+ # are stored in the filesystem.
20
+ #
21
+ # @return [ String ] The theme assets directory
22
+ #
23
+ def root_dir
24
+ File.join(self.runner.path, 'public')
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ class ThemeAssetsArray
32
+
33
+ attr_accessor :root_dir
34
+
35
+ def initialize(root_dir)
36
+ self.root_dir = root_dir
37
+ end
38
+
39
+ def list
40
+ return @list unless @list.nil?
41
+
42
+ # Follows symlinks and makes sure subdirectories are handled
43
+ glob_pattern = '**/*/**/*'
44
+
45
+ @list = [].tap do |list|
46
+ Dir.glob(File.join(self.root_dir, glob_pattern)).each do |file|
47
+ next if self.exclude?(file)
48
+
49
+ folder = File.dirname(file.gsub("#{self.root_dir}/", ''))
50
+
51
+ asset = Locomotive::Mounter::Models::ThemeAsset.new(folder: folder, filepath: file)
52
+
53
+ list << asset
54
+ end
55
+ end
56
+ end
57
+
58
+ alias :values :list
59
+
60
+ # Tell if the file has to be excluded from the array
61
+ # of theme assets. It does not have to be a folder
62
+ # or be in the samples folder or owns a name starting with
63
+ # the underscore character.
64
+ #
65
+ # @param [ String ] file The full path to the file
66
+ #
67
+ # @return [ Boolean ] True if it does not have to be included in the list.
68
+ #
69
+ def exclude?(file)
70
+ File.directory?(file) ||
71
+ file.starts_with?(File.join(self.root_dir, 'samples')) ||
72
+ File.basename(file).starts_with?('_')
73
+ end
74
+
75
+ # This class acts a proxy of an array
76
+ def method_missing(name, *args, &block)
77
+ self.list.send(name.to_sym, *args, &block)
78
+ end
79
+
80
+ end
81
+ end
82
+ end
83
+ end