lotus-assets 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|