diff --git a/doc/TODO b/doc/TODO index d9eb8b20..93746b9c 100644 --- a/doc/TODO +++ b/doc/TODO @@ -61,7 +61,9 @@ x bugs x delete an item => okay x bug: duplicate fields (new entry) when errors in the content type form x tinyMCE => fullscreen -- rake task to import a remote template / export ? +- export site: + - generate database.yml +- rake task to import a remote template => MERGE diff --git a/lib/locomotive.rb b/lib/locomotive.rb index 0e5ebd95..e441ca57 100644 --- a/lib/locomotive.rb +++ b/lib/locomotive.rb @@ -17,6 +17,7 @@ require 'locomotive/routing' require 'locomotive/regexps' require 'locomotive/render' require 'locomotive/import' +require 'locomotive/export' require 'locomotive/delayed_job' require 'locomotive/middlewares' require 'locomotive/session_store' diff --git a/lib/locomotive/export.rb b/lib/locomotive/export.rb new file mode 100644 index 00000000..d41ae997 --- /dev/null +++ b/lib/locomotive/export.rb @@ -0,0 +1,286 @@ +require 'fileutils' + +# Just a simple helper class to export quickly an existing and live locomotive website. +# FIXME: will be replaced by the API in the next months +module Locomotive + class Export + + @@instance = nil + + def initialize(site) + @site = site + @target_folder = File.join(Rails.root, 'tmp', 'export', Time.now.to_i) + @site_hash = {} # used to generate the site.yml and compiled_site.yml files + + self.create_target_folder + end + + def run! + self.initialize_site_hash + + self.log('copying assets') + self.copy_assets + + self.log('copying theme assets') + self.copy_theme_assets + + self.log('copying pages') + self.copy_pages + + self.log('copying snippets') + self.copy_snippets + + self.log('copying content types') + self.copy_content_types + + self.log('copying config files') + self.copy_config_files + + @target_folder + end + + # returns the path to the zipfile + def self.run!(site) + @@instance = self.new(site) + @@instance.run! + end + + protected + + def create_target_folder + FileUtils.mkdir_p(@target_folder) + %w(app/content_types app/views/snippets app/views/pages config data public).each do |f| + FileUtils.mkdir_p(File.join(@target_folder, f)) + end + end + + def initialize_site_hash + attributes = self.extract_attributes(@site, %w(name locale seo_title meta_description meta_keywords)) + + attributes['pages'] = [] + attributes['content_types'] = {} + + @site_hash = { 'site' => attributes } + end + + def copy_config_files + File.open(File.join(self.config_folder, 'compiled_site.yml'), 'w') do |f| + f.write(yaml(@site_hash)) + end + + @site_hash.delete(content_types) + + File.open(File.join(self.config_folder, 'site.yml'), 'w') do |f| + f.write(yaml(@site_hash)) + end + end + + def copy_pages + @site.all_pages_in_once.each { |p| self._copy_pages(p) } + end + + def copy_snippets + @site.snippets.each do |snippet| + File.open(File.join(self.snippets_folder, snippet.slug), 'w') do |f| + f.write(snippet.template) + end + end + end + + def copy_content_types + @site.content_types.each do |content_type| + attributes = self.extract_attributes(content_type, %w(description slug order_by order_direction highlighted_field_name group_by_field_name api_enabled)) + + # custom_fields + fields = [] + content_type.content_custom_fields.each do |field| + fields << { + field._alias => self.extract_attributes(field, %w(label kind hints target)) + } + end + + attributes['fields'] = fields + + @site_hash['site']['content_types'][content_type.name] = attributes.clone + + # [structure] copy it into its own file + attributes['name'] = content_type.name + + File.open(File.join(self.content_types_folder, "#{content_type.slug}.yml"), 'w') do |f| + f.write(self.yaml(attributes)) + end + + # data + data = self.extract_contents(content_type) + + # [data] copy them into their own file + File.open(File.join(self.content_data_folder, "#{content_type.slug}.yml"), 'w') do |f| + f.write(self.yaml(data)) + end + + @site_hash['site']['content_types'][content_type.name]['contents'] = data + end + end + + def copy_theme_assets + @site.theme_assets.each do |theme_asset| + target_path = File.join(self.public_folder, theme_asset.local_path) + + self.copy_file_from_an_uploader(theme_asset.source, target_path) do |bytes| + if theme_asset.stylesheet_or_javascript? + base_url = theme_asset.source.url.gsub(theme_asset.local_path) + bytes.gsub!(base_url, '/') + else + bytes + end + end + end + end + + def copy_assets + @site.assets.each do |asset| + target_path = File.join(self.samples_folder, asset.source_filename) + self.copy_file_from_an_uploader(asset.source, target_path) + end + end + + protected + + def _copy_pages(page) + attributes = self.extract_attributes(page, %w{title seo_title meta_description meta_keywords listed redirect_url content_type published}) + + unless page.redirected? + # add editable elements + page.editable_elements.each do |element| + next if element.disabled? || (element.from_parent && element.default_content == element.content) + + el_attributes = self.extract_attributes(element, %w{slug block hint}) + + case element + when EditableShortText, EditableLongText + el_attributes['content'] = self.replace_asset_urls_in(element.content) + when EditableImage + filepath = File.join('/', 'samples', element.source_filename) + self.copy_file_from_an_uploader(element.source, File.join(self.samples_folder, filepath)) + el_attributes['content'] = filepath + end + + (attributes['editable_elements'] ||= []) << el_attributes + end + + File.open(File.join(self.pages_folder, "#{page.fullpath}.liquid"), 'w') do |f| + f.write(self.replace_asset_urls_in(page.raw_template)) + end + end + + @site_hash['site']['pages'] << { page.fullpath => attributes } + + page.children.each { |p| self._copy_pages(p) } + end + + def extract_attributes(object, fields) + object.attributes.select { |k, v| fields.include?(k) } + end + + def pages_folder + File.join(@target_folder, 'app', 'views', 'pages') + end + + def snippets_folder + File.join(@target_folder, 'app', 'views', 'snippets') + end + + def content_types_folder + File.join(@target_folder, 'app', 'content_types') + end + + def config_folder + File.join(@target_folder, 'config') + end + + def content_data_folder + File.join(@target_folder, 'data') + end + + def public_folder + File.join(@target_folder, 'public') + end + + def samples_folder + File.join(@target_folder, 'public', 'samples') + end + + def copy_file_from_an_uploader(uploader, target_path, &block) + FileUtils.mkdir_p(File.dirname(target_path)) + File.open(target_path, 'w') do |f| + bytes = uploader.read + + bytes = block.call(bytes) if block_given? + + bytes = bytes.force_encoding('UTF-8') if RUBY_VERSION =~ /1\.9/ + f.write(bytes) + end + end + + def extract_contents(content_type) + data = [] + + highlighted_field_name = content_type.highlighted_field_name + + content_type.contents.each do |content| + hash = { content.highlighted_field_value => {} } + + content.custom_fields.each do |field| + next if field._name == highlighted_field_name + + case field.kind + when 'file' + uploader = content.send(field._name) + filepath = File.join('/samples', content_type.slug, content.send("#{field._name}_filename")) + + self.copy_file_from_an_uploader(uploader, File.join(self.public_folder, filepath)) + + hash[field._alias] = filepath + when 'text' + value = self.replace_asset_urls_in(content.send(field._name.to_sym)) + hash[field._alias] = value + else + hash[field._alias] = content.send(field._name.to_sym) + end + end + + data << hash + end + + data + end + + def replace_asset_urls_in(text) + base_url = AssetUploader.new(Asset.new(:site => @site)).store_dir + base_url.split('/').pop + base_url.join('/') + + changed_text = text.gsub(%r(#{base_url}/[a-z0-9]{10,}/), 'samples') + + changed_text.starts_with?('/') ? changed_text : "/#{changed_text}" + end + + def yaml(hash_or_array) + method = hash_or_array.respond_to?(:ya2yaml) ? :ya2yaml : :to_yaml + string = (if hash_or_array.respond_to?(:keys) + hash_or_array.dup.stringify_keys! + else + hash_or_array + end).send(method) + string.gsub('!ruby/symbol ', ':').sub('---', '').split("\n").map(&:rstrip).join("\n").strip + end + + def log(message, domain = '') + head = "[export_template][#{site.name}]" + head += "[#{domain}]" unless domain.blank? + ::Locomotive::Logger.info "\t#{head} #{message}" + end + + end + +end \ No newline at end of file diff --git a/lib/locomotive/import/logger.rb b/lib/locomotive/import/logger.rb index 54b3ae8b..81fded77 100644 --- a/lib/locomotive/import/logger.rb +++ b/lib/locomotive/import/logger.rb @@ -3,7 +3,7 @@ module Locomotive module Logger def log(message, domain = '') - head = "[import_theme]" + head = "[import_template]" head += "[#{domain}]" unless domain.blank? ::Locomotive::Logger.info "\t#{head} #{message}" end