diff --git a/.travis.yml b/.travis.yml index 5b8b888..6f9fab0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,17 @@ gemfile: - gemfiles/sprockets_rails_2_with_sprockets_2.gemfile - gemfiles/sprockets_rails_2_with_sprockets_3.gemfile - gemfiles/sprockets_rails_3_with_sprockets_3.gemfile + - gemfiles/sprockets_rails_3_with_sprockets_4.gemfile matrix: exclude: - rvm: 2.0 gemfile: gemfiles/sprockets_rails_3_with_sprockets_3.gemfile - rvm: 2.1.9 gemfile: gemfiles/sprockets_rails_3_with_sprockets_3.gemfile + - rvm: 2.0 + gemfile: gemfiles/sprockets_rails_3_with_sprockets_4.gemfile + - rvm: 2.1.9 + gemfile: gemfiles/sprockets_rails_3_with_sprockets_4.gemfile branches: only: - master diff --git a/Appraisals b/Appraisals index 71f2fbe..f24e99f 100644 --- a/Appraisals +++ b/Appraisals @@ -20,3 +20,8 @@ appraise "sprockets-rails 3 with sprockets 3" do gem "sprockets-rails", "~> 3.0" gem "sprockets", "~> 3.0" end + +appraise "sprockets-rails 3 with sprockets 4" do + gem "sprockets-rails", "~> 3.0" + gem "sprockets", "~> 4.0.0.beta1" +end diff --git a/README.md b/README.md index af68ed2..bb5af9f 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,14 @@ This will generate `public/404.html`. ## Template engines Gakubuchi supports some template engines: `ERB`, `Haml` and `Slim`. -If you want to use `Haml` or `Slim`, you need to put them in your Gemfile: +When you want to use `Haml` or `Slim`, you need to put them in your Gemfile: ```ruby # Use Haml -gem 'haml-rails' +gem 'haml' # Use Slim -gem 'slim-rails' +gem 'slim' ``` ## Using assets @@ -123,7 +123,7 @@ bundle install appraisal install # Run RSpec with a specific combination -appraisal 'sprockets-rails 3 with sprockets 3' rspec +appraisal 'sprockets-rails 3 with sprockets 4' rspec # Run RSpec with all combinations appraisal rspec diff --git a/gakubuchi.gemspec b/gakubuchi.gemspec index 3fb225c..7703f0a 100644 --- a/gakubuchi.gemspec +++ b/gakubuchi.gemspec @@ -14,9 +14,10 @@ Gem::Specification.new do |s| s.description = "Gakubuchi provides a simple way to manage static pages with Asset Pipeline." s.license = "MIT" - s.required_ruby_version = '>= 2.0.0' + s.required_ruby_version = ">= 2.0.0" s.files = Dir["lib/**/*", "MIT-LICENSE", "Rakefile", "README.md"] + s.add_dependency "grease", "~> 0.3.0" s.add_dependency "railties", ">= 4.0.0" s.add_dependency "sprockets-rails", ">= 2.0.0" @@ -28,6 +29,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rspec-rails", ">= 3.0.1" s.add_development_dependency "rubocop" s.add_development_dependency "simplecov" - s.add_development_dependency "slim-rails" + # TODO: Deal with the bugs on the latest version of slim-rails + s.add_development_dependency "slim-rails", "< 3.1.1" s.add_development_dependency "sqlite3" end diff --git a/gemfiles/sprockets_rails_3_with_sprockets_4.gemfile b/gemfiles/sprockets_rails_3_with_sprockets_4.gemfile new file mode 100644 index 0000000..ee91bf3 --- /dev/null +++ b/gemfiles/sprockets_rails_3_with_sprockets_4.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "sprockets-rails", "~> 3.0" +gem "sprockets", "~> 4.0.0.beta1" + +group :development, :test do + gem "pry-byebug" + gem "pry-coolline" +end + +gemspec :path => "../" diff --git a/lib/gakubuchi.rb b/lib/gakubuchi.rb index f0fad92..855b2a6 100644 --- a/lib/gakubuchi.rb +++ b/lib/gakubuchi.rb @@ -6,6 +6,7 @@ require "gakubuchi/configuration" require "gakubuchi/error" require "gakubuchi/fileutils" +require "gakubuchi/mime_type" require "gakubuchi/task" require "gakubuchi/version" diff --git a/lib/gakubuchi/engine_registrar.rb b/lib/gakubuchi/engine_registrar.rb index 5a71b44..9dcdde1 100644 --- a/lib/gakubuchi/engine_registrar.rb +++ b/lib/gakubuchi/engine_registrar.rb @@ -1,30 +1,53 @@ +require "active_support/inflector" +require "grease" +require "sprockets" + module Gakubuchi class EngineRegistrar + EXTENSION_WITH_SIGLE_DOT = /\A\.[^\.]+\z/ + def initialize(env) @env = env end - def register(target, engine) - klass = constantize(engine) - return false if !klass.instance_of?(::Class) || registered?(target) + def register(mime_type, engine_name_or_class) + engine = constantize(engine_name_or_class) + return false if !engine.instance_of?(::Class) || mime_type.extensions.empty? - args = [target, klass] - args << { silence_deprecation: true } if Sprockets::VERSION.start_with?("3") + if sprockets_major_version >= 4 + register_as_transformer(mime_type, engine) + else + register_as_engine(mime_type, engine) + end - @env.register_engine(*args) true end - def registered?(target) - @env.engines.key?(::Sprockets::Utils.normalize_extension(target)) - end - private - def constantize(klass) - klass.to_s.constantize + def constantize(constant_name) + constant_name.to_s.constantize rescue ::LoadError, ::NameError nil end + + def sprockets_major_version + @sprockets_major_version ||= ::Gem::Version.new(::Sprockets::VERSION).segments.first + end + + def register_as_engine(mime_type, engine) + mime_type.extensions.select { |ext| ext =~ EXTENSION_WITH_SIGLE_DOT }.each do |ext| + args = [ext, engine] + args << { silence_deprecation: true } if sprockets_major_version == 3 + @env.register_engine(*args) + end + end + + def register_as_transformer(mime_type, engine) + content_type = mime_type.content_type + + @env.register_mime_type(content_type, extensions: mime_type.extensions) + @env.register_transformer(content_type, engine.default_mime_type, ::Grease.apply(engine)) + end end end diff --git a/lib/gakubuchi/error.rb b/lib/gakubuchi/error.rb index 75e5160..0f3a49c 100644 --- a/lib/gakubuchi/error.rb +++ b/lib/gakubuchi/error.rb @@ -1,5 +1,6 @@ module Gakubuchi class Error < StandardError InvalidTemplate = Class.new(self) + InvalidMimeType = Class.new(self) end end diff --git a/lib/gakubuchi/mime_type.rb b/lib/gakubuchi/mime_type.rb new file mode 100644 index 0000000..cf26f20 --- /dev/null +++ b/lib/gakubuchi/mime_type.rb @@ -0,0 +1,20 @@ +require "gakubuchi/error" +require "set" + +module Gakubuchi + class MimeType + CONTENT_TYPE_FORMAT = %r(\A[^/]+/[^/]+\z) + + attr_reader :content_type, :extensions + + def initialize(content_type, extensions: []) + unless content_type =~ CONTENT_TYPE_FORMAT + message = %(`#{content_type}' is invalid as Content-Type) + raise ::Gakubuchi::Error::InvalidMimeType, message + end + + @content_type = content_type + @extensions = ::Set.new(extensions).map(&:to_s) + end + end +end diff --git a/lib/gakubuchi/railtie.rb b/lib/gakubuchi/railtie.rb index db70db1..ab90e43 100644 --- a/lib/gakubuchi/railtie.rb +++ b/lib/gakubuchi/railtie.rb @@ -3,8 +3,17 @@ class Railtie < ::Rails::Railtie config.assets.configure do |env| engine_registrar = EngineRegistrar.new(env) - engine_registrar.register(:haml, "::Tilt::HamlTemplate") - engine_registrar.register(:slim, "::Slim::Template") + haml = ::Gakubuchi::MimeType.new("text/haml", extensions: %w(.haml .html.haml)) + engine_registrar.register(haml, "Tilt::HamlTemplate") + + slim = ::Gakubuchi::MimeType.new("text/slim", extensions: %w(.slim .html.slim)) + engine_registrar.register(slim, "Slim::Template") + end + + config.after_initialize do + # NOTE: Call #to_s for Sprockets 4 or later + templates = ::Gakubuchi::Template.all.map { |template| template.logical_path.to_s } + config.assets.precompile += templates end rake_tasks do diff --git a/lib/gakubuchi/template.rb b/lib/gakubuchi/template.rb index 877a45a..c7c643e 100644 --- a/lib/gakubuchi/template.rb +++ b/lib/gakubuchi/template.rb @@ -39,7 +39,8 @@ def destination_path end def digest_path - asset = assets.find_asset(logical_path) + # NOTE: Call #to_s for Sprockets 4 or later + asset = assets.find_asset(logical_path.to_s) return if asset.nil? ::Pathname.new(::File.join(::Rails.public_path, app.config.assets.prefix, asset.digest_path)) diff --git a/spec/dummy/app/assets/config/manifest.js b/spec/dummy/app/assets/config/manifest.js new file mode 100644 index 0000000..b16e53d --- /dev/null +++ b/spec/dummy/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/spec/dummy/app/assets/images/.keep b/spec/dummy/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/spec/lib/gakubuchi/engine_registrar_spec.rb b/spec/lib/gakubuchi/engine_registrar_spec.rb index fc6d85b..e55f543 100644 --- a/spec/lib/gakubuchi/engine_registrar_spec.rb +++ b/spec/lib/gakubuchi/engine_registrar_spec.rb @@ -1,71 +1,68 @@ require "rails_helper" RSpec.describe Gakubuchi::EngineRegistrar do - let(:env) { Sprockets::Environment.new } let(:engine_registrar) { described_class.new(env) } + let(:env) { Sprockets::Environment.new } describe "#register" do - let(:described_method) { -> { engine_registrar.register(target, engine) } } + let(:described_method) { -> { engine_registrar.register(mime_type, engine) } } + let(:mime_type) { Gakubuchi::MimeType.new(content_type, extensions: extensions) } + let(:sprokcets_extensions) do + extension_type = major_version_of(Sprockets) >= 4 ? :transformers : :engines + -> { env.public_send(extension_type) } + end context "when specified engine is an uninitialized constant" do - let(:target) { :foo } + let(:content_type) { "application/csv+ruby" } let(:engine) { "Foo" } + let(:extensions) { %w(.rcsv .csv.ruby) } - describe "env.engines" do - subject { -> { env.engines } } - it { expect(&described_method).not_to change(&subject) } + it "should return false" do + expect(described_method.call).to eq false end - describe "return value" do - subject { described_method.call } - it { is_expected.to eq false } + it "shouldn't register the engine for the MIME type" do + expect(&described_method).not_to change(&sprokcets_extensions) end end - context "when specified target is already registered" do - let(:target) { :sass } - let(:engine) { "Sprockets::SassTemplate" } + context "when there is no extensions corresponding the MIME type" do + let(:content_type) { "application/csv+ruby" } + let(:engine) { Tilt::CSVTemplate } + let(:extensions) { nil } - describe "env.engines" do - subject { -> { env.engines } } - it { expect(&described_method).not_to change(&subject) } + it "should return false" do + expect(described_method.call).to eq false end - describe "return value" do - subject { described_method.call } - it { is_expected.to eq false } + it "shouldn't register the engine for the MIME type" do + expect(&described_method).not_to change(&sprokcets_extensions) end end - context "when specified target is not registered" do - let(:target) { :foo } - let(:engine) { "Sprockets::SassTemplate" } + context "when all parameters are valid" do + let(:content_type) { "application/csv+ruby" } + let(:engine) { Tilt::CSVTemplate } + let(:extensions) { %w(.rcsv .csv.ruby) } + let(:extensions_with_single_dot) { extensions.select { |ext| ext =~ /\A\.[^\.]+\z/ } } - describe "env.engines" do - subject { -> { env.engines } } - let(:expectation) { a_hash_including(".foo" => Sprockets::SassTemplate) } - - it { expect(&described_method).to change(&subject).to(expectation) } + it "should return true" do + expect(described_method.call).to eq true end - describe "return value" do - subject { described_method.call } - it { is_expected.to eq true } - end - end - end - - describe "#registered?" do - subject { engine_registrar.registered?(target) } + it "should register the engine for the MIME type" do + diff = + case major_version_of(Sprockets) + when 2 + Hash[extensions_with_single_dot.map { |ext| [ext, engine] }] + when 3 + Hash[extensions_with_single_dot.map { |ext| [ext, an_object_responding_to(:call)] }] + when 4..Float::INFINITY + { content_type => { engine.default_mime_type => an_object_responding_to(:call) } } + end - context "when specified target is not registered" do - let(:target) { :foo } - it { is_expected.to eq false } - end - - context "when specified target is already registered" do - let(:target) { :erb } - it { is_expected.to eq true } + expect(&described_method).to change(&sprokcets_extensions).to(hash_including(diff)) + end end end end diff --git a/spec/lib/gakubuchi/mime_type_spec.rb b/spec/lib/gakubuchi/mime_type_spec.rb new file mode 100644 index 0000000..e1107b9 --- /dev/null +++ b/spec/lib/gakubuchi/mime_type_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Gakubuchi::MimeType do + describe "#new" do + subject { -> { described_class.new(content_type) } } + + context "when an invalid `content_type` is specified" do + let(:content_type) { "foo" } + it { is_expected.to raise_error(Gakubuchi::Error::InvalidMimeType, /#{content_type}/) } + end + + context "when a valid `content_type` is specified" do + let(:content_type) { "application/csv+ruby" } + it { is_expected.not_to raise_error } + end + end + + describe "#content_type" do + subject { described_class.new(content_type).content_type } + let(:content_type) { "application/csv+ruby" } + + it { is_expected.to eq content_type } + end + + describe "#extensions" do + subject { described_class.new("application/csv+ruby", options).extensions } + + context "when `extensions` isn't specified" do + let(:options) { { extensions: nil } } + + it { is_expected.to be_an_instance_of(Array) } + it { is_expected.to be_empty } + end + + context "when `extensions` includes duplicated values" do + let(:options) { { extensions: %w(.rcsv .csv.ruby .rcsv) } } + + it { is_expected.to be_an_instance_of(Array) } + it { is_expected.to contain_exactly(*options[:extensions].uniq) } + end + + context "when `extensions` includes non-string values" do + let(:options) { { extensions: %i(.rcsv .csv.ruby) } } + + it { is_expected.to be_an_instance_of(Array) } + it { is_expected.to contain_exactly(*options[:extensions].map(&:to_s)) } + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 2884feb..5a8d566 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -28,13 +28,16 @@ # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. # -# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } +Dir[File.expand_path("../../spec/support/**/*.rb", __FILE__)].each { |f| require f } # Checks for pending migrations before tests are run. # If you are not using ActiveRecord, you can remove this line. # ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| + # Include modules in all example groups + config.include VersionHelpers + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures" diff --git a/spec/support/version_helpers.rb b/spec/support/version_helpers.rb new file mode 100644 index 0000000..bf85266 --- /dev/null +++ b/spec/support/version_helpers.rb @@ -0,0 +1,7 @@ +module VersionHelpers + module_function + + def major_version_of(mod) + Gem::Version.new(mod::VERSION).segments.first + end +end