lotus-assets 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/{LICENSE.txt → LICENSE.md} +1 -1
- data/README.md +429 -7
- data/bin/lotus-assets +22 -0
- data/lib/lotus/assets.rb +153 -2
- data/lib/lotus/assets/bundler.rb +173 -0
- data/lib/lotus/assets/cache.rb +58 -0
- data/lib/lotus/assets/compiler.rb +212 -0
- data/lib/lotus/assets/compressors/abstract.rb +119 -0
- data/lib/lotus/assets/compressors/builtin_javascript.rb +36 -0
- data/lib/lotus/assets/compressors/builtin_stylesheet.rb +57 -0
- data/lib/lotus/assets/compressors/closure_javascript.rb +25 -0
- data/lib/lotus/assets/compressors/javascript.rb +77 -0
- data/lib/lotus/assets/compressors/jsmin.rb +283 -0
- data/lib/lotus/assets/compressors/null_compressor.rb +19 -0
- data/lib/lotus/assets/compressors/sass_stylesheet.rb +38 -0
- data/lib/lotus/assets/compressors/stylesheet.rb +77 -0
- data/lib/lotus/assets/compressors/uglifier_javascript.rb +25 -0
- data/lib/lotus/assets/compressors/yui_javascript.rb +25 -0
- data/lib/lotus/assets/compressors/yui_stylesheet.rb +25 -0
- data/lib/lotus/assets/config/global_sources.rb +50 -0
- data/lib/lotus/assets/config/manifest.rb +112 -0
- data/lib/lotus/assets/config/sources.rb +77 -0
- data/lib/lotus/assets/configuration.rb +539 -0
- data/lib/lotus/assets/helpers.rb +733 -0
- data/lib/lotus/assets/precompiler.rb +67 -0
- data/lib/lotus/assets/version.rb +4 -1
- data/lotus-assets.gemspec +25 -11
- metadata +192 -15
- data/.gitignore +0 -22
- data/Gemfile +0 -4
- data/Rakefile +0 -2
data/bin/lotus-assets
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: lotus-assets --config=path/to/config.rb"
|
9
|
+
|
10
|
+
opts.on("-c", "--config FILE", "Path to config") do |c|
|
11
|
+
options[:config] = c
|
12
|
+
end
|
13
|
+
end.parse!
|
14
|
+
|
15
|
+
config = options.fetch(:config) { raise ArgumentError.new("You must specify a configuration file") }
|
16
|
+
config = Pathname.new(config)
|
17
|
+
config.exist? or raise ArgumentError.new("Cannot find configuration file: #{ config }")
|
18
|
+
|
19
|
+
require 'lotus/assets'
|
20
|
+
load config
|
21
|
+
|
22
|
+
Lotus::Assets.deploy
|
data/lib/lotus/assets.rb
CHANGED
@@ -1,7 +1,158 @@
|
|
1
|
-
require
|
1
|
+
require 'thread'
|
2
|
+
require 'lotus/utils/class_attribute'
|
2
3
|
|
3
4
|
module Lotus
|
5
|
+
# Assets management for Ruby web applications
|
6
|
+
#
|
7
|
+
# @since 0.1.0
|
4
8
|
module Assets
|
5
|
-
#
|
9
|
+
# Base error for Lotus::Assets
|
10
|
+
#
|
11
|
+
# All the errors defined in this framework MUST inherit from it.
|
12
|
+
#
|
13
|
+
# @since 0.1.0
|
14
|
+
class Error < ::StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'lotus/assets/version'
|
18
|
+
require 'lotus/assets/configuration'
|
19
|
+
require 'lotus/assets/config/global_sources'
|
20
|
+
require 'lotus/assets/helpers'
|
21
|
+
|
22
|
+
include Utils::ClassAttribute
|
23
|
+
|
24
|
+
# Configuration
|
25
|
+
#
|
26
|
+
# @since 0.1.0
|
27
|
+
# @api private
|
28
|
+
class_attribute :configuration
|
29
|
+
self.configuration = Configuration.new
|
30
|
+
|
31
|
+
# Configure framework
|
32
|
+
#
|
33
|
+
# @param blk [Proc] configuration code block
|
34
|
+
#
|
35
|
+
# @return self
|
36
|
+
#
|
37
|
+
# @since 0.1.0
|
38
|
+
#
|
39
|
+
# @see Lotus::Assets::Configuration
|
40
|
+
def self.configure(&blk)
|
41
|
+
configuration.instance_eval(&blk)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Prepare assets for deploys
|
46
|
+
#
|
47
|
+
# @since 0.1.0
|
48
|
+
def self.deploy
|
49
|
+
require 'lotus/assets/precompiler'
|
50
|
+
require 'lotus/assets/bundler'
|
51
|
+
|
52
|
+
Precompiler.new(configuration, duplicates).run
|
53
|
+
Bundler.new(configuration, duplicates).run
|
54
|
+
end
|
55
|
+
|
56
|
+
# Preload the framework
|
57
|
+
#
|
58
|
+
# This MUST be used in production mode
|
59
|
+
#
|
60
|
+
# @since 0.1.0
|
61
|
+
#
|
62
|
+
# @example Direct Invocation
|
63
|
+
# require 'lotus/assets'
|
64
|
+
#
|
65
|
+
# Lotus::Assets.load!
|
66
|
+
#
|
67
|
+
# @example Load Via Configuration Block
|
68
|
+
# require 'lotus/assets'
|
69
|
+
#
|
70
|
+
# Lotus::Assets.configure do
|
71
|
+
# # ...
|
72
|
+
# end.load!
|
73
|
+
def self.load!
|
74
|
+
configuration.load!
|
75
|
+
end
|
76
|
+
|
77
|
+
# Global assets sources
|
78
|
+
#
|
79
|
+
# This is designed for third party integration gems with frontend frameworks
|
80
|
+
# like Bootstrap, Ember.js or React.
|
81
|
+
#
|
82
|
+
# Developers can maintain gems that ship static assets for these frameworks
|
83
|
+
# and make them available to Lotus::Assets.
|
84
|
+
#
|
85
|
+
# @return [Lotus::Assets::Config::GlobalSources]
|
86
|
+
#
|
87
|
+
# @since 0.1.0
|
88
|
+
#
|
89
|
+
# @example Ember.js Integration
|
90
|
+
# # lib/lotus/emberjs.rb (third party gem)
|
91
|
+
# require 'lotus/assets'
|
92
|
+
#
|
93
|
+
# Lotus::Assets.sources << '/path/to/emberjs/assets'
|
94
|
+
def self.sources
|
95
|
+
synchronize do
|
96
|
+
@@sources ||= Config::GlobalSources.new
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Duplicate the framework and generate modules for the target application
|
101
|
+
#
|
102
|
+
# @param mod [Module] the Ruby namespace of the application
|
103
|
+
# @param blk [Proc] an optional block to configure the framework
|
104
|
+
#
|
105
|
+
# @return [Module] a copy of Lotus::Assets
|
106
|
+
#
|
107
|
+
# @since 0.1.0
|
108
|
+
#
|
109
|
+
# @see Lotus::Assets#dupe
|
110
|
+
# @see Lotus::Assets::Configuration
|
111
|
+
def self.duplicate(mod, &blk)
|
112
|
+
dupe.tap do |duplicated|
|
113
|
+
duplicated.configure(&blk) if block_given?
|
114
|
+
duplicates << duplicated
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Duplicate Lotus::Assets in order to create a new separated instance
|
119
|
+
# of the framework.
|
120
|
+
#
|
121
|
+
# The new instance of the framework will be completely decoupled from the
|
122
|
+
# original. It will inherit the configuration, but all the changes that
|
123
|
+
# happen after the duplication, won't be reflected on the other copies.
|
124
|
+
#
|
125
|
+
# @return [Module] a copy of Lotus::Assets
|
126
|
+
#
|
127
|
+
# @since 0.1.0
|
128
|
+
# @api private
|
129
|
+
def self.dupe
|
130
|
+
dup.tap do |duplicated|
|
131
|
+
duplicated.configuration = configuration.duplicate
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Keep track of duplicated frameworks
|
136
|
+
#
|
137
|
+
# @return [Array] a collection of duplicated frameworks
|
138
|
+
#
|
139
|
+
# @since 0.1.0
|
140
|
+
# @api private
|
141
|
+
#
|
142
|
+
# @see Lotus::Assets#duplicate
|
143
|
+
# @see Lotus::Assets#dupe
|
144
|
+
def self.duplicates
|
145
|
+
synchronize do
|
146
|
+
@@duplicates ||= Array.new
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# @since 0.1.0
|
153
|
+
# @api private
|
154
|
+
def self.synchronize(&blk)
|
155
|
+
Mutex.new.synchronize(&blk)
|
156
|
+
end
|
6
157
|
end
|
7
158
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Lotus
|
6
|
+
module Assets
|
7
|
+
# Bundle assets from a single application.
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
# @api private
|
11
|
+
class Bundler
|
12
|
+
# @since 0.1.0
|
13
|
+
# @api private
|
14
|
+
DEFAULT_PERMISSIONS = 0644
|
15
|
+
|
16
|
+
# @since 0.1.0
|
17
|
+
# @api private
|
18
|
+
JAVASCRIPT_EXT = '.js'.freeze
|
19
|
+
|
20
|
+
# @since 0.1.0
|
21
|
+
# @api private
|
22
|
+
STYLESHEET_EXT = '.css'.freeze
|
23
|
+
|
24
|
+
# @since 0.1.0
|
25
|
+
# @api private
|
26
|
+
WILDCARD_EXT = '.*'.freeze
|
27
|
+
|
28
|
+
# @since 0.1.0
|
29
|
+
# @api private
|
30
|
+
URL_SEPARATOR = '/'.freeze
|
31
|
+
|
32
|
+
# @since 0.1.0
|
33
|
+
# @api private
|
34
|
+
URL_REPLACEMENT = ''.freeze
|
35
|
+
|
36
|
+
# Return a new instance
|
37
|
+
#
|
38
|
+
# @param configuration [Lotus::Assets::Configuration] a single application configuration
|
39
|
+
#
|
40
|
+
# @param duplicates [Array<Lotus::Assets>] the duplicated frameworks
|
41
|
+
# (one for each application)
|
42
|
+
#
|
43
|
+
# @return [Lotus::Assets::Bundler] a new instance
|
44
|
+
#
|
45
|
+
# @since 0.1.0
|
46
|
+
# @api private
|
47
|
+
def initialize(configuration, duplicates)
|
48
|
+
@manifest = Hash.new
|
49
|
+
@configuration = configuration
|
50
|
+
@configurations = if duplicates.empty?
|
51
|
+
[@configuration]
|
52
|
+
else
|
53
|
+
duplicates.map(&:configuration)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Start the process.
|
58
|
+
#
|
59
|
+
# For each asset contained in the sources and third party gems, it will:
|
60
|
+
#
|
61
|
+
# * Compress
|
62
|
+
# * Create a checksum version
|
63
|
+
#
|
64
|
+
# At the end it will generate a digest manifest
|
65
|
+
#
|
66
|
+
# @see Lotus::Assets::Configuration#digest
|
67
|
+
# @see Lotus::Assets::Configuration#manifest
|
68
|
+
# @see Lotus::Assets::Configuration#manifest_path
|
69
|
+
def run
|
70
|
+
assets.each do |asset|
|
71
|
+
next if ::File.directory?(asset)
|
72
|
+
|
73
|
+
compress(asset)
|
74
|
+
checksum(asset)
|
75
|
+
end
|
76
|
+
|
77
|
+
generate_manifest
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# @since 0.1.0
|
83
|
+
# @api private
|
84
|
+
def assets
|
85
|
+
Dir.glob("#{ public_directory }#{ ::File::SEPARATOR }**#{ ::File::SEPARATOR }*")
|
86
|
+
end
|
87
|
+
|
88
|
+
# @since 0.1.0
|
89
|
+
# @api private
|
90
|
+
def compress(asset)
|
91
|
+
case File.extname(asset)
|
92
|
+
when JAVASCRIPT_EXT then _compress(compressor(:js, asset), asset)
|
93
|
+
when STYLESHEET_EXT then _compress(compressor(:css, asset), asset)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# @since 0.1.0
|
98
|
+
# @api private
|
99
|
+
def checksum(asset)
|
100
|
+
digest = Digest::MD5.file(asset)
|
101
|
+
filename, ext = ::File.basename(asset, WILDCARD_EXT), ::File.extname(asset)
|
102
|
+
directory = ::File.dirname(asset)
|
103
|
+
target = [directory, "#{ filename }-#{ digest }#{ ext }"].join(::File::SEPARATOR)
|
104
|
+
|
105
|
+
FileUtils.cp(asset, target)
|
106
|
+
_set_permissions(target)
|
107
|
+
|
108
|
+
store_manifest(asset, target)
|
109
|
+
end
|
110
|
+
|
111
|
+
# @since 0.1.0
|
112
|
+
# @api private
|
113
|
+
def generate_manifest
|
114
|
+
_write(@configuration.manifest_path, JSON.dump(@manifest))
|
115
|
+
end
|
116
|
+
|
117
|
+
# @since 0.1.0
|
118
|
+
# @api private
|
119
|
+
def store_manifest(asset, target)
|
120
|
+
@manifest[_convert_to_url(::File.expand_path(asset))] = _convert_to_url(::File.expand_path(target))
|
121
|
+
end
|
122
|
+
|
123
|
+
# @since 0.1.0
|
124
|
+
# @api private
|
125
|
+
def compressor(type, asset)
|
126
|
+
_configuration_for(asset).__send__(:"#{ type }_compressor")
|
127
|
+
end
|
128
|
+
|
129
|
+
# @since 0.1.0
|
130
|
+
# @api private
|
131
|
+
def _compress(compressor, asset)
|
132
|
+
_write(asset, compressor.compress(asset))
|
133
|
+
rescue => e
|
134
|
+
warn "Skipping compression of: `#{ asset }'\nReason: #{ e }\n\t#{ e.backtrace.join("\n\t") }\n\n"
|
135
|
+
end
|
136
|
+
|
137
|
+
# @since 0.1.0
|
138
|
+
# @api private
|
139
|
+
def _convert_to_url(path)
|
140
|
+
path.sub(public_directory.to_s, URL_REPLACEMENT).
|
141
|
+
gsub(File::SEPARATOR, URL_SEPARATOR)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @since 0.1.0
|
145
|
+
# @api private
|
146
|
+
def _write(path, content)
|
147
|
+
Pathname.new(path).dirname.mkpath
|
148
|
+
::File.write(path, content)
|
149
|
+
|
150
|
+
_set_permissions(path)
|
151
|
+
end
|
152
|
+
|
153
|
+
# @since 0.1.0
|
154
|
+
# @api private
|
155
|
+
def _set_permissions(path)
|
156
|
+
::File.chmod(DEFAULT_PERMISSIONS, path)
|
157
|
+
end
|
158
|
+
|
159
|
+
def _configuration_for(asset)
|
160
|
+
url = _convert_to_url(asset)
|
161
|
+
|
162
|
+
@configurations.find {|config| url.start_with?(config.prefix) } ||
|
163
|
+
@configuration
|
164
|
+
end
|
165
|
+
|
166
|
+
# @since 0.1.0
|
167
|
+
# @api private
|
168
|
+
def public_directory
|
169
|
+
@configuration.public_directory
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
module Assets
|
5
|
+
# Store assets references when compile mode is on.
|
6
|
+
#
|
7
|
+
# This is expecially useful in development mode, where we want to compile
|
8
|
+
# only the assets that were changed from last browser refresh.
|
9
|
+
#
|
10
|
+
# @since 0.1.0
|
11
|
+
# @api private
|
12
|
+
class Cache
|
13
|
+
# Return a new instance
|
14
|
+
#
|
15
|
+
# @return [Lotus::Assets::Cache] a new instance
|
16
|
+
def initialize
|
17
|
+
@data = Hash.new{|h,k| h[k] = 0 }
|
18
|
+
@mutex = Mutex.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Check if the given file is fresh or changed from last check.
|
22
|
+
#
|
23
|
+
# @param file [String,Pathname] the file path
|
24
|
+
#
|
25
|
+
# @return [TrueClass,FalseClass] the result of the check
|
26
|
+
#
|
27
|
+
# @since 0.1.0
|
28
|
+
# @api private
|
29
|
+
def fresh?(file)
|
30
|
+
@mutex.synchronize do
|
31
|
+
@data[file.to_s] < mtime(file)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Store the given file reference
|
36
|
+
#
|
37
|
+
# @param file [String,Pathname] the file path
|
38
|
+
#
|
39
|
+
# @return [TrueClass,FalseClass] the result of the check
|
40
|
+
#
|
41
|
+
# @since 0.1.0
|
42
|
+
# @api private
|
43
|
+
def store(file)
|
44
|
+
@mutex.synchronize do
|
45
|
+
@data[file.to_s] = mtime(file)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# @since 0.1.0
|
52
|
+
# @api private
|
53
|
+
def mtime(file)
|
54
|
+
file.mtime.utc.to_i
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module Lotus
|
2
|
+
module Assets
|
3
|
+
class MissingAsset < Error
|
4
|
+
def initialize(name, sources)
|
5
|
+
sources = sources.map(&:to_s).join(', ')
|
6
|
+
super("Missing asset: `#{ name }' (sources: #{ sources })")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class UnknownAssetEngine < Error
|
11
|
+
def initialize(source)
|
12
|
+
super("No asset engine registered for `#{ ::File.basename(source) }'")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Assets compiler
|
17
|
+
#
|
18
|
+
# It compiles assets that needs to be preprocessed (eg. Sass or ES6) into
|
19
|
+
# the destination directory.
|
20
|
+
#
|
21
|
+
# Vanilla javascripts or stylesheets are just copied over.
|
22
|
+
#
|
23
|
+
# @since 0.1.0
|
24
|
+
# @api private
|
25
|
+
class Compiler
|
26
|
+
# @since 0.1.0
|
27
|
+
# @api private
|
28
|
+
DEFAULT_PERMISSIONS = 0644
|
29
|
+
|
30
|
+
# @since 0.1.0
|
31
|
+
# @api private
|
32
|
+
COMPILE_PATTERN = '*.*.*'.freeze # Example hello.js.es6
|
33
|
+
|
34
|
+
# @since 0.1.0
|
35
|
+
# @api private
|
36
|
+
EXTENSIONS = {'.js' => true, '.css' => true}.freeze
|
37
|
+
|
38
|
+
# @since 0.1.0
|
39
|
+
# @api private
|
40
|
+
SASS_CACHE_LOCATION = Pathname(Lotus.respond_to?(:root) ?
|
41
|
+
Lotus.root : Dir.pwd).join('tmp', 'sass-cache')
|
42
|
+
|
43
|
+
# Compile the given asset
|
44
|
+
#
|
45
|
+
# @param configuration [Lotus::Assets::Configuration] the application
|
46
|
+
# configuration associated with the given asset
|
47
|
+
#
|
48
|
+
# @param name [String] the asset path
|
49
|
+
#
|
50
|
+
# @since 0.1.0
|
51
|
+
# @api private
|
52
|
+
def self.compile(configuration, name)
|
53
|
+
return unless configuration.compile
|
54
|
+
|
55
|
+
require 'tilt'
|
56
|
+
require 'lotus/assets/cache'
|
57
|
+
new(configuration, name).compile
|
58
|
+
end
|
59
|
+
|
60
|
+
# Assets cache
|
61
|
+
#
|
62
|
+
# @since 0.1.0
|
63
|
+
# @api private
|
64
|
+
#
|
65
|
+
# @see Lotus::Assets::Cache
|
66
|
+
def self.cache
|
67
|
+
@@cache ||= Assets::Cache.new
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return a new instance
|
71
|
+
#
|
72
|
+
# @param configuration [Lotus::Assets::Configuration] the application
|
73
|
+
# configuration associated with the given asset
|
74
|
+
#
|
75
|
+
# @param name [String] the asset path
|
76
|
+
#
|
77
|
+
# @return [Lotus::Assets::Compiler] a new instance
|
78
|
+
#
|
79
|
+
# @since 0.1.0
|
80
|
+
# @api private
|
81
|
+
def initialize(configuration, name)
|
82
|
+
@configuration = configuration
|
83
|
+
@name = Pathname.new(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Compile the asset
|
87
|
+
#
|
88
|
+
# @raise [Lotus::Assets::MissingAsset] if the asset can't be found in
|
89
|
+
# sources
|
90
|
+
#
|
91
|
+
# @since 0.1.0
|
92
|
+
# @api private
|
93
|
+
def compile
|
94
|
+
raise MissingAsset.new(@name, @configuration.sources) unless exist?
|
95
|
+
return unless fresh?
|
96
|
+
|
97
|
+
if compile?
|
98
|
+
compile!
|
99
|
+
else
|
100
|
+
copy!
|
101
|
+
end
|
102
|
+
|
103
|
+
cache!
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# @since 0.1.0
|
109
|
+
# @api private
|
110
|
+
def source
|
111
|
+
@source ||= begin
|
112
|
+
@name.absolute? ? @name :
|
113
|
+
@configuration.find(@name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @since 0.1.0
|
118
|
+
# @api private
|
119
|
+
def destination
|
120
|
+
@destination ||= @configuration.destination_directory.join(basename)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @since 0.1.0
|
124
|
+
# @api private
|
125
|
+
def basename
|
126
|
+
result = ::File.basename(@name)
|
127
|
+
|
128
|
+
if compile?
|
129
|
+
result.scan(/\A[[[:alnum:]][\-\_]]*\.[[\w]]*/).first || result
|
130
|
+
else
|
131
|
+
result
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @since 0.1.0
|
136
|
+
# @api private
|
137
|
+
def exist?
|
138
|
+
!source.nil? &&
|
139
|
+
source.exist?
|
140
|
+
end
|
141
|
+
|
142
|
+
# @since 0.1.0
|
143
|
+
# @api private
|
144
|
+
def fresh?
|
145
|
+
!destination.exist? ||
|
146
|
+
cache.fresh?(source)
|
147
|
+
end
|
148
|
+
|
149
|
+
# @since 0.1.0
|
150
|
+
# @api private
|
151
|
+
def compile?
|
152
|
+
@compile ||= ::File.fnmatch(COMPILE_PATTERN, source.to_s) &&
|
153
|
+
!EXTENSIONS[::File.extname(source.to_s)]
|
154
|
+
end
|
155
|
+
|
156
|
+
# @since 0.1.0
|
157
|
+
# @api private
|
158
|
+
def compile!
|
159
|
+
# NOTE `:load_paths' is useful only for Sass engine, to make `@include' directive to work.
|
160
|
+
# For now we don't want to maintan a specialized Compiler version for Sass.
|
161
|
+
#
|
162
|
+
# If in the future other precompilers will need special treatment,
|
163
|
+
# we can consider to maintain several specialized versions in order to
|
164
|
+
# don't add a perf tax to all the other preprocessors who "just work".
|
165
|
+
#
|
166
|
+
# Example: if Less "just works", we can keep it in the general `Compiler',
|
167
|
+
# but have a `SassCompiler` if it requires more than `:load_paths'.
|
168
|
+
#
|
169
|
+
# NOTE: We need another option to pass for Sass: `:cache_location'.
|
170
|
+
#
|
171
|
+
# This is needed to don't create a `.sass-cache' directory at the root of the project,
|
172
|
+
# but to have it under `tmp/sass-cache'.
|
173
|
+
write { Tilt.new(source, nil, load_paths: @configuration.sources.to_a, cache_location: sass_cache_location).render }
|
174
|
+
rescue RuntimeError
|
175
|
+
raise UnknownAssetEngine.new(source)
|
176
|
+
end
|
177
|
+
|
178
|
+
# @since 0.1.0
|
179
|
+
# @api private
|
180
|
+
def copy!
|
181
|
+
write { source.read }
|
182
|
+
end
|
183
|
+
|
184
|
+
# @since 0.1.0
|
185
|
+
# @api private
|
186
|
+
def cache!
|
187
|
+
cache.store(source)
|
188
|
+
end
|
189
|
+
|
190
|
+
# @since 0.1.0
|
191
|
+
# @api private
|
192
|
+
def write
|
193
|
+
destination.dirname.mkpath
|
194
|
+
destination.open(File::WRONLY|File::CREAT, DEFAULT_PERMISSIONS) do |file|
|
195
|
+
file.write(yield)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# @since 0.1.0
|
200
|
+
# @api private
|
201
|
+
def cache
|
202
|
+
self.class.cache
|
203
|
+
end
|
204
|
+
|
205
|
+
# @since 0.1.0
|
206
|
+
# @api private
|
207
|
+
def sass_cache_location
|
208
|
+
SASS_CACHE_LOCATION
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|