organizing code around Service and Repository classes (dependency injection). In progress
did
committed Jan 30, 2015
commit 897d4d5db4733c4d6cb2e1504aa9e2db9188bc17
Showing 22
changed files with
740 additions
and 261 deletions
Gemfile.lock
+2
-0
| @@ | @@ -11,6 +11,7 @@ PATH |
| kaminari (~> 0.16.2) | |
| kramdown (~> 1.5.0) | |
| locomotivecms-solid (~> 4.0.0.alpha) | |
| + | mimetype-fu (~> 0.1.2) |
| moneta (~> 0.8.0) | |
| rack-cache (~> 1.2) | |
| sprockets (~> 2.12.3) | |
| @@ | @@ -108,6 +109,7 @@ GEM |
| nokogiri (>= 1.5.9) | |
| method_source (0.8.2) | |
| mime-types (2.4.3) | |
| + | mimetype-fu (0.1.2) |
| mini_portile (0.6.2) | |
| minitest (5.5.1) | |
| moneta (0.8.0) | |
locomotive/steam.rb b/lib/locomotive/steam.rb
+16
-1
| @@ | @@ -7,10 +7,18 @@ require_relative 'steam/decorators' |
| require_relative 'steam/configuration' | |
| require_relative 'steam/liquid' | |
| + | require_relative 'steam/morphine' |
| + | # require_relative 'steam/default_repositories/theme_asset' |
| + | require_relative 'steam/repositories' |
| + | require_relative 'steam/services' |
| + | |
| + | # TODO: move into a file named dependencies |
| require 'sprockets' | |
| require 'sprockets-sass' | |
| require 'haml' | |
| require 'compass' | |
| + | require 'mimetype_fu' |
| + | require 'mime-types' |
| require 'active_support' | |
| require 'active_support/concern' | |
| @@ | @@ -23,7 +31,14 @@ require 'mime/types' |
| module Locomotive | |
| module Steam | |
| - | TEMPLATE_EXTENSIONS = %w(liquid haml) |
| + | # Locomotive::Steam.repositories.get(:site) |
| + | # Locomotive::Steam.repositories.get(:theme_assets, site) |
| + | |
| + | # a la fin de chaque requete => on clean les repositories |
| + | |
| + | # Locomotive::Steam.repositories[:theme_assets](site) |
| + | |
| + | # TEMPLATE_EXTENSIONS = %w(liquid haml) |
| class << self | |
| attr_writer :configuration | |
locomotive/steam/configuration.rb b/lib/locomotive/steam/configuration.rb
+4
-2
| @@ | @@ -2,10 +2,12 @@ module Locomotive |
| module Steam | |
| class Configuration | |
| - | attr_accessor :mode |
| + | |
| + | attr_accessor :mode, :theme_assets_checksum, :asset_host |
| def initialize | |
| - | self.mode = :production |
| + | self.mode = :production |
| + | self.theme_assets_checksum = false |
| end | |
| end | |
locomotive/steam/liquid/asset_host.rb b/lib/locomotive/steam/liquid/asset_host.rb
+0
-53
| @@ | @@ -1,53 +0,0 @@ |
| - | module Locomotive |
| - | module Steam |
| - | module Liquid |
| - | |
| - | class AssetHost |
| - | |
| - | IsHTTP = /^https?\/\//o |
| - | |
| - | attr_reader :request, :site, :host |
| - | |
| - | def initialize(request, site, host) |
| - | @request, @site = request, site |
| - | |
| - | @host = build_host(host, request, site) |
| - | end |
| - | |
| - | def compute(source, timestamp = nil) |
| - | return source if source.nil? |
| - | |
| - | return add_timestamp_suffix(source, timestamp) if source =~ IsHTTP |
| - | |
| - | url = self.host ? URI.join(host, source).to_s : source |
| - | |
| - | add_timestamp_suffix(url, timestamp) |
| - | end |
| - | |
| - | private |
| - | |
| - | def build_host(host, request, site) |
| - | if host |
| - | if host.respond_to?(:call) |
| - | host.call(request, site) |
| - | else |
| - | host |
| - | end |
| - | else |
| - | nil |
| - | end |
| - | end |
| - | |
| - | def add_timestamp_suffix(source, timestamp) |
| - | if timestamp.nil? || timestamp == 0 || source.include?('?') |
| - | source |
| - | else |
| - | "#{source}?#{timestamp}" |
| - | end |
| - | end |
| - | |
| - | end |
| - | |
| - | end |
| - | end |
| - | end |
locomotive/steam/liquid/filters/base.rb b/lib/locomotive/steam/liquid/filters/base.rb
+35
-47
| @@ | @@ -1,61 +1,49 @@ |
| module Locomotive | |
| - | module Liquid |
| - | module Filters |
| - | module Base |
| - | |
| - | 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 |
| + | module Steam |
| + | module Liquid |
| + | module Filters |
| + | module Base |
| + | |
| + | 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 | |
| - | 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.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' ' |
| - | end |
| - | # Get the url 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 asset_url(path) |
| - | # keep the query string safe |
| - | path.gsub!(/(\?+.+)$/, '') |
| - | query_string = $1 |
| + | # 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.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' ' |
| + | end |
| - | # build the url of the theme asset based on the site and without loading |
| - | # the whole theme asset from database |
| - | _url = ThemeAssetUploader.url_for(@context.registers[:site], path) |
| + | # Get the url 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 |
| - | # get a timestamp only the source url does not include a query string |
| - | timestamp = query_string.blank? ? @context.registers[:theme_assets_checksum][path] : nil |
| + | def asset_url(path) |
| + | @context.registers[:services].theme_asset_url.build(path) |
| + | end |
| - | # prefix by a asset host if given |
| - | url = @context.registers[:asset_host].compute(_url, timestamp) |
| + | def absolute_url(url) |
| + | url.starts_with?('/') ? url : "/#{url}" |
| + | end |
| - | query_string ? "#{url}#{query_string}" : url |
| end | |
| - | def absolute_url(url) |
| - | url.starts_with?('/') ? url : "/#{url}" |
| - | end |
| + | ::Liquid::Template.register_filter(Base) |
| end | |
| - | |
| - | ::Liquid::Template.register_filter(Base) |
| - | |
| end | |
| end | |
| - | end |
| \ No newline at end of file | |
| + | end |
locomotive/steam/liquid/filters/html.rb b/lib/locomotive/steam/liquid/filters/html.rb
+91
-89
| @@ | @@ -1,117 +1,119 @@ |
| module Locomotive | |
| - | module Liquid |
| - | module Filters |
| - | module Html |
| - | |
| - | # Return 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::Type.lookup_by_extension('rss').to_s |
| - | title = options[:title] || 'RSS' |
| - | |
| - | %{<link rel="#{rel}" type="#{type}" title="#{title}" href="#{input}">} |
| - | end |
| + | module Steam |
| + | module Liquid |
| + | module Filters |
| + | module Html |
| + | |
| + | # Return 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] || 'application/rss+xml' |
| + | 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? |
| - | # Write the url of a theme stylesheet |
| - | # input: name of the css file |
| - | def stylesheet_url(input) |
| - | return '' if input.nil? |
| + | if input =~ /^(\/|https?:)/ |
| + | uri = URI(input) |
| + | else |
| + | uri = URI(asset_url("stylesheets/#{input}")) |
| + | end |
| - | if input =~ /^(\/|https?:)/ |
| - | uri = URI(input) |
| - | else |
| - | uri = URI(asset_url("stylesheets/#{input}")) |
| + | uri.path = "#{uri.path}.css" unless uri.path.ends_with?('.css') |
| + | uri.to_s |
| end | |
| - | uri.path = "#{uri.path}.css" unless uri.path.ends_with?('.css') |
| - | uri.to_s |
| - | end |
| + | # Write the link tag of a theme stylesheet |
| + | # input: url of the css file |
| + | def stylesheet_tag(input, media = 'screen') |
| + | return '' if input.nil? |
| - | # Write the link tag of a theme stylesheet |
| - | # input: url of the css file |
| - | def stylesheet_tag(input, media = 'screen') |
| - | return '' if input.nil? |
| + | input = stylesheet_url(input) |
| - | input = stylesheet_url(input) |
| + | %{<link href="#{input}" media="#{media}" rel="stylesheet" type="text/css">} |
| + | end |
| - | %{<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? |
| - | # Write the url to javascript resource |
| - | # input: name of the javascript file |
| - | def javascript_url(input) |
| - | return '' if input.nil? |
| + | if input =~ /^(\/|https?:)/ |
| + | uri = URI(input) |
| + | else |
| + | uri = URI(asset_url("javascripts/#{input}")) |
| + | end |
| - | if input =~ /^(\/|https?:)/ |
| - | uri = URI(input) |
| - | else |
| - | uri = URI(asset_url("javascripts/#{input}")) |
| + | uri.path = "#{uri.path}.js" unless uri.path.ends_with?('.js') |
| + | uri.to_s |
| end | |
| - | uri.path = "#{uri.path}.js" unless uri.path.ends_with?('.js') |
| - | uri.to_s |
| - | end |
| + | # Write the link to javascript resource |
| + | # input: url of the javascript file |
| + | def javascript_tag(input, *args) |
| + | return '' if input.nil? |
| + | javascript_options = inline_options(args_to_options(args)) |
| + | input = javascript_url(input) |
| - | # Write the link to javascript resource |
| - | # input: url of the javascript file |
| - | def javascript_tag(input, *args) |
| - | return '' if input.nil? |
| - | javascript_options = inline_options(args_to_options(args)) |
| - | input = javascript_url(input) |
| + | "<script src=\"#{input}\" type=\"text/javascript\" #{javascript_options}></script>" |
| + | end |
| - | "<script src=\"#{input}\" type=\"text/javascript\" #{javascript_options}></script>" |
| - | end |
| + | def theme_image_url(input) |
| + | return '' if input.nil? |
| - | def theme_image_url(input) |
| - | return '' if input.nil? |
| + | input = "images/#{input}" unless input.starts_with?('/') |
| - | input = "images/#{input}" unless input.starts_with?('/') |
| + | asset_url(input) |
| + | end |
| - | asset_url(input) |
| - | 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)) |
| - | # 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 |
| - | "<img src=\"#{theme_image_url(input)}\" #{image_options}>" |
| - | 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)) |
| - | # 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 |
| - | "<img src=\"#{get_url_from_asset(input)}\" #{image_options}>" |
| - | 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 |
| - | # 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 | |
| - | end |
| - | |
| - | ::Liquid::Template.register_filter(Html) |
| + | ::Liquid::Template.register_filter(Html) |
| + | end |
| end | |
| end | |
| end | |
locomotive/steam/morphine.rb b/lib/locomotive/steam/morphine.rb
+26
-0
| @@ | @@ -0,0 +1,26 @@ |
| + | # Morphine is a lightweight dependency injection framework for Ruby. It uses a simple Ruby DSL to ease the pain of wiring your dependencies together. |
| + | # We do not use the offical gem but rather the single file from here: |
| + | # https://github.com/bkeepers/morphine |
| + | # |
| + | |
| + | module Morphine |
| + | def self.included(base) |
| + | base.extend ClassMethods |
| + | end |
| + | |
| + | def dependencies |
| + | @dependencies ||= {} |
| + | end |
| + | |
| + | module ClassMethods |
| + | def register(name, &block) |
| + | define_method name do |*args| |
| + | dependencies[name] ||= instance_exec(*args,&block) |
| + | end |
| + | |
| + | define_method "#{name}=" do |service| |
| + | dependencies[name] = service |
| + | end |
| + | end |
| + | end |
| + | end |
locomotive/steam/repositories.rb b/lib/locomotive/steam/repositories.rb
+28
-0
| @@ | @@ -0,0 +1,28 @@ |
| + | Dir[File.join(File.dirname(__FILE__), 'repositories', '*.rb')].each { |lib| require lib } |
| + | |
| + | module Locomotive |
| + | module Steam |
| + | module Repositories |
| + | |
| + | def self.instance(site = nil) |
| + | Registered.new(site) |
| + | end |
| + | |
| + | class Registered < Struct.new(:current_site) |
| + | |
| + | include Morphine |
| + | |
| + | # default repositories |
| + | register :site do |
| + | Repositories::Site.new |
| + | end |
| + | |
| + | register :theme_asset do |
| + | Repositories::ThemeAsset.new(current_site) |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/repositories/content_types_repository.rb b/lib/locomotive/steam/repositories/content_types_repository.rb
+14
-14
| @@ | @@ -1,14 +1,14 @@ |
| - | module Locomotive |
| - | module Steam |
| - | module Repositories |
| - | class ContentTypesRepository |
| - | include Repository |
| - | def [](slug) |
| - | query(:en) do |
| - | where('slug.eq' => slug.to_s) |
| - | end.first |
| - | end |
| - | end |
| - | end |
| - | end |
| - | end |
| + | # module Locomotive |
| + | # module Steam |
| + | # module Repositories |
| + | # class ContentTypesRepository |
| + | # include Repository |
| + | # def [](slug) |
| + | # query(:en) do |
| + | # where('slug.eq' => slug.to_s) |
| + | # end.first |
| + | # end |
| + | # end |
| + | # end |
| + | # end |
| + | # end |
locomotive/steam/repositories/pages_repository.rb b/lib/locomotive/steam/repositories/pages_repository.rb
+21
-21
| @@ | @@ -1,23 +1,23 @@ |
| - | module Locomotive |
| - | module Steam |
| - | module Repositories |
| - | class PagesRepository |
| - | include Repository |
| - | attr_accessor :current_locale |
| + | # module Locomotive |
| + | # module Steam |
| + | # module Repositories |
| + | # class PagesRepository |
| + | # include Repository |
| + | # attr_accessor :current_locale |
| - | def [](path) |
| - | query(current_locale) do |
| - | where('fullpath.eq' => path) |
| - | end.first |
| - | end |
| + | # def [](path) |
| + | # query(current_locale) do |
| + | # where('fullpath.eq' => path) |
| + | # end.first |
| + | # end |
| - | def matching_paths(paths) |
| - | query(current_locale) do |
| - | where('fullpath.in' => paths) |
| - | order_by('position ASC') |
| - | end |
| - | end |
| - | end |
| - | end |
| - | end |
| - | end |
| + | # def matching_paths(paths) |
| + | # query(current_locale) do |
| + | # where('fullpath.in' => paths) |
| + | # order_by('position ASC') |
| + | # end |
| + | # end |
| + | # end |
| + | # end |
| + | # end |
| + | # end |
locomotive/steam/repositories/site_repository.rb b/lib/locomotive/steam/repositories/site_repository.rb
+19
-0
| @@ | @@ -0,0 +1,19 @@ |
| + | module Locomotive |
| + | module Steam |
| + | module Repositories |
| + | |
| + | class Site |
| + | |
| + | def find_by_host(host) |
| + | raise 'TODO' |
| + | # TODO multilocales |
| + | # query(:en) do |
| + | # where('domains.in' => host) |
| + | # end.first |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/repositories/sites_repository.rb b/lib/locomotive/steam/repositories/sites_repository.rb
+0
-16
| @@ | @@ -1,16 +0,0 @@ |
| - | module Locomotive |
| - | module Steam |
| - | module Repositories |
| - | class SitesRepository |
| - | include Repository |
| - | |
| - | def find_by_host(host) |
| - | # TODO multilocales |
| - | query(:en) do |
| - | where('domains.in' => host) |
| - | end.first |
| - | end |
| - | end |
| - | end |
| - | end |
| - | end |
locomotive/steam/repositories/theme_asset.rb b/lib/locomotive/steam/repositories/theme_asset.rb
+19
-0
| @@ | @@ -0,0 +1,19 @@ |
| + | module Locomotive |
| + | module Steam |
| + | module Repositories |
| + | |
| + | class ThemeAsset < Struct.new(:site) |
| + | |
| + | def url_for(path) |
| + | URI.join('sites', site._id, 'theme', path).to_s |
| + | end |
| + | |
| + | def checksums |
| + | @site.theme_assets.checksums |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/server.rb b/lib/locomotive/steam/server.rb
+7
-16
| @@ | @@ -23,36 +23,27 @@ module Locomotive::Steam |
| def _call(env) | |
| set_request(env) | |
| - | set_path(env) |
| + | register_services(env) |
| fetch_site(env) | |
| - | set_services(env) |
| - | |
| @app.call(env) | |
| end | |
| protected | |
| - | def set_path(env) |
| - | env['steam.path'] = options.fetch(:path) |
| - | end |
| def set_request(env) | |
| - | @request = Rack::Request.new(env) |
| - | env['steam.request'] = @request |
| + | env['steam.request'] = Rack::Request.new(env) |
| end | |
| def fetch_site(env) | |
| - | # one single mounting point per site |
| - | env['steam.site'] = Locomotive::Models[:sites].find_by_host(@request.host) |
| + | site = env['steam.services'].site_finder.find |
| + | env['steam.site'] = env['steam.services'].repositories.current_site = site |
| + | |
| end | |
| - | def set_services(env) |
| - | env['steam.services'] = { |
| - | dragonfly: Locomotive::Steam::Services::Dragonfly.new(options.fetch(:path)), |
| - | markdown: Locomotive::Steam::Services::Markdown.new, |
| - | external_api: Locomotive::Steam::Services::ExternalAPI.new |
| - | } |
| + | def register_services(env) |
| + | env['steam.services'] = Locomotive::Steam::Services.instance(env['steam.request'], options) |
| end | |
| end | |
locomotive/steam/services.rb b/lib/locomotive/steam/services.rb
+43
-1
| @@ | @@ -1 +1,43 @@ |
| - | Dir[File.join(File.dirname(__FILE__), 'services', '*.rb')].each { |lib| require lib } |
| \ No newline at end of file | |
| + | Dir[File.join(File.dirname(__FILE__), 'services', '*.rb')].each { |lib| require lib } |
| + | |
| + | module Locomotive |
| + | module Steam |
| + | module Services |
| + | |
| + | def self.instance(request, options = {}) |
| + | Registered.new(request, options) |
| + | end |
| + | |
| + | class Registered < Struct.new(:request, :options) |
| + | |
| + | include Morphine |
| + | |
| + | register :repositories do |
| + | Repositories.instance |
| + | end |
| + | |
| + | register :site_finder do |
| + | Services::SiteFinder.new(request, repositories.site, options) |
| + | end |
| + | |
| + | register :theme_asset_url do |
| + | Services::ThemeAssetUrl.new(current_site, asset_host, configuration.theme_assets_checksum) |
| + | end |
| + | |
| + | register :asset_host do |
| + | Services::AssetHost.new(request, current_site, configuration.asset_host) |
| + | end |
| + | |
| + | def current_site |
| + | repositories.current_site |
| + | end |
| + | |
| + | def configuration |
| + | Locomotive::Steam.configuration |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/services/asset_host.rb b/lib/locomotive/steam/services/asset_host.rb
+53
-0
| @@ | @@ -0,0 +1,53 @@ |
| + | module Locomotive |
| + | module Steam |
| + | module Services |
| + | |
| + | class AssetHost |
| + | |
| + | IsHTTP = /^https?\/\//o |
| + | |
| + | attr_reader :request, :site, :host |
| + | |
| + | def initialize(request, site, host) |
| + | @request, @site = request, site |
| + | |
| + | @host = build_host(host, request, site) |
| + | end |
| + | |
| + | def compute(source, timestamp = nil) |
| + | return source if source.nil? |
| + | |
| + | return add_timestamp_suffix(source, timestamp) if source =~ IsHTTP |
| + | |
| + | url = self.host ? URI.join(host, source).to_s : source |
| + | |
| + | add_timestamp_suffix(url, timestamp) |
| + | end |
| + | |
| + | private |
| + | |
| + | def build_host(host, request, site) |
| + | if host |
| + | if host.respond_to?(:call) |
| + | host.call(request, site) |
| + | else |
| + | host |
| + | end |
| + | else |
| + | nil |
| + | end |
| + | end |
| + | |
| + | def add_timestamp_suffix(source, timestamp) |
| + | if timestamp.nil? || timestamp == 0 || source.include?('?') |
| + | source |
| + | else |
| + | "#{source}?#{timestamp}" |
| + | end |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/services/dragonfly.rb b/lib/locomotive/steam/services/dragonfly.rb
+1
-1
| @@ | @@ -46,4 +46,4 @@ module Locomotive |
| end | |
| end | |
| end | |
| - | end |
| \ No newline at end of file | |
| + | end |
locomotive/steam/services/site_finder.rb b/lib/locomotive/steam/services/site_finder.rb
+15
-0
| @@ | @@ -0,0 +1,15 @@ |
| + | module Locomotive |
| + | module Steam |
| + | module Services |
| + | |
| + | class SiteFinder < Struct.new(:repository, :request, :options) |
| + | |
| + | def find |
| + | repository.find_by_host(request.host) |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/services/theme_asset_url.rb b/lib/locomotive/steam/services/theme_asset_url.rb
+45
-0
| @@ | @@ -0,0 +1,45 @@ |
| + | module Locomotive |
| + | module Steam |
| + | module Services |
| + | |
| + | class ThemeAssetUrl < Struct.new(:repository, :asset_host, :checksum) |
| + | |
| + | def buid(path) |
| + | # keep the query string safe |
| + | path.gsub!(/(\?+.+)$/, '') |
| + | query_string = $1 |
| + | |
| + | # build the url of the theme asset based on the persistence layer |
| + | _url = repository.url_for(path) |
| + | |
| + | # get a timestamp only the source url does not include a query string |
| + | timestamp = query_string.blank? ? checksums[path] : nil |
| + | |
| + | # prefix by a asset host if given |
| + | url = asset_host.compute(_url, timestamp) |
| + | |
| + | query_string ? "#{url}#{query_string}" : url |
| + | end |
| + | |
| + | def checksums |
| + | if checksum? |
| + | @checksums ||= fetch_checksums |
| + | else |
| + | {} |
| + | end |
| + | end |
| + | |
| + | def checksum? |
| + | !!checksum |
| + | end |
| + | |
| + | private |
| + | |
| + | def fetch_checksums |
| + | repository.checksums |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
| + | end |
locomotivecms_steam.gemspec
+1
-0
| @@ | @@ -38,6 +38,7 @@ Gem::Specification.new do |spec| |
| spec.add_dependency 'coffee-script', '~> 2.2.0' | |
| spec.add_dependency 'haml', '~> 4.0.6' | |
| spec.add_dependency 'compass', '~> 1.0.3' | |
| + | spec.add_dependency 'mimetype-fu', '~> 0.1.2' |
| # spec.add_dependency 'locomotivecms_models', '~> 0.0.1.pre.alpha' | |
spec/unit/liquid/filters/html_spec.rb
+256
-0
| @@ | @@ -0,0 +1,256 @@ |
| + | require 'spec_helper' |
| + | |
| + | describe Locomotive::Steam::Liquid::Filters::Html do |
| + | |
| + | include Locomotive::Steam::Liquid::Filters::Base |
| + | include Locomotive::Steam::Liquid::Filters::Html |
| + | |
| + | before(:each) do |
| + | @context = build_context |
| + | end |
| + | |
| + | it 'writes the tag to display a rss/atom feed' do |
| + | expect(auto_discovery_link_tag('/foo/bar')).to eq %( |
| + | <link rel="alternate" type="application/rss+xml" title="RSS" href="/foo/bar"> |
| + | ).strip |
| + | |
| + | expect(auto_discovery_link_tag('/foo/bar', 'rel:alternate2', 'type:atom', 'title:Hello world')).to eq %( |
| + | <link rel="alternate2" type="atom" title="Hello world" href="/foo/bar"> |
| + | ).strip |
| + | end |
| + | |
| + | it 'returns an url for a stylesheet file' do |
| + | result = "/sites/000000000000000000000042/theme/stylesheets/main.css" |
| + | expect(stylesheet_url('main.css')).to eq(result) |
| + | expect(stylesheet_url('main')).to eq(result) |
| + | expect(stylesheet_url(nil)).to eq('') |
| + | end |
| + | |
| + | it 'returnss an url with the checksum' do |
| + | @context.registers.merge!(theme_assets_checksum: { 'stylesheets/main.css' => 42 }) |
| + | result = "/sites/000000000000000000000042/theme/stylesheets/main.css?42" |
| + | expect(stylesheet_url('main.css')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a stylesheet file with folder' do |
| + | result = "/sites/000000000000000000000042/theme/stylesheets/trash/main.css" |
| + | expect(stylesheet_url('trash/main.css')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a stylesheet file without touching the url that starts with "/"' do |
| + | result = "/trash/main.css" |
| + | expect(stylesheet_url('/trash/main.css')).to eq(result) |
| + | expect(stylesheet_url('/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a stylesheet file without touching the url that starts with "http:"' do |
| + | result = "http://cdn.example.com/trash/main.css" |
| + | expect(stylesheet_url('http://cdn.example.com/trash/main.css')).to eq(result) |
| + | expect(stylesheet_url('http://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a stylesheet file without touching the url that starts with "https:"' do |
| + | result = "https://cdn.example.com/trash/main.css" |
| + | expect(stylesheet_url('https://cdn.example.com/trash/main.css')).to eq(result) |
| + | expect(stylesheet_url('https://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a stylesheet file with respect to URL-parameters' do |
| + | result = "/sites/000000000000000000000042/theme/stylesheets/main.css?v=42" |
| + | expect(stylesheet_url('main.css?v=42')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file' do |
| + | result = "<link href=\"/sites/000000000000000000000042/theme/stylesheets/main.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('main.css')).to eq(result) |
| + | expect(stylesheet_tag('main')).to eq(result) |
| + | expect(stylesheet_tag(nil)).to eq('') |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file with folder' do |
| + | result = "<link href=\"/sites/000000000000000000000042/theme/stylesheets/trash/main.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('trash/main.css')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file without touching the url that starts with "/"' do |
| + | result = "<link href=\"/trash/main.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('/trash/main.css')).to eq(result) |
| + | expect(stylesheet_tag('/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file without touching the url that starts with "http:"' do |
| + | result = "<link href=\"http://cdn.example.com/trash/main.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('http://cdn.example.com/trash/main.css')).to eq(result) |
| + | expect(stylesheet_tag('http://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file without touching the url that starts with "https:"' do |
| + | result = "<link href=\"https://cdn.example.com/trash/main.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('https://cdn.example.com/trash/main.css')).to eq(result) |
| + | expect(stylesheet_tag('https://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet stored in Amazon S3' do |
| + | url = 'https://com.citrrus.locomotive.s3.amazonaws.com/sites/42/theme/stylesheets/bootstrap2.css' |
| + | stubs(:asset_url).returns(url) |
| + | result = "<link href=\"#{url}\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('bootstrap2.css')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file and media attribute set to print' do |
| + | result = "<link href=\"/sites/000000000000000000000042/theme/stylesheets/main.css\" media=\"print\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('main.css','print')).to eq(result) |
| + | expect(stylesheet_tag('main','print')).to eq(result) |
| + | expect(stylesheet_tag(nil)).to eq('') |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file with folder and media attribute set to print' do |
| + | result = "<link href=\"/sites/000000000000000000000042/theme/stylesheets/trash/main.css\" media=\"print\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('trash/main.css','print')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file without touching the url that starts with "/" and media attribute set to print' do |
| + | result = "<link href=\"/trash/main.css\" media=\"print\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('/trash/main.css','print')).to eq(result) |
| + | expect(stylesheet_tag('/trash/main','print')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file without touching the url that starts with "http:" and media attribute set to print' do |
| + | result = "<link href=\"http://cdn.example.com/trash/main.css\" media=\"print\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('http://cdn.example.com/trash/main.css','print')).to eq(result) |
| + | expect(stylesheet_tag('http://cdn.example.com/trash/main','print')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a link tag for a stylesheet file without touching the url that starts with "https:" and media attribute set to print' do |
| + | result = "<link href=\"https://cdn.example.com/trash/main.css\" media=\"print\" rel=\"stylesheet\" type=\"text/css\">" |
| + | expect(stylesheet_tag('https://cdn.example.com/trash/main.css','print')).to eq(result) |
| + | expect(stylesheet_tag('https://cdn.example.com/trash/main','print')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a javascript file' do |
| + | result = "/sites/000000000000000000000042/theme/javascripts/main.js" |
| + | expect(javascript_url('main.js')).to eq(result) |
| + | expect(javascript_url('main')).to eq(result) |
| + | expect(javascript_url(nil)).to eq('') |
| + | end |
| + | |
| + | it 'returns an url for a javascript file with folder' do |
| + | result = "/sites/000000000000000000000042/theme/javascripts/trash/main.js" |
| + | expect(javascript_url('trash/main.js')).to eq(result) |
| + | expect(javascript_url('trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a javascript file without touching the url that starts with "/"' do |
| + | result = "/trash/main.js" |
| + | expect(javascript_url('/trash/main.js')).to eq(result) |
| + | expect(javascript_url('/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a javascript file without touching the url that starts with "http:"' do |
| + | result = "http://cdn.example.com/trash/main.js" |
| + | expect(javascript_url('http://cdn.example.com/trash/main.js')).to eq(result) |
| + | expect(javascript_url('http://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a javascript file without touching the url that starts with "https:"' do |
| + | result = "https://cdn.example.com/trash/main.js" |
| + | expect(javascript_url('https://cdn.example.com/trash/main.js')).to eq(result) |
| + | expect(javascript_url('https://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns an url for a javascript file with respect to URL-parameters' do |
| + | result = "/sites/000000000000000000000042/theme/javascripts/main.js?v=42" |
| + | expect(javascript_url('main.js?v=42')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a script tag for a javascript file' do |
| + | result = %{<script src="/sites/000000000000000000000042/theme/javascripts/main.js" type="text/javascript" ></script>} |
| + | expect(javascript_tag('main.js')).to eq(result) |
| + | expect(javascript_tag('main')).to eq(result) |
| + | expect(javascript_tag(nil)).to eq('') |
| + | end |
| + | |
| + | it 'returns a script tag for a javascript file with folder' do |
| + | result = %{<script src="/sites/000000000000000000000042/theme/javascripts/trash/main.js" type="text/javascript" ></script>} |
| + | expect(javascript_tag('trash/main.js')).to eq(result) |
| + | expect(javascript_tag('trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a script tag for a javascript file without touching the url that starts with "/"' do |
| + | result = %{<script src="/trash/main.js" type="text/javascript" ></script>} |
| + | expect(javascript_tag('/trash/main.js')).to eq(result) |
| + | expect(javascript_tag('/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a script tag for a javascript file without touching the url that starts with "http:"' do |
| + | result = %{<script src="http://cdn.example.com/trash/main.js" type="text/javascript" ></script>} |
| + | expect(javascript_tag('http://cdn.example.com/trash/main.js')).to eq(result) |
| + | expect(javascript_tag('http://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a script tag for a javascript file without touching the url that starts with "https:"' do |
| + | result = %{<script src="https://cdn.example.com/trash/main.js" type="text/javascript" ></script>} |
| + | expect(javascript_tag('https://cdn.example.com/trash/main.js')).to eq(result) |
| + | expect(javascript_tag('https://cdn.example.com/trash/main')).to eq(result) |
| + | end |
| + | |
| + | it 'returns a script tag for a javascript file with "defer" option' do |
| + | result = %{<script src="https://cdn.example.com/trash/main.js" type="text/javascript" defer="defer" ></script>} |
| + | expect(javascript_tag('https://cdn.example.com/trash/main.js', ['defer:defer'])).to eq(result) |
| + | end |
| + | |
| + | it 'returns an image tag for a given theme file without parameters' do |
| + | expect(theme_image_tag('foo.jpg')).to eq("<img src=\"/sites/000000000000000000000042/theme/images/foo.jpg\" >") |
| + | end |
| + | |
| + | it 'returns an image tag for a given theme file with size' do |
| + | expect(theme_image_tag('foo.jpg', 'width:100', 'height:100')).to eq("<img src=\"/sites/000000000000000000000042/theme/images/foo.jpg\" height=\"100\" width=\"100\" >") |
| + | end |
| + | |
| + | it 'returns an image tag without parameters' do |
| + | expect(image_tag('foo.jpg')).to eq("<img src=\"foo.jpg\" >") |
| + | end |
| + | |
| + | it 'returns an image tag with size' do |
| + | expect(image_tag('foo.jpg', 'width:100', 'height:50')).to eq("<img src=\"foo.jpg\" height=\"50\" width=\"100\" >") |
| + | end |
| + | |
| + | it 'returns a flash tag without parameters' do |
| + | expect(flash_tag('foo.flv')).to eq(%{ |
| + | <object> |
| + | <param name="movie" value="foo.flv"> |
| + | <embed src="foo.flv"> |
| + | </embed> |
| + | </object> |
| + | }.strip) |
| + | end |
| + | |
| + | it 'returns a flash tag with size' do |
| + | expect(flash_tag('foo.flv', 'width:100', 'height:50')).to eq(%{ |
| + | <object height=\"50\" width=\"100\"> |
| + | <param name="movie" value="foo.flv"> |
| + | <embed src="foo.flv" height=\"50\" width=\"100\"> |
| + | </embed> |
| + | </object> |
| + | }.strip) |
| + | end |
| + | |
| + | def build_context |
| + | klass = Class.new |
| + | klass.class_eval do |
| + | def registers |
| + | @registers ||= { |
| + | site: FactoryGirl.build(:site, id: fake_bson_id(42)), |
| + | theme_assets_checksum: {}, |
| + | asset_host: TimestampAssetHost.new |
| + | } |
| + | end |
| + | |
| + | def fake_bson_id(id) |
| + | BSON::ObjectId.from_string(id.to_s.rjust(24, '0')) |
| + | end |
| + | end |
| + | klass.new |
| + | end |
| + | |
| + | end |
spec/unit/repositories_spec.rb
+44
-0
| @@ | @@ -0,0 +1,44 @@ |
| + | require 'spec_helper' |
| + | |
| + | describe Locomotive::Steam::Repositories do |
| + | |
| + | let(:site) { instance_double('Site', name: 'PCH') } |
| + | let(:repositories) { Locomotive::Steam::Repositories.instance(site) } |
| + | |
| + | describe '#theme_asset' do |
| + | |
| + | subject { repositories.theme_asset } |
| + | |
| + | context 'by default' do |
| + | |
| + | it 'returns a class of ThemeAssetRepository' do |
| + | expect(subject.class).to eq Locomotive::Steam::Repositories::ThemeAsset |
| + | end |
| + | |
| + | it 'gets access to the site' do |
| + | expect(subject.site.name).to eq 'PCH' |
| + | end |
| + | |
| + | end |
| + | |
| + | context 'a different repository' do |
| + | |
| + | before do |
| + | repositories.theme_asset = MyThemeAssetRepository.new(site) |
| + | end |
| + | |
| + | it 'returns a class of ThemeAssetRepository' do |
| + | expect(subject.class).to eq MyThemeAssetRepository |
| + | end |
| + | |
| + | it 'gets access to the site' do |
| + | expect(subject.site.name).to eq 'PCH' |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | class MyThemeAssetRepository < Struct.new(:site); end |
| + | |
| + | end |