Initial commit, first extraction of wagon. birth of steam

Joel AZEMAR committed Mar 31, 2014
commit a2c410eb70288709add4789e6bd49756956f49ff
Showing 148 changed files with 5735 additions and 2 deletions
.gitignore +2 -0
@@ @@ -16,3 +16,5 @@ tmp
.yardoc
_yardoc
doc/
+
+ .rspec
Gemfile +8 -0
@@ @@ -0,0 +1,8 @@
+ source 'https://rubygems.org'
+
+ gemspec
+
+ group :test do
+ gem 'pry'
+ # gem 'coveralls', require: false
+ end
Gemfile.lock +159 -0
@@ @@ -0,0 +1,159 @@
+ PATH
+ remote: .
+ specs:
+ Steam (1.4.0)
+ activesupport (~> 3.2.17)
+ better_errors (~> 1.0.1)
+ dragonfly (~> 0.9.12)
+ listen (~> 2.4.0)
+ locomotivecms-solid
+ locomotivecms_mounter
+ rack-cache (~> 1.1)
+ redcarpet (~> 3.0.0)
+ sprockets (~> 2.0)
+ sprockets-sass (~> 1.0.3)
+ will_paginate (~> 3.0.3)
+
+ GEM
+ remote: https://rubygems.org/
+ specs:
+ RedCloth (4.2.9)
+ activesupport (3.2.17)
+ i18n (~> 0.6, >= 0.6.4)
+ multi_json (~> 1.0)
+ addressable (2.3.6)
+ better_errors (1.0.1)
+ coderay (>= 1.0.0)
+ erubis (>= 2.6.6)
+ celluloid (0.15.2)
+ timers (~> 1.1.0)
+ chronic (0.10.2)
+ chunky_png (1.3.0)
+ coderay (1.1.0)
+ coffee-script (2.2.0)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.7.0)
+ colorize (0.5.8)
+ commonjs (0.2.7)
+ compass (0.12.4)
+ chunky_png (~> 1.2)
+ fssm (>= 0.2.7)
+ sass (~> 3.2.17)
+ crack (0.4.2)
+ safe_yaml (~> 1.0.0)
+ diff-lcs (1.2.5)
+ dragonfly (0.9.15)
+ multi_json (~> 1.0)
+ rack
+ erubis (2.7.0)
+ execjs (2.0.2)
+ ffi (1.9.3)
+ fssm (0.2.10)
+ haml (4.0.5)
+ tilt
+ hike (1.2.3)
+ httmultiparty (0.3.10)
+ httparty (>= 0.7.3)
+ multipart-post
+ httparty (0.13.0)
+ json (~> 1.8)
+ multi_xml (>= 0.5.2)
+ i18n (0.6.9)
+ json (1.8.1)
+ launchy (2.4.2)
+ addressable (~> 2.3)
+ less (2.2.2)
+ commonjs (~> 0.2.6)
+ listen (2.4.1)
+ celluloid (>= 0.15.2)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
+ locomotivecms-liquid (2.6.0)
+ locomotivecms-solid (0.2.2.1)
+ locomotivecms-liquid (~> 2.6.0)
+ locomotivecms_mounter (1.4.0)
+ RedCloth (~> 4.2.3)
+ activesupport (~> 3.2.15)
+ chronic (~> 0.10.2)
+ coffee-script (~> 2.2.0)
+ colorize (~> 0.5.8)
+ compass (~> 0.12.2)
+ haml (~> 4.0.3)
+ httmultiparty (= 0.3.10)
+ i18n (~> 0.6.0)
+ json (~> 1.8.0)
+ less (~> 2.2.1)
+ logger
+ mime-types (~> 1.19)
+ multi_json (~> 1.7.3)
+ sass (~> 3.2.12)
+ sprockets (~> 2.0)
+ sprockets-sass
+ stringex (~> 2.0.3)
+ tilt (= 1.4.1)
+ tzinfo (~> 0.3.29)
+ zip (~> 2.0.2)
+ logger (1.2.8)
+ method_source (0.8.2)
+ mime-types (1.25.1)
+ multi_json (1.7.9)
+ multi_xml (0.5.5)
+ multipart-post (2.0.0)
+ pry (0.9.12.6)
+ coderay (~> 1.0)
+ method_source (~> 0.8)
+ slop (~> 3.4)
+ rack (1.5.2)
+ rack-cache (1.2)
+ rack (>= 0.4)
+ rack-test (0.6.2)
+ rack (>= 1.0)
+ rake (10.2.2)
+ rb-fsevent (0.9.4)
+ rb-inotify (0.9.3)
+ ffi (>= 0.5.0)
+ redcarpet (3.0.0)
+ rspec (2.14.1)
+ rspec-core (~> 2.14.0)
+ rspec-expectations (~> 2.14.0)
+ rspec-mocks (~> 2.14.0)
+ rspec-core (2.14.8)
+ rspec-expectations (2.14.5)
+ diff-lcs (>= 1.1.3, < 2.0)
+ rspec-mocks (2.14.6)
+ safe_yaml (1.0.1)
+ sass (3.2.18)
+ slop (3.5.0)
+ sprockets (2.12.0)
+ hike (~> 1.2)
+ multi_json (~> 1.0)
+ rack (~> 1.0)
+ tilt (~> 1.1, != 1.3.0)
+ sprockets-sass (1.0.3)
+ sprockets (~> 2.0)
+ tilt (~> 1.1)
+ stringex (2.0.11)
+ tilt (1.4.1)
+ timers (1.1.0)
+ tzinfo (0.3.39)
+ vcr (2.9.0)
+ webmock (1.8.11)
+ addressable (>= 2.2.7)
+ crack (>= 0.1.7)
+ will_paginate (3.0.5)
+ zip (2.0.2)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ Steam!
+ bundler (~> 1.5)
+ launchy
+ pry
+ rack-test
+ rake (~> 10.1)
+ rspec (~> 2.14)
+ vcr
+ webmock
README.md +2 -2
@@ @@ -1,4 +1,4 @@
- render
+ steam
======
- The rendering stack used by both Wagon and Station (new name of the engine). It includes the rack stack and the liquid drops/filters/tags.
+ The rendering stack used by both Steam and Station (new name of the engine). It includes the rack stack and the liquid drops/filters/tags.
Rakefile +17 -0
@@ @@ -0,0 +1,17 @@
+ require 'rubygems'
+ require 'bundler/setup'
+
+ require 'rake'
+ require 'rspec'
+
+ require 'rspec/core/rake_task'
+
+ require_relative 'lib/steam'
+
+ RSpec::Core::RakeTask.new('spec:integration') do |spec|
+ spec.pattern = 'spec/integration/**/*_spec.rb'
+ end
+
+ task spec: ['spec:integration']
+
+ task default: :spec
\ No newline at end of file
locomotive/steam/cli.rb b/lib/locomotive/steam/cli.rb +358 -0
@@ @@ -0,0 +1,358 @@
+ require 'thor'
+ require 'thor/runner'
+
+ module Locomotive
+ module Steam
+ module CLI
+
+ module CheckPath
+
+ protected
+
+ # Check if the path given in option ('.' by default) points to a LocomotiveCMS
+ # site. It is also possible to pass a path other than the one from the options.
+ #
+ # @param [ String ] path The optional path of the site instead of options['path']
+ #
+ # @return [ String ] The fullpath to the LocomotiveCMS site or nil if it is not a valid site.
+ #
+ def check_path!(path = nil)
+ path ||= options['path']
+
+ path = path == '.' ? Dir.pwd : File.expand_path(path)
+
+ (File.exists?(File.join(path, 'config', 'site.yml')) ? path : nil).tap do |_path|
+ if _path.nil?
+ say 'The path does not point to a LocomotiveCMS site', :red
+ end
+ end
+ end
+
+ end
+
+ module ForceColor
+
+ def force_color_if_asked(options)
+ if options[:force_color]
+ require 'locomotive/steam/misc/thor'
+ self.shell = Thor::Shell::ForceColor.new
+ end
+ end
+
+ end
+
+ class Generate < Thor
+
+ include Locomotive::Steam::CLI::CheckPath
+
+ class_option :path, aliases: '-p', type: :string, default: '.', optional: true, desc: 'if your LocomotiveCMS site is not in the current path'
+
+ desc 'content_type SLUG FIELDS', 'Creates a content type with the specified slug and fields.'
+ long_desc <<-LONGDESC
+ Creates a content type with the specified slug and fields.
+
+ SLUG should be plural, lowercase, and underscored.
+
+ FIELDS format: field_1[:TYPE][:REQUIRED] field_2[:TYPE][:REQUIRED] ...
+
+ TYPE values: string, text, integer, float, boolean, email, date, date_time, file, tags, select, belongs_to, has_many, or many_to_many. Default is string.
+
+ To require a field, set REQUIRED to true. The first field is required by default.
+
+ Examples:
+
+ * steam generate content_type posts title published_at:date_time:true body:text
+
+ * steam generate content_type products title price:float photo:file category:belongs_to:true
+ LONGDESC
+ def content_type(name, *fields)
+ say('The fields are missing', :red) and return false if fields.empty?
+
+ if check_path!
+ Locomotive::Steam.generate :content_type, name, self.options['path'], fields
+ end
+ end
+
+ desc 'page FULLPATH', 'Create a page. No need to pass an extension to the FULLPATH arg'
+ long_desc <<-LONGDESC
+ Create a page. The generator will ask for the extension (liquid or haml) and also
+ if the page is localized or not.
+
+ Examples:
+
+ * steam generate page contact
+
+ * steam generate page about_us/me
+ LONGDESC
+ def page(fullpath)
+ if path = check_path!
+ locales = self.site_config(path)['locales']
+ Locomotive::Steam.generate :page, fullpath, self.options['path'], locales
+ end
+ end
+
+ desc 'snippet SLUG', 'Create a snippet'
+ long_desc <<-LONGDESC
+ Create a snippet. The generator will ask for the extension (liquid or haml) and also
+ if the snippet is localized or not.
+
+ Example:
+
+ * steam generate snippet footer
+ LONGDESC
+ def snippet(slug)
+ if path = check_path!
+ locales = self.site_config(path)['locales']
+ Locomotive::Steam.generate :snippet, slug, self.options['path'], locales
+ end
+ end
+
+ protected
+
+ # Read the YAML config file of a LocomotiveCMS site.
+ # The path should be returned by the check_path! method first.
+ #
+ # @param [ String ] path The full path to a LocomotiveCMS site.
+ #
+ # @return [ Hash ] The site
+ #
+ def site_config(path = nil)
+ YAML.load_file(File.join(path, 'config', 'site.yml'))
+ end
+
+ end
+
+ class Main < Thor
+
+ include Locomotive::Steam::CLI::CheckPath
+ include Locomotive::Steam::CLI::ForceColor
+
+ class_option :force_color, aliases: '-c', type: :boolean, default: false, desc: 'Whether or not to use ANSI color in the output.'
+
+ desc 'version', 'Version of the LocomotiveCMS Steam'
+ def version
+ require 'locomotive/steam/version'
+ say Locomotive::Steam::VERSION
+ end
+
+ desc 'init NAME [PATH] [OPTIONS]', 'Create a brand new site'
+ method_option :template, aliases: '-t', type: 'string', default: 'blank', desc: 'instead of building from a blank site, you can also have a pre-fetched site from a template (see the templates command)'
+ method_option :lib, aliases: '-l', type: 'string', desc: 'Path to an external ruby lib or generator'
+ method_option :skip_bundle, type: 'boolean', default: false, desc: "Don't run bundle install"
+ method_option :verbose, aliases: '-v', type: 'boolean', default: false, desc: 'display the full error stack trace if an error occurs'
+ def init(name, path = '.', generator_options = nil)
+ force_color_if_asked(options)
+ require 'locomotive/steam/generators/site'
+ require File.expand_path(options[:lib]) if options[:lib]
+ generator = Locomotive::Steam::Generators::Site.get(options[:template])
+ if generator.nil?
+ say "Unknown site template '#{options[:template]}'", :red
+ else
+ begin
+ if Locomotive::Steam.init(name, path, options[:skip_bundle].to_s, generator, generator_options)
+ self.print_next_instructions_when_site_created(name, path, options[:skip_bundle])
+ end
+ rescue GeneratorException => e
+ self.print_exception(e, options[:verbose])
+ exit(1)
+ end
+ end
+ end
+
+ desc 'clone NAME HOST [PATH]', 'Clone a remote LocomotiveCMS site'
+ method_option :verbose, aliases: '-v', type: 'boolean', default: false, desc: 'display the full error stack trace if an error occurs'
+ method_option :email, aliases: '-e', desc: 'email of an administrator account'
+ method_option :password, aliases: '-p', desc: 'password of an administrator account'
+ method_option :api_key, aliases: '-a', desc: 'api key of an administrator account'
+ def clone(name, host, path = '.')
+ begin
+ if Locomotive::Steam.clone(name, path, { host: host }.merge(options))
+ self.print_next_instructions_when_site_created(name, path)
+ end
+ rescue Exception => e
+ self.print_exception(e, options[:verbose])
+ exit(1)
+ end
+ end
+
+ desc 'generate RESOURCE ARGUMENTS', 'Generates a content_type, page, or snippet'
+ long_desc <<-LONGDESC
+ Generates a content_type, page, or snippet
+
+ RESOURCE can be set to content_type, page, or snippet.
+
+ Use steam generate help [RESOURCE] for usage information and examples.
+ LONGDESC
+ subcommand 'generate', Generate
+
+ desc 'list_templates', 'List all the templates to create either a site or a content type'
+ method_option :lib, aliases: '-l', type: 'string', desc: 'Path to an external ruby lib or generator'
+ method_option :json, aliases: '-j', type: :boolean, default: false, desc: 'Output the list in JSON'
+ def list_templates
+ force_color_if_asked(options)
+ require 'locomotive/steam/generators/site'
+ require File.expand_path(options[:lib]) if options[:lib]
+ if Locomotive::Steam::Generators::Site.empty?
+ say 'No templates', :red
+ elsif !options[:json]
+ Locomotive::Steam::Generators::Site.list.each do |info|
+ say info.name, :bold, false
+ say " - #{info.description}" unless info.description.blank?
+ end
+ else
+ say Locomotive::Steam::Generators::Site.list_to_json
+ end
+ end
+
+ desc 'serve [PATH]', 'Serve a site from the file system'
+ method_option :host, aliases: '-h', type: 'string', default: '0.0.0.0', desc: 'The host (address) of the Thin server'
+ method_option :port, aliases: '-p', type: 'string', default: '3333', desc: 'The port of the Thin server'
+ method_option :daemonize, aliases: '-d', type: 'boolean', default: false, desc: 'Run daemonized Thin server in the background'
+ method_option :force, aliases: '-f', type: 'boolean', default: false, desc: 'Stop the current daemonized Thin server if found before starting a new one'
+ method_option :verbose, aliases: '-v', type: 'boolean', default: false, desc: 'display the full error stack trace if an error occurs'
+ def serve(path = '.')
+ parent_pid = Process.pid
+ force_color_if_asked(options)
+ if check_path!(path)
+ begin
+ Locomotive::Steam.serve(path, options)
+ rescue SystemExit => e
+ if parent_pid == Process.pid
+ say "Your site is served now.", :green
+ end
+ rescue Exception => e
+ self.print_exception(e, options[:verbose])
+ exit(1)
+ end
+ end
+ end
+
+ desc 'stop [PATH]', 'Stop serving a site previously launched by the serve command with the -d option'
+ def stop(path = '.')
+ force_color_if_asked(options)
+ if check_path!(path)
+ begin
+ Locomotive::Steam.stop(path)
+ say "Your site is not served anymore.", :green
+ rescue Exception => e
+ say e.message, :red
+ exit(1)
+ end
+ end
+ end
+
+ desc 'push ENV [PATH]', 'Push a site to a remote LocomotiveCMS Engine'
+ method_option :resources, aliases: '-r', type: 'array', default: nil, desc: 'Only push the resource(s) passed in argument'
+ method_option :force, aliases: '-f', type: 'boolean', default: false, desc: 'Force the push of a resource'
+ method_option :translations, aliases: '-t', type: 'boolean', default: false, desc: 'Push the local translations (by default, they are not)'
+ method_option :data, aliases: '-d', type: 'boolean', default: false, desc: 'Push the content entries and the editable elements (by default, they are not)'
+ method_option :verbose, aliases: '-v', type: 'boolean', default: false, desc: 'display the full error stack trace if an error occurs'
+ def push(env, path = '.')
+ if check_path!(path)
+ if connection_info = self.retrieve_connection_info(env, path)
+ begin
+ Locomotive::Steam.push(path, connection_info, options)
+ rescue Exception => e
+ self.print_exception(e, options[:verbose])
+ exit(1)
+ end
+ end
+ end
+ end
+
+ desc 'pull ENV [PATH]', 'Pull a site from a remote LocomotiveCMS Engine'
+ method_option :resources, aliases: '-r', type: 'array', default: nil, desc: 'Only pull the resource(s) passed in argument'
+ method_option :verbose, aliases: '-v', type: 'boolean', default: false, desc: 'display the full error stack trace if an error occurs'
+ def pull(env, path = '.')
+ if check_path!(path)
+ if connection_info = self.retrieve_connection_info(env, path)
+ begin
+ Locomotive::Steam.pull(path, connection_info, options)
+ rescue Exception => e
+ self.print_exception(e, options[:verbose])
+ exit(1)
+ end
+ end
+ end
+ end
+
+ desc 'destroy ENV [PATH]', 'Destroy a remote LocomotiveCMS Engine'
+ def destroy(env, path = '.')
+ if check_path!(path)
+ if connection_info = self.retrieve_connection_info(env, path)
+ if ask('Are you sure ?', limited_to: %w(yes no)) == 'yes'
+ Locomotive::Steam.destroy(path, connection_info)
+ else
+ say 'The destroy operation has been cancelled', :red
+ exit(1)
+ end
+ end
+ end
+ end
+
+ protected
+
+ # Print a nice message when a site has been created.
+ #
+ # @param [ String ] name The name of the site
+ # @param [ String ] path The path of the local site
+ # @param [ Boolean ] skip_bundle Do not run bundle install
+ #
+ def print_next_instructions_when_site_created(name, path, skip_bundle)
+ say "\nCongratulations, your site \"#{name}\" has been created successfully !", :green
+ say 'Next steps:', :bold
+
+ next_instructions = "\tcd #{path}/#{name}\n\t"
+ next_instructions += "bundle install\n\t" if skip_bundle
+ next_instructions += "bundle exec steam serve\n\topen http://0.0.0.0:3333"
+
+ say next_instructions
+ end
+
+ # Print the exception.
+ #
+ # @param [ Object ] exception The raised exception
+ # @param [ Boolean ] verbose Print the full backtrace if true
+ #
+ def print_exception(exception, verbose)
+ say exception.message, :red
+ if verbose
+ say "\t" + exception.backtrace.join("\n\t")
+ end
+ end
+
+ # From a site specified by a path, retrieve the information of the connection
+ # for a environment located in the config/deploy.yml file of the site.
+ #
+ # @param [ String ] env The environment (development, staging, production, ...etc)
+ # @param [ String ] path The path of the local site
+ #
+ # @return [ Hash ] The information of the connection or nil if errors
+ #
+ def retrieve_connection_info(env, path)
+ require 'active_support/core_ext/hash'
+ require 'erb'
+ connection_info = nil
+ begin
+ path_to_deploy_file = File.join(path, 'config', 'deploy.yml')
+ env_parsed_deploy_file = ERB.new(File.open(path_to_deploy_file).read).result
+ connection_info = YAML::load(env_parsed_deploy_file)[env.to_s].with_indifferent_access
+
+ if connection_info[:ssl] && !connection_info[:host].start_with?('https')
+ connection_info[:host] = 'https://' + connection_info[:host]
+ end
+
+ if connection_info.nil?
+ raise "No #{env.to_s} environment found in the config/deploy.yml file"
+ end
+ rescue Exception => e
+ say "Unable to read the information about the remote LocomotiveCMS site (#{e.message})", :red
+ end
+ connection_info
+ end
+
+ end
+
+ end
+ end
+ end
locomotive/steam/exceptions.rb b/lib/locomotive/steam/exceptions.rb +62 -0
@@ @@ -0,0 +1,62 @@
+ module Locomotive
+ module Steam
+
+ class DefaultException < ::Exception
+
+ def initialize(message = nil, parent_exception = nil)
+ self.log_backtrace(parent_exception) if parent_exception
+
+ super(message)
+ end
+
+ protected
+
+ def log_backtrace(parent_exception)
+ full_error_message = "#{parent_exception.message}\n\t"
+ full_error_message += parent_exception.backtrace.join("\n\t")
+ full_error_message += "\n\n"
+ Locomotive::Steam::Logger.fatal full_error_message
+ end
+
+ end
+
+ class SteamerException < DefaultException
+
+ attr_accessor :name, :template, :liquid_context
+
+ def initialize(exception, name, template, liquid_context)
+ self.name, self.template, self.liquid_context = name, template, liquid_context
+
+ self.log_page_into_backtrace(exception)
+
+ super(exception.message)
+
+ self.set_backtrace(exception.backtrace)
+ end
+
+ def log_page_into_backtrace(exception)
+ line = self.template.line_offset
+ line += (exception.respond_to?(:line) ? exception.line || 0 : 0) + 1
+
+ message = "#{self.template.filepath}:#{line}:in `#{self.name}'"
+
+ Locomotive::Steam::Logger.fatal "[ERROR] #{exception.message} - #{message}\n".red
+
+ exception.backtrace.unshift message
+ end
+
+ end
+
+ class MounterException < DefaultException
+ end
+
+ class GeneratorException < DefaultException
+
+ def log_backtrace(parent_exception)
+ # Logger not initialized at this step
+ end
+
+ end
+
+ end
+ end
locomotive/steam/liquid.rb b/lib/locomotive/steam/liquid.rb +21 -0
@@ @@ -0,0 +1,21 @@
+ require 'solid'
+ require 'locomotive/mounter'
+ require 'locomotive/steam/liquid/scopeable'
+ require 'locomotive/steam/liquid/drops/base'
+ require 'locomotive/steam/liquid/tags/hybrid'
+ require 'locomotive/steam/liquid/tags/path_helper'
+
+ %w{. drops tags filters}.each do |dir|
+ Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib }
+ end
+
+ # add to_liquid methods to main models from the mounter
+ %w{site page content_entry}.each do |name|
+ klass = "Locomotive::Mounter::Models::#{name.classify}".constantize
+
+ klass.class_eval <<-EOV
+ def to_liquid
+ ::Locomotive::Steam::Liquid::Drops::#{name.classify}.new(self)
+ end
+ EOV
+ end
locomotive/steam/liquid/drops/base.rb b/lib/locomotive/steam/liquid/drops/base.rb +46 -0
@@ @@ -0,0 +1,46 @@
+ # Code taken from Mephisto sources (http://mephistoblog.com/)
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+ class Base < ::Liquid::Drop
+
+ @@forbidden_attributes = %w{_id _version _index}
+
+ def initialize(source)
+ @_source = source
+ end
+
+ def id
+ (@_source.respond_to?(:id) ? @_source.id : nil) || 'new'
+ end
+
+ # converts an array of records to an array of liquid drops
+ def self.liquify(*records, &block)
+ i = -1
+ records =
+ records.inject [] do |all, r|
+ i+=1
+ attrs = (block && block.arity == 1) ? [r] : [r, i]
+ all << (block ? block.call(*attrs) : r.to_liquid)
+ all
+ end
+ records.compact!
+ records
+ end
+
+ protected
+
+ def liquify(*records, &block)
+ self.class.liquify(*records, &block)
+ end
+
+ def _source
+ @_source
+ end
+
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/drops/content_entry.rb b/lib/locomotive/steam/liquid/drops/content_entry.rb +48 -0
@@ @@ -0,0 +1,48 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+ class ContentEntry < Base
+
+ delegate :seo_title, :meta_keywords, :meta_description, to: :@_source
+
+ def _label
+ @_label ||= @_source._label
+ end
+
+ def _permalink
+ @_source._permalink.try(:parameterize)
+ end
+
+ alias :_slug :_permalink
+
+ def next
+ self
+ end
+
+ def previous
+ self
+ end
+
+ def errors
+ (@_source.errors || []).inject({}) do |memo, name|
+ memo[name] = ::I18n.t('errors.messages.blank')
+ memo
+ end
+ end
+
+ def before_method(meth)
+ return '' if @_source.nil?
+
+ if not @@forbidden_attributes.include?(meth.to_s)
+ @_source.send(meth)
+ else
+ nil
+ end
+ end
+
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/drops/content_types.rb b/lib/locomotive/steam/liquid/drops/content_types.rb +117 -0
@@ @@ -0,0 +1,117 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+ class ContentTypes < ::Liquid::Drop
+
+ def before_method(meth)
+ type = self.mounting_point.content_types[meth.to_s]
+ ProxyCollection.new(type)
+ end
+
+ end
+
+ class ProxyCollection < ::Liquid::Drop
+
+ include Scopeable
+
+ def initialize(content_type)
+ @content_type = content_type
+ @collection = nil
+ end
+
+ def all
+ self.collection
+ end
+
+ def any
+ self.collection.any?
+ end
+
+ def first
+ self.collection.first
+ end
+
+ def last
+ self.collection.last
+ end
+
+ def size
+ self.collection.size
+ end
+
+ alias :length :size
+ alias :count :size
+
+ def each(&block)
+ self.collection.each(&block)
+ end
+
+ def public_submission_url
+ "/entry_submissions/#{@content_type.slug}"
+ end
+
+ def api
+ { 'create' => "/entry_submissions/#{@content_type.slug}" }
+ end
+
+ def before_method(meth)
+ if (meth.to_s =~ /^group_by_(.+)$/) == 0
+ self.group_entries_by(@content_type, $1)
+ elsif (meth.to_s =~ /^(.+)_options$/) == 0
+ self.select_options_for(@content_type, $1)
+ else
+ @content_type.send(meth)
+ end
+ end
+
+ protected
+
+ def group_entries_by(content_type, name)
+ field = @content_type.find_field(name)
+
+ return {} if field.nil? || !%w(belongs_to select).include?(field.type.to_s)
+
+ (@content_type.entries || []).group_by do |entry|
+ entry.send(name.to_sym)
+ end.to_a.collect do |group|
+ { name: group.first, entries: group.last }.with_indifferent_access
+ end
+ end
+
+ def select_options_for(content_type, name)
+ field = @content_type.find_field(name)
+
+ return {} if field.nil? || field.type.to_s != 'select'
+
+ field.select_options.map(&:name)
+ end
+
+ def paginate(options = {})
+ @collection ||= self.collection.paginate(options)
+ {
+ collection: @collection,
+ current_page: @collection.current_page,
+ previous_page: @collection.previous_page,
+ next_page: @collection.next_page,
+ total_entries: @collection.total_entries,
+ total_pages: @collection.total_pages,
+ per_page: @collection.per_page
+ }
+ end
+
+ def collection
+ return unless @collection.blank?
+
+ # define the default order_by if not set
+ if @context['with_scope'] && !@context['with_scope']['order_by'].blank? && !%w(manually position).include?(@content_type.order_by)
+ @context['with_scope']['order_by'] = @content_type.order_by + '.' + @content_type.order_direction
+ end
+
+ @collection = apply_scope(@content_type.entries)
+ end
+ end
+ end
+ end
+ end
+ end
locomotive/steam/liquid/drops/page.rb b/lib/locomotive/steam/liquid/drops/page.rb +28 -0
@@ @@ -0,0 +1,28 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+ class Page < Base
+
+ delegate :title, :slug, :fullpath, :parent, :depth, :seo_title, :redirect_url, :meta_description, :meta_keywords,
+ :templatized?, :published?, :redirect?, :listed?, :handle, to: :@_source
+
+ def children
+ _children = @_source.children || []
+ _children = _children.sort { |a, b| a.position.to_i <=> b.position.to_i }
+ @children ||= liquify(*_children)
+ end
+
+ def content_type
+ ProxyCollection.new(@_source.content_type) if @_source.content_type
+ end
+
+ def breadcrumbs
+ # TODO
+ ''
+ end
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/drops/session_proxy.rb b/lib/locomotive/steam/liquid/drops/session_proxy.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+
+ class SessionProxy < ::Liquid::Drop
+
+ def before_method(meth)
+ request = @context.registers[:request]
+ request.session[meth.to_sym]
+ end
+
+ end
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/drops/site.rb b/lib/locomotive/steam/liquid/drops/site.rb +26 -0
@@ @@ -0,0 +1,26 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+ class Site < Base
+ include Scopeable
+
+ delegate :name, :seo_title, :meta_description, :meta_keywords, to: :@_source
+
+ def index
+ @index ||= self.mounting_point.pages['index']
+ end
+
+ def pages
+ liquify(*apply_scope(self.mounting_point.pages.values))
+ end
+
+ def domains
+ @_source.domains
+ end
+
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/errors.rb b/lib/locomotive/steam/liquid/errors.rb +17 -0
@@ @@ -0,0 +1,17 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ class PageNotFound < ::Liquid::Error; end
+
+ class PageNotTranslated < ::Liquid::Error; end
+
+ class ContentEntryNotTranslated < ::Liquid::Error; end
+
+ class UnknownConditionInScope < ::Liquid::Error; end
+
+ class UnknownConditionInScope < ::Liquid::Error; end
+
+ class ConnectionRefused < ::Liquid::Error; end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/filters/date.rb b/lib/locomotive/steam/liquid/filters/date.rb +136 -0
@@ @@ -0,0 +1,136 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Filters
+ module Date
+
+ def parse_date_time(input, format = nil)
+ return '' if input.blank?
+
+ format ||= I18n.t('time.formats.default')
+ date_time = ::DateTime._strptime(input, format)
+
+ if date_time
+ ::Time.zone.local(date_time[:year], date_time[:mon], date_time[:mday], date_time[:hour], date_time[:min], date_time[:sec] || 0)
+ else
+ ::Time.zone.parse(input) rescue ''
+ end
+ end
+
+ def parse_date(input, format)
+ return '' if input.blank?
+
+ format ||= I18n.t('date.formats.default')
+ date = ::Date._strptime(input, format)
+
+ if date
+ ::Date.new(date[:year], date[:mon], date[:mday])
+ else
+ ::Date.parse(value) rescue ''
+ end
+ end
+
+ def localized_date(input, *args)
+ return '' if input.blank?
+
+ format, locale = args
+
+ locale ||= I18n.locale
+ format ||= I18n.t('date.formats.default', locale: locale)
+
+ if input.is_a?(String)
+ begin
+ fragments = ::Date._strptime(input, format)
+ input = ::Date.new(fragments[:year], fragments[:mon], fragments[:mday])
+ rescue
+ input = Time.zone.parse(input)
+ end
+ end
+
+ return input.to_s unless input.respond_to?(:strftime)
+
+ I18n.l input, format: format, locale: locale
+ end
+
+ alias :format_date :localized_date
+
+ def distance_of_time_in_words(input, from_time = Time.zone.now, include_seconds = false)
+ return '' if input.blank?
+
+ # make sure we deals with instances of Time
+ to_time = to_time(input)
+ from_time = to_time(from_time)
+
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
+ distance_in_seconds = ((to_time - from_time).abs).round
+
+ ::I18n.with_options({ scope: :'datetime.distance_in_words' }) do |locale|
+
+ case distance_in_minutes
+ when 0..1
+ return distance_in_minutes == 0 ?
+ locale.t(:less_than_x_minutes, count: 1) :
+ locale.t(:x_minutes, count: distance_in_minutes) unless include_seconds
+
+ case distance_in_seconds
+ when 0..4 then locale.t :less_than_x_seconds, count: 5
+ when 5..9 then locale.t :less_than_x_seconds, count: 10
+ when 10..19 then locale.t :less_than_x_seconds, count: 20
+ when 20..39 then locale.t :half_a_minute
+ when 40..59 then locale.t :less_than_x_minutes, count: 1
+ else locale.t :x_minutes, count: 1
+ end
+
+ when 2..44 then locale.t :x_minutes, count: distance_in_minutes
+ when 45..89 then locale.t :about_x_hours, count: 1
+ when 90..1439 then locale.t :about_x_hours, count: (distance_in_minutes.to_f / 60.0).round
+ when 1440..2519 then locale.t :x_days, count: 1
+ when 2520..43199 then locale.t :x_days, count: (distance_in_minutes.to_f / 1440.0).round
+ when 43200..86399 then locale.t :about_x_months, count: 1
+ when 86400..525599 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
+ else
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| ::Date.leap?(x)}
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # english it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ remainder = (minutes_with_offset % 525600)
+ distance_in_years = (minutes_with_offset / 525600)
+ if remainder < 131400
+ locale.t(:about_x_years, count: distance_in_years)
+ elsif remainder < 394200
+ locale.t(:over_x_years, count: distance_in_years)
+ else
+ locale.t(:almost_x_years, count: distance_in_years + 1)
+ end
+ end
+ end
+ end
+
+ private
+
+ def to_time(input)
+ case input
+ when Date then input.to_time
+ when String then Time.zone.parse(input)
+ else
+ input
+ end
+ end
+
+ end
+
+ ::Liquid::Template.register_filter(Date)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/filters/html.rb b/lib/locomotive/steam/liquid/filters/html.rb +188 -0
@@ @@ -0,0 +1,188 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Filters
+ module Html
+
+ # Returns a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed.
+ # input: url of the feed
+ # example:
+ # {{ '/foo/bar' | auto_discovery_link_tag: 'rel:alternate', 'type:application/atom+xml', 'title:A title' }}
+ def auto_discovery_link_tag(input, *args)
+ options = args_to_options(args)
+
+ rel = options[:rel] || 'alternate'
+ type = options[:type] || MIME::Types.type_for('rss').first
+ title = options[:title] || 'RSS'
+
+ %{<link rel="#{rel}" type="#{type}" title="#{title}" href="#{input}" />}
+ end
+
+ # Write the url of a theme stylesheet
+ # input: name of the css file
+ def stylesheet_url(input)
+ return '' if input.nil?
+
+ if input =~ /^https?:/
+ input
+ else
+ input = "/stylesheets/#{input}" unless input =~ /^\//
+ input = "#{input}.css" unless input.ends_with?('.css')
+ input
+ end
+ end
+
+ # Write the link to a stylesheet resource
+ # input: url of the css file
+ def stylesheet_tag(input, media = 'screen')
+ return '' if input.nil?
+
+ input = stylesheet_url(input)
+
+ %{<link href="#{input}" media="#{media}" rel="stylesheet" type="text/css" />}
+ end
+
+ # Write the url to javascript resource
+ # input: name of the javascript file
+ def javascript_url(input)
+ return '' if input.nil?
+
+ input = "/javascripts/#{input}" unless input =~ /^(\/|https?:)/
+
+ input = "#{input}.js" unless input.ends_with?('.js')
+
+ input
+ end
+
+ # Write the link to javascript resource
+ # input: url of the javascript file
+ def javascript_tag(input)
+ return '' if input.nil?
+
+ input = javascript_url(input)
+
+ %{<script src="#{input}" type="text/javascript"></script>}
+ end
+
+ # Write an image tag
+ # input: url of the image OR asset drop
+ def image_tag(input, *args)
+ image_options = inline_options(args_to_options(args))
+
+ "<img src=\"#{get_url_from_asset(input)}\" #{image_options}>"
+ end
+
+ # Write a theme image tag
+ # input: name of file including folder
+ # example: 'about/myphoto.jpg' | theme_image # <img src="images/about/myphoto.jpg" />
+ def theme_image_tag(input, *args)
+ image_options = inline_options(args_to_options(args))
+ "<img src=\"#{theme_image_url(input)}\" #{image_options}/>"
+ end
+
+ def theme_image_url(input)
+ return '' if input.nil?
+
+ input = "images/#{input}" unless input.starts_with?('/')
+
+ File.join('/', input)
+ end
+
+ def image_format(input, *args)
+ format = args_to_options(args).first
+ "#{input}.#{format}"
+ end
+
+ # Embed a flash movie into a page
+ # input: url of the flash movie OR asset drop
+ # width: width (in pixel or in %) of the embedded movie
+ # height: height (in pixel or in %) of the embedded movie
+ def flash_tag(input, *args)
+ path = get_url_from_asset(input)
+ embed_options = inline_options(args_to_options(args))
+ %{
+ <object #{embed_options}>
+ <param name="movie" value="#{path}" />
+ <embed src="#{path}" #{embed_options}/>
+ </embed>
+ </object>
+ }.gsub(/ >/, '>').strip
+ end
+
+ # Steam the navigation for a paginated collection
+ def default_pagination(paginate, *args)
+ return '' if paginate['parts'].empty?
+
+ options = args_to_options(args)
+
+ previous_label = options[:previous_label] || I18n.t('pagination.previous')
+ next_label = options[:next_label] || I18n.t('pagination.next')
+
+ previous_link = (if paginate['previous'].blank?
+ "<span class=\"disabled prev_page\">#{previous_label}</span>"
+ else
+ "<a href=\"#{absolute_url(paginate['previous']['url'])}\" class=\"prev_page\">#{previous_label}</a>"
+ end)
+
+ links = ""
+ paginate['parts'].each do |part|
+ links << (if part['is_link']
+ "<a href=\"#{absolute_url(part['url'])}\">#{part['title']}</a>"
+ elsif part['hellip_break']
+ "<span class=\"gap\">#{part['title']}</span>"
+ else
+ "<span class=\"current\">#{part['title']}</span>"
+ end)
+ end
+
+ next_link = (if paginate['next'].blank?
+ "<span class=\"disabled next_page\">#{next_label}</span>"
+ else
+ "<a href=\"#{absolute_url(paginate['next']['url'])}\" class=\"next_page\">#{next_label}</a>"
+ end)
+
+ %{<div class="pagination #{options[:css]}">
+ #{previous_link}
+ #{links}
+ #{next_link}
+ </div>}
+ end
+
+ protected
+
+ # Convert an array of properties ('key:value') into a hash
+ # Ex: ['width:50', 'height:100'] => { :width => '50', :height => '100' }
+ def args_to_options(*args)
+ options = {}
+ args.flatten.each do |a|
+ if (a =~ /^(.*):(.*)$/)
+ options[$1.to_sym] = $2
+ end
+ end
+ options
+ end
+
+ # Write options (Hash) into a string according to the following pattern:
+ # <key1>="<value1>", <key2>="<value2", ...etc
+ def inline_options(options = {})
+ return '' if options.empty?
+ (options.stringify_keys.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
+ end
+
+ # Get the path to be used in html tags such as image_tag, flash_tag, ...etc
+ # input: url (String) OR asset drop
+ def get_url_from_asset(input)
+ input.respond_to?(:url) ? input.url : input
+ end
+
+ def absolute_url(url)
+ url =~ /^\// ? url : "/#{url}"
+ end
+ end
+
+ ::Liquid::Template.register_filter(Html)
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/filters/misc.rb b/lib/locomotive/steam/liquid/filters/misc.rb +49 -0
@@ @@ -0,0 +1,49 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Filters
+ module Misc
+
+ # was called modulo at first
+ def str_modulo(word, index, modulo)
+ (index.to_i + 1) % modulo == 0 ? word : ''
+ end
+
+ # Get the nth element of the passed in array
+ def index(array, position)
+ array.at(position) if array.respond_to?(:at)
+ end
+
+ def default(input, value)
+ input.blank? ? value : input
+ end
+
+ def random(input)
+ rand(input.to_i)
+ end
+
+ # map/collect on a given property (support to_f, to_i)
+ def map(input, property)
+ flatten_if_necessary(input).map do |e|
+ e = e.call if e.is_a?(Proc)
+
+ if property == "to_liquid"
+ e
+ elsif property == "to_f"
+ e.to_f
+ elsif property == "to_i"
+ e.to_i
+ elsif e.respond_to?(:[])
+ e[property]
+ end
+ end
+ end
+
+ end
+
+ ::Liquid::Template.register_filter(Misc)
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/filters/resize.rb b/lib/locomotive/steam/liquid/filters/resize.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Filters
+ module Resize
+
+ def resize(input, resize_string)
+ Locomotive::Steam::Dragonfly.instance.resize_url(input, resize_string)
+ end
+
+ end
+
+ ::Liquid::Template.register_filter(Resize)
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/filters/text.rb b/lib/locomotive/steam/liquid/filters/text.rb +55 -0
@@ @@ -0,0 +1,55 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Filters
+ module Text
+
+ # right justify and padd a string
+ def rjust(input, integer, padstr = '')
+ input.to_s.rjust(integer, padstr)
+ end
+
+ # left justify and padd a string
+ def ljust(input, integer, padstr = '')
+ input.to_s.ljust(integer, padstr)
+ end
+
+ def underscore(input)
+ input.to_s.gsub(' ', '_').gsub('/', '_').underscore
+ end
+
+ def dasherize(input)
+ input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
+ end
+
+ # alias newline_to_br
+ def multi_line(input)
+ input.to_s.gsub("\n", '<br/>')
+ end
+
+ def concat(input, *args)
+ result = input.to_s
+ args.flatten.each { |a| result << a.to_s }
+ result
+ end
+
+ def encode(input)
+ Rack::Utils.escape(input)
+ end
+
+ def textile(input)
+ ::RedCloth.new(input).to_html
+ end
+
+ def markdown(input)
+ Locomotive::Steam::Markdown.steam(input)
+ end
+
+ end
+
+ ::Liquid::Template.register_filter(Text)
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/filters/translate.rb b/lib/locomotive/steam/liquid/filters/translate.rb +28 -0
@@ @@ -0,0 +1,28 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Filters
+ module Translate
+
+ def translate(key, locale = nil, scope = nil)
+ locale ||= I18n.locale.to_s
+ if scope.blank?
+ translation = @context.registers[:mounting_point].translations[key.to_s]
+
+ if translation
+ translation.get(locale) || translation.get(Locomotive::Mounter.locale.to_s)
+ else
+ "[unknown translation key: #{key}]"
+ end
+ else
+ I18n.t(key, scope: scope.split('.'), locale: locale)
+ end
+ end
+ end
+
+ ::Liquid::Template.register_filter(Translate)
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/patches.rb b/lib/locomotive/steam/liquid/patches.rb +47 -0
@@ @@ -0,0 +1,47 @@
+ module Liquid
+
+ class Drop
+
+ def mounting_point
+ @context.registers[:mounting_point]
+ end
+
+ def site
+ @context.registers[:site]
+ end
+
+ end
+
+ class Template
+
+ # creates a new <tt>Template</tt> object from liquid source code
+ def parse_with_utf8(source, context = {})
+ if RUBY_VERSION =~ /1\.9/
+ source = source.force_encoding('UTF-8') if source.present?
+ end
+ self.parse_without_utf8(source, context)
+ end
+
+ alias_method_chain :parse, :utf8
+
+ end
+
+ module StandardFilters
+
+ private
+
+ def to_number(obj)
+ case obj
+ when Numeric
+ obj
+ when String
+ (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
+ when DateTime, Date, Time
+ obj.to_time.to_i
+ else
+ 0
+ end
+ end
+ end
+
+ end
locomotive/steam/liquid/scopeable.rb b/lib/locomotive/steam/liquid/scopeable.rb +149 -0
@@ @@ -0,0 +1,149 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Scopeable
+
+ def apply_scope(entries)
+ if @context['with_scope'].blank?
+ entries
+ else
+ # extract the conditions
+ _conditions = @context['with_scope'].clone.delete_if { |k, _| %w(order_by per_page page).include?(k) }
+
+ # build the chains of conditions
+ conditions = _conditions.map { |name, value| Condition.new(name, value) }
+
+ Locomotive::Steam::Logger.info "[with_scope] conditions: #{conditions.map(&:to_s).join(', ')}"
+
+ # get only the entries matching ALL the conditions
+ _entries = entries.find_all do |content|
+ accepted = true
+
+ conditions.each do |_condition|
+ unless _condition.matches?(content)
+ accepted = false
+ break # no to go further
+ end
+ end
+
+ accepted
+ end
+
+ self._apply_scope_order(_entries, @context['with_scope']['order_by'])
+ end
+ end
+
+ def _apply_scope_order(entries, order_by)
+ return entries if order_by.blank?
+
+ name, direction = order_by.split.map(&:to_sym)
+
+ Locomotive::Steam::Logger.info "[with_scope] order_by #{name} #{direction || 'asc'}"
+
+ if direction == :asc || direction.nil?
+ entries.sort { |a, b| a.send(name) <=> b.send(name) }
+ else
+ entries.sort { |a, b| b.send(name) <=> a.send(name) }
+ end
+ end
+
+ class Condition
+
+ OPERATORS = %w(all gt gte in lt lte ne nin size)
+
+ attr_accessor :name, :operator, :right_operand
+
+ def initialize(name, value)
+ self.name, self.right_operand = name, value
+
+ self.process_right_operand
+
+ # default value
+ self.operator = :==
+
+ self.decode_operator_based_on_name
+ end
+
+ def matches?(entry)
+ value = self.get_value(entry)
+
+ self.decode_operator_based_on_value(value)
+
+ case self.operator
+ when :== then value == self.right_operand
+ when :ne then value != self.right_operand
+ when :matches then self.right_operand =~ value
+ when :gt then value > self.right_operand
+ when :gte then value >= self.right_operand
+ when :lt then value < self.right_operand
+ when :lte then value <= self.right_operand
+ when :size then value.size == self.right_operand
+ when :all then [*self.right_operand].contains?(value)
+ when :in, :nin
+ _matches = if value.is_a?(Array)
+ [*value].contains?([*self.right_operand])
+ else
+ [*self.right_operand].include?(value)
+ end
+ self.operator == :in ? _matches : !_matches
+ else
+ raise UnknownConditionInScope.new("#{self.operator} is unknown or not implemented.")
+ end
+ end
+
+ def to_s
+ "#{name} #{operator} #{self.right_operand.to_s}"
+ end
+
+ protected
+
+ def get_value(entry)
+ value = entry.send(self.name)
+
+ if value.respond_to?(:_slug)
+ # belongs_to
+ value._slug
+ elsif value.respond_to?(:map)
+ # many_to_many or tags ?
+ value.map { |v| v.respond_to?(:_slug) ? v._slug : v }
+ else
+ value
+ end
+ end
+
+ def process_right_operand
+ if self.right_operand.respond_to?(:_slug)
+ # belongs_to
+ self.right_operand = self.right_operand._slug
+ elsif self.right_operand.respond_to?(:map) && self.right_operand.first.respond_to?(:_slug)
+ # many_to_many
+ self.right_operand = self.right_operand.map do |entry|
+ entry.try(&:_slug)
+ end
+ end
+ end
+
+ def decode_operator_based_on_name
+ if name =~ /^([a-z0-9_-]+)\.(#{OPERATORS.join('|')})$/
+ self.name = $1.to_sym
+ self.operator = $2.to_sym
+ end
+
+ if self.right_operand.is_a?(Regexp)
+ self.operator = :matches
+ end
+ end
+
+ def decode_operator_based_on_value(value)
+ case value
+ when Array
+ self.operator = :in if self.operator == :==
+ end
+ end
+
+ end
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/consume.rb b/lib/locomotive/steam/liquid/tags/consume.rb +97 -0
@@ @@ -0,0 +1,97 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ # Consume web services as easy as pie directly in liquid !
+ #
+ # Usage:
+ #
+ # {% consume blog from 'http://nocoffee.tumblr.com/api/read.json?num=3', username: 'john', password: 'easy', format: 'json', expires_in: 3000 %}
+ # {% for post in blog.posts %}
+ # {{ post.title }}
+ # {% endfor %}
+ # {% endconsume %}
+ #
+ class Consume < ::Liquid::Block
+
+ Syntax = /(#{::Liquid::VariableSignature}+)\s*from\s*(#{::Liquid::QuotedString}|#{::Liquid::VariableSignature}+)/
+
+ def initialize(tag_name, markup, tokens, options)
+ if markup =~ Syntax
+ @target = $1
+
+ self.prepare_url($2)
+ self.prepare_options(markup)
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.consume"), options[:line])
+ end
+
+ @local_cache_key = self.hash
+
+ super
+ end
+
+ def render(context)
+ if instance_variable_defined? :@variable_name
+ @url = context[@variable_name]
+ end
+ render_all_without_cache(context)
+ end
+
+ protected
+
+ def prepare_url(token)
+ if token.match(::Liquid::QuotedString)
+ @url = token.gsub(/['"]/, '')
+ elsif token.match(::Liquid::VariableSignature)
+ @variable_name = token
+ else
+ raise ::Liquid::SyntaxError.new("Syntax Error in 'consume' - Valid syntax: consume <var> from \"<url>\" [username: value, password: value]")
+ end
+ end
+
+ def prepare_options(markup)
+ @options = {}
+ markup.scan(::Liquid::TagAttributes) do |key, value|
+ @options[key] = value if key != 'http'
+ end
+ @options['timeout'] = @options['timeout'].to_f if @options['timeout']
+ @expires_in = (@options.delete('expires_in') || 0).to_i
+ end
+
+ def cached_response
+ @@local_cache ||= {}
+ @@local_cache[@local_cache_key]
+ end
+
+ def cached_response=(response)
+ @@local_cache ||= {}
+ @@local_cache[@local_cache_key] = response
+ end
+
+ def render_all_without_cache(context)
+ context.stack do
+ begin
+ context.scopes.last[@target.to_s] = Locomotive::Steam::Httparty::Webservice.consume(@url, @options.symbolize_keys)
+ self.cached_response = context.scopes.last[@target.to_s]
+ rescue Timeout::Error
+ context.scopes.last[@target.to_s] = self.cached_response
+ rescue ::Liquid::Error => e
+ raise e
+ rescue => e
+ liquid_e = ::Liquid::Error.new(e.message, line)
+ liquid_e.set_backtrace(e.backtrace)
+ raise liquid_e
+ end
+
+ render_all(@nodelist, context)
+ end
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('consume', Consume)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/csrf.rb b/lib/locomotive/steam/liquid/tags/csrf.rb +34 -0
@@ @@ -0,0 +1,34 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module Csrf
+
+ class Param < ::Liquid::Tag
+
+ def render(context)
+ %{<input type="hidden" name="authenticity_token" value="helloworld" />}
+ end
+
+ end
+
+ class Meta < ::Liquid::Tag
+
+ def render(context)
+ %{
+ <meta name="csrf-param" content="authenticity_token" />
+ <meta name="csrf-token" content="helloworld" />
+ }
+ end
+
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('csrf_param', Csrf::Param)
+ ::Liquid::Template.register_tag('csrf_meta', Csrf::Meta)
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/editable.rb b/lib/locomotive/steam/liquid/tags/editable.rb +6 -0
@@ @@ -0,0 +1,6 @@
+ require 'locomotive/steam/liquid/tags/editable/base'
+ require 'locomotive/steam/liquid/tags/editable/text'
+ require 'locomotive/steam/liquid/tags/editable/short_text'
+ require 'locomotive/steam/liquid/tags/editable/long_text'
+ require 'locomotive/steam/liquid/tags/editable/file'
+ require 'locomotive/steam/liquid/tags/editable/control'
\ No newline at end of file
locomotive/steam/liquid/tags/editable/base.rb b/lib/locomotive/steam/liquid/tags/editable/base.rb +50 -0
@@ @@ -0,0 +1,50 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module Editable
+ class Base < ::Liquid::Block
+
+ Syntax = /(#{::Liquid::QuotedFragment})(\s*,\s*#{::Liquid::Expression}+)?/
+
+ def initialize(tag_name, markup, tokens, options)
+ if markup =~ Syntax
+ @slug = $1.gsub(/[\"\']/, '')
+ @_options = {}
+ markup.scan(::Liquid::TagAttributes) { |key, value| @_options[key.to_sym] = value.gsub(/^'/, '').gsub(/'$/, '') }
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.#{tag_name}"), options[:line])
+ end
+
+ super
+ end
+
+ def render(context)
+ current_page = context.registers[:page]
+
+ element = current_page.find_editable_element(self.current_block_name(context), @slug)
+
+ if element.present?
+ render_element(context, element)
+ else
+ super
+ end
+ end
+
+ protected
+
+ def render_element(context, element)
+ element.content
+ end
+
+ def current_block_name(context)
+ context['block'].try(:name)
+ end
+
+ end
+
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/editable/control.rb b/lib/locomotive/steam/liquid/tags/editable/control.rb +19 -0
@@ @@ -0,0 +1,19 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module Editable
+ class Control < Base
+
+ def render(context)
+ super
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('editable_control', Control)
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/editable/file.rb b/lib/locomotive/steam/liquid/tags/editable/file.rb +15 -0
@@ @@ -0,0 +1,15 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module Editable
+ class File < Base
+
+ end
+
+ ::Liquid::Template.register_tag('editable_file', File)
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/editable/long_text.rb b/lib/locomotive/steam/liquid/tags/editable/long_text.rb +15 -0
@@ @@ -0,0 +1,15 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module Editable
+ class LongText < ShortText
+
+ end
+
+ ::Liquid::Template.register_tag('editable_long_text', LongText)
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/editable/short_text.rb b/lib/locomotive/steam/liquid/tags/editable/short_text.rb +20 -0
@@ @@ -0,0 +1,20 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module Editable
+ class ShortText < Base
+
+ def render(context)
+ Locomotive::Steam::Logger.warn " [#{self.current_block_name(context)}/#{@slug}] The editable_{short|long}_text tags are deprecated. Use editable_text instead.".colorize(:orange)
+ super(context)
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('editable_short_text', ShortText)
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/editable/text.rb b/lib/locomotive/steam/liquid/tags/editable/text.rb +15 -0
@@ @@ -0,0 +1,15 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module Editable
+ class Text < Base
+
+ end
+
+ ::Liquid::Template.register_tag('editable_text', Text)
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/extends.rb b/lib/locomotive/steam/liquid/tags/extends.rb +25 -0
@@ @@ -0,0 +1,25 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ class Extends < ::Liquid::Extends
+
+ def parse_parent_template
+ mounting_point = @options[:mounting_point]
+
+ page = if @template_name == 'parent'
+ @options[:page].parent
+ else
+ mounting_point.pages[@template_name]
+ end
+
+ ::Liquid::Template.parse(page.source, { mounting_point: mounting_point, page: page })
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('extends', Extends)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/google_analytics.rb b/lib/locomotive/steam/liquid/tags/google_analytics.rb +28 -0
@@ @@ -0,0 +1,28 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ class GoogleAnalytics < ::Liquid::Tag
+
+ Syntax = /(#{::Liquid::Expression}+)?/
+
+ def initialize(tag_name, markup, tokens, options)
+ if markup =~ Syntax
+ @account_id = $1.gsub('\'', '')
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.google_analytics"), options[:line])
+ end
+
+ super
+ end
+
+ def render(context)
+ "<!-- google analytics for #{@account_id} -->"
+ end
+ end
+
+ ::Liquid::Template.register_tag('google_analytics', GoogleAnalytics)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/hybrid.rb b/lib/locomotive/steam/liquid/tags/hybrid.rb +27 -0
@@ @@ -0,0 +1,27 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ class Hybrid < ::Liquid::Block
+ def parse(tokens)
+ nesting = 0
+ tokens.each do |token|
+ next unless token =~ IsTag
+ if token =~ FullToken
+ if nesting == 0 && $1 == block_delimiter
+ @render_as_block = true
+ super
+ return
+ elsif $1 == block_name
+ nesting += 1
+ elsif $1 == block_delimiter
+ nesting -= 1
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/inline_editor.rb b/lib/locomotive/steam/liquid/tags/inline_editor.rb +16 -0
@@ @@ -0,0 +1,16 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ class InlineEditor < ::Liquid::Tag
+
+ def render(context)
+ ''
+ end
+ end
+
+ ::Liquid::Template.register_tag('inline_editor', InlineEditor)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/link_to.rb b/lib/locomotive/steam/liquid/tags/link_to.rb +56 -0
@@ @@ -0,0 +1,56 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ class LinkTo < Hybrid
+
+ Syntax = /(#{::Liquid::Expression}+)(#{::Liquid::TagAttributes}?)/
+
+ include PathHelper
+
+ def initialize(tag_name, markup, tokens, options)
+ if markup =~ Syntax
+ @handle = $1
+ @_options = {}
+ markup.scan(::Liquid::TagAttributes) do |key, value|
+ @_options[key] = value
+ end
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.link_to"), options[:line])
+ end
+
+ super
+ end
+
+ def render(context)
+ render_path(context) do |page, path|
+ label = label_from_page(page)
+
+ if @render_as_block
+ context.scopes.last['target'] = page
+ label = super.html_safe
+ end
+
+ %{<a href="#{path}">#{label}</a>}
+ end
+ end
+
+ protected
+
+ def label_from_page(page)
+ ::Locomotive::Mounter.with_locale(@_options['locale']) do
+ if page.templatized?
+ page.content_entry._label
+ else
+ page.title
+ end
+ end
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('link_to', LinkTo)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/locale_switcher.rb b/lib/locomotive/steam/liquid/tags/locale_switcher.rb +106 -0
@@ @@ -0,0 +1,106 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ # Display the links to change the locale of the current page
+ #
+ # Usage:
+ #
+ # {% locale_switcher %} => <div id="locale-switcher"><a href="/features" class="current en">Features</a><a href="/fr/fonctionnalites" class="fr">Fonctionnalités</a></div>
+ #
+ # {% locale_switcher label: locale, sep: ' - ' }
+ #
+ # options:
+ # - label: iso (de, fr, en, ...etc), locale (Deutsch, Français, English, ...etc), title (page title)
+ # - sep: piece of html code separating 2 locales
+ #
+ # notes:
+ # - "iso" is the default choice for label
+ # - " | " is the default separating code
+ #
+ class LocaleSwitcher < ::Liquid::Tag
+
+ Syntax = /(#{::Liquid::Expression}+)?/
+
+ def initialize(tag_name, markup, tokens, options)
+ @_options = { label: 'iso', sep: ' | ' }
+
+ if markup =~ Syntax
+ markup.scan(::Liquid::TagAttributes) { |key, value| @_options[key.to_sym] = value.gsub(/"|'/, '') }
+
+ @_options[:exclude] = Regexp.new(@_options[:exclude]) if @_options[:exclude]
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.locale_switcher"), options[:line])
+ end
+
+ super
+ end
+
+ def render(context)
+ @site, @page = context.registers[:site], context.registers[:page]
+ @default_locale = context.registers[:mounting_point].default_locale
+
+ output = %(<div id="locale-switcher">)
+
+ output += @site.locales.collect do |locale|
+ Locomotive::Mounter.with_locale(locale) do
+ fullpath = localized_fullpath(locale)
+
+ if @page.templatized?
+ permalink = context['entry']._permalink
+
+ if permalink
+ fullpath.gsub!('*', permalink)
+ else
+ fullpath = '404'
+ end
+ end
+
+ css = link_class(locale, context['locale'])
+
+ %(<a href="/#{fullpath}" class="#{css}">#{link_label(locale)}</a>)
+ end
+ end.join(@_options[:sep])
+
+ output += %(</div>)
+ end
+
+ private
+
+ def link_class(locale, current_locale)
+ css = [locale]
+ css << 'current' if locale.to_s == current_locale.to_s
+ css.join(' ')
+ end
+
+ def link_label(locale)
+ case @_options[:label]
+ when 'iso' then locale
+ when 'locale' then I18n.t("locales.#{locale}")
+ when 'title' then @page.title # FIXME: this returns nil if the page has not been translated in the locale
+ else
+ locale
+ end
+ end
+
+ def localized_fullpath(locale)
+ return nil if @page.fullpath_translations.blank?
+
+ fullpath = @page.safe_fullpath || @page.fullpath_or_default
+
+ if locale.to_s == @default_locale.to_s # no need to specify the locale
+ @page.index? ? '' : fullpath
+ elsif @page.index? # avoid /en/index or /fr/index, prefer /en or /fr instead
+ locale
+ else
+ File.join(locale, fullpath)
+ end
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('locale_switcher', LocaleSwitcher)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/nav.rb b/lib/locomotive/steam/liquid/tags/nav.rb +287 -0
@@ @@ -0,0 +1,287 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ # Display the children pages of the site, current page or the parent page. If not precised, nav is applied on the current page.
+ # The html output is based on the ul/li tags.
+ #
+ # Usage:
+ #
+ # {% nav site %} => <ul class="nav"><li class="on"><a href="/features">Features</a></li></ul>
+ #
+ # {% nav site, no_wrapper: true, exclude: 'contact|about', id: 'main-nav', class: 'nav', active_class: 'on' }
+ #
+ class Nav < ::Liquid::Tag
+
+ Syntax = /(#{::Liquid::Expression}+)?/
+
+ attr_accessor :current_page, :mounting_point
+
+ def initialize(tag_name, markup, tokens, options)
+ if markup =~ Syntax
+ @source = ($1 || 'page').gsub(/"|'/, '')
+
+ self.set_options(markup, options)
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.nav"), options[:line])
+ end
+
+ super
+ end
+
+ def render(context)
+ self.set_accessors_from_context(context)
+
+ entries = self.fetch_entries
+ output = self.build_entries_output(entries)
+
+ if self.no_wrapper?
+ output
+ else
+ self.render_tag(:nav, id: @_options[:id], css: @_options[:class]) do
+ self.render_tag(:ul) { output }
+ end
+ end
+ end
+
+ protected
+
+ # Build recursively the links of all the pages.
+ #
+ # @param [ Array ] entries List of pages
+ #
+ # @return [ String ] The final HTML output
+ #
+ def build_entries_output(entries, depth = 1)
+ output = []
+
+ entries.each_with_index do |page, index|
+ css = []
+ css << 'first' if index == 0
+ css << 'last' if index == entries.size - 1
+
+ output << self.render_entry_link(page, css.join(' '), depth)
+ end
+
+ output.join("\n")
+ end
+
+ # Get all the children of a source: site (index page), parent or page.
+ #
+ # @return [ Array ] List of pages
+ #
+ def fetch_entries
+ children = (case @source
+ when 'site' then self.mounting_point.pages['index']
+ when 'parent' then self.current_page.parent || self.current_page
+ when 'page' then self.current_page
+ else
+ self.mounting_point.pages[@source]
+ end).children.try(:clone) || []
+
+ children.delete_if { |p| !include_page?(p) }
+ end
+
+ # Determine whether or not a page should be a part of the menu.
+ #
+ # @param [ Object ] page The page
+ #
+ # @return [ Boolean ] True if the page can be included or not
+ #
+ def include_page?(page)
+ if !page.listed? || page.templatized? || !page.published?
+ false
+ elsif @_options[:exclude]
+ (page.fullpath =~ @_options[:exclude]).nil?
+ else
+ true
+ end
+ end
+
+ # Determine wether or not a page is currently the displayed one.
+ #
+ # @param [ Object ] page The page
+ #
+ # @return [ Boolean ]
+ #
+ def page_selected?(page)
+ self.current_page.fullpath =~ /^#{page.fullpath}(\/.*)?$/
+ end
+
+ # Determine if the children of a page have to be rendered or not.
+ # It depends on the depth passed in the option.
+ #
+ # @param [ Object ] page The page
+ # @param [ Integer ] depth The current depth
+ #
+ # @return [ Boolean ] True if the children have to be rendered.
+ #
+ def render_children_for_page?(page, depth)
+ depth.succ <= @_options[:depth].to_i &&
+ (page.children || []).select { |child| self.include_page?(child) }.any?
+ end
+
+ # Return the label of an entry. It may use or not the template
+ # given by the snippet option.
+ #
+ # @param [ Object ] page The page
+ #
+ # @return [ String ] The label in HTML
+ #
+ def entry_label(page)
+ icon = @_options[:icon] ? '<span></span>' : ''
+ title = @_options[:liquid_render] ? @_options[:liquid_render].render('page' => page) : page.title
+
+ if icon.blank?
+ title
+ elsif @_options[:icon] == 'after'
+ "#{title} #{icon}"
+ else
+ "#{icon} #{title}"
+ end
+ end
+
+ # Return the localized url of an entry (page).
+ #
+ # @param [ Object ] page The page
+ #
+ # @return [ String ] The localized url
+ #
+ def entry_url(page)
+ if ::I18n.locale.to_s == self.mounting_point.default_locale.to_s
+ "/#{page.fullpath}"
+ else
+ "/#{::I18n.locale}/#{page.fullpath}"
+ end
+ end
+
+ # Return the css of an entry (page).
+ #
+ # @param [ Object ] page The page
+ # @param [ String ] css The extra css
+ #
+ # @return [ String ] The css
+ #
+ def entry_css(page, css = '')
+ _css = 'link'
+ _css += " #{page} #{@_options[:active_class]}" if self.page_selected?(page)
+
+ (_css + " #{css}").strip
+ end
+
+ # Return the HTML output of a page and its children if requested.
+ #
+ # @param [ Object ] page The page
+ # @param [ String ] css The current css to apply to the entry
+ # @param [ Integer] depth Used to know if the children has to be added or not.
+ #
+ # @return [ String ] The HTML output
+ #
+ def render_entry_link(page, css, depth)
+ url = self.entry_url(page)
+ label = self.entry_label(page)
+ css = self.entry_css(page, css)
+ options = ''
+
+ if self.render_children_for_page?(page, depth) && self.bootstrap?
+ url = '#'
+ label += %{ <b class="caret"></b>}
+ css += ' dropdown'
+ options = %{ class="dropdown-toggle" data-toggle="dropdown"}
+ end
+
+ self.render_tag(:li, id: "#{page.slug.to_s.dasherize}-link", css: css) do
+ children_output = depth.succ <= @_options[:depth].to_i ? self.render_entry_children(page, depth.succ) : ''
+ %{<a href="#{url}"#{options}>#{label}</a>} + children_output
+ end
+ end
+
+ # Recursively create a nested unordered list for the depth specified.
+ #
+ # @param [ Array ] entries The children of the page
+ # @param [ Integer ] depth The current depth
+ #
+ # @return [ String ] The HTML code
+ #
+ def render_entry_children(page, depth)
+ entries = (page.children || []).select { |child| self.include_page?(child) }
+ css = self.bootstrap? ? 'dropdown-menu' : ''
+
+ unless entries.empty?
+ self.render_tag(:ul, id: "#{@_options[:id]}-#{page.slug.to_s.dasherize}", css: css) do
+ self.build_entries_output(entries, depth)
+ end
+ else
+ ''
+ end
+ end
+
+ # Set the value (default or assigned by the tag) of the options.
+ #
+ def set_options(markup, options)
+ @_options = { id: 'nav', class: '', active_class: 'on', bootstrap: false, no_wrapper: false }
+
+ markup.scan(::Liquid::TagAttributes) { |key, value| @_options[key.to_sym] = value.gsub(/"|'/, '') }
+
+ @_options[:exclude] = Regexp.new(@_options[:exclude]) if @_options[:exclude]
+
+ if @_options[:snippet]
+ if template = self.parse_snippet_template(options, @_options[:snippet])
+ @_options[:liquid_render] = template
+ end
+ end
+ end
+
+ # Avoid to call context.registers to get the current page
+ # and the mounting point.
+ #
+ def set_accessors_from_context(context)
+ self.current_page = context.registers[:page]
+ self.mounting_point = context.registers[:mounting_point]
+ end
+
+ # Parse the template of the snippet give in option of the tag.
+ # If the template_name contains a liquid tag or drop, it will
+ # be used an inline template.
+ #
+ def parse_snippet_template(context, template_name)
+ source = if template_name.include?('{')
+ template_name
+ else
+ context[:mounting_point].snippets[template_name].try(:source)
+ end
+
+ source ? ::Liquid::Template.parse(source) : nil
+ end
+
+ # Steam any kind HTML tags. The content of the tag comes from
+ # the block.
+ #
+ # @param [ String ] tag_name Name of the HTML tag (li, ul, div, ...etc).
+ # @param [ String ] html_options Id, class, ..etc
+ #
+ # @return [ String ] The HTML
+ #
+ def render_tag(tag_name, html_options = {}, &block)
+ options = ['']
+ options << %{id="#{html_options[:id]}"} if html_options[:id].present?
+ options << %{class="#{html_options[:css]}"} if html_options[:css].present?
+
+ %{<#{tag_name}#{options.join(' ')}>#{yield}</#{tag_name}>}
+ end
+
+ def bootstrap?
+ @_options[:bootstrap].to_bool
+ end
+
+ def no_wrapper?
+ @_options[:no_wrapper].to_bool
+ end
+
+ ::Liquid::Template.register_tag('nav', Nav)
+ end
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/paginate.rb b/lib/locomotive/steam/liquid/tags/paginate.rb +105 -0
@@ @@ -0,0 +1,105 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ # Paginate a collection
+ #
+ # Usage:
+ #
+ # {% paginate contents.projects by 5 %}
+ # {% for project in paginate.collection %}
+ # {{ project.name }}
+ # {% endfor %}
+ # {% endpaginate %}
+ #
+
+ class Paginate < ::Liquid::Block
+
+ Syntax = /(#{::Liquid::Expression}+)\s+by\s+([0-9]+)/
+
+ def initialize(tag_name, markup, tokens, options)
+ if markup =~ Syntax
+ @collection_name = $1
+ @per_page = $2.to_i
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.paginate"), options[:line])
+ end
+
+ super
+ end
+
+ def render(context)
+ context.stack do
+ collection = context[@collection_name]
+
+ raise ::Liquid::ArgumentError.new("Cannot paginate array '#{@collection_name}'. Not found.") if collection.nil?
+
+ pagination = collection.send(:paginate, {
+ :page => context['current_page'],
+ :per_page => @per_page }).stringify_keys!
+
+ page_count, current_page = pagination['total_pages'], pagination['current_page']
+
+ path = sanitize_path(context['fullpath'])
+
+ pagination['previous'] = link(I18n.t('pagination.previous'), current_page - 1, path) if pagination['previous_page']
+ pagination['next'] = link(I18n.t('pagination.next'), current_page + 1, path) if pagination['next_page']
+ pagination['parts'] = []
+
+ hellip_break = false
+
+ if page_count > 1
+ 1.upto(page_count) do |page|
+ if current_page == page
+ pagination['parts'] << no_link(page)
+ elsif page == 1
+ pagination['parts'] << link(page, page, path)
+ elsif page == page_count - 1
+ pagination['parts'] << link(page, page, path)
+ elsif page <= current_page - window_size or page >= current_page + window_size
+ next if hellip_break
+ pagination['parts'] << no_link('&hellip;')
+ hellip_break = true
+ next
+ else
+ pagination['parts'] << link(page, page, path)
+ end
+
+ hellip_break = false
+ end
+ end
+
+ context['paginate'] = pagination
+
+ render_all(@nodelist, context)
+ end
+ end
+
+ private
+
+ def sanitize_path(path)
+ _path = path.gsub(/page=[0-9]+&?/, '').gsub(/_pjax=true&?/, '')
+ _path = _path.slice(0..-2) if _path.last == '?' || _path.last == '&'
+ _path
+ end
+
+ def window_size
+ 3
+ end
+
+ def no_link(title)
+ { 'title' => title, 'is_link' => false, 'hellip_break' => title == '&hellip;' }
+ end
+
+ def link(title, page, path)
+ _path = %(#{path}#{path.include?('?') ? '&' : '?'}page=#{page})
+ { 'title' => title, 'url' => _path, 'is_link' => true }
+ end
+ end
+
+ ::Liquid::Template.register_tag('paginate', Paginate)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/path_helper.rb b/lib/locomotive/steam/liquid/tags/path_helper.rb +98 -0
@@ @@ -0,0 +1,98 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ module PathHelper
+
+ def render_path(context, &block)
+ site = context.registers[:site]
+
+ if page = self.retrieve_page_from_handle(context)
+ path = self.public_page_fullpath(context, page)
+
+ if block_given?
+ block.call page, path
+ else
+ path
+ end
+ else
+ raise Liquid::PageNotTranslated.new(%{[link_to] Unable to find a page for the #{@handle}. Wrong handle or missing template for your content.})
+ end
+ end
+
+ protected
+
+ def retrieve_page_from_handle(context)
+ mounting_point = context.registers[:mounting_point]
+
+ context.scopes.reverse_each do |scope|
+ handle = scope[@handle] || @handle
+
+ page = case handle
+ when Locomotive::Mounter::Models::Page then handle
+ when Liquid::Drops::Page then handle.instance_variable_get(:@_source)
+ when String then fetch_page(mounting_point, handle)
+ when Liquid::Drops::ContentEntry then fetch_page(mounting_point, handle.instance_variable_get(:@_source), true)
+ when Locomotive::Mounter::Models::ContentEntry then fetch_page(mounting_point, handle, true)
+ else
+ nil
+ end
+
+ return page unless page.nil?
+ end
+
+ nil
+ end
+
+ def fetch_page(mounting_point, handle, templatized = false)
+ ::Locomotive::Mounter.with_locale(@_options['locale']) do
+ if templatized
+ page = mounting_point.pages.values.find do |_page|
+ _page.templatized? &&
+ !_page.templatized_from_parent &&
+ _page.content_type.slug == handle.content_type.slug &&
+ (@_options['with'].nil? || _page.handle == @_options['with'])
+ end
+
+ page.content_entry = handle if page
+
+ page
+ else
+ mounting_point.pages.values.find { |_page| _page.handle == handle }
+ end
+ end
+ end
+
+ def public_page_fullpath(context, page)
+ mounting_point = context.registers[:mounting_point]
+ locale = @_options['locale'] || ::I18n.locale
+
+ if !page.translated_in?(locale)
+ title = page.title_translations.values.compact.first
+ raise Liquid::PageNotTranslated.new(%{the "#{title}" page is not translated in #{locale.upcase}})
+ end
+
+ fullpath = ::Locomotive::Mounter.with_locale(locale) do
+ page.fullpath.clone
+ end
+
+ fullpath = "#{locale}/#{fullpath}" if locale.to_s != mounting_point.default_locale.to_s
+
+ if page.templatized?
+ if page.content_entry._slug.nil?
+ title = %{#{page.content_entry.content_type.name.singularize} "#{page.content_entry.send(page.content_entry.content_type.label_field_name)}"}
+ raise Liquid::ContentEntryNotTranslated.new(%{the #{title} slug is not translated in #{locale.upcase}})
+ end
+ fullpath.gsub!(/(content[_-]type[_-]template|template)/, page.content_entry._slug)
+ end
+
+ File.join('/', fullpath)
+ end
+
+ end
+ end
+
+ end
+ end
+ end
locomotive/steam/liquid/tags/path_to.rb b/lib/locomotive/steam/liquid/tags/path_to.rb +36 -0
@@ @@ -0,0 +1,36 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ class PathTo < ::Liquid::Tag
+
+ include PathHelper
+
+ Syntax = /(#{::Liquid::Expression}+)(#{::Liquid::TagAttributes}?)/
+
+ def initialize(tag_name, markup, tokens, context)
+ if markup =~ Syntax
+ @handle = $1
+ @_options = {}
+ markup.scan(::Liquid::TagAttributes) do |key, value|
+ @_options[key] = value
+ end
+ else
+ raise SyntaxError.new("Syntax Error in 'path_to' - Valid syntax: path_to <page|page_handle|content_entry>(, locale: [fr|de|...], with: <page_handle>")
+ end
+
+ super
+ end
+
+ def render(context)
+ render_path(context)
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('path_to', PathTo)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/seo.rb b/lib/locomotive/steam/liquid/tags/seo.rb +74 -0
@@ @@ -0,0 +1,74 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ module SEO
+
+ class Base < ::Liquid::Tag
+
+ def render(context)
+ %{
+ #{self.render_title(context)}
+ #{self.render_metadata(context)}
+ }
+ end
+
+ protected
+
+ def render_title(context)
+ title = self.value_for(:seo_title, context)
+ title = context.registers[:site].name if title.blank?
+
+ %{
+ <title>#{title}</title>
+ }
+ end
+
+ def render_metadata(context)
+ %{
+ <meta name="description" content="#{self.value_for(:meta_description, context)}" />
+ <meta name="keywords" content="#{self.value_for(:meta_keywords, context)}" />
+ }
+ end
+
+ # Removes whitespace and quote characters from the input
+ def sanitized_string(string)
+ string ? string.strip.gsub(/"/, '') : ''
+ end
+
+ def value_for(attribute, context)
+ object = self.metadata_object(context)
+ value = object.try(attribute.to_sym).blank? ? context.registers[:site].send(attribute.to_sym) : object.send(attribute.to_sym)
+ self.sanitized_string(value)
+ end
+
+ def metadata_object(context)
+ context['content_instance'] || context['page']
+ end
+ end
+
+ class Title < Base
+
+ def render(context)
+ self.render_title(context)
+ end
+
+ end
+
+ class Metadata < Base
+
+ def render(context)
+ self.render_metadata(context)
+ end
+
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('seo', SEO::Base)
+ ::Liquid::Template.register_tag('seo_title', SEO::Title)
+ ::Liquid::Template.register_tag('seo_metadata', SEO::Metadata)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/session_assign.rb b/lib/locomotive/steam/liquid/tags/session_assign.rb +41 -0
@@ @@ -0,0 +1,41 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ # Assign sets a variable in your session.
+ #
+ # {% session_assign foo = 'monkey' %}
+ #
+ # You can then use the variable later in the page.
+ #
+ # {{ session.foo }}
+ #
+ class SessionAssign < ::Liquid::Tag
+ Syntax = /(#{::Liquid::VariableSignature}+)\s*=\s*(#{::Liquid::QuotedFragment}+)/
+
+ def initialize(tag_name, markup, tokens, options)
+ if markup =~ Syntax
+ @to = $1
+ @from = $2
+ else
+ raise ::Liquid::SyntaxError.new(options[:locale].t("errors.syntax.session_assign"), options[:line])
+ end
+
+ super
+ end
+
+ def render(context)
+ request = context.registers[:request]
+
+ request.session[@to.to_sym] = context[@from]
+ ''
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('session_assign', SessionAssign)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/snippet.rb b/lib/locomotive/steam/liquid/tags/snippet.rb +63 -0
@@ @@ -0,0 +1,63 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ class Snippet < ::Liquid::Include
+
+ def render(context)
+ name = @template_name.gsub(/[\"\']/, '')
+ snippet = context.registers[:mounting_point].snippets[name]
+
+ raise ::Liquid::StandardError.new("Unknown snippet \"#{name}\"") if snippet.nil?
+
+ partial = self.parse_template(snippet)
+
+ variable = context[@variable_name || @template_name[1..-2]]
+
+ context.stack do
+ @attributes.each do |key, value|
+ context[key] = context[value]
+ end
+
+ output = (if variable.is_a?(Array)
+ variable.collect do |variable|
+ context[@template_name[1..-2]] = variable
+ partial.render(context)
+ end
+ else
+ context[@template_name[1..-2]] = variable
+ partial.render(context)
+ end)
+
+ Locomotive::Steam::Logger.info " Steamed snippet #{name}"
+
+ output
+ end
+ end
+
+ protected
+
+ def parse_template(snippet)
+ begin
+ ::Liquid::Template.parse(snippet.source)
+ rescue ::Liquid::Error => e
+ # do it again on the raw source instead so that the error line matches
+ # the source file.
+ begin
+ ::Liquid::Template.parse(snippet.template.raw_source)
+ rescue ::Liquid::Error => e
+ e.backtrace.unshift "#{snippet.template.filepath}:#{e.line + 1}:in `#{snippet.name}'"
+ e.line = self.line - 1
+ raise e
+ end
+ end
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('include', Snippet)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/liquid/tags/with_scope.rb b/lib/locomotive/steam/liquid/tags/with_scope.rb +44 -0
@@ @@ -0,0 +1,44 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ class WithScope < ::Liquid::Block
+
+ SlashedString = /\/[^\/]*\//
+ TagAttributes = /(\w+|\w+\.\w+)\s*\:\s*(#{SlashedString}|#{::Liquid::QuotedFragment})/
+
+ def initialize(tag_name, markup, tokens, options)
+ @tag_options = HashWithIndifferentAccess.new
+ markup.scan(TagAttributes) do |key, value|
+ @tag_options[key] = value
+ end
+ super
+ end
+
+ def render(context)
+ context.stack do
+ context['with_scope'] = decode(@tag_options, context)
+ render_all(@nodelist, context)
+ end
+ end
+
+ private
+
+ def decode(attributes, context)
+ attributes.each_pair do |key, value|
+ attributes[key] = (case value
+ when /^true|false$/i then value == 'true'
+ when /^\/[^\/]*\/$/ then Regexp.new(value[1..-2])
+ when /^["|'](.+)["|']$/ then $1.gsub(/^["|']/, '').gsub(/["|']$/, '')
+ else
+ context[value] || value
+ end)
+ end
+ end
+ end
+
+ ::Liquid::Template.register_tag('with_scope', WithScope)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/listen.rb b/lib/locomotive/steam/listen.rb +64 -0
@@ @@ -0,0 +1,64 @@
+ require 'listen'
+
+ module Locomotive::Steam
+ class Listen
+
+ attr_accessor :reader
+
+ def self.instance
+ @@instance = new
+ end
+
+ def start(reader)
+ # if $parent_pid && $parent_pid == Process.pid
+ # puts "bypassing Listen in the parent process"
+ # return false
+ # end
+
+ puts "Listening here: #{Process.pid}"
+
+ self.reader = reader
+
+ self.definitions.each do |definition|
+ self.apply(definition)
+ end
+ end
+
+ def definitions
+ [
+ ['config', /\.yml/, [:site, :content_types, :pages, :snippets, :content_entries, :translations]],
+ ['app/views', /\.liquid/, [:pages, :snippets]],
+ ['app/content_types', /\.yml/, [:content_types, :content_entries]],
+ ['data', /\.yml/, :content_entries]
+ ]
+ end
+
+ protected
+
+ def apply(definition)
+ reloader = Proc.new do |modified, added, removed|
+ resources = [*definition.last]
+ names = resources.map { |n| "\"#{n}\"" }.join(', ')
+
+ Locomotive::Steam::Logger.info "* Reloaded #{names} at #{Time.now}"
+
+ begin
+ reader.reload(resources)
+ rescue Exception => e
+ Locomotive::Steam::MounterException.new('Unable to reload', e)
+ end
+ end
+
+ filter = definition[1]
+ path = File.join(self.reader.mounting_point.path, definition.first)
+ path = File.expand_path(path)
+
+ listener = ::Listen.to(path, only: filter, &reloader)
+
+ # non blocking listener
+ listener.start #(false)
+ end
+
+ end
+
+ end
\ No newline at end of file
locomotive/steam/logger.rb b/lib/locomotive/steam/logger.rb +54 -0
@@ @@ -0,0 +1,54 @@
+ module Locomotive
+ module Steam
+
+ class Logger
+
+ attr_accessor :logger, :logfile_path, :stdout
+
+ def initialize
+ self.logger = nil
+ end
+
+ # Setup the single instance of the ruby logger.
+ #
+ # @param [ String ] path The path to the log file (default: log/steam.log)
+ # @param [ Boolean ] stdout Instead of having a file, log to the standard output
+ #
+ def setup(path, stdout = false)
+ require 'logger'
+
+ self.stdout = stdout
+
+ self.logfile_path = File.expand_path(File.join(path, 'log', 'steam.log'))
+ FileUtils.mkdir_p(File.dirname(logfile_path))
+
+ out = self.stdout ? STDOUT : self.logfile_path
+
+ self.logger = ::Logger.new(out).tap do |log|
+ log.level = ::Logger::DEBUG
+ log.formatter = proc do |severity, datetime, progname, msg|
+ "#{msg}\n"
+ end
+ end
+ end
+
+ def self.instance
+ @@instance ||= self.new
+ end
+
+ def self.setup(path, stdout = false)
+ self.instance.setup(path, stdout)
+ end
+
+ class << self
+ %w(debug info warn error fatal unknown).each do |name|
+ define_method(name) do |message|
+ self.instance.logger.send(name.to_sym, message)
+ end
+ end
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/misc.rb b/lib/locomotive/steam/misc.rb +9 -0
@@ @@ -0,0 +1,9 @@
+ require 'locomotive/steam/misc/core_ext.rb'
+ require 'locomotive/steam/misc/will_paginate.rb'
+ require 'locomotive/steam/misc/httparty.rb'
+ require 'locomotive/steam/misc/dragonfly.rb'
+ require 'locomotive/steam/misc/i18n.rb'
+ require 'locomotive/steam/misc/mounter.rb'
+ require 'locomotive/steam/misc/markdown.rb'
+ require 'locomotive/steam/misc/haml.rb'
+ require 'locomotive/steam/misc/better_errors.rb'
\ No newline at end of file
locomotive/steam/misc/better_errors.rb b/lib/locomotive/steam/misc/better_errors.rb +70 -0
@@ @@ -0,0 +1,70 @@
+ require 'ostruct'
+
+ module BetterErrors
+ class MiddlewareWrapper
+
+ def initialize(app)
+ @@middleware ||= BetterErrors::Middleware.new(app)
+ @@middleware.instance_variable_set(:@app, app)
+ end
+
+ def call(env)
+ env['action_dispatch.request.parameters'] = Rack::Request.new(env).params
+
+ @@middleware.call(env)
+ end
+
+ end
+
+ module FrameWithLiquidContext
+
+ extend ActiveSupport::Concern
+
+ included do
+
+ attr_accessor :liquid_context
+
+ alias_method_chain :local_variables, :liquid_context
+
+ class << self
+
+ alias_method_chain :from_exception, :liquid_context
+
+ end
+ end
+
+ def local_variables_with_liquid_context
+ if self.liquid_context
+ scope = self.liquid_context.scopes.last.clone
+
+ scope.delete_if { |k, _| %w(models contents params session).include?(k) }.tap do |_scope|
+ _scope['site'] = _scope['site'].send(:_source).to_hash
+ _scope['page'] = _scope['page'].to_hash.delete_if { |k, _| %w(template).include?(k) }
+ end
+ else
+ self.local_variables_without_liquid_context
+ end
+ rescue Exception => e
+ puts "[BetterError] Fatal error: #{e.message}".red
+ puts e.backtrace.join("\n")
+ {}
+ end
+
+ module ClassMethods
+
+ def from_exception_with_liquid_context(exception)
+ from_exception_without_liquid_context(exception).tap do |list|
+ if exception.respond_to?(:liquid_context)
+ list.first.liquid_context = exception.liquid_context
+ end
+ end
+ end
+
+ end
+ end
+
+ class StackFrame
+ include FrameWithLiquidContext
+ end
+
+ end
\ No newline at end of file
locomotive/steam/misc/core_ext.rb b/lib/locomotive/steam/misc/core_ext.rb +54 -0
@@ @@ -0,0 +1,54 @@
+ unless Hash.instance_methods.include?(:underscore_keys)
+ class Hash
+
+ def underscore_keys
+ new_hash = {}
+
+ self.each_pair do |key, value|
+ if value.respond_to?(:collect!) # Array
+ value.collect do |item|
+ if item.respond_to?(:each_pair) # Hash item within
+ item.underscore_keys
+ else
+ item
+ end
+ end
+ elsif value.respond_to?(:each_pair) # Hash
+ value = value.underscore_keys
+ end
+
+ new_key = key.is_a?(String) ? key.underscore : key # only String keys
+
+ new_hash[new_key] = value
+ end
+
+ self.replace(new_hash)
+ end
+
+ end
+ end
+
+ unless String.instance_methods.include?(:to_bool)
+ class String
+ def to_bool
+ return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
+ return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
+ raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
+ end
+ end
+
+ class TrueClass
+ def to_bool; self; end
+ end
+
+ class FalseClass
+ def to_bool; self; end
+ end
+ end
+
+ unless Array.instance_methods.include?(:contains?)
+ class Array
+ def contains?(other); (self & other) == other; end
+ end
+ end
+
locomotive/steam/misc/dragonfly.rb b/lib/locomotive/steam/misc/dragonfly.rb +79 -0
@@ @@ -0,0 +1,79 @@
+ module Locomotive
+ module Steam
+ class Dragonfly
+
+ attr_accessor :path, :enabled
+
+ def enabled?
+ !!self.enabled
+ end
+
+ def resize_url(source, resize_string)
+ _source = (case source
+ when String then source
+ when Hash then source['url'] || source[:url]
+ else
+ source.try(:url)
+ end)
+
+ if _source.blank?
+ Locomotive::Steam::Logger.error "Unable to resize on the fly: #{source.inspect}"
+ return
+ end
+
+ return _source unless self.enabled?
+
+ if _source =~ /^http/
+ file = self.class.app.fetch_url(_source)
+ else
+ file = self.class.app.fetch_file(File.join(self.path, 'public', _source))
+ end
+
+ file.process(:thumb, resize_string).url
+ end
+
+ def self.app
+ ::Dragonfly[:images]
+ end
+
+
+ def self.instance
+ @@instance ||= new
+ end
+
+ def self.setup!(path)
+ self.instance.path = path
+ self.instance.enabled = false
+
+ begin
+ require 'rack/cache'
+ require 'dragonfly'
+
+ ## initialize Dragonfly ##
+ app = ::Dragonfly[:images].configure_with(:imagemagick)
+
+ ## configure it ##
+ ::Dragonfly[:images].configure do |c|
+ convert = `which convert`.strip.presence || '/usr/bin/env convert'
+ c.convert_command = convert
+ c.identify_command = convert
+
+ c.allow_fetch_url = true
+ c.allow_fetch_file = true
+
+ c.url_format = '/images/dynamic/:job/:basename.:format'
+ end
+
+ self.instance.enabled = true
+ rescue Exception => e
+
+ Locomotive::Steam::Logger.warn %{
+ [Dragonfly] !disabled!
+ [Dragonfly] If you want to take full benefits of all the features in the LocomotiveSteam, we recommend you to install ImageMagick and RMagick. Check out the documentation here: http://doc.locomotivecms.com/editor/installation.
+ }
+ end
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/misc/haml.rb b/lib/locomotive/steam/misc/haml.rb +15 -0
@@ @@ -0,0 +1,15 @@
+ module Haml::Filters
+
+ remove_filter("Markdown") #remove the existing Markdown filter
+
+ module Markdown # the contents of this are as before, but without the lazy_require call
+
+ include Haml::Filters::Base
+
+ def render text
+ Locomotive::Steam::Markdown.steam text
+ end
+
+ end
+
+ end
\ No newline at end of file
locomotive/steam/misc/httparty.rb b/lib/locomotive/steam/misc/httparty.rb +46 -0
@@ @@ -0,0 +1,46 @@
+ require 'uri'
+
+ module Locomotive
+ module Steam
+ module Httparty
+ class Webservice
+
+ include ::HTTParty
+
+ def self.consume(url, options = {})
+ url = ::HTTParty.normalize_base_uri(url)
+
+ uri = URI.parse(url)
+ options[:base_uri] = "#{uri.scheme}://#{uri.host}"
+ options[:base_uri] += ":#{uri.port}" if uri.port != 80
+ path = uri.request_uri
+
+ options.delete(:format) if options[:format] == 'default'
+
+ username, password = options.delete(:username), options.delete(:password)
+ options[:basic_auth] = { username: username, password: password } if username
+
+ path ||= '/'
+
+ # Locomotive::Steam::Logger.debug "[WebService] consuming #{path}, #{options.inspect}"
+
+ response = self.get(path, options)
+
+ if response.code == 200
+ _response = response.parsed_response
+ if _response.respond_to?(:underscore_keys)
+ _response.underscore_keys
+ else
+ _response.collect(&:underscore_keys)
+ end
+ else
+ Locomotive::Steam::Logger.error "[WebService] consumed #{path}, #{options.inspect}, response = #{response.inspect}"
+ nil
+ end
+
+ end
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/misc/i18n.rb b/lib/locomotive/steam/misc/i18n.rb +2 -0
@@ @@ -0,0 +1,2 @@
+ I18n.load_path = Dir[File.join(File.dirname(__FILE__), "/../../../../locales/*.yml")]
+ I18n.backend.reload!
\ No newline at end of file
locomotive/steam/misc/markdown.rb b/lib/locomotive/steam/misc/markdown.rb +27 -0
@@ @@ -0,0 +1,27 @@
+ require 'redcarpet'
+
+ module Locomotive
+ module Steam
+ module Markdown
+
+ def self.render(text)
+ self.parser.render(text)
+ end
+
+ def self.parser
+ @@markdown ||= Redcarpet::Markdown.new Redcarpet::Steam::HTML, {
+ autolink: true,
+ fenced_code: true,
+ generate_toc: true,
+ gh_blockcode: true,
+ hard_wrap: true,
+ no_intraemphasis: true,
+ strikethrough: true,
+ tables: true,
+ xhtml: true
+ }
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/misc/mounter.rb b/lib/locomotive/steam/misc/mounter.rb +32 -0
@@ @@ -0,0 +1,32 @@
+ module Locomotive
+ module Mounter
+ module Models
+ class Page
+
+ def render(context)
+ self.parse(context).render(context)
+ end
+
+ protected
+
+ def parse(context)
+ options = {
+ page: self,
+ mounting_point: context.registers[:mounting_point],
+ error_mode: :strict,
+ count_lines: true
+ }
+
+ begin
+ template = ::Liquid::Template.parse(self.source, options)
+ rescue Liquid::SyntaxError => e
+ # do it again on the raw source instead so that the error line matches
+ # the source file.
+ ::Liquid::Template.parse(self.template.raw_source, options)
+ end
+ end
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/misc/thor.rb b/lib/locomotive/steam/misc/thor.rb +15 -0
@@ @@ -0,0 +1,15 @@
+ require 'thor/shell/color'
+
+ class Thor
+ module Shell
+ class ForceColor < ::Thor::Shell::Color
+
+ protected
+
+ def can_display_colors?
+ true
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/misc/will_paginate.rb b/lib/locomotive/steam/misc/will_paginate.rb +16 -0
@@ @@ -0,0 +1,16 @@
+ require 'will_paginate'
+ require 'will_paginate/collection'
+
+ Array.class_eval do
+ def paginate(options = {})
+ raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options
+
+ WillPaginate::Collection.create(
+ options[:page] || 1,
+ options[:per_page] || 30,
+ options[:total_entries] || self.length
+ ) { |pager|
+ pager.replace self[pager.offset, pager.per_page].to_a
+ }
+ end
+ end
\ No newline at end of file
locomotive/steam/server.rb b/lib/locomotive/steam/server.rb +80 -0
@@ @@ -0,0 +1,80 @@
+ require 'better_errors'
+ require 'coffee_script'
+
+ require 'locomotive/steam/listen'
+ require 'locomotive/steam/server/middleware'
+ require 'locomotive/steam/server/favicon'
+ require 'locomotive/steam/server/dynamic_assets'
+ require 'locomotive/steam/server/logging'
+ require 'locomotive/steam/server/entry_submission'
+ require 'locomotive/steam/server/path'
+ require 'locomotive/steam/server/locale'
+ require 'locomotive/steam/server/page'
+ require 'locomotive/steam/server/timezone'
+ require 'locomotive/steam/server/templatized_page'
+ require 'locomotive/steam/server/renderer'
+
+ require 'locomotive/steam/liquid'
+ require 'locomotive/steam/misc'
+
+ module Locomotive::Steam
+ class Server
+
+ def initialize(reader, options = {})
+ Locomotive::Steam::Dragonfly.setup!(reader.mounting_point.path)
+
+ Sprockets::Sass.add_sass_functions = false
+
+ @reader = reader
+ @app = self.create_rack_app(@reader)
+
+ BetterErrors.application_root = reader.mounting_point.path
+ end
+
+ def call(env)
+ env['steam.mounting_point'] = @reader.mounting_point
+ @app.call(env)
+ end
+
+ protected
+
+ def create_rack_app(reader)
+ Rack::Builder.new do
+ use Rack::Lint
+
+ use BetterErrors::MiddlewareWrapper
+
+ use Rack::Session::Cookie, {
+ key: 'steam.session',
+ path: '/',
+ expire_after: 2592000,
+ secret: 'uselessinlocal'
+ }
+
+ use ::Dragonfly::Middleware, :images
+
+ use Rack::Static, {
+ urls: ['/images', '/fonts', '/samples', '/media'],
+ root: File.join(reader.mounting_point.path, 'public')
+ }
+
+ use Favicon
+ use DynamicAssets, reader.mounting_point.path
+
+ use Logging
+
+ use EntrySubmission
+
+ use Path
+ use Locale
+ use Timezone
+
+ use Page
+ use TemplatizedPage
+
+ run Steamer.new
+ end
+ end
+
+ end
+ end
locomotive/steam/server/dynamic_assets.rb b/lib/locomotive/steam/server/dynamic_assets.rb +33 -0
@@ @@ -0,0 +1,33 @@
+ module Locomotive::Steam
+ class Server
+
+ class DynamicAssets < Middleware
+
+ attr_reader :app, :sprockets, :regexp
+
+ def initialize(app, site_path)
+ super(app)
+
+ @regexp = /^\/(javascripts|stylesheets)\/(.*)$/
+
+ @sprockets = Locomotive::Mounter::Extensions::Sprockets.environment(site_path)
+ end
+
+ def call(env)
+ if env['PATH_INFO'] =~ self.regexp
+ env['PATH_INFO'] = $2
+
+ begin
+ self.sprockets.call(env)
+ rescue Exception => e
+ raise Locomotive::Steam::DefaultException.new "Unable to serve a dynamic asset. Please check the logs.", e
+ end
+ else
+ app.call(env)
+ end
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/server/entry_submission.rb b/lib/locomotive/steam/server/entry_submission.rb +120 -0
@@ @@ -0,0 +1,120 @@
+ module Locomotive::Steam
+ class Server
+
+ # Mimic the submission of a content entry
+ #
+ class EntrySubmission < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ if self.request.post? && env['PATH_INFO'] =~ /^\/entry_submissions\/(.*)/
+ self.process_form($1)
+
+ # puts "html? #{html?} / json? #{json?} / #{self.callback_url} / #{params.inspect}"
+
+ if @entry.valid?
+ if self.html?
+ self.record_submitted_entry
+ self.redirect_to self.callback_url
+ elsif self.json?
+ self.json_response
+ end
+ else
+ if self.html?
+ if self.callback_url =~ /^http:\/\//
+ self.redirect_to self.callback_url
+ else
+ env['PATH_INFO'] = self.callback_url
+ self.liquid_assigns[@content_type.slug.singularize] = @entry
+ app.call(env)
+ end
+ elsif self.json?
+ self.json_response(422)
+ end
+ end
+ else
+ self.fetch_submitted_entry
+
+ app.call(env)
+ end
+ end
+
+ protected
+
+ def record_submitted_entry
+ self.request.session[:now] ||= {}
+ self.request.session[:now][:submitted_entry] = [@content_type.slug, @entry._slug]
+ end
+
+ def fetch_submitted_entry
+ if data = self.request.session[:now].try(:delete, :submitted_entry)
+ content_type = self.mounting_point.content_types[data.first.to_s]
+
+ entry = (content_type.entries || []).detect { |e| e._slug == data.last }
+
+ # do not keep track of the entry
+ content_type.entries.delete(entry) if entry
+
+ # add it to the additional liquid assigns for the next liquid rendering
+ if entry
+ self.liquid_assigns[content_type.slug.singularize] = entry
+ end
+ end
+ end
+
+ # Mimic the creation of a content entry with a minimal validation.
+ #
+ # @param [ String ] permalink The permalink (or slug) of the content type
+ #
+ #
+ def process_form(permalink)
+ permalink = permalink.split('.').first
+
+ @content_type = self.mounting_point.content_types[permalink]
+
+ raise "Unknown content type '#{@content_type.inspect}'" if @content_type.nil?
+
+ attributes = self.params[:entry] || self.params[:content] || {}
+
+ @entry = @content_type.build_entry(attributes)
+
+ # if not valid, we do not need to keep track of the entry
+ @content_type.entries.delete(@entry) if !@entry.valid?
+ end
+
+ def callback_url
+ (@entry.valid? ? params[:success_callback] : params[:error_callback]) || '/'
+ end
+
+ # Build the JSON response
+ #
+ # @param [ Integer ] status The HTTP return code
+ #
+ # @return [ Array ] The rack response depending on the validation status and the requested format
+ #
+ def json_response(status = 200)
+ locale = self.mounting_point.default_locale
+
+ if self.request.path =~ /^\/(#{self.mounting_point.locales.join('|')})+(\/|$)/
+ locale = $1
+ end
+
+ hash = @entry.to_hash(false).tap do |_hash|
+ if !@entry.valid?
+ _hash['errors'] = @entry.errors.inject({}) do |memo, name|
+ memo[name] = ::I18n.t('errors.messages.blank', locale: locale)
+ memo
+ end
+ end
+ end
+
+ [status, { 'Content-Type' => 'application/json' }, [
+ { @content_type.slug.singularize => hash }.to_json
+ ]]
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/server/favicon.rb b/lib/locomotive/steam/server/favicon.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Locomotive::Steam
+ class Server
+
+ class Favicon < Middleware
+
+ def call(env)
+
+ if env['PATH_INFO'] == '/favicon.ico'
+ [200, { 'Content-Type' => 'image/vnd.microsoft.icon' }, ['']]
+ else
+ app.call(env)
+ end
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/server/locale.rb b/lib/locomotive/steam/server/locale.rb +42 -0
@@ @@ -0,0 +1,42 @@
+ module Locomotive::Steam
+ class Server
+
+ # Set the locale from the path if possible or use the default one
+ # Examples:
+ # /fr/index => locale = :fr
+ # /fr/ => locale = :fr
+ # /index => locale = :en (default one)
+ #
+ class Locale < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ self.set_locale!(env)
+
+ app.call(env)
+ end
+
+ protected
+
+ def set_locale!(env)
+ locale = self.mounting_point.default_locale
+
+ if self.path =~ /^(#{self.mounting_point.locales.join('|')})+(\/|$)/
+ locale = $1
+ self.path = self.path.gsub($1 + $2, '')
+ self.path = 'index' if self.path.blank?
+ end
+
+ Locomotive::Mounter.locale = locale
+ ::I18n.locale = locale
+
+ self.log "Detecting locale #{locale.upcase}"
+
+ env['steam.locale'] = locale
+ env['steam.path'] = self.path
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/server/logging.rb b/lib/locomotive/steam/server/logging.rb +32 -0
@@ @@ -0,0 +1,32 @@
+ module Locomotive::Steam
+ class Server
+
+ # Track the request into the current logger
+ #
+ class Logging < Middleware
+
+ def call(env)
+ now = Time.now
+
+ log "Started #{env['REQUEST_METHOD'].upcase} \"#{env['PATH_INFO']}\" at #{now}".light_white
+
+ app.call(env).tap do |response|
+ done_in_ms = ((Time.now - now) * 10000).truncate / 10.0
+ log "Completed #{code_to_human(response.first)} in #{done_in_ms}ms\n\n".green
+ end
+ end
+
+ protected
+
+ def code_to_human(code)
+ case code.to_i
+ when 200 then '200 OK'
+ when 301 then '301 Found'
+ when 302 then '302 Found'
+ when 404 then '404 Not Found'
+ end
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/server/middleware.rb b/lib/locomotive/steam/server/middleware.rb +61 -0
@@ @@ -0,0 +1,61 @@
+ module Locomotive::Steam
+ class Server
+
+ class Middleware
+
+ attr_accessor :app, :request, :path, :liquid_assigns
+
+ attr_accessor :mounting_point, :page, :content_entry
+
+ def initialize(app = nil)
+ @app = app
+ end
+
+ def call(env)
+ app.call(env)
+ end
+
+ protected
+
+ def set_accessors(env)
+ self.path = env['steam.path']
+ self.request = Rack::Request.new(env)
+ self.mounting_point = env['steam.mounting_point']
+ self.page = env['steam.page']
+ self.content_entry = env['steam.content_entry']
+
+ env['steam.liquid_assigns'] ||= {}
+ self.liquid_assigns = env['steam.liquid_assigns']
+ end
+
+ def site
+ self.mounting_point.site
+ end
+
+ def params
+ self.request.params.deep_symbolize_keys
+ end
+
+ def html?
+ ['text/html', 'application/x-www-form-urlencoded'].include?(self.request.media_type) &&
+ !self.request.xhr? &&
+ !self.json?
+ end
+
+ def json?
+ self.request.content_type == 'application/json' || File.extname(self.request.path) == '.json'
+ end
+
+ def redirect_to(location, type = 301)
+ self.log "Redirected to #{location}"
+ [type, { 'Content-Type' => 'text/html', 'Location' => location }, []]
+ end
+
+ def log(msg)
+ Locomotive::Steam::Logger.info msg
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/server/page.rb b/lib/locomotive/steam/server/page.rb +69 -0
@@ @@ -0,0 +1,69 @@
+ module Locomotive::Steam
+ class Server
+
+ # Sanitize the path from the previous middleware in order
+ # to make it work for the renderer.
+ #
+ class Page < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ self.set_page!(env)
+
+ app.call(env)
+ end
+
+ protected
+
+ def set_page!(env)
+ page = self.fetch_page
+
+ if page
+ self.log "Found page \"#{page.title}\" [#{page.safe_fullpath}]"
+ end
+
+ env['steam.page'] = page
+ end
+
+ def fetch_page
+
+
+ matchers = self.path_combinations(self.path)
+
+ pages = self.mounting_point.pages.values.find_all do |_page|
+ matchers.include?(_page.safe_fullpath) ||
+ matchers.include?(_page.safe_fullpath.try(:underscore))
+ end.sort_by { |p| p.position || Float::INFINITY }
+
+ if pages.size > 1
+ self.log "Found multiple pages: #{pages.collect(&:title).join(', ')}"
+ end
+
+ pages.first
+ end
+
+ def path_combinations(path)
+ self._path_combinations(path.split('/'))
+ end
+
+ def _path_combinations(segments, can_include_template = true)
+ return nil if segments.empty?
+
+ segment = segments.shift
+
+ (can_include_template ? [segment, '*'] : [segment]).map do |_segment|
+ if (_combinations = _path_combinations(segments.clone, can_include_template && _segment != '*'))
+ [*_combinations].map do |_combination|
+ File.join(_segment, _combination)
+ end
+ else
+ [_segment]
+ end
+ end.flatten
+ end
+
+ end
+
+ end
+ end
locomotive/steam/server/path.rb b/lib/locomotive/steam/server/path.rb +34 -0
@@ @@ -0,0 +1,34 @@
+ module Locomotive::Steam
+ class Server
+
+ # Sanitize the path from the previous middleware in order
+ # to make it work for the renderer.
+ #
+ class Path < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ self.set_path!(env)
+
+ app.call(env)
+ end
+
+ protected
+
+ def set_path!(env)
+ path = env['PATH_INFO'].clone
+
+ path.gsub!(/\.[a-zA-Z][a-zA-Z0-9]{2,}$/, '')
+ path.gsub!(/^\//, '')
+ path.gsub!(/^[A-Z]:\//, '')
+
+ path = 'index' if path.blank?
+
+ env['steam.path'] = path
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/server/renderer.rb b/lib/locomotive/steam/server/renderer.rb +118 -0
@@ @@ -0,0 +1,118 @@
+ module Locomotive::Steam
+ class Server
+
+ class Steamer < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ if self.page
+ if self.page.redirect?
+ self.redirect_to(self.page.redirect_url, self.page.redirect_type)
+ else
+ type = self.page.response_type || 'text/html'
+ html = self.render_page
+
+ self.log " Steamed liquid page template"
+
+ [200, { 'Content-Type' => type }, [html]]
+ end
+ else
+ [404, { 'Content-Type' => 'text/html' }, [self.render_404]]
+ end
+ end
+
+ protected
+
+ def render_page
+ context = self.locomotive_context
+ begin
+ self.page.render(context)
+ rescue Exception => e
+ raise SteamerException.new(e, self.page.title, self.page.template, context)
+ end
+ end
+
+ def render_404
+ if self.page = self.mounting_point.pages['404']
+ self.render_page
+ else
+ 'Page not found'
+ end
+ end
+
+ # Build the Liquid context used to render the Locomotive page. It
+ # stores both assigns and registers.
+ #
+ # @param [ Hash ] other_assigns Assigns coming for instance from the controler (optional)
+ #
+ # @return [ Object ] A new instance of the Liquid::Context class.
+ #
+ def locomotive_context(other_assigns = {})
+ assigns = self.locomotive_default_assigns
+
+ # assigns from other middlewares
+ assigns.merge!(self.liquid_assigns)
+
+ assigns.merge!(other_assigns)
+
+ # templatized page
+ if self.page && self.content_entry
+ ['content_entry', 'entry', self.page.content_type.slug.singularize].each do |key|
+ assigns[key] = self.content_entry
+ end
+ end
+
+ # Tip: switch from false to true to enable the re-thrown exception flag
+ ::Liquid::Context.new({}, assigns, self.locomotive_default_registers, true)
+ end
+
+ # Return the default Liquid assigns used inside the Locomotive Liquid context
+ #
+ # @return [ Hash ] The default liquid assigns object
+ #
+ def locomotive_default_assigns
+ {
+ 'site' => self.site.to_liquid,
+ 'page' => self.page,
+ 'models' => Locomotive::Steam::Liquid::Drops::ContentTypes.new,
+ 'contents' => Locomotive::Steam::Liquid::Drops::ContentTypes.new,
+ 'current_page' => self.params[:page],
+ 'params' => self.params.stringify_keys,
+ 'path' => self.request.path,
+ 'fullpath' => self.request.fullpath,
+ 'url' => self.request.url,
+ 'ip_address' => self.request.ip,
+ 'post?' => self.request.post?,
+ 'host' => self.request.host_with_port,
+ 'now' => Time.zone.now,
+ 'today' => Date.today,
+ 'locale' => I18n.locale.to_s,
+ 'default_locale' => self.mounting_point.default_locale.to_s,
+ 'locales' => self.mounting_point.locales.map(&:to_s),
+ 'current_user' => {},
+ 'session' => Locomotive::Steam::Liquid::Drops::SessionProxy.new,
+ 'steam' => true,
+ 'editing' => false
+ }
+ end
+
+ # Return the default Liquid registers used inside the Locomotive Liquid context
+ #
+ # @return [ Hash ] The default liquid registers object
+ #
+ def locomotive_default_registers
+ {
+ request: self.request,
+ site: self.site,
+ page: self.page,
+ mounting_point: self.mounting_point,
+ inline_editor: false,
+ logger: Locomotive::Steam::Logger
+ }
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/server/templatized_page.rb b/lib/locomotive/steam/server/templatized_page.rb +32 -0
@@ @@ -0,0 +1,32 @@
+ module Locomotive::Steam
+ class Server
+
+ class TemplatizedPage < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ if self.page && self.page.templatized?
+ self.set_content_entry!(env)
+ end
+
+ app.call(env)
+ end
+
+ protected
+
+ def set_content_entry!(env)
+ %r(^#{self.page.safe_fullpath.gsub('*', '([^\/]+)')}$) =~ self.path
+
+ permalink = $1
+
+ if content_entry = self.page.content_type.find_entry(permalink)
+ env['steam.content_entry'] = content_entry
+ else
+ env['steam.page'] = nil
+ end
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/server/timezone.rb b/lib/locomotive/steam/server/timezone.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Locomotive::Steam
+ class Server
+
+ # Set the timezone according to the settings of the site
+ #
+ class Timezone < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ Time.use_zone(site.try(:timezone) || 'UTC') do
+ app.call(env)
+ end
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/standalone_server.rb b/lib/locomotive/steam/standalone_server.rb +28 -0
@@ @@ -0,0 +1,28 @@
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../..'))
+
+ require 'locomotive/steam/logger'
+ require 'locomotive/steam/version'
+ require 'locomotive/steam/exceptions'
+ require 'locomotive/steam/server'
+ require 'locomotive/mounter'
+
+ module Locomotive
+ module Steam
+ class StandaloneServer < Server
+
+ def initialize(path)
+ Locomotive::Steam::Logger.setup(path, false)
+
+ # get the reader
+ reader = Locomotive::Mounter::Reader::FileSystem.instance
+ reader.run!(path: path)
+ reader
+
+ Bundler.require 'misc'
+
+ # run the rack app
+ super(reader, disable_listen: true)
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/version.rb b/lib/locomotive/steam/version.rb +5 -0
@@ @@ -0,0 +1,5 @@
+ module Locomotive
+ module Steam
+ VERSION = '1.4.0'
+ end
+ end
steam.rb b/lib/steam.rb +4 -0
@@ @@ -0,0 +1,4 @@
+ require_relative 'locomotive/steam/version'
+ require_relative 'locomotive/steam/logger'
+ require_relative 'locomotive/steam/exceptions'
+
locomotivecms_steam.gemspec +42 -0
@@ @@ -0,0 +1,42 @@
+ require_relative 'lib/steam'
+
+ Gem::Specification.new do |spec|
+ spec.name = 'Steam'
+ spec.version = Locomotive::Steam::VERSION
+ spec.authors = ['Didier Lafforgue', 'Rodrigo Alvarez', 'Arnaud Sellenet', 'Joel Azemar']
+ spec.email = ['did@locomotivecms.com', 'papipo@gmail.com', 'arnaud@sellenet.fr', 'joel.azemar@gmail.com']
+ spec.description = %q{The LocomotiveCMS steam is a front end server LocomotiveCMS libraries}
+ spec.summary = %q{The LocomotiveCMS steam is a technical piece for compiled and steam front end stuff for LocomotiveCMS libraries}
+ spec.homepage = 'http://www.locomotivecms.com'
+ spec.homepage = 'https://github.com/joel/scoped_rolify'
+ spec.license = 'MIT'
+
+ spec.files = `git ls-files`.split($/)
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
+ spec.require_paths = ['lib']
+ spec.executables = ['bin']
+
+ spec.add_development_dependency 'bundler', '~> 1.5'
+ spec.add_development_dependency 'rake', '~> 10.1'
+ spec.add_development_dependency 'rspec', '~> 2.14'
+ spec.add_development_dependency 'launchy'
+ spec.add_development_dependency 'vcr'
+ spec.add_development_dependency 'webmock'
+ spec.add_development_dependency 'rack-test'
+
+ spec.add_dependency 'rack-cache', '~> 1.1'
+ spec.add_dependency 'sprockets', '~> 2.0'
+ spec.add_dependency 'sprockets-sass', '~> 1.0.3'
+ spec.add_dependency 'better_errors', '~> 1.0.1'
+ spec.add_dependency 'dragonfly', '~> 0.9.12'
+ spec.add_dependency 'activesupport', '~> 3.2.17'
+ spec.add_dependency 'listen', '~> 2.4.0'
+ spec.add_dependency 'will_paginate', '~> 3.0.3'
+ spec.add_dependency 'redcarpet', '~> 3.0.0'
+
+ spec.add_dependency 'locomotivecms_mounter'
+ spec.add_dependency 'locomotivecms-solid'
+
+ spec.required_ruby_version = '~> 2.0'
+ end
spec/fixtures/default/README +0 -0
spec/fixtures/default/app/content_types/bands.yml +19 -0
@@ @@ -0,0 +1,19 @@
+ name: Bands
+ description: List of bands
+ slug: bands
+ order_by: name
+ fields:
+ - name:
+ hint: Name of the band
+ - leader:
+ type: string
+ label: Fullname of the leader
+ - kind:
+ type: select
+ label: "Music kind (grunge, rock, pop, country)"
+ select_options: ['grunge', 'rock', 'country']
+ - songs:
+ type: has_many
+ class_name: songs
+ inverse_of: band
+ ui_enabled: true
\ No newline at end of file
spec/fixtures/default/app/content_types/events.yml +25 -0
@@ @@ -0,0 +1,25 @@
+ name: Events
+ description: List of upcoming events
+ slug: events
+ order_by: created_at
+ label_field_name: place
+ fields:
+ - place:
+ hint: Name of the place
+ - date:
+ type: date
+ hint: Date of the event
+ - city:
+ type: string
+ label: City of the event
+ - state:
+ type: string
+ label: State of the event
+ - notes:
+ type: text
+ - tags:
+ type: tags
+ label: List of tags
+ - price:
+ type: Float
+ label: Price of the event
\ No newline at end of file
spec/fixtures/default/app/content_types/messages.yml +17 -0
@@ @@ -0,0 +1,17 @@
+ name: Messages
+ description: Messages posted by new potential customers
+ slug: messages
+ label_field_name: name
+ public_submission_enabled: true
+ order_by: created_at
+ fields:
+ - name:
+ hint: Full name
+ required: true
+ - email:
+ hint: Email
+ required: true
+ - message:
+ hint: Customer message
+ type: text
+ required: true
\ No newline at end of file
spec/fixtures/default/app/content_types/songs.yml +25 -0
@@ @@ -0,0 +1,25 @@
+ name: Songs
+ label_field_name: title
+ order_by: _position
+ slug: songs
+ fields:
+ - title:
+ type: string
+ required: true
+ hint: Title of your song
+ - band:
+ label: Band
+ type: belongs_to
+ target: bands
+ - cover:
+ label: Cover
+ type: file
+ required: true
+ - short_description:
+ type: text
+ text_formatting: html
+ - audio_url:
+ type: string
+ hint: Url to a service like Blip for instance
+ - duration:
+ hint: "format like: mm:ss"
\ No newline at end of file
spec/fixtures/default/app/content_types/updates.yml +33 -0
@@ @@ -0,0 +1,33 @@
+ name: Updates
+ description: List of updates
+ slug: updates
+ order_by: date
+ label_field_name: title
+ public_submission_enabled: false
+ fields:
+ - title:
+ hint: Not displayed in the website
+ localized: true
+ - text:
+ type: text
+ hint: Text displayed in the home page
+ localized: true
+ text_formatting: html
+ - category:
+ type: select
+ hint: Pick a category
+ localized: true
+ # select_options:
+ # en: ['General', 'Gigs', 'Bands']
+ # fr: ['Général', 'Concerts', 'Groupes']
+ select_options:
+ - en: General
+ fr: Général
+ - en: Gigs
+ fr: Concerts
+ - en: Bands
+ fr: Groupes
+ - Albums
+ - date:
+ type: date
+ hint: Date of the update
\ No newline at end of file
spec/fixtures/default/app/views/pages/404.liquid.haml +10 -0
@@ @@ -0,0 +1,10 @@
+ ---
+ title: Page not found
+ ---
+ {% extends index %}
+
+ {% block content %}
+
+ %p.error page not found
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/about_us.fr.liquid.haml +7 -0
@@ @@ -0,0 +1,7 @@
+ ---
+ title: A notre sujet
+ slug: a-notre-sujet
+ editable_elements:
+ 'banner/page_image': "/samples/photo.jpg"
+ 'banner/pitch': "<h2>A notre sujet</h2><p>Lorem ipsum...(FR)</p>"
+ ---
\ No newline at end of file
spec/fixtures/default/app/views/pages/about_us.liquid.haml +21 -0
@@ @@ -0,0 +1,21 @@
+ ---
+ title: About Us
+ listed: true
+ position: 1
+ handle: about-us
+ editable_elements:
+ 'banner/page_image': "/samples/photo_2.jpg"
+ 'banner/pitch': "<h2>About us</h2><p>Lorem ipsum...</p>"
+ ---
+ {% extends parent %}
+
+ {% block content %}
+
+ {% editable_long_text 'content' %}
+
+ %p
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc. Quisque mauris diam, tincidunt in commodo sed, feugiat eu nibh. Nulla erat nunc, dapibus vel eleifend et, egestas sed quam. Vestibulum mollis eros at dolor vulputate vel sollicitudin enim convallis. Etiam velit nisi, rutrum vel sagittis facilisis, pretium id lorem. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mauris nisl, consequat sed tincidunt nec, lacinia in odio. In hac habitasse platea dictumst. Nam semper libero aliquam turpis gravida vel varius erat vulputate. Integer consequat ipsum vitae augue porttitor ullamcorper. Nam vulputate aliquet ante at gravida. Vestibulum luctus urna et dui hendrerit eu suscipit velit varius. Sed ornare eleifend sem, vitae pharetra dolor sodales egestas. Mauris lobortis hendrerit odio, vitae porttitor urna rutrum at. Ut at lectus erat, nec dictum dolor. Praesent in sapien interdum nibh euismod vestibulum. Vestibulum tincidunt pulvinar accumsan.
+
+ {% endeditable_long_text %}
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/about_us.nb.liquid.haml +4 -0
@@ @@ -0,0 +1,4 @@
+ ---
+ title: Om oss
+ slug: om-oss
+ ---
\ No newline at end of file
spec/fixtures/default/app/views/pages/about_us/jane_doe.liquid.haml +4 -0
@@ @@ -0,0 +1,4 @@
+ ---
+ position: 2
+ ---
+ {% extends parent %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/about_us/john_doe.fr.liquid.haml +5 -0
@@ @@ -0,0 +1,5 @@
+ ---
+ title: Jean Personne
+ slug: jean-personne
+ ---
+ {% extends parent %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/about_us/john_doe.liquid.haml +6 -0
@@ @@ -0,0 +1,6 @@
+ ---
+ position: 1
+ published: true
+ listed: true
+ ---
+ {% extends parent %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/all.liquid.haml +13 -0
@@ @@ -0,0 +1,13 @@
+ ---
+ title: All the pages
+ listed: false
+ published: false
+ ---
+ {% extends parent %}
+ {% block content %}
+ <ul>
+ {% for page in site.pages %}
+ <li>{{ page.title }}</li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
spec/fixtures/default/app/views/pages/archives/news.liquid.haml +10 -0
@@ @@ -0,0 +1,10 @@
+ ---
+ title: News archive
+ ---
+ {% extends parent %}
+
+ {% block content %}
+
+ %p News Archives. Lorem ipsum....
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/contact.liquid.haml +54 -0
@@ @@ -0,0 +1,54 @@
+ ---
+ title: Contact Us
+ listed: true
+ position: 4
+ ---
+ {% extends 'parent' %}
+
+ {% block content %}
+
+ .text
+ {% editable_long_text 'text' %}
+ %p
+ Ut imperdiet velit eu metus semper tristique. Vivamus risus nisi, tincidunt et euismod a, auctor pretium eros. Vestibulum sed magna et velit pulvinar euismod. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sed velit quis nisl blandit vulputate id non tortor. Mauris nec placerat massa. Vivamus sed odio non ligula pharetra pretium. Ut convallis, purus et lobortis suscipit, mauris orci ullamcorper lectus, nec vulputate turpis mauris sed augue. Maecenas faucibus ultricies nisl, non ullamcorper justo bibendum nec. Duis vitae mauris condimentum risus commodo mattis vel sed libero. Fusce diam elit, porta id vestibulum ut, aliquet mattis neque.
+ {% endeditable_long_text %}
+
+ %form#contactform{ :name => 'contact', :action => '{{ contents.messages.public_submission_url }}.json', :method => 'post' }
+ / %input{ type: 'hidden', name: 'success_callback', value: '/events' }
+ / %input{ type: 'hidden', name: 'error_callback', value: '/contact' }
+
+ %p
+ %label{ :for => 'name' } Name
+ %input{ :type => 'text', :id => 'name', :name => 'content[name]', :placeholder => 'First and last name', :tabindex => '1', required: 'required', value: '{{ message.name }}' }
+ %span {{ message.errors.name }}
+
+ %p
+ %label{ :for => 'email' } Email
+ %input{ :type => 'text', :id => 'email', :name => 'content[email]', :placeholder => 'example@domain.com', :tabindex => '2', required: 'required', value: '{{ message.email }}' }
+ %span {{ message.errors.email }}
+
+ %p
+ %label{ :for => 'comment' } Your Message
+ %textarea{ :id => 'comment', :name => 'content[message]', :tabindex => '3', required: 'required' } {{ message.message }}
+ %span {{ message.errors.message }}
+
+ %p.action
+ %input{ :name => 'submit', :type => 'submit', :tabindex => '4', :value => 'Send Message' }
+
+ :javascript
+ $(document).ready(function() {
+ var form = $('form[name=contact]');
+ form.submit(function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ $.post(form.attr('action'),
+ form.serializeArray(),
+ function() {
+ alert("Thank you ! Your message have been received");
+ }, "json").error(function(response) {
+ alert("We are sorry but we were unable to treat your message. Please try later.");
+ });
+ });
+ });
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/contest.liquid.haml +18 -0
@@ @@ -0,0 +1,18 @@
+ ---
+ title: A sample contest
+ listed: false
+ ---
+ {% extends 'parent' %}
+
+ {% block content %}
+
+ %h1 Contest sample
+
+ {% if session.already_participated %}
+ %p You've already participated to that contest ! Come back later.
+ {% else %}
+ %p Your code is: HELLO WORLD
+ {% session_assign already_participated = true %}
+ {% endif %}
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/events.liquid.haml +42 -0
@@ @@ -0,0 +1,42 @@
+ ---
+ position: 5
+ ---
+ {% extends parent %}
+
+ {% block content %}
+
+ %p Thank you {{ message.name }} !
+
+ #events.unit.size2of3
+ %h2 Upcoming events
+
+ %ul.list
+ {% for event in contents.events %}
+ %li
+ %em {{ event.date | localized_date: '%a, %B %d, %Y' }}
+ &nbsp;-&nbsp;
+ {{ event.place }}, {{ event.city }}, {{ event.state }}
+ {% endfor %}
+
+ #sidebar.unit.size1of3
+ {% editable_long_text 'sidebar' %}
+
+ %p
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc. Quisque mauris diam, tincidunt in commodo sed, feugiat eu nibh. Nulla erat nunc, dapibus vel eleifend et, egestas sed quam. Vestibulum mollis eros at dolor vulputate vel sollicitudin enim convallis. Etiam velit nisi, rutrum vel sagittis facilisis, pretium id lorem. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mauris nisl, consequat sed tincidunt nec, lacinia in odio. In hac habitasse platea dictumst. Nam semper libero aliquam turpis gravida vel varius erat vulputate. Integer consequat ipsum vitae augue porttitor ullamcorper. Nam vulputate aliquet ante at gravida. Vestibulum luctus urna et dui hendrerit eu suscipit velit varius. Sed ornare eleifend sem, vitae pharetra dolor sodales egestas. Mauris lobortis hendrerit odio, vitae porttitor urna rutrum at. Ut at lectus erat, nec dictum dolor. Praesent in sapien interdum nibh euismod vestibulum. Vestibulum tincidunt pulvinar accumsan
+
+ {% endeditable_long_text %}
+
+ %p
+ %strong Discover: {% link_to our-music %}
+ %br
+ %strong More about us: {% link_to about-us %}Who are we ?{% endlink_to %}
+ %br
+ %strong Plus à notre sujet: {% link_to about-us, locale: fr %}Qui sommes nous ?{% endlink_to %}
+ %br
+ {% assign song = contents.songs.first %}
+ %strong {% link_to song %}
+ %br
+ {% assign another_song = contents.songs.last %}
+ %strong {% link_to another_song, with: a-song-template %}
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/filtered.liquid.haml +10 -0
@@ @@ -0,0 +1,10 @@
+ ---
+ title: Various uses of the with_scope tag
+ ---
+ {% assign begin = '2012-06-01 00:00:00' %}
+ {% assign end = '2012-06-10 23:59:59' %}
+ {% assign prices = '5.0,5.5' | split: ',' | map: 'to_f' %}
+
+ {% with_scope date.gte: begin, date.lte: end, city: /Kansas/, state.ne: 'Illinois', tags: 'awesome', tags.nin: 'bad', price.in: prices %}
+ events={{ contents.events.count }}.
+ {% endwith_scope %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/grunge_bands.liquid.haml +8 -0
@@ @@ -0,0 +1,8 @@
+ ---
+ title: Grunge leaders
+ ---
+ {% with_scope kind: "grunge" %}
+ {% for band in contents.bands %}
+ {{ band.leader }}
+ {% endfor %}
+ {% endwith_scope %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/index.fr.liquid.haml +3 -0
@@ @@ -0,0 +1,3 @@
+ ---
+ title: Page d'accueil
+ ---
spec/fixtures/default/app/views/pages/index.liquid.haml +100 -0
@@ @@ -0,0 +1,100 @@
+ ---
+ title: Home page
+ ---
+ !!! XML
+ !!!
+ %html{ :lang => "en" }
+ %head
+ %meta{ :charset => "utf-8" }
+
+ %title {{ site.name }}
+
+ %meta{ :content => "{{ site.meta_description }}", :name => "description" }
+ %meta{ :content => "{{ site.meta_keywords }}", :name => "keywords" }
+
+ {{ '/foo/bar' | auto_discovery_link_tag: 'rel:alternate', 'type:application/atom+xml', 'title:A title' }}
+
+ / Le HTML5 shim, for IE6-8 support of HTML elements
+
+ /[if lt IE 9]
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+
+ <link href="/fonts/chunkfive.css" media="screen" rel="stylesheet" type="text/css" />
+
+ / Le styles
+ {{ 'http://fonts.googleapis.com/css?family=Open+Sans:400,700' | stylesheet_tag }}
+ {{ 'reboot' | stylesheet_tag }}
+ {{ 'application' | stylesheet_tag }}
+ %script{ :src => "{{ 'application.js' | javascript_url }}", :type => 'text/javascript' }
+
+ %script{ :src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', :type => 'text/javascript' }
+
+ {% inline_editor %}
+
+ %body
+ .container
+ #menu
+ %ul#nav
+ %li#home{ :class => "{% if page.fullpath == 'index' %}on{% endif %} link" }
+ %a{ :href => '/' } Home
+ {% nav site, no_wrapper: true, exclude: 'events' %}
+ .clear
+
+ #banner
+ {% block banner %}
+ .photo
+ %img{ :src => "{% editable_file 'Page image', hint: 'Top banner image in each page (602px by 397px)' %}/samples/photo.jpg{% endeditable_file %}" }
+
+ .text
+ {% editable_long_text 'pitch' %}
+ %h2 About Us
+ %p
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vitae egestas neque. Proin ac ante ante, sit amet egestas purus. Fusce tincidunt mattis sapien eget sodales. Cras aliquet odio eu nisl dapibus placerat.
+ %br
+ %a{ :href => '/about_us' } read more...
+ {% endeditable_long_text %}
+ {% endblock %}
+
+ .clear
+
+ #content
+ {% block content %}
+
+ #events.unit.size1of2
+ %h2 Upcoming events
+
+ %ul.list
+ {% for event in contents.events limit: 6 %}
+ %li
+ %em {{ event.date | localized_date: '%a, %B %d, %Y' }}
+ &nbsp;-&nbsp;
+ {{ event.place }}, {{ event.city }}, {{ event.state }}
+ {% endfor %}
+
+ %p.more
+ %a{ :href => '/events' } See more events ...
+
+ #updates.unit.size1of2
+ %h2 Site updates
+
+ %ul.list
+ {% for update in contents.updates %}
+ %li
+ %em {{ update.date | localized_date: '%B %d' }}
+ &nbsp;-&nbsp;
+ {{ update.title }}
+ {% endfor %}
+
+ {% endblock %}
+
+ .clear
+
+ #footer
+ #is_templatized{templatized: "{{ page.templatized? }}"}
+ #scoped_translation{scoped_translation: "{{ 'fr' | translate: 'en', 'locomotive.locales' }}"}
+
+ {% include footer %}
+
+ {% include a_complicated-one %}
+
+ {% google_analytics 'UA-20661758-1' %}
spec/fixtures/default/app/views/pages/music.fr.liquid.haml +4 -0
@@ @@ -0,0 +1,4 @@
+ ---
+ title: Notre musique
+ slug: notre-musique
+ ---
spec/fixtures/default/app/views/pages/music.liquid.haml +42 -0
@@ @@ -0,0 +1,42 @@
+ ---
+ listed: true
+ handle: our-music
+ position: 2
+ ---
+ {% extends parent %}
+
+ {% block content %}
+
+ {% editable_long_text 'introduction' %}
+
+ %p
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc. Quisque mauris diam, tincidunt in commodo sed, feugiat eu nibh. Nulla erat nunc, dapibus vel eleifend et, egestas sed quam. Vestibulum mollis eros at dolor vulputate vel sollicitudin enim convallis. Etiam velit nisi, rutrum vel sagittis facilisis, pretium id lorem.
+
+ {% endeditable_long_text %}
+
+ .unit.size1of2
+ %ul.songs
+ {% for song in contents.songs limit: 4 offset: 0 %}
+ {% include 'song' with song %}
+ {% endfor %}
+
+ .unit.size1of2
+ %ul.songs
+ {% for song in contents.songs offset: 4 %}
+ {% include 'song' with song %}
+ {% endfor %}
+
+ #is_listed{ listed: "{{ page.listed? }}" }
+
+ #test_for_scope
+ {% with_scope _slug: "song-number-3" %}
+ {% assign selected_songs = contents.songs.all %}
+ {% endwith_scope %}
+ {% for s in selected_songs %}
+ %p.scoped_song {{ s._label }}
+ %p.scoped_song_link
+ %a{ href: "{% path_to s %}" } {{ s._label }}
+ {% endfor %}
+ %p.collection_equality {{ contents.songs.all.size }}={{ contents.songs.size }}
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/songs/template.fr.liquid.haml +16 -0
@@ @@ -0,0 +1,16 @@
+ ---
+ title: Le template d'une chanson
+ ---
+ {% extends 'index' %}
+
+ {% block content %}
+
+ %h3 {{ song.title }} [FR]
+
+ %p {{ song.short_description }}
+
+ #is_templatized{templatized: "{{ page.templatized? }}"}
+ #content_type_size{content_type_size: "{{ page.content_type.size }}"}
+ #content_type_count{content_type_count: "{{ page.content_type.count }}"}
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/songs/template.liquid.haml +18 -0
@@ @@ -0,0 +1,18 @@
+ ---
+ title: A song template
+ content_type: songs
+ handle: a-song-template
+ ---
+ {% extends 'index' %}
+
+ {% block content %}
+
+ %h3 {{ song.title }}
+
+ %p {{ song.short_description }}
+
+ #is_templatized{templatized: "{{ page.templatized? }}"}
+ #content_type_size{content_type_size: "{{ page.content_type.size }}"}
+ #content_type_count{content_type_count: "{{ page.content_type.count }}"}
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/songs/template/band.liquid.haml +16 -0
@@ @@ -0,0 +1,16 @@
+ ---
+ title: Band
+ ---
+ {% extends parent %}
+
+ {% block content %}
+
+ %h3 {{ song.title }}
+
+ %h4 Leader: {{ song.band.leader }}
+
+ #is_templatized{templatized: "{{ page.templatized? }}"}
+ #content_type_size{content_type_size: "{{ page.content_type.size }}"}
+ #content_type_count{content_type_count: "{{ page.content_type.count }}"}
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/store.fr.liquid.haml +5 -0
@@ @@ -0,0 +1,5 @@
+ ---
+ title: Magasin
+ slug: magasin
+ redirect_url: http://www.apple.com/fr/itunes/
+ ---
\ No newline at end of file
spec/fixtures/default/app/views/pages/store.liquid +5 -0
@@ @@ -0,0 +1,5 @@
+ ---
+ redirect_url: http://www.apple.com/en/itunes/
+ listed: true
+ position: 3
+ ---
\ No newline at end of file
spec/fixtures/default/app/views/pages/tags/nav.liquid.haml +6 -0
@@ @@ -0,0 +1,6 @@
+ ---
+ title: Page to test the nav tag
+ listed: false
+ published: false
+ ---
+ {% nav site %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/tags/nav_in_deep.liquid.haml +6 -0
@@ @@ -0,0 +1,6 @@
+ ---
+ title: Page to test the nav tag with a high depth
+ listed: false
+ published: false
+ ---
+ {% nav 'site', depth: 2 %}
\ No newline at end of file
spec/fixtures/default/app/views/pages/unlisted_pages.liquid.haml +9 -0
@@ @@ -0,0 +1,9 @@
+ ---
+ title: Unlisted pages
+ ---
+ %ul
+ {% with_scope listed: false %}
+ {% for page in site.pages %}
+ %li {{ page.title }}
+ {% endfor %}
+ {% endwith_scope %}
\ No newline at end of file
spec/fixtures/default/app/views/snippets/A_Complicated-one.liquid.haml +1 -0
@@ @@ -0,0 +1 @@
+ %p A complicated one name indeed.
\ No newline at end of file
spec/fixtures/default/app/views/snippets/footer.liquid.haml +6 -0
@@ @@ -0,0 +1,6 @@
+ %p {% locale_switcher %}
+
+ %p
+ {{ 'powered_by' | translate }} <a href="http://www.locomotivecms.com">LocomotiveCMS</a>. Designed by <a href="http://www.sachagreif.com">Sacha Greif</a>.
+ %p
+ All photos are licensed under Creative Commons. (see original ones <a href='http://www.flickr.com/photos/38687875@N00/3391588262/'>here</a> or <a href='http://www.flickr.com/photos/cool_dry_place/55454498/'>here</a>).
\ No newline at end of file
spec/fixtures/default/app/views/snippets/header.liquid.haml +1 -0
@@ @@ -0,0 +1 @@
+ %h1 {{ site.name }}
\ No newline at end of file
spec/fixtures/default/app/views/snippets/song.fr.liquid.haml +8 -0
@@ @@ -0,0 +1,8 @@
+ %li
+ %h3 {% link_to song, with: a-song-template %}
+ .cover {{ song.cover.url | image_tag }}
+ .info
+ {{ song.short_description }}
+ %p.listen
+ %a{ :href => "{{ song.audio_url }}" } &rarr; écouter ({{ song.duration }} min)
+ .clear
\ No newline at end of file
spec/fixtures/default/app/views/snippets/song.liquid +12 -0
@@ @@ -0,0 +1,12 @@
+ <li>
+ <h3><a href="/songs/{{ song._permalink }}">{{ song.title }}</a></h3>
+ <div class="cover">{{ song.cover.url | image_tag }}</div>
+ <div class="info">
+ {{ song.short_description }}
+ <p class="listen">
+ <a href="{{ song.audio_url }}">&rarr; Listen ({{ song.duration }} min)</a>
+ </p>
+ <p>écouter en Francais</p>
+ </div>
+ <div class="clear"></div>
+ </li>
\ No newline at end of file
spec/fixtures/default/config/deploy.yml +12 -0
@@ @@ -0,0 +1,12 @@
+ development:
+ host: development.example.com
+ email: john@doe.net
+ password: easyone
+ staging:
+ host: staging.example.com
+ email: john@doe.net
+ password: easyone
+ production:
+ host: www.example.com
+ email: john@doe.net
+ password: easyone
spec/fixtures/default/config/deploy_example.yml +12 -0
@@ @@ -0,0 +1,12 @@
+ development:
+ host: development.example.com
+ email: john@doe.net
+ password: easyone
+ staging:
+ host: staging.example.com
+ email: john@doe.net
+ password: easyone
+ production:
+ host: www.example.com
+ email: john@doe.net
+ password: easyone
spec/fixtures/default/config/site.yml +15 -0
@@ @@ -0,0 +1,15 @@
+ name: Sample website
+
+ subdomain: sample
+
+ domains: ['sample.example.com']
+
+ locales: ['en', 'fr', 'nb']
+
+ seo_title:
+ en: A simple LocomotiveCMS website
+ fr: Un simple LocomotiveCMS site web
+ meta_keywords:
+ en: some meta keywords
+ fr: quelques mots cles
+ meta_description: some meta description
\ No newline at end of file
spec/fixtures/default/config/translations.yml +3 -0
@@ @@ -0,0 +1,3 @@
+ powered_by:
+ en: Powered by
+ fr: Propulsé par
\ No newline at end of file
spec/fixtures/default/data/bands.yml +10 -0
@@ @@ -0,0 +1,10 @@
+ - Pearl Jam:
+ leader: Eddie
+ kind: grunge
+ - Alice in Chains:
+ leader: Layne
+ kind: grunge
+ - The who:
+ leader: Peter
+ kind: rock
+
spec/fixtures/default/data/events.yml +53 -0
@@ @@ -0,0 +1,53 @@
+ - Avogadro's Number:
+ date: 2012/06/11
+ city: Fort Collins
+ state: Colorado
+ notes: '<p>Lorem ipsum<img src="/samples/assets/" alt="" /></p>'
+ - Quixote's True Blue:
+ date: 2012/06/10
+ city: Denver
+ state: Colorado
+ - Kelly's Westport Inn:
+ date: 2012/06/06
+ city: Kansas City
+ state: Missouri
+ tags: [awesome, open bar]
+ price: 5.5
+ - Browne's Market:
+ date: 2012/06/06
+ city: Kansas City
+ state: Missouri
+ tags: [awesome, open bar]
+ price: 15.0
+ - Ballydoyle's:
+ date: 2012/06/05
+ city: Aurora
+ state: Illinois
+ - The Belmont:
+ date: 2012/06/04
+ city: Hamtramk
+ state: Michigan
+ - Avogadro's Number:
+ date: 2011/06/11
+ city: Fort Collins
+ state: Colorado
+ - Quixote's True Blue:
+ date: 2011/06/10
+ city: Denver
+ state: Colorado
+ - Kelly's Westport Inn:
+ date: 2011/06/06
+ city: Kansas City
+ state: Missouri
+ - Browne's Market:
+ date: 2011/06/06
+ city: Kansas City
+ state: Missouri
+ - Ballydoyle's:
+ date: 2011/06/05
+ city: Aurora
+ state: Illinois
+ - The Belmont:
+ date: 2011/06/04
+ city: Hamtramk
+ state: Michigan
\ No newline at end of file
spec/fixtures/default/data/songs.yml +46 -0
@@ @@ -0,0 +1,46 @@
+ - "Song #1":
+ duration: "6:28"
+ short_description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc."
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
+ band: pearl-jam
+ - "Song #2":
+ duration: "6:28"
+ short_description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc.
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
+ band: pearl-jam
+ - "Song #3":
+ duration: "6:28"
+ short_description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc.
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
+ band: pearl-jam
+ - "Song #4":
+ duration: "6:28"
+ short_description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc.
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
+ band: pearl-jam
+ - "Song #5":
+ duration: "6:28"
+ short_description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc.
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
+ band: the-who
+ - "Song #6":
+ duration: "6:28"
+ short_description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc.
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
+ band: the-who
+ - "Song #7":
+ duration: "6:28"
+ short_description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc.
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
+ - "Song #8":
+ duration: "6:28"
+ short_description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae tincidunt urna. Nunc felis purus, ultricies et venenatis bibendum, fringilla eu lectus. Sed cursus, sem at blandit mattis, libero quam egestas tortor, eget cursus dolor tellus id nunc.
+ audio_url: http://blip.tv/file/get/NicolasRaynier-Milk617.flv
+ cover: /samples/asset_collections/cover.jpg
\ No newline at end of file
spec/fixtures/default/data/updates.yml +48 -0
@@ @@ -0,0 +1,48 @@
+ - ! 'Update #6':
+ title:
+ fr: "Mise a jour #6"
+ text:
+ en: john posted a new post
+ fr: phrase FR
+ date: 2009/11/16
+ category: General
+ - "Update #5":
+ title:
+ fr: "Mise a jour #5"
+ text:
+ en: brian posted a new post
+ fr: phrase FR
+ date: 2009/11/02
+ category: General
+ - "Update #4":
+ title:
+ fr: "Mise a jour #4"
+ text:
+ en: NEW ALBUM FINISHED!!
+ fr: phrase FR
+ date: 2009/10/09
+ category: General
+ - "Update #3":
+ title:
+ fr: "Mise a jour #3"
+ text:
+ en: site redesign
+ fr: phrase FR
+ date: 2009/06/29
+ category: General
+ - "Update #2":
+ title:
+ fr: "Mise a jour #2"
+ text:
+ en: added a chatroom
+ fr: phrase FR
+ date: 2009/05/23
+ category: General
+ - "Update #1":
+ title:
+ fr: "Mise a jour #1"
+ text:
+ en: added some free stuff
+ fr: phrase FR
+ date: 2009/05/12
+ category: General
\ No newline at end of file
spec/fixtures/default/log/steam.log +9 -0
@@ @@ -0,0 +1,9 @@
+ # Logfile created on 2014-03-31 18:17:27 +0200 by logger.rb/1.2.8
+ Started GET "/filtered" at 2014-03-31 18:17:27 +0200
+ Detecting locale EN
+ Found page "Various uses of the with_scope tag" [filtered]
+ [with_scope] conditions: date gte 2012-06-01 00:00:00, date lte 2012-06-10 23:59:59, city matches (?-mix:Kansas), state ne Illinois, tags == awesome, tags nin bad, price in [5.0, 5.5]
+ Steamed liquid page template
+ Completed 200 OK in 35.6ms
+
+ 
spec/fixtures/default/public/fonts/chunkfive-webfont.eot +0 -0
spec/fixtures/default/public/fonts/chunkfive-webfont.svg +213 -0
@@ @@ -0,0 +1,213 @@
+ <?xml version="1.0" standalone="no"?>
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <metadata>
+ This is a custom SVG webfont generated by Font Squirrel.
+ Copyright : Copyright c 2007 by Georg All rights reserved
+ </metadata>
+ <defs>
+ <font id="webfontwlZ5ec8v" horiz-adv-x="1292" >
+ <font-face units-per-em="2048" ascent="1536" descent="-512" />
+ <missing-glyph horiz-adv-x="512" />
+ <glyph unicode=" " horiz-adv-x="512" />
+ <glyph unicode="&#x09;" horiz-adv-x="512" />
+ <glyph unicode="&#xa0;" horiz-adv-x="512" />
+ <glyph unicode="!" horiz-adv-x="630" d="M66 1042v390h489v-390l-109 -516h-270zM57 209q0 84 64.5 150.5t169.5 66.5q106 0 175 -63.5t69 -153.5q0 -88 -68 -149.5t-176 -61.5q-98 0 -166 64.5t-68 146.5z" />
+ <glyph unicode="&#x22;" horiz-adv-x="1085" d="M520 1436l-74 -613h-256l-75 613h405zM971 1436l-74 -613h-256l-76 613h406z" />
+ <glyph unicode="#" horiz-adv-x="1331" d="M1223 659h-191l-24 -137h190l-47 -272h-190l-37 -209h-273l37 209h-137l-37 -209h-272l37 209h-232l47 272h232l24 137h-231l47 273h231l37 213h273l-37 -213h137l37 213h272l-37 -213h191zM735 522l25 137h-137l-25 -137h137z" />
+ <glyph unicode="$" horiz-adv-x="1454" d="M1366 918h-371q0 111 -74.5 174t-187.5 63q-51 0 -96 -29.5t-45 -78.5q0 -57 84 -100.5t202.5 -81.5t237.5 -87t203 -145t84 -230q0 -115 -49 -201.5t-131 -136t-174.5 -73t-188.5 -23.5q-27 0 -72 4v-131h-131v162q-133 47 -225 154l-22 -158h-359v516h381 q0 -61 26.5 -103t71.5 -62.5t82 -29t78 -10.5q174 0 174 103q0 37 -58 65.5t-145.5 48t-189.5 57.5t-189 87t-145.5 141t-58.5 215q0 109 47 196t122 139t156.5 78.5t161.5 26.5q45 0 92 -6v152h131v-182q143 -55 205 -172l21 172h352v-514z" />
+ <glyph unicode="%" horiz-adv-x="1228" d="M342 766q-104 0 -164.5 72.5t-60.5 212.5q0 121 59.5 196.5t155.5 75.5q113 0 169 -76t56 -211q0 -127 -56.5 -198.5t-158.5 -71.5zM946 1317h-235l-424 -1311h235zM342 885q27 0 42 42t15 109q0 164 -69 164q-20 0 -40 -43t-20 -106q0 -74 22.5 -120t49.5 -46zM895 4 q-104 0 -164.5 73t-60.5 212q0 121 59 196.5t156 75.5q113 0 169 -75.5t56 -211.5q0 -127 -56.5 -198.5t-158.5 -71.5zM895 123q27 0 42 42t15 109q0 164 -69 164q-20 0 -40 -43t-20 -106q0 -74 22.5 -120t49.5 -46z" />
+ <glyph unicode="&#x26;" horiz-adv-x="1433" d="M1325 729h-338q0 -172 -14 -221q-12 -35 -37 -76q-74 78 -178 258q147 88 212.5 209t46 222.5t-112.5 172t-240 70.5q-123 0 -221.5 -78t-120 -204t74.5 -285q-125 -82 -176 -142q-113 -129 -112 -311q0 -131 61 -221t156.5 -125t202 -40t203.5 21.5t159 71.5 q117 -84 205 -62.5t138 83t50 116.5q-102 -59 -207 78q49 66 68 152q18 -4 84.5 -1t77.5 3zM635 924q-35 37 -50.5 99t10.5 115.5t95 53.5q86 0 94 -70q8 -80 -90 -159q-26 -21 -59 -39zM518 565q113 -217 221 -340q-98 -80 -223 -50t-140.5 132t142.5 258z" />
+ <glyph unicode="'" horiz-adv-x="559" d="M479 1436l-73 -613h-256l-76 613h405z" />
+ <glyph unicode="(" horiz-adv-x="794" d="M532 1669l211 -217q-59 -74 -94 -122t-96 -156.5t-93 -243.5t-32 -291q0 -162 29.5 -301t85 -245.5t97.5 -168t103 -133.5l-211 -219q-193 174 -337 459.5t-144 607.5q0 170 50.5 341t128 303t154 227.5t148.5 158.5z" />
+ <glyph unicode=")" horiz-adv-x="794" d="M51 1450l211 217q72 -63 148.5 -158.5t154.5 -227.5t128 -303t50 -341q0 -322 -144 -607.5t-337 -459.5l-211 219q61 72 103.5 133.5t97.5 168t85 245.5t30 301q0 156 -32 291t-93.5 243.5t-96.5 156.5t-94 122z" />
+ <glyph unicode="*" horiz-adv-x="1228" d="M510 1425l-39 -61l45 -238l-182 95l-90 4l-66 -209l84 -39l193 -43l-176 -182v-58l170 -129l55 51l108 197l103 -184l63 -66l172 131l-18 74l-158 164l201 41l76 43l-66 209l-86 -4l-184 -97l45 238l-39 63h-211z" />
+ <glyph unicode="+" d="M1190 625v-273h-416v-415h-272v415h-416v273h416v415h272v-415h416z" />
+ <glyph unicode="," horiz-adv-x="557" d="M223 -397l-235 133q115 145 162 294.5t32 370.5l373 -98q6 -195 -82 -376t-250 -324z" />
+ <glyph unicode="-" horiz-adv-x="790" d="M96 625h594v-273h-594v273z" />
+ <glyph unicode="." horiz-adv-x="528" d="M31 213q0 86 63.5 159.5t168 73.5t170 -73.5t65.5 -159.5q0 -96 -62.5 -158.5t-173.5 -62.5q-98 0 -164.5 67.5t-66.5 153.5z" />
+ <glyph unicode="/" horiz-adv-x="823" d="M823 1497h-331l-594 -1814h331z" />
+ <glyph unicode="0" horiz-adv-x="1226" d="M618 -25q-266 0 -426.5 190.5t-160.5 557.5q0 326 160.5 530.5t416.5 204.5q293 0 440.5 -199.5t147.5 -549.5q0 -344 -151 -539t-427 -195zM618 293q72 0 119 115.5t47 300.5q0 182 -51 300.5t-127 118.5q-61 2 -115.5 -113.5t-54.5 -291.5q0 -186 57 -308t125 -122z " />
+ <glyph unicode="1" horiz-adv-x="1015" d="M145 936l-94 227l42 21l44 21l39 21q29 15 43 26l41 31q27 19 47 40l48 48q28 28 55 61h364v-1160h191v-272h-820v272h205v760z" />
+ <glyph unicode="2" horiz-adv-x="1208" d="M289 967l-221 233q219 254 532 254q209 0 348 -123t139 -325q0 -164 -70.5 -251t-256.5 -208q-86 -55 -180.5 -136t-126.5 -139h360v164h344v-436h-1106v297q82 135 140.5 201.5t158.5 146.5q47 43 150.5 111.5t158 121t54.5 109.5q0 135 -172 135q-51 0 -103.5 -29.5 t-77 -53t-71.5 -72.5z" />
+ <glyph unicode="3" horiz-adv-x="1105" d="M274 1020l-198 240q66 72 188.5 133t249.5 61q248 0 385 -117.5t137 -316.5q0 -143 -151 -260q190 -137 190 -336q0 -213 -170 -332t-397 -119q-141 0 -278.5 70t-198.5 162l209 217q133 -133 270 -133q86 0 135 41t49 116q0 150 -170 150h-153v244h149q76 0 125 36.5 t49 104.5q0 78 -54 120t-126 42q-123 0 -240 -123z" />
+ <glyph unicode="4" horiz-adv-x="1196" d="M20 696l590 736h424v-807h109v-222h-109v-133h111v-270h-664v270h129v133h-590v293zM610 625v379l-303 -379h303z" />
+ <glyph unicode="5" horiz-adv-x="1181" d="M487 1126l-34 -198q76 49 221 49q193 0 335 -130t142 -355q0 -125 -53.5 -225.5t-141.5 -163t-195.5 -96.5t-221.5 -34q-150 0 -296.5 75t-211.5 173l223 232q147 -152 287 -152q102 0 163.5 57.5t61.5 147.5q0 82 -60.5 140.5t-148.5 58.5q-102 0 -192 -95l-263 117 l125 705h811v-306h-551z" />
+ <glyph unicode="6" horiz-adv-x="1189" d="M1118 1292l-139 -266q-178 104 -299 104q-111 -2 -181.5 -80.5t-86.5 -197.5q41 41 115.5 71.5t152.5 32.5q201 8 340 -121.5t139 -334.5q0 -213 -143.5 -361.5t-386.5 -156.5q-256 -8 -422 164.5t-174 476.5q-2 96 8 191t47 213t99.5 207t178 152.5t269.5 67.5 q109 4 253 -42t230 -120zM627 662q-121 -6 -211 -117q0 -88 50 -165t140 -77q78 0 133.5 51.5t53.5 124.5q-2 72 -42 128.5t-124 54.5z" />
+ <glyph unicode="7" horiz-adv-x="1159" d="M31 1432h1108v-273l-551 -1159h-443l549 1159h-336v-133h-327v406z" />
+ <glyph unicode="8" horiz-adv-x="1144" d="M31 420q0 102 61.5 206.5t159.5 153.5q-74 45 -114 120t-40 153q0 176 131 289.5t357 113.5q231 0 360 -113.5t129 -289.5q0 -96 -49 -170t-123 -109q100 -49 155.5 -147.5t55.5 -206.5q0 -193 -148.5 -320t-392.5 -127q-250 0 -396 126t-146 321zM735 1069q0 55 -46 92 t-107 37q-59 0 -103.5 -39t-44.5 -90t39 -84t79 -41t85 -10q98 47 98 135zM385 432q0 -72 56.5 -120t133.5 -48q80 0 133.5 44t55.5 124q0 94 -81 136t-179 46q-49 -29 -84 -79t-35 -103z" />
+ <glyph unicode="9" horiz-adv-x="1202" d="M72 143l139 267q178 -104 299 -105q117 4 184.5 76t65.5 203q-98 -98 -250 -105q-201 -8 -340 122t-139 335q0 213 143 361.5t387 156.5q258 8 429 -166t179 -475q2 -106 -8 -205.5t-50 -215.5t-104.5 -202t-181 -145t-270.5 -63q-109 -4 -253 41.5t-230 119.5zM563 774 q102 4 193 117q0 94 -42 168t-130 74q-78 0 -133.5 -51.5t-53.5 -125.5q2 -72 42 -128t124 -54z" />
+ <glyph unicode=":" horiz-adv-x="520" d="M31 823q0 86 62.5 158t164.5 72q104 0 167.5 -73t63.5 -157q0 -94 -62 -155.5t-169 -61.5q-92 0 -159.5 67.5t-67.5 149.5zM31 201q0 86 62.5 157.5t164.5 71.5q104 0 167.5 -72.5t63.5 -156.5q0 -92 -64.5 -154.5t-166.5 -62.5q-96 0 -161.5 66.5t-65.5 150.5z" />
+ <glyph unicode=";" horiz-adv-x="618" d="M121 788q0 86 63.5 160t168 74t170 -74t65.5 -160q0 -94 -63.5 -157.5t-172.5 -63.5q-94 0 -162.5 68.5t-68.5 152.5zM10 -264l236 -133q162 143 250 324.5t82 375.5l-373 98q14 -221 -33 -370.5t-162 -294.5z" />
+ <glyph unicode="&#x3c;" horiz-adv-x="1064" d="M756 1024l-613 -307v-287l660 -307l78 213l-506 227l506 232z" />
+ <glyph unicode="=" horiz-adv-x="1028" d="M90 831h844v-272h-844v272zM90 379h844v-273h-844v273z" />
+ <glyph unicode="&#x3e;" horiz-adv-x="1064" d="M268 1024l613 -307v-287l-660 -307l-78 213l506 227l-506 232z" />
+ <glyph unicode="?" horiz-adv-x="1064" d="M225 977l-221 223q227 264 533 264q213 0 350 -109.5t137 -310.5q0 -74 -34 -136t-82 -104t-95 -77t-81 -69.5t-34 -67.5v-113h-381v164q0 63 51.5 126.5t112 105.5t111.5 90.5t51 85.5q0 96 -151 96q-61 0 -134.5 -49t-132.5 -119zM264 209q0 84 64.5 150.5t169.5 66.5 q106 0 174.5 -63.5t68.5 -153.5q0 -88 -67.5 -149.5t-175.5 -61.5q-98 0 -166 64.5t-68 146.5z" />
+ <glyph unicode="@" horiz-adv-x="1705" d="M1198 592v-258q4 -61 57 -94q63 -41 105 34q35 66 35 205q0 94 -36 189.5t-100.5 177.5t-172 133t-236.5 51q-170 0 -297 -88t-184.5 -211t-57.5 -252q0 -213 155 -368.5t394 -151.5v-184q-322 -2 -530.5 209t-208.5 495q0 125 47 253t135 236.5t230.5 177t316.5 68.5 t317.5 -68.5t232.5 -177t137 -236.5t48 -253q0 -137 -37 -232t-94 -136t-127 -54.5t-136.5 2t-118.5 44t-77 63.5l-32 59q-51 -86 -179 -86q-104 0 -181 57.5t-77 163.5q0 113 78 178.5t195 65.5q82 0 129 -47v47q0 74 -125 74q-84 0 -195 -62l-55 152q147 96 289 96 q88 0 163.5 -22.5t134 -86t58.5 -163.5zM928 356v47l-4 7q-4 8 -8.5 12t-12.5 10t-22.5 9t-30.5 3q-74 0 -74 -71q0 -27 19.5 -43.5t50.5 -16.5q27 0 52.5 13.5t29.5 29.5z" />
+ <glyph unicode="A" horiz-adv-x="1685" d="M1655 0h-821v268h194l-43 140h-420l-43 -140h160v-268h-651v268h139l276 899h-84v265h787l362 -1164h144v-268zM641 653h268l-135 443z" />
+ <glyph unicode="B" horiz-adv-x="1406" d="M51 0v266h119v901h-119v265h764q502 0 502 -351q0 -131 -44 -201.5t-157 -115.5q121 -33 195.5 -120t74.5 -228q0 -217 -131 -316.5t-426 -99.5h-778zM594 1161v-307h125q78 0 108.5 40t30.5 118q0 70 -38 109.5t-97 39.5h-129zM594 266h164q68 0 117 48t49 128 t-45 130.5t-127 50.5h-158v-357z" />
+ <glyph unicode="C" horiz-adv-x="1404" d="M1135 518l239 -262q-262 -289 -620 -289q-328 0 -525.5 197.5t-197.5 537.5q0 168 59.5 314.5t151.5 240t200.5 146.5t210.5 53q211 0 316 -139l47 115h321v-582h-366q-4 109 -59.5 183.5t-157.5 74.5q-109 0 -176.5 -117t-67.5 -289q0 -162 80 -273.5t219 -111.5 q31 0 62.5 8.5t58 20.5t53.5 29.5t46.5 34t40 36t30.5 31.5l22 27z" />
+ <glyph unicode="D" horiz-adv-x="1474" d="M51 0v272h127v889h-127v271h625q362 0 570 -197t208 -500q0 -350 -205 -542.5t-581 -192.5h-617zM598 272h78q156 2 231.5 130t75.5 333q0 180 -77 303t-230 123h-78v-889z" />
+ <glyph unicode="E" horiz-adv-x="1337" d="M51 1432h1235v-453h-354v182h-328v-282h389v-273h-389v-334h328v191h354v-463h-1235v272h129v889h-129v271z" />
+ <glyph unicode="F" horiz-adv-x="1306" d="M51 1432h1225v-453h-363v182h-309v-356h389v-273h-389v-260h209v-272h-762v272h127v889h-127v271z" />
+ <glyph unicode="G" horiz-adv-x="1589" d="M1067 0l-63 129q-37 -61 -127.5 -104t-202.5 -43q-274 0 -458.5 197.5t-184.5 522.5q0 324 189.5 539t438.5 215q70 0 130.5 -11t98.5 -25.5t70.5 -37t48 -38t32 -36t20.5 -24.5l43 148h305v-605h-367q-4 121 -71.5 190.5t-161.5 69.5q-121 0 -201 -113.5t-80 -271.5 q0 -162 76 -269t205 -107q139 0 197 100v86h-125v219h680v-219h-82v-512h-410z" />
+ <glyph unicode="H" horiz-adv-x="1572" d="M51 1432h664v-271h-127v-309h389v309h-121v271h666v-271h-119v-891h119v-270h-666v270h121v310h-389v-310h127v-270h-664v270h113v891h-113v271z" />
+ <glyph unicode="I" horiz-adv-x="782" d="M51 1432h680v-271h-127v-889h127v-272h-680v272h129v889h-129v271z" />
+ <glyph unicode="J" horiz-adv-x="948" d="M41 -395v270q41 -18 92 -18q170 0 170 295v1011h-176v269h770v-269h-170v-1011q0 -283 -135 -426.5t-442 -143.5q-50 0 -109 23z" />
+ <glyph unicode="K" horiz-adv-x="1669" d="M696 1167h-110v-333l381 333h-148v265h682v-265h-119l-297 -256l414 -645h119v-266h-799v266h139l-221 348l-151 -129v-219h110v-266h-645v266h111v901h-111v265h645v-265z" />
+ <glyph unicode="L" horiz-adv-x="1249" d="M51 1432h647v-271h-108v-889h264v260h344v-532h-1147v272h113v889h-113v271z" />
+ <glyph unicode="M" horiz-adv-x="2064" d="M1481 1071h-8q-35 -190 -60 -274l-215 -797h-428l-229 817q-35 119 -43 254h-9v-807h117v-264h-555v264h111v903h-109v265h776l207 -766l197 766h780v-265h-112v-901h112v-266h-647v266h115v684v121z" />
+ <glyph unicode="N" horiz-adv-x="1671" d="M1620 1167h-135v-1167h-441l-522 780v-516h158v-264h-629v264h121v903h-119v265h588l502 -752v487h-139v265h616v-265z" />
+ <glyph unicode="O" horiz-adv-x="1515" d="M764 -37q-334 0 -533.5 193.5t-199.5 566.5q0 332 200.5 539.5t518.5 207.5q367 0 551 -202.5t184 -558.5q0 -350 -188.5 -548t-532.5 -198zM764 285q96 0 152.5 115.5t56.5 308.5q0 199 -56.5 322.5t-168.5 123.5h-4q-92 0 -149 -120q-58 -122 -58 -312 q0 -199 63.5 -318.5t163.5 -119.5z" />
+ <glyph unicode="P" horiz-adv-x="1310" d="M51 1432h664q172 0 287.5 -34t174 -99.5t81 -137t22.5 -168.5q0 -195 -153.5 -300t-385.5 -105h-155v-318h125v-270h-660v270h115v893h-115v269zM586 1165v-356h92q90 0 145.5 45t55.5 125q0 90 -50.5 138t-140.5 48h-102z" />
+ <glyph unicode="Q" horiz-adv-x="1546" d="M1516 217v-104q0 -143 -80 -235.5t-228 -92.5q-119 0 -199.5 64.5t-117.5 162.5q-74 -12 -160 -12q-319 0 -509.5 186.5t-190.5 538.5q0 315 190.5 514t497.5 199q350 0 525 -193.5t175 -533.5q0 -326 -170 -512q35 -133 90 -133q41 0 41 92v59h136zM731 711 q119 0 230 -80q4 49 4 80q0 182 -69 305t-179 123h-3q-92 0 -162 -118q-71 -120 -71 -296q0 -66 8 -113q95 99 242 99zM731 307q57 0 109 45q-12 86 -36 129t-73 43q-84 0 -137 -149q61 -68 137 -68z" />
+ <glyph unicode="R" horiz-adv-x="1509" d="M51 1432h666q172 0 287.5 -32t175 -93.5t82 -131t22.5 -165.5q0 -117 -83 -198t-214 -99q109 -16 188.5 -99.5t79.5 -191.5q0 -139 50 -139q47 0 47 65v68h137v-113q0 -143 -70.5 -235.5t-228.5 -92.5q-199 0 -298 99.5t-99 281.5q0 143 -36 187.5t-169 44.5v-318h125 v-270h-662v270h115v893h-115v269zM588 1165v-348h92q90 0 143.5 43t53.5 123q0 182 -187 182h-102z" />
+ <glyph unicode="S" horiz-adv-x="1454" d="M51 0v516h381q0 -61 26.5 -103t71.5 -62.5t82 -29t78 -10.5q174 0 174 103q0 37 -58 65.5t-145.5 48t-189.5 57.5t-189 87t-145.5 141t-58.5 215q0 109 47 196t122 139t156.5 78.5t161.5 26.5q317 0 428 -208l21 172h352v-514h-371q0 111 -74.5 174t-187.5 63 q-51 0 -96 -29.5t-45 -78.5q0 -57 84 -100.5t202.5 -81.5t237.5 -87t203 -145t84 -230q0 -115 -49 -201.5t-131 -136t-174.5 -73t-188.5 -23.5q-268 0 -428 189l-22 -158h-359z" />
+ <glyph unicode="T" horiz-adv-x="1435" d="M1405 1432v-533h-301v262h-172v-889h170v-272h-762v272h168v889h-176v-262h-301v533h1374z" />
+ <glyph unicode="U" horiz-adv-x="1509" d="M31 1432h651v-265h-111v-442q0 -233 49.5 -316t155.5 -83q70 0 111 25.5t68.5 116.5t27.5 259v434h-111v271h607v-265h-111v-497q0 -705 -588 -705q-635 18 -635 694v506h-114v267z" />
+ <glyph unicode="V" horiz-adv-x="1583" d="M860 459h10q6 92 74 260l186 446h-108v267h582v-267h-101l-491 -1165h-377l-543 1165h-112v267h753v-267h-110l174 -458q59 -180 63 -248z" />
+ <glyph unicode="W" horiz-adv-x="2078" d="M655 463h11q4 47 100 508l102 461h426l129 -566q39 -182 62 -403h10q16 178 61 391l70 311h-113v267h586v-267h-112l-306 -1165h-485l-123 487q-27 98 -51 254h-8q-14 -102 -49 -249l-125 -492h-475l-271 1165h-114v267h675v-267h-118l53 -299q28 -122 65 -403z" />
+ <glyph unicode="X" horiz-adv-x="1710" d="M731 0h-680v264h178l301 391l-370 510h-107v267h811v-267h-119l201 -272l209 272h-180v267h680v-267h-174l-310 -395l369 -504h119v-266h-817v266h116l-155 209l-43 57l-211 -268h182v-264z" />
+ <glyph unicode="Y" horiz-adv-x="1370" d="M756 786h8q4 18 76 177l112 206h-112v263h551v-263h-111l-379 -665v-238h115v-266h-656v266h117v242l-397 661h-100v263h700v-263h-107l119 -217q6 -16 33 -82t31 -84z" />
+ <glyph unicode="Z" horiz-adv-x="1462" d="M63 893v539h1344l4 -273l-749 -889h380v262h363v-532h-1354v266l746 893h-385v-266h-349z" />
+ <glyph unicode="[" horiz-adv-x="794" d="M793 -430h-510v2097h512v-268h-123v-1561h121v-268z" />
+ <glyph unicode="\" horiz-adv-x="823" d="M0 1497h332l594 -1814h-332z" />
+ <glyph unicode="]" horiz-adv-x="794" d="M2 -430h510v2097h-512v-268h123v-1561h-121v-268z" />
+ <glyph unicode="_" d="M86 -90h1104v-111h-1104v111z" />
+ <glyph unicode="a" horiz-adv-x="1228" d="M160 700l-86 238q242 145 446 145q100 0 191.5 -20.5t176.5 -65.5t136 -132t51 -203v-400h123v-262h-438l-49 92q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115 q-135 -1 -305 -95zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="b" horiz-adv-x="1343" d="M63 0v266h87v1002h-87v268h496v-592q33 39 113 67.5t155 28.5q223 0 350 -158q122 -154 122 -364q0 -7 -1 -14q-4 -240 -131 -383.5t-342 -143.5q-74 0 -128 14.5t-80.5 31t-61.5 47.5l-31 -70h-461zM848 500q0 98 -41 175t-107 77q-63 0 -107 -73q-39 -64 -39 -162 q0 -14 1 -28q0 -102 44 -173.5t103 -71.5q66 0 106 69.5t40 186.5z" />
+ <glyph unicode="c" horiz-adv-x="1087" d="M1038 561h-301q-4 88 -34.5 151.5t-94.5 63.5q-86 0 -133 -76.5t-47 -185.5q0 -102 57.5 -171t153.5 -71h5q136 0 243 117l180 -209q-80 -88 -213 -148.5t-289 -60.5q-240 0 -401.5 143.5t-161.5 403.5q0 256 145.5 390t358.5 134q158 0 256 -116l35 98h241v-463z" />
+ <glyph unicode="d" horiz-adv-x="1298" d="M754 1270h-95v266h510v-1270h99v-266h-480l-34 76q-102 -94 -258 -94q-211 0 -343.5 159.5t-132.5 380.5q0 231 139.5 376.5t336.5 145.5q135 0 258 -77v303zM461 522q0 -98 41 -175t106 -77q63 0 108 73q39 64 38 162v27q0 102 -44 174t-104 72q-66 0 -105.5 -69.5 t-39.5 -186.5z" />
+ <glyph unicode="e" horiz-adv-x="1110" d="M907 340l172 -199q-207 -178 -528 -178q-215 0 -378 141.5t-163 413.5q0 246 156 390.5t395 144.5q180 0 312.5 -83t177.5 -214q23 -66 27 -156q1 -14 1 -27q0 -78 -24 -163h-645q6 -31 14 -51.5t29.5 -52.5t64.5 -48t103 -16q167 0 286 98zM416 612h309q-2 74 -32.5 119 t-119.5 45q-82 0 -117.5 -53.5t-39.5 -110.5z" />
+ <glyph unicode="f" horiz-adv-x="790" d="M842 1522l-123 -236q-45 20 -113 21q-66 0 -65 -109v-174h186v-266h-186v-490h123v-268h-611v268h82v490h-84v266h84v174q0 43 6 84t31 97.5t66 96.5t119.5 68.5t183.5 28.5q80 0 170 -15.5t131 -35.5z" />
+ <glyph unicode="g" horiz-adv-x="1310" d="M49 -240l109 228q49 -43 151.5 -87t204.5 -44q125 0 181.5 57t56.5 135v70q-88 -113 -240 -113q-246 0 -369 148.5t-123 363.5q0 244 133.5 385t381.5 141q63 0 123.5 -25.5t93.5 -57.5l41 63h487v-268h-123v-613q0 -262 -161.5 -411.5t-477.5 -151.5q-152 2 -272.5 56 t-196.5 124zM457 522q0 -98 41 -175t106 -77q63 0 108 73q39 64 38 162v27q0 102 -44 174t-104 72q-66 0 -105.5 -69.5t-39.5 -186.5z" />
+ <glyph unicode="h" horiz-adv-x="1353" d="M694 0h-643v268h86v1002h-86v266h496v-639q25 41 124 102.5t197 61.5q113 0 189 -38t110.5 -108.5t47 -140t12.5 -164.5v-342h96v-268h-526v557q0 39 -2 61.5t-12.5 57.5t-37 53.5t-69.5 18.5q-129 0 -129 -191v-289h147v-268z" />
+ <glyph unicode="i" horiz-adv-x="675" d="M109 1323q0 78 60 143.5t160.5 65.5t163 -65.5t62.5 -143.5q0 -88 -60.5 -147.5t-164.5 -59.5q-92 0 -156.5 64.5t-64.5 142.5zM625 0h-574v268h86v490h-86v266h475v-756h99v-268z" />
+ <glyph unicode="j" horiz-adv-x="737" d="M233 1323q0 78 60.5 143.5t161 65.5t163 -65.5t62.5 -143.5q0 -88 -60.5 -147.5t-164.5 -59.5q-92 0 -157 64.5t-65 142.5zM262 758h-86v266h475v-881q0 -86 -12 -157.5t-51 -151.5t-104.5 -135t-181.5 -91t-273 -36v274q68 0 114.5 24t69.5 51.5t35 77.5t13 74.5t1 69.5 v615z" />
+ <glyph unicode="k" horiz-adv-x="1339" d="M78 0v266h84v1002h-84v268h473v-926l237 142h-147v270h573v-270h-157l-139 -88l276 -396h102v-268h-606v268h86l-155 221l-72 -49v-172h76v-268h-547z" />
+ <glyph unicode="l" horiz-adv-x="679" d="M649 0h-598v268h88v1004h-86v264h475v-1268h121v-268z" />
+ <glyph unicode="m" horiz-adv-x="1859" d="M659 0h-608v268h86v484h-86v270h387l45 -121q125 150 256 150q72 0 128.5 -14.5t92 -42t51 -46t33.5 -45.5q131 147 302 148q119 0 198.5 -40t117.5 -115t52.5 -152.5t14.5 -184.5v-283h100v-276h-520v502q0 12 1 44t1 50t-2 46t-7.5 45.5t-13.5 34.5t-22.5 25.5 t-34.5 8.5q-61 0 -85 -38t-24 -112v-332h123v-274h-530v522q0 137 -14.5 185.5t-71.5 48.5q-92 0 -92 -123v-365h122v-268z" />
+ <glyph unicode="n" horiz-adv-x="1325" d="M682 0h-631v268h86v490h-86v266h436l48 -127q119 164 292 164q92 0 161 -26.5t108 -68.5t62.5 -104.5t31.5 -121t8 -130.5v-342h96v-268h-516v559v48q0 11 -5 47t-16.5 50.5t-35 30t-57.5 15.5q-129 0 -129 -191v-291h147v-268z" />
+ <glyph unicode="o" horiz-adv-x="1175" d="M594 -27q-238 0 -406 143q-166 141 -165 394q0 248 168 394q167 144 393 145q250 -4 407 -148q162 -147 162 -399q0 -243 -158 -385q-159 -144 -401 -144zM594 236q63 0 101 76.5t38 195.5t-40 195.5t-109 78.5q-59 0 -100.5 -77.5t-41.5 -190.5q0 -125 43 -201.5 t109 -76.5z" />
+ <glyph unicode="p" horiz-adv-x="1306" d="M51 -150h86v902h-86v272h469l35 -72q119 88 248 88q229 0 356 -153.5t127 -382.5q0 -240 -132 -383.5t-335 -143.5q-172 0 -266 80v-207h111v-268h-613v268zM846 500q0 98 -41 175t-107 77q-63 0 -107 -73q-39 -64 -39 -162q0 -14 1 -28q0 -102 44 -173.5t103 -71.5 q66 0 106 69.5t40 186.5z" />
+ <glyph unicode="q" horiz-adv-x="1351" d="M651 -203h133v271q-104 -86 -262 -86q-201 0 -336 147t-135 389q0 256 137.5 391t352.5 135q88 0 155.5 -27.5t87.5 -53.5l39 61h477v-266h-100v-955h100v-274h-649v268zM784 526q0 98 -41 175t-106 77q-63 0 -108 -72q-39 -64 -38 -162q0 -14 1 -28q0 -102 44 -174 t103 -72q66 0 105.5 69.5t39.5 186.5z" />
+ <glyph unicode="r" horiz-adv-x="847" d="M631 0h-580v268h90v490h-88v266h385l62 -131q25 45 44 70.5t54 55.5t89 43t130 13v-368q-147 0 -218 -49.5t-71 -178.5v-211h103v-268z" />
+ <glyph unicode="s" horiz-adv-x="1040" d="M31 0v369h274q0 -49 44 -83t89 -44.5t84 -10.5q90 0 90 70q0 18 -16 31.5t-48 24t-66 19.5l-84 22q-50 13 -87 26q-272 96 -272 328q0 143 115.5 220t244.5 77q102 0 187.5 -46.5t118.5 -113.5l16 135h252v-369h-266q0 63 -64.5 109.5t-144.5 46.5t-80 -74 q0 -16 12 -29.5t41 -26.5t51.5 -21.5t72.5 -24.5l75 -25q80 -27 120.5 -45t98 -56t84 -94.5t26.5 -131.5q0 -139 -112.5 -223.5t-268.5 -84.5q-104 0 -189 43t-124 107l-18 -125h-256z" />
+ <glyph unicode="t" horiz-adv-x="743" d="M31 1024h110v188l377 121v-309h195v-266h-195v-365q0 -8 -1 -29.5t0 -34t2 -29.5t5 -27.5t11.5 -21.5t19.5 -16.5t29 -5.5q47 0 129 35v-262q-41 -16 -122 -27.5t-126 -11.5q-90 0 -150.5 21.5t-94.5 52.5t-51 94.5t-21.5 114.5t-6.5 147v365h-110v266z" />
+ <glyph unicode="u" d="M31 1024h495v-494q0 -135 24.5 -196.5t86.5 -61.5q55 0 92 44t37 112v326h-135v270h530v-752h101v-272h-388l-55 129l-20 -23q-20 -25 -28 -31l-30 -29q-23 -22 -41 -30t-44.5 -21t-58.5 -18.5t-69 -5.5q-227 0 -319 119t-92 381v283h-86v270z" />
+ <glyph unicode="v" horiz-adv-x="1255" d="M668 332h8q4 94 61 225l99 207h-109v260h528v-260h-106l-354 -764h-318l-381 764h-96v260h623v-260h-103l94 -217q46 -133 54 -215z" />
+ <glyph unicode="w" horiz-adv-x="1785" d="M578 313h4q20 268 84 482l65 229h379l78 -266q59 -188 88 -445h4q18 193 55 324l35 121h-84v266h500v-266h-84l-240 -758h-432l-141 463l-148 -463h-422l-231 758h-88v266h551v-266h-70l45 -150q42 -139 52 -295z" />
+ <glyph unicode="x" horiz-adv-x="1329" d="M768 1024h522v-256h-135l-217 -219l258 -295h96v-254h-653v254h96l-155 166l-148 -166h133v-254h-524v254h137l209 235l-254 279h-92v256h664v-256h-97l144 -156l153 156h-137v256z" />
+ <glyph unicode="y" horiz-adv-x="1187" d="M782 764h-94v260h520v-254h-98l-389 -946q-23 -49 -40 -80t-54 -76t-80 -70.5t-111.5 -44t-154.5 -18.5q-139 8 -248 103l153 241q68 -59 119 -69q9 -2 18 -2q34 0 62 26q35 33 51 84l29 86l-399 758h-84v262h630v-260h-96l133 -320z" />
+ <glyph unicode="z" horiz-adv-x="1132" d="M51 614v410h1014v-264l-487 -492h215v140h288v-408h-1030v268l494 492h-228v-146h-266z" />
+ <glyph unicode="{" horiz-adv-x="921" d="M532 1669l211 -217q-115 -27 -170 -64.5t-57 -85t7 -106.5t23.5 -128t-5.5 -153q-18 -82 -113 -276q70 -176 84 -252q18 -92 4 -167t-21.5 -140.5t0 -116.5t68 -95t180.5 -77l-211 -219q-123 39 -197.5 92t-103 112.5t-39 130t-2 145.5t5.5 160t-16 171 q-8 37 -39.5 100.5t-60.5 110.5l-29 45q137 201 160 293q23 88 22.5 196.5t-3.5 181t14.5 147.5t87 127t200.5 85z" />
+ <glyph unicode="|" horiz-adv-x="819" d="M313 -207v1882h191v-1882h-191z" />
+ <glyph unicode="}" horiz-adv-x="921" d="M446 1669l-210 -217q115 -27 170 -64.5t57 -85t-7.5 -106.5t-23.5 -128t6 -153q18 -82 113 -276q-70 -176 -84 -252q-18 -92 -4 -167t21.5 -140.5t0 -116.5t-68 -95t-180.5 -77l210 -219q123 39 198 92t103.5 112.5t39 130t2 145.5t-5.5 160t16 171q8 37 39.5 100.5 t60.5 110.5l29 45q-137 201 -160 293q-23 88 -23 196.5t3.5 181t-14 147.5t-87.5 127t-201 85z" />
+ <glyph unicode="~" horiz-adv-x="819" d="M0 629v-223q57 55 163.5 70.5t221.5 -27.5q92 -45 234.5 -44t195.5 64v223q-47 -57 -152.5 -78.5t-228.5 23.5q-113 55 -243 51t-191 -59z" />
+ <glyph unicode="&#xa3;" horiz-adv-x="1454" d="M1399 1432v-332q-88 76 -221.5 130t-272.5 52t-200 -92q-31 -43 -35 -98.5t9 -102.5t42 -115h459v-102h-414q41 -94 59.5 -161.5t11 -162t-58.5 -176.5h264v260h344v-532h-1146v272h112q23 37 37 75t22.5 65.5t3.5 67.5t-5 57.5t-18 62.5l-21 55q-4 10 -30 60.5t-28 56.5 h-293v102h250q-66 178 4 365q74 199 269.5 295t430.5 67.5t425 -169.5z" />
+ <glyph unicode="&#xa5;" horiz-adv-x="1370" d="M973 629l-72 -125v-39h500v-103h-500v-96h115v-266h-656v266h117v96h-502v103h502v43l-71 121h-431v102h369l-264 438h-100v263h700v-263h-107l119 -217q6 -16 33 -81.5t31 -84.5h8q4 18 76 177l112 206h-112v263h551v-263h-111l-250 -438h371v-102h-428z" />
+ <glyph unicode="&#xa9;" horiz-adv-x="1705" d="M100 664q0 98 31 200.5t94.5 198.5t151.5 171t211 120t262 45q174 0 320.5 -68.5t239.5 -177.5t144.5 -237t51.5 -252q0 -315 -200 -520t-556 -205q-317 0 -533.5 221t-216.5 504zM291 664q0 -211 161.5 -376t397.5 -165q266 0 415.5 152.5t149.5 388.5 q0 203 -156.5 376.5t-408.5 173.5q-170 0 -302 -88t-194.5 -210.5t-62.5 -251.5zM1192 690h-213q-2 61 -23.5 106.5t-66.5 45.5q-61 0 -96 -55.5t-35 -129t42 -124t111 -50.5q98 0 176 82l127 -145q-57 -63 -152 -107.5t-206 -44.5q-170 0 -284.5 102.5t-114.5 291.5 q0 182 103.5 276t254.5 94q111 0 182 -84l23 72h172v-330z" />
+ <glyph unicode="&#xaa;" horiz-adv-x="1228" d="M160 700l-86 238q242 145 446 145q100 0 191.5 -20.5t176.5 -65.5t136 -132t51 -203v-400h123v-262h-438l-49 92q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115 q-135 -1 -305 -95zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xab;" horiz-adv-x="1433" d="M664 1229l-510 -512v-287l557 -512l77 213l-403 432l403 436zM1155 1229l-510 -512v-287l557 -512l78 213l-403 432l403 436z" />
+ <glyph unicode="&#xad;" horiz-adv-x="790" d="M96 625h594v-273h-594v273z" />
+ <glyph unicode="&#xae;" horiz-adv-x="1705" d="M100 664q0 98 31 200.5t94.5 198.5t151.5 171t211 120t262 45q174 0 320.5 -68.5t239.5 -177.5t144.5 -237t51.5 -252q0 -315 -200 -520t-556 -205q-317 0 -533.5 221t-216.5 504zM291 664q0 -211 161.5 -376t397.5 -165q266 0 415.5 152.5t149.5 388.5 q0 203 -156.5 376.5t-408.5 173.5q-170 0 -302 -88t-194.5 -210.5t-62.5 -251.5zM510 1030h356q174 0 236.5 -58.5t62.5 -166.5q0 -61 -44 -102t-113 -52q59 -16 101 -59t42 -98q0 -76 27 -76q23 0 22 35v36h76v-61q0 -76 -38 -125t-122 -49q-203 0 -203 203 q0 76 -22.5 100.5t-95.5 24.5v-170h67v-142h-352v142h63v473h-63v145zM795 887v-185h49q106 0 106 89q0 96 -102 96h-53z" />
+ <glyph unicode="&#xb1;" d="M750 934h342v-223h-342v-344h-224v344h-342v223h342v340h224v-340zM184 37v223h908v-223h-908z" />
+ <glyph unicode="&#xb2;" horiz-adv-x="819" d="M252 1161l-119 125q123 135 283 135q113 0 186.5 -66.5t73.5 -172.5q0 -88 -37 -133t-135 -111q-133 -86 -166 -145h192v88h183v-234h-586v160q45 72 74.5 106.5t83.5 75.5q23 18 80 57t84.5 67t27.5 58q0 72 -94 72q-27 0 -54.5 -15.5t-38.5 -27.5t-38 -39z" />
+ <glyph unicode="&#xb3;" horiz-adv-x="819" d="M262 1204l-106 129q35 37 100.5 69t132.5 32q131 0 204 -61.5t73 -168.5q0 -74 -80 -137q100 -76 100 -178q0 -115 -89 -177.5t-208 -62.5q-78 0 -150.5 37t-105.5 86l109 115q82 -70 147 -70q96 0 96 82t-96 82h-78v127h78q43 0 69.5 20.5t26.5 55.5q0 41 -29.5 63.5 t-66.5 22.5q-66 0 -127 -66z" />
+ <glyph unicode="&#xb9;" horiz-adv-x="1015" d="M145 936l-94 227l42 21l44 21l39 21q29 15 43 26l41 31q27 19 47 40l48 48q28 28 55 61h364v-1160h191v-272h-820v272h205v760z" />
+ <glyph unicode="&#xba;" horiz-adv-x="1175" d="M594 -27q-238 0 -406 143q-166 141 -165 394q0 248 168 394q167 144 393 145q250 -4 407 -148q162 -147 162 -399q0 -243 -158 -385q-159 -144 -401 -144zM594 236q63 0 101 76.5t38 195.5t-40 195.5t-109 78.5q-59 0 -100.5 -77.5t-41.5 -190.5q0 -125 43 -201.5 t109 -76.5z" />
+ <glyph unicode="&#xbb;" horiz-adv-x="1433" d="M279 1229l509 -512v-287l-557 -512l-77 213l403 432l-403 436zM770 1229l510 -512v-287l-557 -512l-78 213l404 432l-404 436z" />
+ <glyph unicode="&#xbf;" horiz-adv-x="1064" d="M764 741q0 -84 -64.5 -150.5t-169.5 -66.5q-106 0 -174.5 63.5t-68.5 153.5q0 88 67.5 149.5t175.5 61.5q98 0 166 -64.5t68 -146.5zM803 -27l221 -223q-227 -264 -532 -264q-213 0 -350.5 109.5t-137.5 310.5q0 74 34 136t82 104t95 77t81 70t34 67v113h381v-164 q0 -63 -51.5 -126.5t-112 -105.5t-111.5 -90t-51 -85q0 -96 152 -97q61 0 134 49t132 119z" />
+ <glyph unicode="&#xc0;" horiz-adv-x="1685" d="M535 1700l141 196l268 -368l-176 -90zM1655 0h-821v268h194l-43 140h-420l-43 -140h160v-268h-651v268h139l276 899h-84v265h787l362 -1164h144v-268zM641 653h268l-135 443z" />
+ <glyph unicode="&#xc1;" horiz-adv-x="1685" d="M1006 1700l-142 196l-268 -368l176 -90zM1655 0h-821v268h194l-43 140h-420l-43 -140h160v-268h-651v268h139l276 899h-84v265h787l362 -1164h144v-268zM641 653h268l-135 443z" />
+ <glyph unicode="&#xc2;" horiz-adv-x="1685" d="M778 1612l240 -148l127 107l-303 229h-144l-329 -264l176 -90zM1655 0h-821v268h194l-43 140h-420l-43 -140h160v-268h-651v268h139l276 899h-84v265h787l362 -1164h144v-268zM641 653h268l-135 443z" />
+ <glyph unicode="&#xc3;" horiz-adv-x="1685" d="M360 1694v-224q57 55 164 70.5t221 -27.5q94 -43 236.5 -38.5t169.5 59.5v205q-143 -129 -356 -37q-113 55 -243.5 51t-191.5 -59zM1655 0h-821v268h194l-43 140h-420l-43 -140h160v-268h-651v268h139l276 899h-84v265h787l362 -1164h144v-268zM641 653h268l-135 443z " />
+ <glyph unicode="&#xc4;" horiz-adv-x="1685" d="M508 1657h192v-172h-192v172zM836 1657h192v-172h-192v172zM1655 0h-821v268h194l-43 140h-420l-43 -140h160v-268h-651v268h139l276 899h-84v265h787l362 -1164h144v-268zM641 653h268l-135 443z" />
+ <glyph unicode="&#xc5;" horiz-adv-x="1685" d="M1655 0h-821v268h194l-43 140h-420l-43 -140h160v-268h-651v268h139l276 899h-84v265h787l362 -1164h144v-268zM641 653h268l-135 443z" />
+ <glyph unicode="&#xc6;" horiz-adv-x="2181" d="M2159 979h-355v182h-327v-282h389v-273h-389v-334h327v191h355v-463h-1235v272h129v136h-455l-84 -140h160v-268h-651v268h139l563 899h-84v265h1518v-453zM743 653h310v443z" />
+ <glyph unicode="&#xc7;" horiz-adv-x="1404" d="M1135 518l239 -262q-262 -289 -620 -289q-328 0 -525.5 197.5t-197.5 537.5q0 168 59.5 314.5t151.5 240t200.5 146.5t210.5 53q211 0 316 -139l47 115h321v-582h-366q-4 109 -59.5 183.5t-157.5 74.5q-109 0 -176.5 -117t-67.5 -289q0 -162 80 -273.5t219 -111.5 q31 0 62.5 8.5t58 20.5t53.5 29.5t46.5 34t40 36t30.5 31.5l22 27zM690 -18l-57 -191q104 47 190 45q74 -2 54 -59q-23 -57 -104 -74.5t-157 23.5l-16 -109l18 -4q18 -4 48 -7t67 -4t75 8t71.5 26.5t59.5 56.5t34 94q10 78 -16.5 110.5t-72.5 28.5l-96 -8l27 64h-125z" />
+ <glyph unicode="&#xc8;" horiz-adv-x="1337" d="M453 1706l141 197l268 -369l-176 -90zM51 1432h1235v-453h-354v182h-328v-282h389v-273h-389v-334h328v191h354v-463h-1235v272h129v889h-129v271z" />
+ <glyph unicode="&#xc9;" horiz-adv-x="1337" d="M874 1708l-141 197l-268 -369l176 -90zM51 1432h1235v-453h-354v182h-328v-282h389v-273h-389v-334h328v191h354v-463h-1235v272h129v889h-129v271z" />
+ <glyph unicode="&#xca;" horiz-adv-x="1337" d="M737 1612l240 -148l127 107l-303 229h-144l-329 -264l176 -90zM51 1432h1235v-453h-354v182h-328v-282h389v-273h-389v-334h328v191h354v-463h-1235v272h129v889h-129v271z" />
+ <glyph unicode="&#xcb;" horiz-adv-x="1275" d="M406 1657h192v-172h-192v172zM733 1657h193v-172h-193v172zM51 1432h1235v-453h-354v182h-328v-282h389v-273h-389v-334h328v191h354v-463h-1235v272h129v889h-129v271z" />
+ <glyph unicode="&#xcc;" horiz-adv-x="782" d="M158 1708l141 197l268 -369l-176 -90zM51 1432h680v-271h-127v-889h127v-272h-680v272h129v889h-129v271z" />
+ <glyph unicode="&#xcd;" horiz-adv-x="782" d="M629 1708l-142 197l-268 -369l176 -90zM51 1432h680v-271h-127v-889h127v-272h-680v272h129v889h-129v271z" />
+ <glyph unicode="&#xce;" horiz-adv-x="782" d="M389 1612l240 -148l127 107l-303 229h-144l-329 -264l176 -90zM51 1432h680v-271h-127v-889h127v-272h-680v272h129v889h-129v271z" />
+ <glyph unicode="&#xcf;" horiz-adv-x="782" d="M121 1677h192v-172h-192v172zM449 1677h192v-172h-192v172zM51 1432h680v-271h-127v-889h127v-272h-680v272h129v889h-129v271z" />
+ <glyph unicode="&#xd1;" horiz-adv-x="1671" d="M428 1694v-224q57 55 163.5 70.5t221.5 -27.5q94 -43 238.5 -38.5t191.5 59.5v205q-63 -57 -160.5 -70.5t-220.5 33.5q-113 55 -243 51t-191 -59zM1620 1167h-135v-1167h-441l-522 780v-516h158v-264h-629v264h121v903h-119v265h588l502 -752v487h-139v265h616v-265z" />
+ <glyph unicode="&#xd2;" horiz-adv-x="1515" d="M516 1735l141 196l269 -368l-176 -90zM764 -37q-334 0 -533.5 193.5t-199.5 566.5q0 332 200.5 539.5t518.5 207.5q367 0 551 -202.5t184 -558.5q0 -350 -188.5 -548t-532.5 -198zM764 285q96 0 152.5 115.5t56.5 308.5q0 199 -56.5 322.5t-168.5 123.5 q-94 2 -152.5 -120t-58.5 -312q0 -199 63.5 -318.5t163.5 -119.5z" />
+ <glyph unicode="&#xd3;" horiz-adv-x="1515" d="M983 1739l-141 196l-269 -368l177 -90zM764 -37q-334 0 -533.5 193.5t-199.5 566.5q0 332 200.5 539.5t518.5 207.5q367 0 551 -202.5t184 -558.5q0 -350 -188.5 -548t-532.5 -198zM764 285q96 0 152.5 115.5t56.5 308.5q0 199 -56.5 322.5t-168.5 123.5 q-94 2 -152.5 -120t-58.5 -312q0 -199 63.5 -318.5t163.5 -119.5z" />
+ <glyph unicode="&#xd4;" horiz-adv-x="1515" d="M758 1632l239 -147l127 106l-303 230h-143l-330 -265l176 -90zM764 -37q-334 0 -533.5 193.5t-199.5 566.5q0 332 200.5 539.5t518.5 207.5q367 0 551 -202.5t184 -558.5q0 -350 -188.5 -548t-532.5 -198zM764 285q96 0 152.5 115.5t56.5 308.5q0 199 -56.5 322.5 t-168.5 123.5q-94 2 -152.5 -120t-58.5 -312q0 -199 63.5 -318.5t163.5 -119.5z" />
+ <glyph unicode="&#xd5;" horiz-adv-x="1515" d="M342 1819v-223q57 55 163.5 70.5t221.5 -27.5q92 -45 234.5 -44t195.5 64v223q-47 -57 -152.5 -78.5t-228.5 23.5q-113 55 -243 51t-191 -59zM764 -37q-334 0 -533.5 193.5t-199.5 566.5q0 332 200.5 539.5t518.5 207.5q367 0 551 -202.5t184 -558.5q0 -350 -188.5 -548 t-532.5 -198zM764 285q96 0 152.5 115.5t56.5 308.5q0 199 -56.5 322.5t-168.5 123.5h-4q-92 0 -149 -120q-58 -122 -58 -312q0 -199 63.5 -318.5t163.5 -119.5z" />
+ <glyph unicode="&#xd6;" horiz-adv-x="1515" d="M487 1698h193v-172h-193v172zM815 1698h193v-172h-193v172zM764 -37q-334 0 -533.5 193.5t-199.5 566.5q0 332 200.5 539.5t518.5 207.5q367 0 551 -202.5t184 -558.5q0 -350 -188.5 -548t-532.5 -198zM764 285q96 0 152.5 115.5t56.5 308.5q0 199 -56.5 322.5 t-168.5 123.5q-94 2 -152.5 -120t-58.5 -312q0 -199 63.5 -318.5t163.5 -119.5z" />
+ <glyph unicode="&#xd7;" d="M152 195l294 294l-294 293l192 193l295 -293l293 293l192 -193l-293 -293l293 -294l-192 -193l-293 295l-295 -295z" />
+ <glyph unicode="&#xd9;" horiz-adv-x="1509" d="M555 1706l141 197l269 -369l-177 -90zM31 1432h651v-265h-111v-442q0 -233 49.5 -316t155.5 -83q70 0 111 25.5t68.5 116.5t27.5 259v434h-111v271h607v-265h-111v-497q0 -705 -588 -705q-635 18 -635 694v506h-114v267z" />
+ <glyph unicode="&#xda;" horiz-adv-x="1509" d="M1006 1706l-142 197l-268 -369l176 -90zM31 1432h651v-265h-111v-442q0 -233 49.5 -316t155.5 -83q70 0 111 25.5t68.5 116.5t27.5 259v434h-111v271h607v-265h-111v-497q0 -705 -588 -705q-635 18 -635 694v506h-114v267z" />
+ <glyph unicode="&#xdb;" horiz-adv-x="1509" d="M778 1612l240 -148l127 107l-303 229h-144l-329 -264l176 -90zM31 1432h651v-265h-111v-442q0 -233 49.5 -316t155.5 -83q70 0 111 25.5t68.5 116.5t27.5 259v434h-111v271h607v-265h-111v-497q0 -705 -588 -705q-635 18 -635 694v506h-114v267z" />
+ <glyph unicode="&#xdc;" horiz-adv-x="1509" d="M489 1657h193v-172h-193v172zM870 1657h193v-172h-193v172zM31 1432h651v-265h-111v-442q0 -233 49.5 -316t155.5 -83q70 0 111 25.5t68.5 116.5t27.5 259v434h-111v271h607v-265h-111v-497q0 -705 -588 -705q-635 18 -635 694v506h-114v267z" />
+ <glyph unicode="&#xe0;" horiz-adv-x="1228" d="M1075 262h123v-262h-438l-49 92q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115q-135 0 -305 -95l-86 238q242 145 446 145h39l-229 256l141 197l268 -369l-166 -86 q94 -4 178.5 -27.5t160 -70.5t119.5 -129t44 -192v-400zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xe1;" horiz-adv-x="1228" d="M801 1339l-142 197l-268 -369l176 -90zM160 700l-86 238q242 145 446 145q100 0 191.5 -20.5t176.5 -65.5t136 -132t51 -203v-400h123v-262h-438l-49 92q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5q55 0 103 -16.5t69.5 -31 t29.5 -24.5v72q0 115 -194 115q-135 -1 -305 -95zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xe2;" horiz-adv-x="1228" d="M516 1243l240 -147l127 106l-303 230h-144l-330 -265l177 -90zM160 700l-86 238q242 145 446 145q100 0 191.5 -20.5t176.5 -65.5t136 -132t51 -203v-400h123v-262h-438l-49 92q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5 q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115q-135 -1 -305 -95zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xe3;" horiz-adv-x="1228" d="M162 1366v-223q57 55 163.5 70.5t221.5 -27.5q94 -43 238.5 -39t191.5 59v205q-63 -57 -160.5 -70.5t-220.5 33.5q-113 55 -243 51t-191 -59zM160 700l-86 238q242 145 446 145q100 0 191.5 -20.5t176.5 -65.5t136 -132t51 -203v-400h123v-262h-438l-49 92 q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115q-135 -1 -305 -95zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xe4;" horiz-adv-x="1228" d="M260 1370h193v-172h-193v172zM588 1370h192v-172h-192v172zM160 700l-86 238q242 145 446 145q100 0 191.5 -20.5t176.5 -65.5t136 -132t51 -203v-400h123v-262h-438l-49 92q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5 q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115q-135 -1 -305 -95zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xe5;" horiz-adv-x="1228" d="M160 700l-86 238q242 145 446 145q100 0 191.5 -20.5t176.5 -65.5t136 -132t51 -203v-400h123v-262h-438l-49 92q-51 -70 -129 -100.5t-148 -30.5q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115 q-135 -1 -305 -95zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xe6;" horiz-adv-x="1912" d="M1710 340l172 -199q-207 -178 -528 -178q-129 0 -247 53.5t-194 159.5q-84 -215 -479 -215q-162 0 -282.5 88t-120.5 254q0 170 124 273.5t302 103.5q55 0 103 -16.5t69.5 -31t29.5 -24.5v72q0 115 -194 115q-135 0 -305 -95l-86 238q242 145 446 145q315 0 463 -161 q154 131 381 131q180 0 312 -83t177 -214q23 -66 28 -156t-23 -190h-646q6 -31 14.5 -51.5t30 -52.5t64.5 -48t102 -16q168 0 287 98zM1219 612h309q-2 74 -33 119t-119 45q-82 0 -117.5 -53.5t-39.5 -110.5zM659 295v78q-35 59 -120 59q-119 0 -119 -108q0 -43 30.5 -70 t77.5 -27q43 0 79 20.5t52 47.5z" />
+ <glyph unicode="&#xe7;" horiz-adv-x="1087" d="M887 389l180 -209q-76 -82 -203 -142.5t-274 -66.5l-17 -43l83 9q40 4 62.5 -26t14.5 -95q-10 -66 -47 -104t-83 -47t-89 -8t-74 7l-30 6l16 92q66 -33 136.5 -18.5t86.5 61.5q18 53 -53 54q-68 -2 -158 -39l47 155q-211 25 -347 165.5t-136 377.5q0 256 145.5 390 t358.5 134q158 0 256 -116l35 98h241v-463h-301q-4 88 -34.5 151.5t-94.5 63.5q-86 0 -133 -76.5t-47 -185.5q0 -102 57.5 -171t153.5 -71q139 -2 248 117z" />
+ <glyph unicode="&#xe8;" horiz-adv-x="1110" d="M330 1339l141 197l268 -369l-176 -90zM907 340l172 -199q-207 -178 -528 -178q-215 0 -378 141.5t-163 413.5q0 246 156 390.5t395 144.5q180 0 312.5 -83t177.5 -214q23 -66 27.5 -156t-23.5 -190h-645q6 -31 14 -51.5t29.5 -52.5t64.5 -48t103 -16q167 0 286 98z M416 612h309q-2 74 -32.5 119t-119.5 45q-82 0 -117.5 -53.5t-39.5 -110.5z" />
+ <glyph unicode="&#xe9;" horiz-adv-x="1110" d="M793 1339l-142 197l-268 -369l176 -90zM907 340l172 -199q-207 -178 -528 -178q-215 0 -378 141.5t-163 413.5q0 246 156 390.5t395 144.5q180 0 312.5 -83t177.5 -214q23 -66 27.5 -156t-23.5 -190h-645q6 -31 14 -51.5t29.5 -52.5t64.5 -48t103 -16q167 0 286 98z M416 612h309q-2 74 -32.5 119t-119.5 45q-82 0 -117.5 -53.5t-39.5 -110.5z" />
+ <glyph unicode="&#xea;" horiz-adv-x="1110" d="M567 1243l240 -147l127 106l-303 230h-144l-329 -265l176 -90zM907 340l172 -199q-207 -178 -528 -178q-215 0 -378 141.5t-163 413.5q0 246 156 390.5t395 144.5q180 0 312.5 -83t177.5 -214q23 -66 27.5 -156t-23.5 -190h-645q6 -31 14 -51.5t29.5 -52.5t64.5 -48 t103 -16q167 0 286 98zM416 612h309q-2 74 -32.5 119t-119.5 45q-82 0 -117.5 -53.5t-39.5 -110.5z" />
+ <glyph unicode="&#xeb;" horiz-adv-x="1110" d="M299 1370h193v-172h-193v172zM627 1370h192v-172h-192v172zM907 340l172 -199q-207 -178 -528 -178q-215 0 -378 141.5t-163 413.5q0 246 156 390.5t395 144.5q180 0 312.5 -83t177.5 -214q23 -66 27.5 -156t-23.5 -190h-645q6 -31 14 -51.5t29.5 -52.5t64.5 -48t103 -16 q167 0 286 98zM416 612h309q-2 74 -32.5 119t-119.5 45q-82 0 -117.5 -53.5t-39.5 -110.5z" />
+ <glyph unicode="&#xec;" horiz-adv-x="675" d="M104 1339l142 197l268 -369l-176 -90zM625 0h-574v268h86v490h-86v266h475v-756h99v-268z" />
+ <glyph unicode="&#xed;" horiz-adv-x="675" d="M547 1339l-141 197l-269 -369l176 -90zM625 0h-574v268h86v490h-86v266h475v-756h99v-268z" />
+ <glyph unicode="&#xee;" horiz-adv-x="675" d="M307 1243l240 -147l127 106l-303 230h-144l-329 -265l176 -90zM625 0h-574v268h86v490h-86v266h475v-756h99v-268z" />
+ <glyph unicode="&#xef;" horiz-adv-x="675" d="M53 1370h193v-172h-193v172zM334 1370h192v-172h-192v172zM625 0h-574v268h86v490h-86v266h475v-756h99v-268z" />
+ <glyph unicode="&#xf1;" horiz-adv-x="1325" d="M244 1366v-223q57 55 163.5 70.5t221.5 -27.5q94 -43 238.5 -39t191.5 59v205q-63 -57 -160.5 -70.5t-220.5 33.5q-113 55 -243 51t-191 -59zM682 0h-631v268h86v490h-86v266h436l48 -127q119 164 292 164q92 0 161 -26.5t108 -68.5t62.5 -104.5t31.5 -121t8 -130.5v-342 h96v-268h-516v559v48q0 11 -5 47t-16.5 50.5t-35 30t-57.5 15.5q-129 0 -129 -191v-291h147v-268z" />
+ <glyph unicode="&#xf2;" horiz-adv-x="1175" d="M348 1315l141 196l269 -368l-176 -90zM594 -27q-238 0 -406 142.5t-165 400.5q2 244 168.5 388.5t392.5 144.5q250 -4 407.5 -147.5t161.5 -393.5q2 -248 -157.5 -391.5t-401.5 -143.5zM594 236q63 0 101 76.5t38 195.5t-40 195.5t-109 78.5q-59 0 -100.5 -77.5 t-41.5 -190.5q0 -125 43 -201.5t109 -76.5z" />
+ <glyph unicode="&#xf3;" horiz-adv-x="1175" d="M819 1315l-141 196l-268 -368l176 -90zM594 -27q-238 0 -406 142.5t-165 400.5q2 244 168.5 388.5t392.5 144.5q250 -4 407.5 -147.5t161.5 -393.5q2 -248 -157.5 -391.5t-401.5 -143.5zM594 236q63 0 101 76.5t38 195.5t-40 195.5t-109 78.5q-59 0 -100.5 -77.5 t-41.5 -190.5q0 -125 43 -201.5t109 -76.5z" />
+ <glyph unicode="&#xf4;" horiz-adv-x="1175" d="M584 1243l239 -147l127 106l-303 230h-143l-330 -265l176 -90zM594 -27q-238 0 -406 142.5t-165 400.5q2 244 168.5 388.5t392.5 144.5q250 -4 407.5 -147.5t161.5 -393.5q2 -248 -157.5 -391.5t-401.5 -143.5zM594 236q63 0 101 76.5t38 195.5t-40 195.5t-109 78.5 q-59 0 -100.5 -77.5t-41.5 -190.5q0 -125 43 -201.5t109 -76.5z" />
+ <glyph unicode="&#xf5;" horiz-adv-x="1175" d="M176 1397v-223q57 55 163.5 70.5t221.5 -27.5q92 -45 234.5 -44t195.5 64v223q-47 -57 -152.5 -78.5t-228.5 23.5q-113 55 -243 51t-191 -59zM594 -27q-238 0 -406 143q-166 141 -165 394q0 248 168 394q167 144 393 145q250 -4 407 -148q162 -147 162 -399 q0 -243 -158 -385q-159 -144 -401 -144zM594 236q63 0 101 76.5t38 195.5t-40 195.5t-109 78.5q-59 0 -100.5 -77.5t-41.5 -190.5q0 -125 43 -201.5t109 -76.5z" />
+ <glyph unicode="&#xf6;" horiz-adv-x="1175" d="M324 1370h192v-172h-192v172zM651 1370h193v-172h-193v172zM594 -27q-238 0 -406 142.5t-165 400.5q2 244 168.5 388.5t392.5 144.5q250 -4 407.5 -147.5t161.5 -393.5q2 -248 -157.5 -391.5t-401.5 -143.5zM594 236q63 0 101 76.5t38 195.5t-40 195.5t-109 78.5 q-59 0 -100.5 -77.5t-41.5 -190.5q0 -125 43 -201.5t109 -76.5z" />
+ <glyph unicode="&#xf7;" d="M420 913q0 86 62.5 158t164.5 72q104 0 168 -73t64 -157q0 -94 -62.5 -155.5t-169.5 -61.5q-92 0 -159.5 67.5t-67.5 149.5zM86 627h1104v-111h-1104v111zM420 221q0 86 62.5 158t164.5 72q104 0 168 -73t64 -157q0 -92 -64.5 -154.5t-167.5 -62.5q-96 0 -161.5 66.5 t-65.5 150.5z" />
+ <glyph unicode="&#xf9;" d="M330 1339l141 197l268 -369l-176 -90zM31 1024h495v-494q0 -135 24.5 -196.5t86.5 -61.5q55 0 92 44t37 112v326h-135v270h530v-752h101v-272h-388l-55 129l-20 -23q-20 -25 -28 -31l-30 -29q-23 -22 -41 -30t-44.5 -21t-58.5 -18.5t-69 -5.5q-227 0 -319 119t-92 381 v283h-86v270z" />
+ <glyph unicode="&#xfa;" d="M821 1339l-141 197l-268 -369l176 -90zM31 1024h495v-494q0 -135 24.5 -196.5t86.5 -61.5q55 0 92 44t37 112v326h-135v270h530v-752h101v-272h-388l-55 129l-20 -23q-20 -25 -28 -31l-30 -29q-23 -22 -41 -30t-44.5 -21t-58.5 -18.5t-69 -5.5q-227 0 -319 119t-92 381 v283h-86v270z" />
+ <glyph unicode="&#xfb;" d="M588 1243l239 -147l127 106l-303 230h-143l-330 -265l176 -90zM31 1024h495v-494q0 -135 24.5 -196.5t86.5 -61.5q55 0 92 44t37 112v326h-135v270h530v-752h101v-272h-388l-55 129l-20 -23q-20 -25 -28 -31l-30 -29q-23 -22 -41 -30t-44.5 -21t-58.5 -18.5t-69 -5.5 q-227 0 -319 119t-92 381v283h-86v270z" />
+ <glyph unicode="&#xfc;" d="M299 1370h193v-172h-193v172zM627 1370h192v-172h-192v172zM31 1024h495v-494q0 -135 24.5 -196.5t86.5 -61.5q55 0 92 44t37 112v326h-135v270h530v-752h101v-272h-388l-55 129l-20 -23q-20 -25 -28 -31l-30 -29q-23 -22 -41 -30t-44.5 -21t-58.5 -18.5t-69 -5.5 q-227 0 -319 119t-92 381v283h-86v270z" />
+ <glyph unicode="&#x152;" horiz-adv-x="2181" d="M2255 979h-354v182h-328v-282h389v-273h-389v-334h328v191h354v-463h-1235v96q-162 -98 -393 -98q-317 0 -508 185.5t-191 539.5q0 315 190.5 514t493.5 199q242 0 408 -99v95h1235v-453zM627 305q92 0 145 110.5t53 293.5q0 188 -53 308t-162 120q-88 0 -144 -116 t-56 -298q0 -188 61.5 -303t155.5 -115z" />
+ <glyph unicode="&#x153;" horiz-adv-x="1916" d="M1714 340l172 -199q-207 -178 -528 -178q-221 0 -373 137q-152 -127 -383 -127q-238 0 -405.5 142.5t-165.5 400.5q2 244 169 388.5t392 144.5q238 -4 391 -131q152 135 385 135q180 0 312 -83t178 -214q23 -66 27.5 -156t-23.5 -190h-645q6 -31 14 -51.5t29.5 -52.5 t64.5 -48t102 -16q168 0 287 98zM602 236q63 0 101 76.5t38 195.5t-39.5 195.5t-109.5 78.5q-59 0 -100 -77.5t-41 -190.5q0 -125 42.5 -201.5t108.5 -76.5zM1223 612h309q-2 74 -33 119t-119 45q-82 0 -117.5 -53.5t-39.5 -110.5z" />
+ <glyph unicode="&#x2da;" horiz-adv-x="1705" />
+ <glyph unicode="&#x2dc;" horiz-adv-x="512" d="M-408 348v-223q57 55 163.5 70.5t221.5 -27.5q92 -45 234.5 -44t195.5 64v223q-47 -57 -152.5 -78.5t-228.5 23.5q-113 55 -243 51t-191 -59z" />
+ <glyph unicode="&#x2000;" horiz-adv-x="966" />
+ <glyph unicode="&#x2001;" horiz-adv-x="1935" />
+ <glyph unicode="&#x2002;" horiz-adv-x="966" />
+ <glyph unicode="&#x2003;" horiz-adv-x="1935" />
+ <glyph unicode="&#x2004;" horiz-adv-x="645" />
+ <glyph unicode="&#x2005;" horiz-adv-x="483" />
+ <glyph unicode="&#x2006;" horiz-adv-x="321" />
+ <glyph unicode="&#x2007;" horiz-adv-x="321" />
+ <glyph unicode="&#x2008;" horiz-adv-x="241" />
+ <glyph unicode="&#x2009;" horiz-adv-x="387" />
+ <glyph unicode="&#x200a;" horiz-adv-x="106" />
+ <glyph unicode="&#x2010;" horiz-adv-x="790" d="M96 625h594v-273h-594v273z" />
+ <glyph unicode="&#x2011;" horiz-adv-x="790" d="M96 625h594v-273h-594v273z" />
+ <glyph unicode="&#x2012;" horiz-adv-x="790" d="M96 625h594v-273h-594v273z" />
+ <glyph unicode="&#x2013;" horiz-adv-x="915" d="M66 625h772v-273h-772v273z" />
+ <glyph unicode="&#x2014;" d="M86 625h1104v-273h-1104v273z" />
+ <glyph unicode="&#x2018;" horiz-adv-x="628" d="M362 1548l236 -133q-115 -145 -162 -294.5t-33 -370.5l-372 98q-6 195 81.5 376t249.5 324z" />
+ <glyph unicode="&#x2019;" horiz-adv-x="628" d="M266 750l-235 133q115 145 162 294.5t32 370.5l373 -98q6 -195 -82 -376t-250 -324z" />
+ <glyph unicode="&#x201c;" horiz-adv-x="1062" d="M336 1548l235 -133q-115 -145 -162 -294.5t-32 -370.5l-373 98q-6 195 82 376t250 324zM827 1548l236 -133q-115 -145 -162 -294.5t-33 -370.5l-372 98q-6 195 81.5 376t249.5 324z" />
+ <glyph unicode="&#x201d;" horiz-adv-x="1062" d="M240 750l-236 133q115 145 162 294.5t33 370.5l372 -98q6 -195 -81.5 -376t-249.5 -324zM731 750l-235 133q115 145 162 294.5t32 370.5l373 -98q6 -195 -82 -376t-250 -324z" />
+ <glyph unicode="&#x2022;" horiz-adv-x="712" d="M123 659q0 86 63.5 160t168 74t170 -74t65.5 -160q0 -96 -62.5 -158.5t-173.5 -62.5q-98 0 -164.5 67.5t-66.5 153.5z" />
+ <glyph unicode="&#x2026;" horiz-adv-x="2048" d="M238 213q0 86 63.5 159.5t167.5 73.5t170 -73.5t66 -159.5q0 -96 -62.5 -158.5t-173.5 -62.5q-98 0 -164.5 67.5t-66.5 153.5zM791 213q0 86 63.5 159.5t167.5 73.5t169.5 -73.5t65.5 -159.5q0 -96 -62 -158.5t-173 -62.5q-98 0 -164.5 67.5t-66.5 153.5zM1343 213 q0 86 63.5 159.5t168 73.5t170 -73.5t65.5 -159.5q0 -96 -62 -158.5t-173 -62.5q-98 0 -165 67.5t-67 153.5z" />
+ <glyph unicode="&#x202f;" horiz-adv-x="387" />
+ <glyph unicode="&#x2039;" horiz-adv-x="1064" d="M756 1024l-613 -307v-287l660 -307l78 213l-506 227l506 232z" />
+ <glyph unicode="&#x203a;" horiz-adv-x="1064" d="M268 1024l613 -307v-287l-660 -307l-78 213l506 227l-506 232z" />
+ <glyph unicode="&#x205f;" horiz-adv-x="483" />
+ <glyph unicode="&#x20ac;" horiz-adv-x="1638" d="M1188 426h411v-426h-348l-63 129q-37 -61 -127 -104t-203 -43q-236 0 -407.5 147t-219.5 401h-229l49 142h164v30v33h-141l49 142h108q49 258 224.5 418.5t388.5 160.5q70 0 130 -11t98 -25.5t71 -37t48 -38t31.5 -36t20.5 -24.5l43 148h305v-605h-366q-4 121 -72 190.5 t-162 69.5q-84 0 -150.5 -57t-101.5 -153h383l-49 -142h-362v-33v-30h340l-50 -142h-264q33 -94 98.5 -149t155.5 -55q140 0 197 100z" />
+ <glyph unicode="&#x2122;" horiz-adv-x="3500" d="M2917 1071h-8q-35 -190 -60 -274l-215 -797h-428l-229 817q-35 119 -43 254h-9v-807h117v-264h-555v264h111v903h-109v265h776l207 -766l197 766h780v-265h-112v-901h112v-266h-647v266h115v684v121zM1405 1432v-533h-301v262h-172v-889h170v-272h-762v272h168v889h-176 v-262h-301v533h1374z" />
+ <glyph unicode="&#xe000;" horiz-adv-x="1025" d="M0 1025h1025v-1025h-1025v1025z" />
+ <glyph unicode="&#xfb01;" horiz-adv-x="1466" d="M900 1323q0 78 60 143.5t160.5 65.5t163 -65.5t62.5 -143.5q0 -88 -60.5 -147.5t-164.5 -59.5q-92 0 -156.5 64.5t-64.5 142.5zM1416 0h-574v268h86v490h-86v266h475v-756h99v-268zM842 1522l-123 -236q-45 20 -113 21q-66 0 -65 -109v-174h186v-266h-186v-490h123v-268 h-611v268h82v490h-84v266h84v174q0 43 6 84t31 97.5t66 96.5t119.5 68.5t183.5 28.5q80 0 170 -15.5t131 -35.5z" />
+ <glyph unicode="&#xfb02;" horiz-adv-x="1470" d="M1440 0h-598v268h88v1004h-86v264h475v-1268h121v-268zM842 1522l-123 -236q-45 20 -113 21q-66 0 -65 -109v-174h186v-266h-186v-490h123v-268h-611v268h82v490h-84v266h84v174q0 43 6 84t31 97.5t66 96.5t119.5 68.5t183.5 28.5q80 0 170 -15.5t131 -35.5z" />
+ <glyph unicode="&#xfb03;" horiz-adv-x="2256" d="M1690 1323q0 78 60 143.5t160.5 65.5t163 -65.5t62.5 -143.5q0 -88 -60.5 -147.5t-164.5 -59.5q-92 0 -156.5 64.5t-64.5 142.5zM2206 0h-574v268h86v490h-86v266h475v-756h99v-268zM1633 1522l-123 -236q-45 20 -113 21q-66 0 -65 -109v-174h186v-266h-186v-490h123 v-268h-611v268h82v490h-84v266h84v174q0 43 6 84t31 97.5t66 96.5t119.5 68.5t183.5 28.5q80 0 170 -15.5t131 -35.5zM842 1522l-123 -236q-45 20 -113 21q-66 0 -65 -109v-174h186v-266h-186v-490h123v-268h-611v268h82v490h-84v266h84v174q0 43 6 84t31 97.5t66 96.5 t119.5 68.5t183.5 28.5q80 0 170 -15.5t131 -35.5z" />
+ <glyph unicode="&#xfb04;" horiz-adv-x="2260" d="M2230 0h-598v268h88v1004h-86v264h475v-1268h121v-268zM1633 1522l-123 -236q-45 20 -113 21q-66 0 -65 -109v-174h186v-266h-186v-490h123v-268h-611v268h82v490h-84v266h84v174q0 43 6 84t31 97.5t66 96.5t119.5 68.5t183.5 28.5q80 0 170 -15.5t131 -35.5zM842 1522 l-123 -236q-45 20 -113 21q-66 0 -65 -109v-174h186v-266h-186v-490h123v-268h-611v268h82v490h-84v266h84v174q0 43 6 84t31 97.5t66 96.5t119.5 68.5t183.5 28.5q80 0 170 -15.5t131 -35.5z" />
+ </font>
+ </defs></svg>
\ No newline at end of file
spec/fixtures/default/public/fonts/chunkfive-webfont.ttf +0 -0
spec/fixtures/default/public/fonts/chunkfive-webfont.woff +0 -0
spec/fixtures/default/public/fonts/chunkfive.css +8 -0
@@ @@ -0,0 +1,8 @@
+ @font-face {
+ font-family: 'ChunkFiveRoman';
+ src: url('chunkfive-webfont.eot');
+ src: local('☺'), url('chunkfive-webfont.woff') format('woff'), url('chunkfive-webfont.ttf') format('truetype'), url('chunkfive-webfont.svg#webfontwlZ5ec8v') format('svg');
+ font-weight: normal;
+ font-style: normal;
+ }
+
spec/fixtures/default/public/fonts/chunkfive.otf +0 -0
spec/fixtures/default/public/images/nav_on.png +0 -0
spec/fixtures/default/public/images/photo_frame.png +0 -0
spec/fixtures/default/public/images/sep.png +0 -0
spec/fixtures/default/public/images/top.jpg +0 -0
spec/fixtures/default/public/javascripts/application.js.coffee +2 -0
@@ @@ -0,0 +1,2 @@
+ $(document).ready ->
+ alert 'hello world'
\ No newline at end of file
spec/fixtures/default/public/javascripts/common.js +1 -0
@@ @@ -0,0 +1 @@
+ console.log('hello world')
\ No newline at end of file
spec/fixtures/default/public/samples/asset_collections/cover.jpg +0 -0
spec/fixtures/default/public/samples/photo.jpg +0 -0
spec/fixtures/default/public/samples/photo_2.jpg +0 -0
spec/fixtures/default/public/stylesheets/application.css +64 -0
@@ @@ -0,0 +1,64 @@
+ body { background: #f0eee3 url("/images/top.jpg") repeat-x center 0; color: #333; font-family: Georgia, helvetica; font-size: 14px; }
+
+ h2 { font-size: 30px; font-style: italic; font-weight: normal; font-family: ChunkFiveRoman; margin-bottom: 6px; }
+
+ a { color: #2196b2; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+
+ .sep { height: 1px; width: 950px; background: transparent url(/images/sep.png) repeat-x 0 0; }
+
+ .more a { font-style: italic; font-weight: bold; }
+
+ .container { width: 950px; padding-top: 0px; }
+
+ .error { text-align: center; font-size: 24px; text-transform: capitalize; }
+
+ /* ___ menu ___ */
+
+ #menu { height: 40px; }
+
+ #nav { list-style: none; height: 40px; }
+ #nav li { float: left; padding: 0px 20px 0 20px; height: 40px; }
+ #nav li a { position: relative; top: 7px; color: #fff; font-size: 18px; text-transform: lowercase; font-family: ChunkFiveRoman; }
+ #nav li.on { background: transparent url(/images/nav_on.png) repeat-x 0 0; }
+
+ /* ___ banner ___ */
+
+ #banner { padding-top: 20px; background: transparent url(/images/sep.png) repeat-x 0 bottom; margin-bottom: 30px; padding-bottom: 20px; }
+
+ #banner .photo { float: left; position: relative; left: -23px; background: transparent url(/images/photo_frame.png) no-repeat 0 0; padding: 24px 0 0px 32px; width: 636px; height: 438px; }
+ #banner .text { padding-top: 130px; margin-left: 686px; }
+ #banner .text p { font-size: 18px; }
+ #banner .text p a { font-weight: bold; font-size: 17px; }
+
+ /* ___ list ___ */
+
+ .list { list-style: none; font-size: 13px; margin-bottom: 10px; }
+ .list li { margin-bottom: 5px; }
+ .list li em { font-weight: bold; }
+
+ /* ___ songs ___ */
+
+ .songs { list-style: none; }
+ .songs li { margin: 0 20px 20px 0; padding: 0 0 20px 0px; background: transparent url(/images/sep.png) repeat-x 0 bottom; }
+ .songs li h3 { font-style: italic; margin-bottom: 6px; }
+ .songs li .cover { float: left; }
+ .songs li .cover img { border: 4px solid #fff; }
+ .songs li .info { margin-left: 165px; }
+ .songs li .info .listen { text-align: right; margin: 5px 0 0 0; }
+
+ /* ___ contact form ___ */
+
+ #contactform { margin-top: 30px; }
+ #contactform p { margin-bottom: 10px; clear: both; }
+ #contactform p label { float: left; width: 200px; }
+ #contactform p input[type=text], #contactform p textarea { font-size: 1em; padding: 3px 5px; }
+ #contactform p input[type=text] { width: 200px; }
+ #contactform p textarea { width: 400px; height: 200px; }
+ #contactform p.action { padding: 0 0px 15px 200px; }
+ #contactform p.action input { background: #2196b2; color: #fff; padding: 3px 5px; text-transform: uppercase; margin: 0 5px; border: none; }
+
+ /* ___ footer ___ */
+
+ #footer { background: transparent url(/images/sep.png) repeat-x 0 0; margin-top: 20px; padding-top: 10px; }
+ #footer p { text-align: center; font-size: 12px; }
\ No newline at end of file
spec/fixtures/default/public/stylesheets/other/extra.css.less +8 -0
@@ @@ -0,0 +1,8 @@
+ @color: #4D926F;
+
+ #header {
+ color: @color;
+ }
+ h2 {
+ color: @color;
+ }
\ No newline at end of file
spec/fixtures/default/public/stylesheets/other/style.css.scss +13 -0
@@ @@ -0,0 +1,13 @@
+ @import "compass/reset/utilities";
+
+ html {
+ @include global-reset;
+ }
+
+ body {
+ background: red;
+
+ h1 {
+ color: green;
+ }
+ }
\ No newline at end of file
spec/fixtures/default/public/stylesheets/reboot.css +82 -0
@@ @@ -0,0 +1,82 @@
+ html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}
+
+ /* **************** GRIDS ***************** */
+ .container { margin: 0 auto; width: 900px; padding: 20px; text-align:left; }
+ .line, .lastUnit {overflow: hidden;_overflow:visible;_zoom:1;}
+ .line2 {_zoom:1; background-color:#FFF;}
+ .unit{float:left;_zoom:1;}
+ .unitExt{float:right;}
+ .size1of1{float:none;}
+ .size1of2{width:50%;}
+ .size1of3{width:33.33333%;}
+ .size2of3{width:66.66666%;}
+ .size1of4{width:25%;}
+ .size3of4{width:75%;}
+ .size1of5{width:20%;}
+ .size2of5{width:40%;}
+ .size3of5{width:60%;}
+ .size4of5{width:80%;}
+ .lastUnit {float:none;_position:relative; _left:-3px; _margin-right: -3px;width:auto;}
+ .padding10px{padding:10px;}
+ .padding15px{padding:15px;}
+ .padding20px{padding:20px;}
+ .clear{clear:both;height:1px;line-height:1px;}
+
+ /* **************** TYPOGRAPHY ***************** */
+ body {font-size:75%;line-height:1.5;}
+ h1, h2, h3, h4, h5, h6 {font-weight:normal;}
+ h1 {
+ font-size:3.6em;
+ line-height:1;
+ margin-bottom:0.5em;
+ color:#434343;
+ font-weight:bold;
+ letter-spacing:-0.05em;
+ }
+ h2 {font-size:2em;margin-bottom:0.75em; color:#434343;}
+ h3 {font-size:1.5em;line-height:1;margin-bottom:1em;}
+ h4 {font-size:1.25em;line-height:1.25;margin:0.5em 0 0.5em 0;color:#565757;font-weight:bold;}
+ h5 {
+ font-size:1.3em;
+ font-weight:normal;
+ color:#414240;
+ }
+
+ h6 {
+ font-size:1.3em;
+ font-weight:bold;
+ font-style:italic;
+ color:#434343;
+ margin:-20px 0 20px 0;
+ letter-spacing:-0.05em;
+ }
+ p {
+ margin:0 0 1.5em;
+ color:#414240;
+ }
+
+ /* **************** ALIGNMENTS ***************** */
+ .tcenter { text-align: center; }
+ .tright { text-align: right; }
+ .tleft { text-align: left; }
+ .tjustify { text-align: justify; }
+ .fright { float: right; }
+
+ /* **************** MARGINS ***************** */
+ .nomargin { margin: 0 !important; }
+ .mt30 { margin-top: 30px !important; }
+ .mt20 { margin-top: 20px !important; }
+ .mb20 { margin-bottom: 20px !important; }
+ .m20 { margin: 20px !important; }
+ .mt10 { margin-top: 10px !important; }
+ .ml10 { margin-left: 10px !important; }
+ .mr10 { margin-right: 10px !important; }
+ .mb10 { margin-bottom: 10px !important; }
+ .mb20 { margin-bottom: 20px !important; }
+ .mb30 { margin-bottom: 30px !important; }
+ .m10 { margin: 10px !important; }
+
+
+
+
+
\ No newline at end of file
spec/integration/integration_helper.rb +15 -0
@@ @@ -0,0 +1,15 @@
+ # encoding: utf-8
+
+ require File.dirname(__FILE__) + '/../spec_helper'
+ # require 'vcr'
+ # require 'webmock/rspec'
+ require 'active_support/core_ext'
+
+ # VCR.configure do |c|
+ # c.ignore_localhost = false
+ # c.cassette_library_dir = File.dirname(__FILE__) + '/cassettes'
+ # c.hook_into :webmock
+ # c.default_cassette_options = { record: :new_episodes }
+ # c.configure_rspec_metadata!
+ # c.allow_http_connections_when_no_cassette = false
+ # end
\ No newline at end of file
spec/integration/server/basic_spec.rb +170 -0
@@ @@ -0,0 +1,170 @@
+ # encoding: utf-8
+
+ require File.dirname(__FILE__) + '/../integration_helper'
+ require 'locomotive/steam/server'
+ require 'rack/test'
+
+ describe Locomotive::Steam::Server do
+
+ include Rack::Test::Methods
+
+ def app
+ run_server
+ end
+
+ it 'shows the index page' do
+
+ get '/index'
+
+ last_response.body.should =~ /Upcoming events/
+ end
+
+ it 'shows the 404 page' do
+ get '/void'
+ last_response.status.should eq(404)
+ last_response.body.should =~ /page not found/
+ end
+
+ it 'shows the 404 page with 200 status code when its called explicitly' do
+ get '/404'
+ last_response.status.should eq(200)
+ last_response.body.should =~ /page not found/
+ end
+
+ it 'shows content' do
+ get '/about-us/jane-doe'
+ last_response.body.should =~ /Lorem ipsum dolor sit amet/
+ end
+
+ it 'shows a content type template ' do
+ get '/songs/song-number-1'
+ last_response.body.should =~ /Song #1/
+ end
+
+ it 'renders a page under a templatized one' do
+ get '/songs/song-number-1/band'
+ last_response.body.should =~ /Song #1/
+ last_response.body.should =~ /Leader: Eddie/
+ end
+
+ it 'translates strings' do
+ get '/en'
+ last_response.body.should =~ /Powered by/
+ get '/fr'
+ last_response.body.should =~ /Propulsé par/
+ get '/nb'
+ last_response.body.should_not =~ /Powered by/
+ end
+
+ it 'provides translation in scopes', pending: true do
+ get '/'
+ last_response.body.should =~ /scoped_translation=.French./
+ end
+
+ it 'translates a page with link_to tags inside' do
+ get '/fr/notre-musique'
+ last_response.body.should =~ /<h3><a href="\/fr\/songs\/song-number-8">Song #8<\/a><\/h3>/
+ last_response.body.should =~ /Propulsé par/
+ end
+
+ it 'returns all the pages' do
+ get '/all'
+ last_response.body.should =~ /Home page/
+ last_response.body.should =~ /<li>Home page<\/li>/
+ last_response.body.should =~ /<li>John-doe<\/li>/
+ last_response.body.should =~ /<li>Songs<\/li>/
+ last_response.body.should =~ /<li>A song template<\/li>/
+ end
+
+ describe 'snippets' do
+
+ it 'includes a basic snippet' do
+ get '/'
+ last_response.body.should =~ /All photos are licensed under Creative Commons\./
+ end
+
+ it 'includes a snippet whose name is composed of dash' do
+ get '/'
+ last_response.body.should =~ /<p>A complicated one name indeed.<\/p>/
+ end
+
+ end
+
+ describe 'nav' do
+
+ subject { get '/all'; last_response.body }
+
+ it { should_not match(/<nav id="nav">/) }
+
+ it { should match(/<li id="about-us-link" class="link first"><a href="\/about-us">About Us<\/a><\/li>/) }
+
+ it { should match(/<li id="music-link" class="link"><a href="\/music">Music<\/a><\/li>/) }
+
+ it { should match(/<li id="store-link" class="link"><a href="\/store">Store<\/a><\/li>/) }
+
+ it { should match(/<li id="contact-link" class="link last"><a href="\/contact">Contact Us<\/a><\/li>/) }
+
+ it { should_not match(/<li id="events-link" class="link"><a href="\/events">Events<\/a><\/li>/) }
+
+ describe 'with wrapper' do
+
+ subject { get '/tags/nav'; last_response.body }
+
+ it { should match(/<nav id="nav">/) }
+
+ end
+
+ describe 'very deep' do
+
+ subject { get '/tags/nav_in_deep'; last_response.body }
+
+ it { should match(/<li id=\"john-doe-link\" class=\"link first last\">/) }
+
+ end
+
+ end
+
+ describe 'contents with_scope' do
+ subject { get '/grunge_bands'; last_response.body }
+
+ it { should match(/Layne/)}
+ it { should_not match(/Peter/) }
+ end
+
+ describe "pages with_scope" do
+ subject { get '/unlisted_pages'; last_response.body }
+ it { subject.should match(/Page to test the nav tag/)}
+ it { should_not match(/About Us/)}
+ end
+
+ describe 'theme assets' do
+
+ subject { get '/all'; last_response.body }
+
+ it { should match(/<link href="\/stylesheets\/application.css" media="screen" rel="stylesheet" type="text\/css" \/>/) }
+
+ it { should match(/<script src="\/javascripts\/application.js" type='text\/javascript'><\/script>/) }
+
+ it { should match(/<link rel="alternate" type="application\/atom\+xml" title="A title" href="\/foo\/bar" \/>/) }
+
+ end
+
+ describe 'session' do
+
+ subject { get '/contest'; last_response.body }
+
+ it { should match(/Your code is: HELLO WORLD/) }
+ it { should_not match(/You've already participated to that contest ! Come back later./) }
+
+ describe 'assign tag' do
+
+ subject { 2.times { get '/contest' }; last_response.body }
+
+ it { should_not match(/Your code is: HELLO WORLD/) }
+ it { should match(/You've already participated to that contest ! Come back later./) }
+
+ end
+
+ end
+
+ end
\ No newline at end of file
spec/integration/server/contact_form_spec.rb +111 -0
@@ @@ -0,0 +1,111 @@
+ # encoding: utf-8
+
+ require File.dirname(__FILE__) + '/../integration_helper'
+ require 'locomotive/steam/server'
+ require 'rack/test'
+
+ describe 'ContactForm' do
+
+ include Rack::Test::Methods
+
+ def app
+ run_server
+ end
+
+ it 'renders the form' do
+ get '/contact'
+ last_response.body.should =~ /\/entry_submissions\/messages.json/
+ end
+
+ describe '#submit' do
+
+ let(:params) { {
+ 'entry' => { 'name' => 'John', 'email' => 'j@doe.net', 'message' => 'Bla bla' },
+ 'success_callback' => '/events',
+ 'error_callback' => '/contact' } }
+ let(:response) { post_contact_form(params, false) }
+ let(:status) { response.status }
+
+ describe 'with json request' do
+
+ let(:response) { post_contact_form(params, true) }
+ let(:entry) { JSON.parse(response.body)['message'] }
+
+ context 'when not valid' do
+
+ let(:params) { {} }
+
+ it 'returns an error status' do
+ response.status.should == 422
+ end
+
+ describe 'errors', pending: true do
+
+ subject { entry['errors'] }
+
+ it { should have_key_with_value('name', "can't not be blank") }
+
+ it { should have_key_with_value('email', "can't not be blank") }
+
+ it { should have_key_with_value('message', "can't not be blank") }
+
+ end
+
+ end
+
+ context 'when valid' do
+
+ it 'returns a success status' do
+ response.status.should == 200
+ end
+
+ end
+
+ end
+
+ describe 'with html request' do
+
+ context 'when not valid' do
+
+ let(:params) { { 'error_callback' => '/contact' } }
+
+ it 'returns a success status' do
+ response.status.should == 200
+ end
+
+ it 'displays errors', pending: true do
+ response.body.to_s.should =~ /can't not be blank/
+ end
+
+ end
+
+ context 'when valid' do
+
+ let(:response) { post_contact_form(params, false, true) }
+
+ it 'returns a success status' do
+ response.status.should == 200
+ end
+
+ it 'displays a success message' do
+ response.body.should =~ /Thank you John/
+ end
+
+ end
+
+ end
+
+ end
+
+ def post_contact_form(params, json = false, follow_redirect = false)
+ url = '/entry_submissions/messages'
+ url += '.json' if json
+ params = params.symbolize_keys if json
+ post url, params
+ if follow_redirect
+ follow_redirect!
+ end
+ last_response
+ end
+
+ end
spec/integration/server/liquid_spec.rb +91 -0
@@ @@ -0,0 +1,91 @@
+ # encoding: utf-8
+
+ require File.dirname(__FILE__) + '/../integration_helper'
+ require 'locomotive/steam/server'
+ require 'rack/test'
+
+ describe Locomotive::Steam::Server do
+
+ include Rack::Test::Methods
+
+ def app
+ run_server
+ end
+
+ it "converts {{ page.templatized? }} => true on templatized page" do
+ get '/songs/song-number-1'
+ last_response.body.should =~ /templatized=.true./
+ end
+
+ it "converts {{ page.templatized? }} => false on regular page" do
+ get '/index'
+ last_response.body.should =~ /templatized=.false./
+ end
+
+ it "converts {{ page.listed? }} => true on listed page" do
+ get '/music'
+ last_response.body.should =~ /listed=.true./
+ end
+
+ it "provides an access to page's content_type collection" do
+ get '/songs/song-number-1'
+ last_response.body.should =~ /content_type_size=.8./
+ end
+
+ it "provides count alias on collections" do
+ get '/songs/song-number-1'
+ last_response.body.should =~ /content_type_count=.8./
+ end
+
+ describe '.link_to' do
+
+ it "writes a link to a page" do
+ get '/events'
+ last_response.body.should =~ /Discover: <a href="\/music">Music<\/a>/
+ end
+
+ it "writes a localized a link", pending: true do
+ get '/events'
+ last_response.body.should =~ /Plus à notre sujet: <a href="\/a-notre-sujet">Qui sommes nous \?<\/a>/
+ end
+
+ it "writes a link to a page with a custom label" do
+ get '/events'
+ last_response.body.should =~ /More about us: <a href="\/about-us">Who are we \?<\/a>/
+ end
+
+ it "writes a link to a templatized page" do
+ get '/events'
+ last_response.body.should =~ /<a href="\/songs\/song-number-1">Song #1<\/a>/
+ end
+
+ it "writes a link to a templatized page with a different handle" do
+ get '/events'
+ last_response.body.should =~ /<a href="\/songs\/song-number-8">Song #8<\/a>/
+ end
+
+ end
+
+ describe 'scope & assigns' do
+
+ it "evaluates collection when called all inside of scope" do
+ get '/music'
+ last_response.body.should =~ /<p class=.scoped_song.>Song #3/
+ last_response.body.should =~ /<p class=.scoped_song_link.>\s+<a href=.\/songs\/song-number-3.>Song #3/m
+ end
+
+ it "size of evaluated unscoped collection equal to unevaluated one" do
+ get '/music'
+ last_response.body.should =~ /class=.collection_equality.>8=8/
+ end
+
+ end
+
+ describe 'html helpers' do
+ it 'bypass url for css resource' do
+ get '/'
+ last_response.body.should =~ /<link href=("|')http:\/\/fonts\.googleapis\.com\/css\?family=Open\+Sans:400,700("|')/
+ end
+ end
+
+ end
\ No newline at end of file
spec/integration/server/with_scope_spec.rb +20 -0
@@ @@ -0,0 +1,20 @@
+ # encoding: utf-8
+
+ require File.dirname(__FILE__) + '/../integration_helper'
+ require 'locomotive/steam/server'
+ require 'rack/test'
+
+ describe 'Complex with_scope conditions' do
+
+ include Rack::Test::Methods
+
+ def app
+ run_server
+ end
+
+ it 'returns the right number of events' do
+ get '/filtered'
+ last_response.body.should =~ /events=1./
+ end
+
+ end
\ No newline at end of file
spec/spec_helper.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ require_relative '../lib/steam'
+
+ require 'rspec'
+ require 'launchy'
+ require 'pry'
+
+ Dir["#{File.expand_path('../support', __FILE__)}/*.rb"].each do |file|
+ require file
+ end
+
+ RSpec.configure do |c|
+ c.filter_run focused: true
+ c.run_all_when_everything_filtered = true
+ c.include Spec::Helpers
+ c.before(:all) { remove_logs }
+ c.before { reset! }
+ c.after { reset! }
+ end
\ No newline at end of file
spec/support/helpers.rb +22 -0
@@ @@ -0,0 +1,22 @@
+ module Spec
+ module Helpers
+
+ def reset!
+ FileUtils.rm_rf(File.expand_path('../../../site', __FILE__))
+ end
+
+ def remove_logs
+ FileUtils.rm_rf(File.expand_path('../../fixtures/default/log', __FILE__))
+ end
+
+ def run_server
+ path = 'spec/fixtures/default'
+ Locomotive::Steam::Logger.setup(path, false)
+ reader = Locomotive::Mounter::Reader::FileSystem.instance
+ reader.run!(path: path)
+
+ Locomotive::Steam::Server.new(reader, disable_listen: true)
+ end
+
+ end
+ end
\ No newline at end of file
spec/support/matchers.rb +5 -0
@@ @@ -0,0 +1,5 @@
+ RSpec::Matchers.define :have_key_with_value do |key, expected|
+ match do |actual|
+ actual[key] == expected
+ end
+ end
\ No newline at end of file