ram 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +19 -0
- data/bin/ram +5 -0
- data/lib/ram.rb +216 -0
- data/lib/ram/command_line.rb +84 -0
- data/lib/ram/compressor.rb +253 -0
- data/lib/ram/dependencies.rb +35 -0
- data/lib/ram/helper.rb +87 -0
- data/lib/ram/jst.js +1 -0
- data/lib/ram/packager.rb +173 -0
- data/lib/ram/uglifier.rb +3 -0
- data/ram.gemspec +33 -0
- metadata +82 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Jeremy Ashkenas, DocumentCloud
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
== Ram
|
2
|
+
|
3
|
+
Ram is an asset packaging library, written in Ruby. It is entirely taken from the Jammit gem, but stripped of any Rails dependencies or helpers. It was primarily written for packaging nanoc assets, though I suppose it could be used for many types of web development projects.
|
4
|
+
|
5
|
+
Installation:
|
6
|
+
|
7
|
+
gem install ram
|
8
|
+
|
9
|
+
Documentation, usage, and examples:
|
10
|
+
|
11
|
+
hmmm
|
12
|
+
|
13
|
+
To suggest a feature or report a bug:
|
14
|
+
|
15
|
+
hmmm
|
16
|
+
|
17
|
+
For source documentation:
|
18
|
+
|
19
|
+
one more
|
data/bin/ram
ADDED
data/lib/ram.rb
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
# @Ram@ is the central namespace for all Ram classes, and provides access
|
4
|
+
# to all of the configuration options.
|
5
|
+
module Ram
|
6
|
+
|
7
|
+
VERSION = "0.6.3"
|
8
|
+
|
9
|
+
ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
|
10
|
+
|
11
|
+
ASSET_ROOT = '.' unless defined?(ASSET_ROOT)
|
12
|
+
|
13
|
+
DEFAULT_PUBLIC_ROOT = 'output' unless defined?(PUBLIC_ROOT)
|
14
|
+
|
15
|
+
DEFAULT_CONFIG_PATH = File.join(ASSET_ROOT, 'config', 'assets.yml')
|
16
|
+
|
17
|
+
DEFAULT_PACKAGE_PATH = "assets"
|
18
|
+
|
19
|
+
DEFAULT_JST_SCRIPT = File.join(ROOT, 'lib/ram/jst.js')
|
20
|
+
|
21
|
+
DEFAULT_JST_COMPILER = "template"
|
22
|
+
|
23
|
+
DEFAULT_JST_NAMESPACE = "window.JST"
|
24
|
+
|
25
|
+
COMPRESSORS = [:yui, :closure, :uglifier]
|
26
|
+
|
27
|
+
DEFAULT_COMPRESSOR = :yui
|
28
|
+
|
29
|
+
# Extension matchers for JavaScript and JST, which need to be disambiguated.
|
30
|
+
JS_EXTENSION = /\.js\Z/
|
31
|
+
DEFAULT_JST_EXTENSION = "jst"
|
32
|
+
|
33
|
+
# Ram raises a @PackageNotFound@ exception when a non-existent package is
|
34
|
+
# requested by a browser -- rendering a 404.
|
35
|
+
class PackageNotFound < NameError; end
|
36
|
+
|
37
|
+
# Ram raises a MissingConfiguration exception when you try to load the
|
38
|
+
# configuration of an assets.yml file that doesn't exist, or are missing
|
39
|
+
# a piece of required configuration.
|
40
|
+
class MissingConfiguration < NameError; end
|
41
|
+
|
42
|
+
# Ram raises an OutputNotWritable exception if the output directory for
|
43
|
+
# cached packages is locked.
|
44
|
+
class OutputNotWritable < StandardError; end
|
45
|
+
|
46
|
+
# Ram raises a DeprecationError if you try to use an outdated feature.
|
47
|
+
class DeprecationError < StandardError; end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
attr_reader :configuration, :template_function, :template_namespace,
|
51
|
+
:embed_assets, :package_assets, :compress_assets, :gzip_assets,
|
52
|
+
:package_path, :mhtml_enabled, :include_jst_script, :config_path,
|
53
|
+
:javascript_compressor, :compressor_options, :css_compressor_options,
|
54
|
+
:template_extension, :template_extension_matcher, :allow_debugging,
|
55
|
+
:public_root
|
56
|
+
attr_accessor :compressors
|
57
|
+
end
|
58
|
+
|
59
|
+
# The minimal required configuration.
|
60
|
+
@configuration = {}
|
61
|
+
@public_root = DEFAULT_PUBLIC_ROOT
|
62
|
+
@package_path = DEFAULT_PACKAGE_PATH
|
63
|
+
@compressors = COMPRESSORS
|
64
|
+
|
65
|
+
# Load the complete asset configuration from the specified @config_path@.
|
66
|
+
# If we're loading softly, don't let missing configuration error out.
|
67
|
+
def self.load_configuration(config_path, soft=false)
|
68
|
+
exists = config_path && File.exists?(config_path)
|
69
|
+
return false if soft && !exists
|
70
|
+
raise MissingConfiguration, "could not find the \"#{config_path}\" configuration file" unless exists
|
71
|
+
conf = YAML.load(ERB.new(File.read(config_path)).result)
|
72
|
+
|
73
|
+
@config_path = config_path
|
74
|
+
@configuration = symbolize_keys(conf)
|
75
|
+
@package_path = conf[:package_path] || DEFAULT_PACKAGE_PATH
|
76
|
+
@embed_assets = conf[:embed_assets] || conf[:embed_images]
|
77
|
+
@compress_assets = !(conf[:compress_assets] == false)
|
78
|
+
@gzip_assets = !(conf[:gzip_assets] == false)
|
79
|
+
@allow_debugging = !(conf[:allow_debugging] == false)
|
80
|
+
@mhtml_enabled = @embed_assets && @embed_assets != "datauri"
|
81
|
+
@compressor_options = symbolize_keys(conf[:compressor_options] || {})
|
82
|
+
@css_compressor_options = symbolize_keys(conf[:css_compressor_options] || {})
|
83
|
+
set_javascript_compressor(conf[:javascript_compressor])
|
84
|
+
set_package_assets(conf[:package_assets])
|
85
|
+
set_template_function(conf[:template_function])
|
86
|
+
set_template_namespace(conf[:template_namespace])
|
87
|
+
set_template_extension(conf[:template_extension])
|
88
|
+
set_public_root(conf[:public_root]) if conf[:public_root]
|
89
|
+
symbolize_keys(conf[:stylesheets]) if conf[:stylesheets]
|
90
|
+
symbolize_keys(conf[:javascripts]) if conf[:javascripts]
|
91
|
+
check_for_deprecations
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
# Force a reload by resetting the Packager and reloading the configuration.
|
96
|
+
# In development, this will be called as a before_filter before every request.
|
97
|
+
def self.reload!
|
98
|
+
Thread.current[:ram_packager] = nil
|
99
|
+
load_configuration(@config_path)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Keep a global (thread-local) reference to a @Ram::Packager@, to avoid
|
103
|
+
# recomputing asset lists unnecessarily.
|
104
|
+
def self.packager
|
105
|
+
Thread.current[:ram_packager] ||= Packager.new
|
106
|
+
end
|
107
|
+
|
108
|
+
# Generate the base filename for a version of a given package.
|
109
|
+
def self.filename(package, extension, suffix=nil)
|
110
|
+
suffix_part = suffix ? "-#{suffix}" : ''
|
111
|
+
"#{package}#{suffix_part}.#{extension}"
|
112
|
+
end
|
113
|
+
|
114
|
+
# Generates the server-absolute URL to an asset package.
|
115
|
+
def self.asset_url(package, extension, suffix=nil, mtime=nil)
|
116
|
+
timestamp = mtime ? "?#{mtime.to_i}" : ''
|
117
|
+
"/#{package_path}/#{filename(package, extension, suffix)}#{timestamp}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# Convenience method for packaging up Ram, using the default options.
|
121
|
+
def self.package!(options={})
|
122
|
+
options = {
|
123
|
+
:config_path => Ram::DEFAULT_CONFIG_PATH,
|
124
|
+
:output_folder => nil,
|
125
|
+
:base_url => nil,
|
126
|
+
:public_root => nil,
|
127
|
+
:force => false
|
128
|
+
}.merge(options)
|
129
|
+
load_configuration(options[:config_path])
|
130
|
+
set_public_root(options[:public_root]) if options[:public_root]
|
131
|
+
packager.force = options[:force]
|
132
|
+
packager.package_names = options[:package_names]
|
133
|
+
packager.precache_all(options[:output_folder], options[:base_url])
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Allows command-line definition of `PUBLIC_ROOT`
|
139
|
+
def self.set_public_root(public_root=nil)
|
140
|
+
@public_root = public_root if public_root
|
141
|
+
end
|
142
|
+
|
143
|
+
# Ensure that the JavaScript compressor is a valid choice.
|
144
|
+
def self.set_javascript_compressor(value)
|
145
|
+
value = value && value.to_sym
|
146
|
+
@javascript_compressor = compressors.include?(value) ? value : DEFAULT_COMPRESSOR
|
147
|
+
end
|
148
|
+
|
149
|
+
# Turn asset packaging on or off, depending on configuration and environment.
|
150
|
+
def self.set_package_assets(value)
|
151
|
+
package_env = 'production'
|
152
|
+
@package_assets = value == true || value.nil? ? package_env :
|
153
|
+
value == 'always' ? true : false
|
154
|
+
end
|
155
|
+
|
156
|
+
# Assign the JST template function, unless explicitly turned off.
|
157
|
+
def self.set_template_function(value)
|
158
|
+
@template_function = value == true || value.nil? ? DEFAULT_JST_COMPILER :
|
159
|
+
value == false ? '' : value
|
160
|
+
@include_jst_script = @template_function == DEFAULT_JST_COMPILER
|
161
|
+
end
|
162
|
+
|
163
|
+
# Set the root JS object in which to stash all compiled JST.
|
164
|
+
def self.set_template_namespace(value)
|
165
|
+
@template_namespace = value == true || value.nil? ? DEFAULT_JST_NAMESPACE : value.to_s
|
166
|
+
end
|
167
|
+
|
168
|
+
# Set the extension for JS templates.
|
169
|
+
def self.set_template_extension(value)
|
170
|
+
@template_extension = (value == true || value.nil? ? DEFAULT_JST_EXTENSION : value.to_s).gsub(/\A\.?(.*)\Z/, '\1')
|
171
|
+
@template_extension_matcher = /\.#{Regexp.escape(@template_extension)}\Z/
|
172
|
+
end
|
173
|
+
|
174
|
+
# The YUI Compressor requires Java > 1.4, and Closure requires Java > 1.6.
|
175
|
+
def self.check_java_version
|
176
|
+
return true if @checked_java_version
|
177
|
+
java = @compressor_options[:java] || 'java'
|
178
|
+
@css_compressor_options[:java] ||= java if @compressor_options[:java]
|
179
|
+
version = (`#{java} -version 2>&1`)[/\d+\.\d+/]
|
180
|
+
disable_compression if !version ||
|
181
|
+
(@javascript_compressor == :closure && version < '1.6') ||
|
182
|
+
(@javascript_compressor == :yui && version < '1.4')
|
183
|
+
@checked_java_version = true
|
184
|
+
end
|
185
|
+
|
186
|
+
# If we don't have a working Java VM, then disable asset compression and
|
187
|
+
# complain loudly.
|
188
|
+
def self.disable_compression
|
189
|
+
@compress_assets = false
|
190
|
+
warn("Asset compression disabled -- Java unavailable.")
|
191
|
+
end
|
192
|
+
|
193
|
+
# Ram 0.5+ no longer supports separate template packages.
|
194
|
+
def self.check_for_deprecations
|
195
|
+
if @configuration[:templates]
|
196
|
+
raise DeprecationError, "Ram 0.5+ no longer supports separate packages for templates.\nPlease fold your templates into the appropriate 'javascripts' package instead."
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.warn(message)
|
201
|
+
message = "Ram Warning: #{message}"
|
202
|
+
$stderr.puts message
|
203
|
+
end
|
204
|
+
|
205
|
+
# Clone of active_support's symbolize_keys, so that we don't have to depend
|
206
|
+
# on active_support in any fashion. Converts a hash's keys to all symbols.
|
207
|
+
def self.symbolize_keys(hash)
|
208
|
+
hash.keys.each do |key|
|
209
|
+
hash[(key.to_sym rescue key) || key] = hash.delete(key)
|
210
|
+
end
|
211
|
+
hash
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
require 'ram/dependencies'
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../ram')
|
3
|
+
|
4
|
+
module Ram
|
5
|
+
|
6
|
+
# The @CommandLine@ is able to compress, pre-package, and pre-gzip all the
|
7
|
+
# assets specified in the configuration file, in order to avoid an initial
|
8
|
+
# round of slow requests after a fresh deployment.
|
9
|
+
class CommandLine
|
10
|
+
|
11
|
+
BANNER = <<-EOS
|
12
|
+
|
13
|
+
Usage: ram OPTIONS
|
14
|
+
|
15
|
+
Run ram inside a Rails application to compresses all JS, CSS,
|
16
|
+
and JST according to config/assets.yml, saving the packaged
|
17
|
+
files and corresponding gzipped versions.
|
18
|
+
|
19
|
+
If you're using "embed_assets", and you wish to precompile the
|
20
|
+
MHTML stylesheet variants, you must specify the "base-url".
|
21
|
+
|
22
|
+
Options:
|
23
|
+
EOS
|
24
|
+
|
25
|
+
# The @Ram::CommandLine@ runs from the contents of @ARGV@.
|
26
|
+
def initialize
|
27
|
+
parse_options
|
28
|
+
ensure_configuration_file
|
29
|
+
Ram.package!(@options)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Make sure that we have a readable configuration file. The @ram@
|
36
|
+
# command can't run without one.
|
37
|
+
def ensure_configuration_file
|
38
|
+
config = @options[:config_path]
|
39
|
+
return true if File.exists?(config) && File.readable?(config)
|
40
|
+
puts "Could not find the asset configuration file \"#{config}\""
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Uses @OptionParser@ to grab the options: *--output*, *--config*, and
|
45
|
+
# *--base-url*...
|
46
|
+
def parse_options
|
47
|
+
@options = {
|
48
|
+
:config_path => Ram::DEFAULT_CONFIG_PATH,
|
49
|
+
:output_folder => nil,
|
50
|
+
:base_url => nil,
|
51
|
+
:force => false
|
52
|
+
}
|
53
|
+
@option_parser = OptionParser.new do |opts|
|
54
|
+
opts.on('-o', '--output PATH', 'output folder for packages (default: "public/assets")') do |output_folder|
|
55
|
+
@options[:output_folder] = output_folder
|
56
|
+
end
|
57
|
+
opts.on('-c', '--config PATH', 'path to assets.yml (default: "config/assets.yml")') do |config_path|
|
58
|
+
@options[:config_path] = config_path
|
59
|
+
end
|
60
|
+
opts.on('-u', '--base-url URL', 'base URL for MHTML (ex: "http://example.com")') do |base_url|
|
61
|
+
@options[:base_url] = base_url
|
62
|
+
end
|
63
|
+
opts.on('-f', '--force', 'force a rebuild of all assets') do |force|
|
64
|
+
@options[:force] = force
|
65
|
+
end
|
66
|
+
opts.on('-p', '--packages LIST', 'list of packages to build (ex: "core,ui", default: all)') do |package_names|
|
67
|
+
@options[:package_names] = package_names.split(/,\s*/).map {|n| n.to_sym }
|
68
|
+
end
|
69
|
+
opts.on('-P', '--public-root PATH', 'path to public assets (default: "public")') do |public_root|
|
70
|
+
puts "Option for PUBLIC_ROOT"
|
71
|
+
@options[:public_root] = public_root
|
72
|
+
end
|
73
|
+
opts.on_tail('-v', '--version', 'display Ram version') do
|
74
|
+
puts "Ram version #{Ram::VERSION}"
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
@option_parser.banner = BANNER
|
79
|
+
@option_parser.parse!(ARGV)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
module Ram
|
2
|
+
|
3
|
+
# Uses the YUI Compressor or Closure Compiler to compress JavaScript.
|
4
|
+
# Always uses YUI to compress CSS (Which means that Java must be installed.)
|
5
|
+
# Also knows how to create a concatenated JST file.
|
6
|
+
# If "embed_assets" is turned on, creates "mhtml" and "datauri" versions of
|
7
|
+
# all stylesheets, with all enabled assets inlined into the css.
|
8
|
+
class Compressor
|
9
|
+
|
10
|
+
# Mapping from extension to mime-type of all embeddable assets.
|
11
|
+
EMBED_MIME_TYPES = {
|
12
|
+
'.png' => 'image/png',
|
13
|
+
'.jpg' => 'image/jpeg',
|
14
|
+
'.jpeg' => 'image/jpeg',
|
15
|
+
'.gif' => 'image/gif',
|
16
|
+
'.tif' => 'image/tiff',
|
17
|
+
'.tiff' => 'image/tiff',
|
18
|
+
'.ttf' => 'font/truetype',
|
19
|
+
'.otf' => 'font/opentype',
|
20
|
+
'.woff' => 'font/woff'
|
21
|
+
}
|
22
|
+
|
23
|
+
# Font extensions for which we allow embedding:
|
24
|
+
EMBED_EXTS = EMBED_MIME_TYPES.keys
|
25
|
+
EMBED_FONTS = ['.ttf', '.otf', '.woff']
|
26
|
+
|
27
|
+
# (32k - padding) maximum length for data-uri assets (an IE8 limitation).
|
28
|
+
MAX_IMAGE_SIZE = 32700
|
29
|
+
|
30
|
+
# CSS asset-embedding regexes for URL rewriting.
|
31
|
+
EMBED_DETECTOR = /url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
|
32
|
+
EMBEDDABLE = /[\A\/]embed\//
|
33
|
+
EMBED_REPLACER = /url\(__EMBED__(.+?)(\?\d+)?\)/
|
34
|
+
|
35
|
+
# MHTML file constants.
|
36
|
+
MHTML_START = "/*\r\nContent-Type: multipart/related; boundary=\"MHTML_MARK\"\r\n\r\n"
|
37
|
+
MHTML_SEPARATOR = "--MHTML_MARK\r\n"
|
38
|
+
MHTML_END = "\r\n--MHTML_MARK--\r\n*/\r\n"
|
39
|
+
|
40
|
+
# JST file constants.
|
41
|
+
JST_START = "(function(){"
|
42
|
+
JST_END = "})();"
|
43
|
+
|
44
|
+
COMPRESSORS = {
|
45
|
+
:yui => YUI::JavaScriptCompressor,
|
46
|
+
:closure => Ram.compressors.include?(:closure) ? Closure::Compiler : nil,
|
47
|
+
:uglifier => Ram.compressors.include?(:uglifier) ? Ram::Uglifier : nil
|
48
|
+
}
|
49
|
+
|
50
|
+
DEFAULT_OPTIONS = {
|
51
|
+
:yui => {:munge => true},
|
52
|
+
:closure => {},
|
53
|
+
:uglifier => {:copyright => false}
|
54
|
+
}
|
55
|
+
|
56
|
+
# The css compressor is always the YUI Compressor. JS compression can be
|
57
|
+
# provided with YUI Compressor, Google Closure Compiler or UglifyJS.
|
58
|
+
def initialize
|
59
|
+
Ram.check_java_version
|
60
|
+
@css_compressor = YUI::CssCompressor.new(Ram.css_compressor_options || {})
|
61
|
+
flavor = Ram.javascript_compressor || Ram::DEFAULT_COMPRESSOR
|
62
|
+
@options = DEFAULT_OPTIONS[flavor].merge(Ram.compressor_options || {})
|
63
|
+
@js_compressor = COMPRESSORS[flavor].new(@options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Concatenate together a list of JavaScript paths, and pass them through the
|
67
|
+
# YUI Compressor (with munging enabled). JST can optionally be included.
|
68
|
+
def compress_js(paths)
|
69
|
+
if (jst_paths = paths.grep(Ram.template_extension_matcher)).empty?
|
70
|
+
js = concatenate(paths)
|
71
|
+
else
|
72
|
+
js = concatenate(paths - jst_paths) + compile_jst(jst_paths)
|
73
|
+
end
|
74
|
+
Ram.compress_assets ? @js_compressor.compress(js) : js
|
75
|
+
end
|
76
|
+
|
77
|
+
# Concatenate and compress a list of CSS stylesheets. When compressing a
|
78
|
+
# :datauri or :mhtml variant, post-processes the result to embed
|
79
|
+
# referenced assets.
|
80
|
+
def compress_css(paths, variant=nil, asset_url=nil)
|
81
|
+
@asset_contents = {}
|
82
|
+
css = concatenate_and_tag_assets(paths, variant)
|
83
|
+
css = @css_compressor.compress(css) if Ram.compress_assets
|
84
|
+
case variant
|
85
|
+
when nil then return css
|
86
|
+
when :datauri then return with_data_uris(css)
|
87
|
+
when :mhtml then return with_mhtml(css, asset_url)
|
88
|
+
else raise PackageNotFound, "\"#{variant}\" is not a valid stylesheet variant"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Compiles a single JST file by writing out a javascript that adds
|
93
|
+
# template properties to a top-level template namespace object. Adds a
|
94
|
+
# JST-compilation function to the top of the package, unless you've
|
95
|
+
# specified your own preferred function, or turned it off.
|
96
|
+
# JST templates are named with the basename of their file.
|
97
|
+
def compile_jst(paths)
|
98
|
+
namespace = Ram.template_namespace
|
99
|
+
paths = paths.grep(Ram.template_extension_matcher).sort
|
100
|
+
base_path = find_base_path(paths)
|
101
|
+
compiled = paths.map do |path|
|
102
|
+
contents = read_binary_file(path)
|
103
|
+
contents = contents.gsub(/\r?\n/, "\\n").gsub("'", '\\\\\'')
|
104
|
+
name = template_name(path, base_path)
|
105
|
+
"#{namespace}['#{name}'] = #{Ram.template_function}('#{contents}');"
|
106
|
+
end
|
107
|
+
compiler = Ram.include_jst_script ? read_binary_file(DEFAULT_JST_SCRIPT) : '';
|
108
|
+
setup_namespace = "#{namespace} = #{namespace} || {};"
|
109
|
+
[JST_START, setup_namespace, compiler, compiled, JST_END].flatten.join("\n")
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Given a set of paths, find a common prefix path.
|
116
|
+
def find_base_path(paths)
|
117
|
+
return nil if paths.length <= 1
|
118
|
+
paths.sort!
|
119
|
+
first = paths.first.split('/')
|
120
|
+
last = paths.last.split('/')
|
121
|
+
i = 0
|
122
|
+
while first[i] == last[i] && i <= first.length
|
123
|
+
i += 1
|
124
|
+
end
|
125
|
+
res = first.slice(0, i).join('/')
|
126
|
+
res.empty? ? nil : res
|
127
|
+
end
|
128
|
+
|
129
|
+
# Determine the name of a JS template. If there's a common base path, use
|
130
|
+
# the namespaced prefix. Otherwise, simply use the filename.
|
131
|
+
def template_name(path, base_path)
|
132
|
+
return File.basename(path, ".#{Ram.template_extension}") unless base_path
|
133
|
+
path.gsub(/\A#{Regexp.escape(base_path)}\/(.*)\.#{Ram.template_extension}\Z/, '\1')
|
134
|
+
end
|
135
|
+
|
136
|
+
# In order to support embedded assets from relative paths, we need to
|
137
|
+
# expand the paths before contatenating the CSS together and losing the
|
138
|
+
# location of the original stylesheet path. Validate the assets while we're
|
139
|
+
# at it.
|
140
|
+
def concatenate_and_tag_assets(paths, variant=nil)
|
141
|
+
stylesheets = [paths].flatten.map do |css_path|
|
142
|
+
contents = read_binary_file(css_path)
|
143
|
+
contents.gsub(EMBED_DETECTOR) do |url|
|
144
|
+
ipath, cpath = Pathname.new($1), Pathname.new(File.expand_path(css_path))
|
145
|
+
is_url = URI.parse($1).absolute?
|
146
|
+
is_url ? url : "url(#{construct_asset_path(ipath, cpath, variant)})"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
stylesheets.join("\n")
|
150
|
+
end
|
151
|
+
|
152
|
+
# Re-write all enabled asset URLs in a stylesheet with their corresponding
|
153
|
+
# Data-URI Base-64 encoded asset contents.
|
154
|
+
def with_data_uris(css)
|
155
|
+
css.gsub(EMBED_REPLACER) do |url|
|
156
|
+
"url(\"data:#{mime_type($1)};charset=utf-8;base64,#{encoded_contents($1)}\")"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent.
|
161
|
+
# The newlines ("\r\n") in the following method are critical. Without them
|
162
|
+
# your MHTML will look identical, but won't work.
|
163
|
+
def with_mhtml(css, asset_url)
|
164
|
+
paths, index = {}, 0
|
165
|
+
css = css.gsub(EMBED_REPLACER) do |url|
|
166
|
+
i = paths[$1] ||= "#{index += 1}-#{File.basename($1)}"
|
167
|
+
"url(mhtml:#{asset_url}!#{i})"
|
168
|
+
end
|
169
|
+
mhtml = paths.sort.map do |path, identifier|
|
170
|
+
mime, contents = mime_type(path), encoded_contents(path)
|
171
|
+
[MHTML_SEPARATOR, "Content-Location: #{identifier}\r\n", "Content-Type: #{mime}\r\n", "Content-Transfer-Encoding: base64\r\n\r\n", contents, "\r\n"]
|
172
|
+
end
|
173
|
+
[MHTML_START, mhtml, MHTML_END, css].flatten.join('')
|
174
|
+
end
|
175
|
+
|
176
|
+
# Return a rewritten asset URL for a new stylesheet -- the asset should
|
177
|
+
# be tagged for embedding if embeddable, and referenced at the correct level
|
178
|
+
# if relative.
|
179
|
+
def construct_asset_path(asset_path, css_path, variant)
|
180
|
+
public_path = absolute_path(asset_path, css_path)
|
181
|
+
return "__EMBED__#{public_path}" if embeddable?(public_path, variant)
|
182
|
+
source = asset_path.absolute? ? asset_path.to_s : relative_path(public_path)
|
183
|
+
rewrite_asset_path(source, public_path)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Get the site-absolute public path for an asset file path that may or may
|
187
|
+
# not be relative, given the path of the stylesheet that contains it.
|
188
|
+
def absolute_path(asset_pathname, css_pathname)
|
189
|
+
(asset_pathname.absolute? ?
|
190
|
+
Pathname.new(File.join(Ram.public_root, asset_pathname)) :
|
191
|
+
css_pathname.dirname + asset_pathname).cleanpath
|
192
|
+
end
|
193
|
+
|
194
|
+
# CSS assets that are referenced by relative paths, and are *not* being
|
195
|
+
# embedded, must be rewritten relative to the newly-merged stylesheet path.
|
196
|
+
def relative_path(absolute_path)
|
197
|
+
File.join('../', absolute_path.sub(Ram.public_root, ''))
|
198
|
+
end
|
199
|
+
|
200
|
+
# Similar to the AssetTagHelper's method of the same name, this will
|
201
|
+
# append the ASSET_ID cache-buster to URLs, if it's defined.
|
202
|
+
def rewrite_asset_path(path, file_path)
|
203
|
+
asset_id = get_asset_id(file_path)
|
204
|
+
(!asset_id || asset_id == '') ? path : "#{path}?#{asset_id}"
|
205
|
+
end
|
206
|
+
|
207
|
+
# Similar to the AssetTagHelper's method of the same name, this will
|
208
|
+
# determine the correct asset id for a file.
|
209
|
+
def get_asset_id(path)
|
210
|
+
asset_id = ENV["ASSET_ID"]
|
211
|
+
return asset_id if asset_id
|
212
|
+
File.exists?(path) ? File.mtime(path).to_i.to_s : ''
|
213
|
+
end
|
214
|
+
|
215
|
+
# An asset is valid for embedding if it exists, is less than 32K, and is
|
216
|
+
# stored somewhere inside of a folder named "embed". IE does not support
|
217
|
+
# Data-URIs larger than 32K, and you probably shouldn't be embedding assets
|
218
|
+
# that large in any case. Because we need to check the base64 length here,
|
219
|
+
# save it so that we don't have to compute it again later.
|
220
|
+
def embeddable?(asset_path, variant)
|
221
|
+
font = EMBED_FONTS.include?(asset_path.extname)
|
222
|
+
return false unless variant
|
223
|
+
return false unless asset_path.to_s.match(EMBEDDABLE) && asset_path.exist?
|
224
|
+
return false unless EMBED_EXTS.include?(asset_path.extname)
|
225
|
+
return false unless font || encoded_contents(asset_path).length < MAX_IMAGE_SIZE
|
226
|
+
return false if font && variant == :mhtml
|
227
|
+
return true
|
228
|
+
end
|
229
|
+
|
230
|
+
# Return the Base64-encoded contents of an asset on a single line.
|
231
|
+
def encoded_contents(asset_path)
|
232
|
+
return @asset_contents[asset_path] if @asset_contents[asset_path]
|
233
|
+
data = read_binary_file(asset_path)
|
234
|
+
@asset_contents[asset_path] = Base64.encode64(data).gsub(/\n/, '')
|
235
|
+
end
|
236
|
+
|
237
|
+
# Grab the mime-type of an asset, by filename.
|
238
|
+
def mime_type(asset_path)
|
239
|
+
EMBED_MIME_TYPES[File.extname(asset_path)]
|
240
|
+
end
|
241
|
+
|
242
|
+
# Concatenate together a list of asset files.
|
243
|
+
def concatenate(paths)
|
244
|
+
[paths].flatten.map {|p| read_binary_file(p) }.join("\n")
|
245
|
+
end
|
246
|
+
|
247
|
+
# `File.read`, but in "binary" mode.
|
248
|
+
def read_binary_file(path)
|
249
|
+
File.open(path, 'rb:UTF-8') {|f| f.read }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Standard Library Dependencies:
|
2
|
+
require 'uri'
|
3
|
+
require 'erb'
|
4
|
+
require 'zlib'
|
5
|
+
require 'yaml'
|
6
|
+
require 'base64'
|
7
|
+
require 'pathname'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
# Include YUI as the default
|
11
|
+
require 'yui/compressor'
|
12
|
+
|
13
|
+
# Try Closure.
|
14
|
+
begin
|
15
|
+
require 'closure-compiler'
|
16
|
+
rescue LoadError
|
17
|
+
Ram.compressors.delete :closure
|
18
|
+
end
|
19
|
+
|
20
|
+
# Try Uglifier.
|
21
|
+
begin
|
22
|
+
require 'uglifier'
|
23
|
+
rescue LoadError
|
24
|
+
Ram.compressors.delete :uglifier
|
25
|
+
end
|
26
|
+
|
27
|
+
# Load initial configuration before the rest of Ram.
|
28
|
+
Ram.load_configuration(Ram::DEFAULT_CONFIG_PATH, true)
|
29
|
+
|
30
|
+
# Ram Core:
|
31
|
+
require 'ram/uglifier' if Ram.compressors.include? :uglifier
|
32
|
+
require 'ram/compressor'
|
33
|
+
require 'ram/packager'
|
34
|
+
|
35
|
+
# TODO: See if the helper might be useful in a nanoc environment...
|
data/lib/ram/helper.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# module Ram
|
2
|
+
#
|
3
|
+
# # The Ram::Helper module, which is made available to every view, provides
|
4
|
+
# # helpers for writing out HTML tags for asset packages. In development you
|
5
|
+
# # get the ordered list of source files -- in any other environment, a link
|
6
|
+
# # to the cached packages.
|
7
|
+
# module Helper
|
8
|
+
#
|
9
|
+
# DATA_URI_START = "<!--[if (!IE)|(gte IE 8)]><!-->" unless defined?(DATA_URI_START)
|
10
|
+
# DATA_URI_END = "<!--<![endif]-->" unless defined?(DATA_URI_END)
|
11
|
+
# MHTML_START = "<!--[if lte IE 7]>" unless defined?(MHTML_START)
|
12
|
+
# MHTML_END = "<![endif]-->" unless defined?(MHTML_END)
|
13
|
+
#
|
14
|
+
# # If embed_assets is turned on, writes out links to the Data-URI and MHTML
|
15
|
+
# # versions of the stylesheet package, otherwise the package is regular
|
16
|
+
# # compressed CSS, and in development the stylesheet URLs are passed verbatim.
|
17
|
+
# def include_stylesheets(*packages)
|
18
|
+
# options = packages.extract_options!
|
19
|
+
# return html_safe(individual_stylesheets(packages, options)) unless should_package?
|
20
|
+
# disabled = (options.delete(:embed_assets) == false) || (options.delete(:embed_images) == false)
|
21
|
+
# return html_safe(packaged_stylesheets(packages, options)) if disabled || !Ram.embed_assets
|
22
|
+
# return html_safe(embedded_image_stylesheets(packages, options))
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # Writes out the URL to the bundled and compressed javascript package,
|
26
|
+
# # except in development, where it references the individual scripts.
|
27
|
+
# def include_javascripts(*packages)
|
28
|
+
# html_safe packages.map {|pack|
|
29
|
+
# should_package? ? Ram.asset_url(pack, :js) : Ram.packager.individual_urls(pack.to_sym, :js)
|
30
|
+
# }.flatten.map {|pack|
|
31
|
+
# javascript_include_tag pack
|
32
|
+
# }.join("\n")
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # Writes out the URL to the concatenated and compiled JST file -- we always
|
36
|
+
# # have to pre-process it, even in development.
|
37
|
+
# def include_templates(*packages)
|
38
|
+
# raise DeprecationError, "Ram 0.5+ no longer supports separate packages for templates.\nYou can include your JST alongside your JS, and use include_javascripts."
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
#
|
42
|
+
# private
|
43
|
+
#
|
44
|
+
# def should_package?
|
45
|
+
# Ram.package_assets && !(Ram.allow_debugging && params[:debug_assets])
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# def html_safe(string)
|
49
|
+
# string.respond_to?(:html_safe) ? string.html_safe : string
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# # HTML tags, in order, for all of the individual stylesheets.
|
53
|
+
# def individual_stylesheets(packages, options)
|
54
|
+
# tags_with_options(packages, options) {|p| Ram.packager.individual_urls(p.to_sym, :css) }
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # HTML tags for the stylesheet packages.
|
58
|
+
# def packaged_stylesheets(packages, options)
|
59
|
+
# tags_with_options(packages, options) {|p| Ram.asset_url(p, :css) }
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # HTML tags for the 'datauri', and 'mhtml' versions of the packaged
|
63
|
+
# # stylesheets, using conditional comments to load the correct variant.
|
64
|
+
# def embedded_image_stylesheets(packages, options)
|
65
|
+
# datauri_tags = tags_with_options(packages, options) {|p| Ram.asset_url(p, :css, :datauri) }
|
66
|
+
# ie_tags = Ram.mhtml_enabled ?
|
67
|
+
# tags_with_options(packages, options) {|p| Ram.asset_url(p, :css, :mhtml) } :
|
68
|
+
# packaged_stylesheets(packages, options)
|
69
|
+
# [DATA_URI_START, datauri_tags, DATA_URI_END, MHTML_START, ie_tags, MHTML_END].join("\n")
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# # Generate the stylesheet tags for a batch of packages, with options, by
|
73
|
+
# # yielding each package to a block.
|
74
|
+
# def tags_with_options(packages, options)
|
75
|
+
# packages.dup.map {|package|
|
76
|
+
# yield package
|
77
|
+
# }.flatten.map {|package|
|
78
|
+
# stylesheet_link_tag package, options
|
79
|
+
# }.join("\n")
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# # Include the Ram asset helpers in all views, a-la ApplicationHelper.
|
87
|
+
# ::ActionView::Base.send(:include, Ram::Helper)
|
data/lib/ram/jst.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
var template = function(str){var fn = new Function('obj', 'var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push(\''+str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g,function(match,code){return "',"+code.replace(/\\'/g, "'")+",'";}).replace(/<%([\s\S]+?)%>/g,function(match,code){return "');"+code.replace(/\\'/g, "'").replace(/[\r\n\t]/g,' ')+"__p.push('";}).replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/\t/g,'\\t')+"');}return __p.join('');");return fn;};
|
data/lib/ram/packager.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
module Ram
|
2
|
+
|
3
|
+
# The Ram::Packager resolves the configuration file into lists of real
|
4
|
+
# assets that get merged into individual asset packages. Given the compiled
|
5
|
+
# contents of an asset package, the Packager knows how to cache that package
|
6
|
+
# with the correct timestamps.
|
7
|
+
class Packager
|
8
|
+
|
9
|
+
|
10
|
+
# Set force to false to allow packages to only be rebuilt when their source
|
11
|
+
# files have changed since the last time their package was built.
|
12
|
+
attr_accessor :force, :package_names
|
13
|
+
|
14
|
+
# Creating a new Packager will rebuild the list of assets from the
|
15
|
+
# Ram.configuration. When assets.yml is being changed on the fly,
|
16
|
+
# create a new Packager.
|
17
|
+
def initialize
|
18
|
+
# The difference between a path and an asset URL is "public".
|
19
|
+
@path_diff = Ram.public_root.sub(ASSET_ROOT, '')
|
20
|
+
@path_to_url = /\A#{Regexp.escape(ASSET_ROOT)}(\/?#{Regexp.escape(@path_diff)})?/
|
21
|
+
|
22
|
+
@compressor = Compressor.new
|
23
|
+
@force = false
|
24
|
+
@package_names = nil
|
25
|
+
@config = {
|
26
|
+
:css => (Ram.configuration[:stylesheets] || {}),
|
27
|
+
:js => (Ram.configuration[:javascripts] || {})
|
28
|
+
}
|
29
|
+
@packages = {
|
30
|
+
:css => create_packages(@config[:css]),
|
31
|
+
:js => create_packages(@config[:js])
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Ask the packager to precache all defined assets, along with their gzip'd
|
36
|
+
# versions. In order to prebuild the MHTML stylesheets, we need to know the
|
37
|
+
# base_url, because IE only supports MHTML with absolute references.
|
38
|
+
# Unless forced, will only rebuild assets whose source files have been
|
39
|
+
# changed since their last package build.
|
40
|
+
def precache_all(output_dir=nil, base_url=nil)
|
41
|
+
output_dir ||= File.join(Ram.public_root, Ram.package_path)
|
42
|
+
cacheable(:js, output_dir).each {|p| cache(p, 'js', pack_javascripts(p), output_dir) }
|
43
|
+
cacheable(:css, output_dir).each do |p|
|
44
|
+
cache(p, 'css', pack_stylesheets(p), output_dir)
|
45
|
+
if Ram.embed_assets
|
46
|
+
cache(p, 'css', pack_stylesheets(p, :datauri), output_dir, :datauri)
|
47
|
+
if Ram.mhtml_enabled
|
48
|
+
raise MissingConfiguration, "A --base-url option is required in order to generate MHTML." unless base_url
|
49
|
+
mtime = latest_mtime package_for(p, :css)[:paths]
|
50
|
+
asset_url = "#{base_url}#{Ram.asset_url(p, :css, :mhtml, mtime)}"
|
51
|
+
cache(p, 'css', pack_stylesheets(p, :mhtml, asset_url), output_dir, :mhtml, mtime)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Caches a single prebuilt asset package and gzips it at the highest
|
58
|
+
# compression level. Ensures that the modification time of both both
|
59
|
+
# variants is identical, for web server caching modules, as well as MHTML.
|
60
|
+
def cache(package, extension, contents, output_dir, suffix=nil, mtime=nil)
|
61
|
+
FileUtils.mkdir_p(output_dir) unless File.exists?(output_dir)
|
62
|
+
raise OutputNotWritable, "Ram doesn't have permission to write to \"#{output_dir}\"" unless File.writable?(output_dir)
|
63
|
+
mtime ||= latest_mtime package_for(package, extension.to_sym)[:paths]
|
64
|
+
files = []
|
65
|
+
files << file_name = File.join(output_dir, Ram.filename(package, extension, suffix))
|
66
|
+
File.open(file_name, 'wb+') {|f| f.write(contents) }
|
67
|
+
if Ram.gzip_assets
|
68
|
+
files << zip_name = "#{file_name}.gz"
|
69
|
+
Zlib::GzipWriter.open(zip_name, Zlib::BEST_COMPRESSION) {|f| f.write(contents) }
|
70
|
+
end
|
71
|
+
File.utime(mtime, mtime, *files)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the list of individual assets for a package.
|
75
|
+
def individual_urls(package, extension)
|
76
|
+
package_for(package, extension)[:urls]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return the compressed contents of a stylesheet package.
|
80
|
+
def pack_stylesheets(package, variant=nil, asset_url=nil)
|
81
|
+
@compressor.compress_css(package_for(package, :css)[:paths], variant, asset_url)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return the compressed contents of a javascript package.
|
85
|
+
def pack_javascripts(package)
|
86
|
+
@compressor.compress_js(package_for(package, :js)[:paths])
|
87
|
+
end
|
88
|
+
|
89
|
+
# Return the compiled contents of a JST package.
|
90
|
+
def pack_templates(package)
|
91
|
+
@compressor.compile_jst(package_for(package, :js)[:paths])
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Look up a package asset list by name, raising an exception if the
|
97
|
+
# package has gone missing.
|
98
|
+
def package_for(package, extension)
|
99
|
+
pack = @packages[extension] && @packages[extension][package]
|
100
|
+
pack || not_found(package, extension)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Absolute globs are absolute -- relative globs are relative to ASSET_ROOT.
|
104
|
+
# Print a warning if no files were found that match the glob.
|
105
|
+
def glob_files(glob)
|
106
|
+
absolute = Pathname.new(glob).absolute?
|
107
|
+
paths = Dir[absolute ? glob : File.join(ASSET_ROOT, glob)].sort
|
108
|
+
Ram.warn("No assets match '#{glob}'") if paths.empty?
|
109
|
+
paths
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get the latest mtime of a list of files (plus the config path).
|
113
|
+
def latest_mtime(paths)
|
114
|
+
paths += [Ram.config_path]
|
115
|
+
paths.map {|p| File.mtime(p) }.max || Time.now
|
116
|
+
end
|
117
|
+
|
118
|
+
# Return a list of all of the packages that should be cached. If "force" is
|
119
|
+
# true, this is all of them -- otherwise only the packages that are missing
|
120
|
+
# or whose source files have changed since the last package build.
|
121
|
+
def cacheable(extension, output_dir)
|
122
|
+
names = @packages[extension].keys
|
123
|
+
names = names.select {|n| @package_names.include? n } if @package_names
|
124
|
+
config_mtime = File.mtime(Ram.config_path)
|
125
|
+
return names if @force
|
126
|
+
return names.select do |name|
|
127
|
+
pack = package_for(name, extension)
|
128
|
+
cached = [Ram.filename(name, extension)]
|
129
|
+
cached.push Ram.filename(name, extension, :datauri) if Ram.embed_assets
|
130
|
+
cached.push Ram.filename(name, extension, :mhtml) if Ram.mhtml_enabled
|
131
|
+
cached.map! {|file| File.join(output_dir, file) }
|
132
|
+
if cached.any? {|file| !File.exists?(file) }
|
133
|
+
true
|
134
|
+
else
|
135
|
+
since = cached.map {|file| File.mtime(file) }.min
|
136
|
+
config_mtime > since || pack[:paths].any? {|src| File.mtime(src) > since }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Compiles the list of assets that goes into each package. Runs an
|
142
|
+
# ordered list of Dir.globs, taking the merged unique result.
|
143
|
+
# If there are JST files in this package we need to add an extra
|
144
|
+
# path for when package_assets is off (e.g. in a dev environment).
|
145
|
+
# This package (e.g. /assets/package-name.jst) will never exist as
|
146
|
+
# an actual file but will be dynamically generated by Ram on
|
147
|
+
# every request.
|
148
|
+
def create_packages(config)
|
149
|
+
packages = {}
|
150
|
+
return packages if !config
|
151
|
+
config.each do |name, globs|
|
152
|
+
globs ||= []
|
153
|
+
packages[name] = {}
|
154
|
+
paths = globs.flatten.uniq.map {|glob| glob_files(glob) }.flatten.uniq
|
155
|
+
packages[name][:paths] = paths
|
156
|
+
if !paths.grep(Ram.template_extension_matcher).empty?
|
157
|
+
packages[name][:urls] = paths.grep(JS_EXTENSION).map {|path| path.sub(@path_to_url, '') }
|
158
|
+
packages[name][:urls] += [Ram.asset_url(name, Ram.template_extension)]
|
159
|
+
else
|
160
|
+
packages[name][:urls] = paths.map {|path| path.sub(@path_to_url, '') }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
packages
|
164
|
+
end
|
165
|
+
|
166
|
+
# Raise a PackageNotFound exception for missing packages...
|
167
|
+
def not_found(package, extension)
|
168
|
+
raise PackageNotFound, "assets.yml does not contain a \"#{package}\" #{extension.to_s.upcase} package"
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
data/lib/ram/uglifier.rb
ADDED
data/ram.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'ram'
|
3
|
+
s.version = '0.0.1' # Keep version in sync with ram.rb
|
4
|
+
s.date = '2011-10-25'
|
5
|
+
|
6
|
+
s.homepage = "http://fleetventures.com/"
|
7
|
+
s.summary = "Asset Packaging"
|
8
|
+
s.description = <<-EOS
|
9
|
+
Ram is an asset packaging library for any type of web project. It was
|
10
|
+
originalyl adapted from the excellent Jammit gem, but without any Rails
|
11
|
+
dependencies or tie ins. This provides both the CSS and JavaScript
|
12
|
+
concatenation and compression that you'd expect, as well as YUI Compressor
|
13
|
+
and Closure Compiler compatibility, ahead-of-time gzipping, built-in
|
14
|
+
JavaScript template support, and optional Data-URI / MHTML image embedding.
|
15
|
+
EOS
|
16
|
+
|
17
|
+
s.authors = ['David Richards']
|
18
|
+
s.email = 'david@fleetventures.com'
|
19
|
+
s.rubyforge_project = 'ram'
|
20
|
+
|
21
|
+
s.require_paths = ['lib']
|
22
|
+
s.executables = ['ram']
|
23
|
+
|
24
|
+
s.extra_rdoc_files = ['README.md']
|
25
|
+
s.rdoc_options << '--title' << 'Ram' <<
|
26
|
+
'--exclude' << 'test' <<
|
27
|
+
'--main' << 'README.md' <<
|
28
|
+
'--all'
|
29
|
+
|
30
|
+
s.add_dependency 'yui-compressor', ['>= 0.9.3']
|
31
|
+
|
32
|
+
s.files = Dir['lib/**/*', 'bin/*', 'ram.gemspec', 'LICENSE', 'README.md']
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ram
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Richards
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-25 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: yui-compressor
|
16
|
+
requirement: &2153929620 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.9.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2153929620
|
25
|
+
description: ! " Ram is an asset packaging library for any type of web project.
|
26
|
+
\ It was \n originalyl adapted from the excellent Jammit gem, but without any
|
27
|
+
Rails\n dependencies or tie ins. This provides both the CSS and JavaScript
|
28
|
+
\n concatenation and compression that you'd expect, as well as YUI Compressor
|
29
|
+
\n and Closure Compiler compatibility, ahead-of-time gzipping, built-in \n JavaScript
|
30
|
+
template support, and optional Data-URI / MHTML image embedding.\n"
|
31
|
+
email: david@fleetventures.com
|
32
|
+
executables:
|
33
|
+
- ram
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files:
|
36
|
+
- README.md
|
37
|
+
files:
|
38
|
+
- lib/ram/command_line.rb
|
39
|
+
- lib/ram/compressor.rb
|
40
|
+
- lib/ram/dependencies.rb
|
41
|
+
- lib/ram/helper.rb
|
42
|
+
- lib/ram/jst.js
|
43
|
+
- lib/ram/packager.rb
|
44
|
+
- lib/ram/uglifier.rb
|
45
|
+
- lib/ram.rb
|
46
|
+
- bin/ram
|
47
|
+
- ram.gemspec
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
homepage: http://fleetventures.com/
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options:
|
54
|
+
- --title
|
55
|
+
- Ram
|
56
|
+
- --exclude
|
57
|
+
- test
|
58
|
+
- --main
|
59
|
+
- README.md
|
60
|
+
- --all
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project: ram
|
77
|
+
rubygems_version: 1.8.10
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Asset Packaging
|
81
|
+
test_files: []
|
82
|
+
has_rdoc:
|