make Steam as thread-safe as possible + prepare the future integration into a rails app + close issues #15 and #16

did committed Apr 13, 2014
commit 4e0a6225331bc19ee358c3385fe21adcb5478f4e
Showing 51 changed files with 1016 additions and 986 deletions
Gemfile.lock +5 -25
@@ @@ -3,11 +3,10 @@ PATH
specs:
locomotivecms_steam (0.1.0)
activesupport (~> 3.2)
- better_errors (~> 1.0)
- dragonfly (~> 0.9)
- listen (~> 2.7)
+ dragonfly (~> 1.0.3)
locomotivecms-solid
locomotivecms_mounter
+ moneta (~> 0.7.20)
rack-cache (~> 1.1)
redcarpet (~> 3.1)
sprockets (~> 2.0)
@@ @@ -22,14 +21,6 @@ GEM
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
addressable (2.3.6)
- better_errors (1.1.0)
- coderay (>= 1.0.0)
- erubis (>= 2.6.6)
- celluloid (0.15.2)
- timers (~> 1.1.0)
- celluloid-io (0.15.0)
- celluloid (>= 0.15.0)
- nio4r (>= 0.5.0)
chronic (0.10.2)
chunky_png (1.3.0)
coderay (1.1.0)
@@ @@ -53,12 +44,10 @@ GEM
safe_yaml (~> 1.0.0)
diff-lcs (1.2.5)
docile (1.1.3)
- dragonfly (0.9.15)
+ dragonfly (1.0.4)
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
@@ @@ -66,7 +55,7 @@ GEM
httmultiparty (0.3.10)
httparty (>= 0.7.3)
multipart-post
- httparty (0.13.0)
+ httparty (0.13.1)
json (~> 1.8)
multi_xml (>= 0.5.2)
i18n (0.6.9)
@@ @@ -79,11 +68,6 @@ GEM
addressable (~> 2.3)
less (2.2.2)
commonjs (~> 0.2.6)
- listen (2.7.1)
- celluloid (>= 0.15.2)
- celluloid-io (>= 0.15.0)
- 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)
@@ @@ -112,10 +96,10 @@ GEM
logger (1.2.8)
method_source (0.8.2)
mime-types (1.25.1)
+ moneta (0.7.20)
multi_json (1.7.9)
multi_xml (0.5.5)
multipart-post (2.0.0)
- nio4r (1.0.0)
pry (0.9.12.6)
coderay (~> 1.0)
method_source (~> 0.8)
@@ @@ -126,9 +110,6 @@ GEM
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.1.1)
rest-client (1.6.7)
mime-types (>= 1.16)
@@ @@ -161,7 +142,6 @@ GEM
tins (~> 1.0)
thor (0.18.1)
tilt (1.4.1)
- timers (1.1.0)
tins (1.0.0)
tzinfo (0.3.39)
vcr (2.9.0)
example/server.rb +22 -0
@@ @@ -0,0 +1,22 @@
+ #!/usr/bin/env ruby
+
+ require 'thin'
+
+ DIR = File.expand_path(File.dirname(__FILE__))
+
+ require File.join(DIR, '../lib/steam')
+ require File.join(DIR, '../lib/locomotive/steam/server')
+ require File.join(DIR, '../lib/locomotive/steam/initializers')
+
+ path = File.join(DIR, '../spec/fixtures/default')
+ Locomotive::Steam::Logger.setup(path, false)
+ reader = Locomotive::Mounter::Reader::FileSystem.instance
+ reader.run!(path: path)
+
+ app = Locomotive::Steam::Server.new(reader, {
+ serve_assets: true
+ })
+
+ server = Thin::Server.new('localhost', '3333', app)
+ server.threaded = true
+ server.start
\ No newline at end of file
locomotive/steam/initializers.rb b/lib/locomotive/steam/initializers.rb +2 -4
@@ @@ -1,5 +1,3 @@
- require_relative 'core_ext.rb'
-
+ require_relative 'initializers/sprockets.rb'
require_relative 'initializers/i18n.rb'
- require_relative 'initializers/markdown.rb'
- require_relative 'initializers/will_paginate.rb'
\ No newline at end of file
+ require_relative 'initializers/dragonfly.rb'
locomotive/steam/initializers/better_errors.rb b/lib/locomotive/steam/initializers/better_errors.rb +73 -0
@@ @@ -0,0 +1,73 @@
+ # BetterErrors.application_root = reader.mounting_point.path
+
+ # require 'better_errors'
+ # 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/initializers/dragonfly.rb b/lib/locomotive/steam/initializers/dragonfly.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ require 'dragonfly'
+
+ # Configure
+ Dragonfly.app(:steam).configure do
+ plugin :imagemagick,
+ convert_command: `which convert`.strip.presence || '/usr/local/bin/convert',
+ identify_command: `which identify`.strip.presence || '/usr/local/bin/identify'
+
+ protect_from_dos_attacks true
+
+ url_format '/images/dynamic/:job/:basename.:ext'
+
+ fetch_file_whitelist /public/
+
+ fetch_url_whitelist /.+/
+ end
+
+ Dragonfly.logger = Locomotive::Steam::Logger.instance
\ No newline at end of file
locomotive/steam/initializers/markdown.rb b/lib/locomotive/steam/initializers/markdown.rb +0 -27
@@ @@ -1,27 +0,0 @@
- 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/initializers/sprockets.rb b/lib/locomotive/steam/initializers/sprockets.rb +1 -0
@@ @@ -0,0 +1 @@
+ Sprockets::Sass.add_sass_functions = false
\ No newline at end of file
locomotive/steam/initializers/will_paginate.rb b/lib/locomotive/steam/initializers/will_paginate.rb +0 -16
@@ @@ -1,16 +0,0 @@
- 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/liquid/filters/resize.rb b/lib/locomotive/steam/liquid/filters/resize.rb +2 -1
@@ @@ -5,7 +5,8 @@ module Locomotive
module Resize
def resize(input, resize_string)
- Locomotive::Steam::Dragonfly.instance.resize_url(input, resize_string)
+ dragonfly = @context.registers[:services][:dragonfly]
+ dragonfly.resize_url(input, resize_string)
end
end
locomotive/steam/liquid/tags/consume.rb b/lib/locomotive/steam/liquid/tags/consume.rb +6 -1
@@ @@ -72,7 +72,8 @@ module Locomotive
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)
+ context.scopes.last[@target.to_s] = external_api_service(context).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
@@ @@ -88,6 +89,10 @@ module Locomotive
end
end
+ def external_api_service(context)
+ context.registers[:services][:external_api]
+ end
+
end
::Liquid::Template.register_tag('consume', Consume)
locomotive/steam/listen.rb b/lib/locomotive/steam/listen.rb +0 -64
@@ @@ -1,64 +0,0 @@
- 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/middlewares.rb b/lib/locomotive/steam/middlewares.rb +15 -0
@@ @@ -0,0 +1,15 @@
+ require_relative 'middlewares/base'
+
+ require_relative 'middlewares/favicon'
+ require_relative 'middlewares/static_assets'
+ require_relative 'middlewares/dynamic_assets'
+ require_relative 'middlewares/logging'
+ require_relative 'middlewares/entry_submission'
+ require_relative 'middlewares/path'
+ require_relative 'middlewares/locale'
+ require_relative 'middlewares/page'
+ require_relative 'middlewares/timezone'
+ require_relative 'middlewares/templatized_page'
+ require_relative 'middlewares/renderer'
+
+ require_relative 'middlewares/stack'
\ No newline at end of file
locomotive/steam/middlewares/base.rb b/lib/locomotive/steam/middlewares/base.rb +63 -0
@@ @@ -0,0 +1,63 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ class Base
+
+ attr_accessor :app, :request, :path
+ attr_accessor :liquid_assigns, :services
+ attr_accessor :mounting_point, :page, :content_entry
+
+ def initialize(app = nil)
+ @app = app
+ end
+
+ def call(env)
+ dup._call(env) # thread-safe purpose
+ end
+
+ def _call(env)
+ self.set_accessors(env)
+ end
+
+ protected
+
+ def set_accessors(env)
+ %w(path request mounting_point page content_entry services).each do |name|
+ self.send(:"#{name}=", env["steam.#{name}"])
+ end
+
+ 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/middlewares/dynamic_assets.rb b/lib/locomotive/steam/middlewares/dynamic_assets.rb +40 -0
@@ @@ -0,0 +1,40 @@
+ require 'coffee_script'
+
+ module Locomotive::Steam
+ module Middlewares
+
+ class DynamicAssets < Base
+
+ attr_reader :app, :regexp
+
+ def initialize(app)
+ super(app)
+
+ @regexp = /^\/(javascripts|stylesheets)\/(.*)$/
+ end
+
+ def call(env)
+ dup._call(env) # thread-safe purpose
+ end
+
+ def _call(env)
+ if env['PATH_INFO'] =~ self.regexp
+ env['PATH_INFO'] = $2
+
+ base_path = env['steam.mounting_point'].path
+
+ begin
+ sprockets = Locomotive::Mounter::Extensions::Sprockets.environment(base_path)
+ 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/middlewares/entry_submission.rb b/lib/locomotive/steam/middlewares/entry_submission.rb +120 -0
@@ @@ -0,0 +1,120 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # Mimic the submission of a content entry
+ #
+ class EntrySubmission < Base
+
+ def _call(env)
+ super
+
+ 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/middlewares/favicon.rb b/lib/locomotive/steam/middlewares/favicon.rb +17 -0
@@ @@ -0,0 +1,17 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ class Favicon < Base
+
+ 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/middlewares/locale.rb b/lib/locomotive/steam/middlewares/locale.rb +42 -0
@@ @@ -0,0 +1,42 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # 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 < Base
+
+ def _call(env)
+ super
+
+ 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/middlewares/logging.rb b/lib/locomotive/steam/middlewares/logging.rb +32 -0
@@ @@ -0,0 +1,32 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # Track the request into the current logger
+ #
+ class Logging < Base
+
+ 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/middlewares/page.rb b/lib/locomotive/steam/middlewares/page.rb +67 -0
@@ @@ -0,0 +1,67 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # Sanitize the path from the previous middleware in order
+ # to make it work for the renderer.
+ #
+ class Page < Base
+
+ def _call(env)
+ super
+
+ 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/middlewares/path.rb b/lib/locomotive/steam/middlewares/path.rb +34 -0
@@ @@ -0,0 +1,34 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # Sanitize the path from the previous middleware in order
+ # to make it work for the renderer.
+ #
+ class Path < Base
+
+ def _call(env)
+ super
+
+ 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/middlewares/renderer.rb b/lib/locomotive/steam/middlewares/renderer.rb +119 -0
@@ @@ -0,0 +1,119 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ class Renderer < Base
+
+ def _call(env)
+ super
+
+ 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 'Rendered 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 RendererException.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,
+ services: self.services,
+ inline_editor: false,
+ logger: Locomotive::Steam::Logger
+ }
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/middlewares/stack.rb b/lib/locomotive/steam/middlewares/stack.rb +63 -0
@@ @@ -0,0 +1,63 @@
+ require 'rack/session/moneta'
+
+ module Locomotive
+ module Steam
+ module Middlewares
+
+ class Stack
+
+ def initialize(options)
+ @options = prepare_options(options)
+ end
+
+ def create
+ options = @options
+
+ Rack::Builder.new do
+ use Rack::Lint
+
+ use Middlewares::Favicon
+
+ if options[:serve_assets]
+ use Middlewares::StaticAssets, {
+ urls: ['/images', '/fonts', '/samples', '/media']
+ }
+
+ use Middlewares::DynamicAssets
+ end
+
+ use ::Dragonfly::Middleware, :steam
+
+ use Rack::Session::Moneta, options[:moneta]
+
+ use Middlewares::Logging
+
+ use Middlewares::EntrySubmission
+
+ use Middlewares::Path
+ use Middlewares::Locale
+ use Middlewares::Timezone
+
+ use Middlewares::Page
+ use Middlewares::TemplatizedPage
+
+ run Middlewares::Renderer.new
+ end
+ end
+
+ protected
+
+ def prepare_options(options)
+ {
+ serve_assets: false,
+ moneta: {
+ store: Moneta.new(:Memory, :expires => true)
+ }
+ }.merge(options)
+ end
+
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/middlewares/static_assets.rb b/lib/locomotive/steam/middlewares/static_assets.rb +25 -0
@@ @@ -0,0 +1,25 @@
+ require 'rack/static'
+
+ module Locomotive::Steam
+ module Middlewares
+
+ class StaticAssets < ::Rack::Static
+
+ alias_method :call_without_threadsafety, :call
+
+ def call(env)
+ dup._call(env) # thread-safe purpose
+ end
+
+ def _call(env)
+ mounting_point = env['steam.mounting_point']
+
+ @file_server = Rack::File.new(mounting_point.assets_path)
+
+ call_without_threadsafety(env)
+ end
+
+ end
+
+ end
+ end
\ No newline at end of file
locomotive/steam/middlewares/templatized_page.rb b/lib/locomotive/steam/middlewares/templatized_page.rb +32 -0
@@ @@ -0,0 +1,32 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ class TemplatizedPage < Base
+
+ def _call(env)
+ super
+
+ 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/middlewares/timezone.rb b/lib/locomotive/steam/middlewares/timezone.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Locomotive::Steam
+ module Middlewares
+
+ # Set the timezone according to the settings of the site
+ #
+ class Timezone < Base
+
+ def _call(env)
+ super
+
+ Time.use_zone(site.try(:timezone) || 'UTC') do
+ app.call(env)
+ end
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/monkey_patches.rb b/lib/locomotive/steam/monkey_patches.rb +3 -3
@@ @@ -1,5 +1,5 @@
- require_relative 'monkey_patches/httparty.rb'
- require_relative 'monkey_patches/dragonfly.rb'
+ # require_relative 'monkey_patches/httparty.rb'
+ # require_relative 'monkey_patches/dragonfly.rb'
require_relative 'monkey_patches/mounter.rb'
require_relative 'monkey_patches/haml.rb'
- require_relative 'monkey_patches/better_errors.rb'
\ No newline at end of file
+ # require_relative 'monkey_patches/better_errors.rb'
\ No newline at end of file
locomotive/steam/monkey_patches/better_errors.rb b/lib/locomotive/steam/monkey_patches/better_errors.rb +0 -70
@@ @@ -1,70 +0,0 @@
- 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/monkey_patches/dragonfly.rb b/lib/locomotive/steam/monkey_patches/dragonfly.rb +0 -79
@@ @@ -1,79 +0,0 @@
- 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/monkey_patches/haml.rb b/lib/locomotive/steam/monkey_patches/haml.rb +3 -1
@@ @@ -1,3 +1,5 @@
+ require 'haml'
+
module Haml::Filters
remove_filter("Markdown") #remove the existing Markdown filter
@@ @@ -7,7 +9,7 @@ module Haml::Filters
include Haml::Filters::Base
def render text
- Locomotive::Steam::Markdown.steam text
+ Locomotive::Steam::Markdown.new.render text
end
end
locomotive/steam/monkey_patches/httparty.rb b/lib/locomotive/steam/monkey_patches/httparty.rb +0 -46
@@ @@ -1,46 +0,0 @@
- 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/monkey_patches/mounter.rb b/lib/locomotive/steam/monkey_patches/mounter.rb +22 -0
@@ @@ -1,3 +1,5 @@
+ require 'locomotive/mounter'
+
module Locomotive
module Mounter
module Models
@@ @@ -28,5 +30,25 @@ module Locomotive
end
end
+
+ module Reader
+ module FileSystem
+ class Runner
+
+ def new_mounting_point(host)
+ self.mounting_point
+ end
+
+ end
+ end
+ end
+
+ class MountingPoint
+
+ def assets_path
+ File.join(self.path, 'public')
+ end
+
+ end
end
end
\ No newline at end of file
locomotive/steam/server.rb b/lib/locomotive/steam/server.rb +33 -58
@@ @@ -1,80 +1,55 @@
- require 'better_errors'
- require 'coffee_script'
-
- require_relative 'listen'
- require_relative 'server/middleware'
- require_relative 'server/favicon'
- require_relative 'server/dynamic_assets'
- require_relative 'server/logging'
- require_relative 'server/entry_submission'
- require_relative 'server/path'
- require_relative 'server/locale'
- require_relative 'server/page'
- require_relative 'server/timezone'
- require_relative 'server/templatized_page'
- require_relative 'server/renderer'
-
- require_relative 'liquid'
- require_relative 'initializers'
+ require_relative 'core_ext'
require_relative 'monkey_patches'
+ require_relative 'liquid'
+ require_relative 'services'
+ require_relative 'middlewares'
module Locomotive::Steam
class Server
- def initialize(reader, options = {})
- Locomotive::Steam::Dragonfly.setup!(reader.mounting_point.path)
-
- Sprockets::Sass.add_sass_functions = false
+ attr_reader :reader, :app, :options
- @reader = reader
- @app = self.create_rack_app(@reader)
+ def initialize(reader, options = {})
+ @reader = reader
+ @options = options
- BetterErrors.application_root = reader.mounting_point.path
+ stack = Middlewares::Stack.new(options)
+ @app = stack.create
end
def call(env)
- env['steam.mounting_point'] = @reader.mounting_point
- @app.call(env)
+ dup._call(env) # thread-safe purpose
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'
- }
+ def _call(env)
+ set_request(env)
- use ::Dragonfly::Middleware, :images
+ set_mounting_point(env)
- use Rack::Static, {
- urls: ['/images', '/fonts', '/samples', '/media'],
- root: File.join(reader.mounting_point.path, 'public')
- }
+ set_services(env)
- use Favicon
- use DynamicAssets, reader.mounting_point.path
-
- use Logging
+ @app.call(env)
+ end
- use EntrySubmission
+ protected
- use Path
- use Locale
- use Timezone
+ def set_request(env)
+ @request = Rack::Request.new(env)
+ env['steam.request'] = @request
+ end
- use Page
- use TemplatizedPage
+ def set_mounting_point(env)
+ # one single mounting point per site
+ @mounting_point = @reader.new_mounting_point(@request.host)
+ env['steam.mounting_point'] = @reader.mounting_point
+ end
- run Renderer.new
- end
+ def set_services(env)
+ env['steam.services'] = {
+ dragonfly: Locomotive::Steam::Services::Dragonfly.new(@mounting_point.path),
+ markdown: Locomotive::Steam::Services::Markdown.new,
+ external_api: Locomotive::Steam::Services::ExternalAPI.new
+ }
end
end
locomotive/steam/server/dynamic_assets.rb b/lib/locomotive/steam/server/dynamic_assets.rb +0 -33
@@ @@ -1,33 +0,0 @@
- 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 +0 -120
@@ @@ -1,120 +0,0 @@
- 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 +0 -18
@@ @@ -1,18 +0,0 @@
- 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 +0 -42
@@ @@ -1,42 +0,0 @@
- 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 +0 -32
@@ @@ -1,32 +0,0 @@
- 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 +0 -61
@@ @@ -1,61 +0,0 @@
- 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 +0 -69
@@ @@ -1,69 +0,0 @@
- 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 +0 -34
@@ @@ -1,34 +0,0 @@
- 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 +0 -118
@@ @@ -1,118 +0,0 @@
- module Locomotive::Steam
- class Server
-
- class Renderer < 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 'Rendered 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 RendererException.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 +0 -32
@@ @@ -1,32 +0,0 @@
- 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 +0 -18
@@ @@ -1,18 +0,0 @@
- 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/services.rb b/lib/locomotive/steam/services.rb +1 -0
@@ @@ -0,0 +1 @@
+ Dir[File.join(File.dirname(__FILE__), 'services', '*.rb')].each { |lib| require lib }
\ No newline at end of file
locomotive/steam/services/dragonfly.rb b/lib/locomotive/steam/services/dragonfly.rb +49 -0
@@ @@ -0,0 +1,49 @@
+ module Locomotive
+ module Steam
+ module Services
+ class Dragonfly
+
+ attr_reader :path
+
+ def initialize(path = nil)
+ @path = path
+ end
+
+ def enabled?
+ !!self.enabled
+ end
+
+ def resize_url(source, resize_string)
+ image = (case url_or_path = get_url_or_path(source)
+ when '', nil
+ Locomotive::Steam::Logger.error "Unable to resize on the fly: #{source.inspect}"
+ nil
+ when /^http:\/\//
+ app.fetch_url(url_or_path)
+ else
+ app.fetch_file(File.join([self.path, 'public', url_or_path].compact))
+ end)
+
+ # apply the conversion if possible
+ image ? image.thumb(resize_string).url : source
+ end
+
+ def self.app
+ ::Dragonfly.app
+ end
+
+ protected
+
+ def get_url_or_path(source)
+ case source
+ when String then source.strip
+ when Hash then source['url'] || source[:url]
+ else
+ source.try(:url)
+ end
+ end
+
+ end
+ end
+ end
+ end
\ No newline at end of file
locomotive/steam/services/external_api.rb b/lib/locomotive/steam/services/external_api.rb +46 -0
@@ @@ -0,0 +1,46 @@
+ require 'uri'
+
+ module Locomotive
+ module Steam
+ module Services
+ class ExternalAPI
+
+ include ::HTTParty
+
+ def 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.class.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/services/markdown.rb b/lib/locomotive/steam/services/markdown.rb +29 -0
@@ @@ -0,0 +1,29 @@
+ require 'redcarpet'
+
+ module Locomotive
+ module Steam
+ module Services
+ class Markdown
+
+ def render(text)
+ self.class.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
+ end
\ No newline at end of file
locomotive/steam/standalone_server.rb b/lib/locomotive/steam/standalone_server.rb +1 -1
@@ @@ -23,7 +23,7 @@ module Locomotive
Bundler.require 'initializers'
# run the rack app
- super(reader, disable_listen: true)
+ super(reader, serve_assets: true)
end
end
end
locomotivecms_steam.gemspec +9 -9
@@ @@ -29,15 +29,15 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'rack-test'
spec.add_development_dependency 'i18n-spec'
- spec.add_dependency 'rack-cache', '~> 1.1'
- spec.add_dependency 'sprockets', '~> 2.0'
- spec.add_dependency 'sprockets-sass', '~> 1.0'
- spec.add_dependency 'better_errors', '~> 1.0'
- spec.add_dependency 'dragonfly', '~> 0.9'
- spec.add_dependency 'activesupport', '~> 3.2'
- spec.add_dependency 'listen', '~> 2.7'
- spec.add_dependency 'will_paginate', '~> 3.0'
- spec.add_dependency 'redcarpet', '~> 3.1'
+ spec.add_dependency 'rack-cache', '~> 1.1'
+ spec.add_dependency 'moneta', '~> 0.7.20'
+ spec.add_dependency 'sprockets', '~> 2.0'
+ spec.add_dependency 'sprockets-sass', '~> 1.0'
+ # spec.add_dependency 'better_errors', '~> 1.0'
+ spec.add_dependency 'dragonfly', '~> 1.0.3'
+ spec.add_dependency 'activesupport', '~> 3.2' # TODO: upgrade to 4.x
+ spec.add_dependency 'will_paginate', '~> 3.0' # TODO: move to kaminari
+ spec.add_dependency 'redcarpet', '~> 3.1'
spec.add_dependency 'locomotivecms_mounter'
spec.add_dependency 'locomotivecms-solid'
spec/integration/server/basic_spec.rb +0 -2
@@ @@ -13,9 +13,7 @@ describe Locomotive::Steam::Server do
end
it 'shows the index page' do
-
get '/index'
-
last_response.body.should =~ /Upcoming events/
end
spec/support/helpers.rb +4 -2
@@ @@ -6,7 +6,7 @@ module Spec
end
def remove_logs
- FileUtils.rm_rf(File.expand_path('../../fixtures/default/log', __FILE__))
+ # FileUtils.rm_rf(File.expand_path('../../fixtures/default/log', __FILE__))
end
def run_server
@@ @@ -15,7 +15,9 @@ module Spec
reader = Locomotive::Mounter::Reader::FileSystem.instance
reader.run!(path: path)
- Locomotive::Steam::Server.new(reader, disable_listen: true)
+ require 'locomotive/steam/initializers'
+
+ Locomotive::Steam::Server.new(reader)
end
end