embarista 1.1.5 → 2.0.3
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 +7 -0
- data/.rspec +1 -1
- data/.ruby-version +1 -0
- data/.travis.yml +2 -1
- data/Gemfile +4 -9
- data/embarista.gemspec +4 -5
- data/lib/embarista.rb +7 -0
- data/lib/embarista/app.rb +46 -0
- data/lib/embarista/dynamic_index.rb +3 -207
- data/lib/embarista/dynamic_index/context.rb +32 -0
- data/lib/embarista/dynamic_index/generator.rb +27 -0
- data/lib/embarista/dynamic_index/middleware.rb +25 -0
- data/lib/embarista/filters/manifest_url_filter.rb +12 -0
- data/lib/embarista/filters/precompile_handlebars_filter.rb +32 -5
- data/lib/embarista/filters/rewrite_minispade_requires_filter.rb +11 -7
- data/lib/embarista/helpers.rb +12 -12
- data/lib/embarista/precompiler.rb +84 -0
- data/lib/embarista/redis.rb +34 -0
- data/lib/embarista/s3.rb +59 -0
- data/lib/embarista/s3sync.rb +23 -75
- data/lib/embarista/sass_functions.rb +17 -7
- data/lib/embarista/tasks.rb +5 -0
- data/lib/embarista/tasks/assets.rb +41 -0
- data/lib/embarista/tasks/generate_index.rb +46 -0
- data/lib/embarista/tasks/generate_s3_index.rb +33 -0
- data/lib/embarista/tasks/set_current_index.rb +31 -0
- data/lib/embarista/tasks/updater.rb +114 -8
- data/lib/embarista/tasks/upload_index.rb +45 -0
- data/lib/embarista/version.rb +1 -1
- data/spec/manifest_builder_spec.rb +3 -3
- data/spec/manifest_url_filter_spec.rb +9 -3
- data/spec/precompile_handlebars_filter_spec.rb +6 -5
- data/spec/spec_helper.rb +3 -1
- data/vendor/ember-template-compiler.js +195 -0
- metadata +67 -98
- data/.rvmrc +0 -49
- data/lib/embarista/server.rb +0 -171
- data/vendor/ember.js +0 -21051
@@ -1,10 +1,17 @@
|
|
1
|
-
require 'barber'
|
2
|
-
|
3
1
|
module Embarista
|
4
2
|
module Filters
|
5
3
|
class PrecompileHandlebarsFilter < Rake::Pipeline::Filter
|
6
|
-
def initialize(options=
|
4
|
+
def initialize(options={}, &block)
|
7
5
|
@template_dir = options[:template_dir] || 'templates'
|
6
|
+
@templates_global = options[:templates_global] || 'Ember.TEMPLATES'
|
7
|
+
|
8
|
+
options[:handlebars] ||= default_handlebars_src
|
9
|
+
options[:ember_template_compiler] ||= default_ember_template_compiler_src
|
10
|
+
|
11
|
+
throw Embarista::PrecompilerConfigurationError.new('Must specify handlebars source path') unless options[:handlebars]
|
12
|
+
throw Embarista::PrecompilerConfigurationError.new('Must specify ember_template_compiler source path') unless options[:ember_template_compiler]
|
13
|
+
@handlebars_src = options[:handlebars]
|
14
|
+
@ember_template_compiler_src = options[:ember_template_compiler]
|
8
15
|
|
9
16
|
super(&block)
|
10
17
|
unless block_given?
|
@@ -21,11 +28,31 @@ module Embarista
|
|
21
28
|
dirname.gsub!(/^\/?#{@template_dir}\/?/,'')
|
22
29
|
|
23
30
|
full_name = [dirname, name].compact.reject(&:empty?).join('/')
|
24
|
-
compiled =
|
31
|
+
compiled = precompile(input.read, @handlebars_src, @ember_template_compiler_src)
|
25
32
|
|
26
|
-
output.write "\
|
33
|
+
output.write "\n#{@templates_global}['#{full_name}'] = #{compiled};\n"
|
27
34
|
end
|
28
35
|
end
|
36
|
+
|
37
|
+
def default_handlebars_src
|
38
|
+
Dir['app/vendor/handlebars-*'].last
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_ember_template_compiler_src
|
42
|
+
Dir['app/vendor/ember-template-compiler-*.js'].last
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def precompile(template_string, handlebars_src, ember_template_compiler_src)
|
48
|
+
precompiler = Embarista::Precompiler.new(
|
49
|
+
:handlebars => handlebars_src,
|
50
|
+
:ember_template_compiler => ember_template_compiler_src
|
51
|
+
)
|
52
|
+
compiled = precompiler.compile(template_string)
|
53
|
+
js = "Ember.Handlebars.template(#{compiled})"
|
54
|
+
"#{js};"
|
55
|
+
end
|
29
56
|
end
|
30
57
|
end
|
31
58
|
end
|
@@ -14,15 +14,19 @@ module Embarista
|
|
14
14
|
code = input.read
|
15
15
|
#puts input.path
|
16
16
|
relative_root = Pathname.new(input.path).dirname
|
17
|
-
code.gsub!(%r{\brequire
|
18
|
-
|
19
|
-
quote_char = $2
|
20
|
-
before_path = $3
|
17
|
+
code.gsub!(%r{(?<!\.)\brequire\s*\(\s*["']([^"']+)["']\s*}) do |m|
|
18
|
+
before_path = $1
|
21
19
|
path = before_path.dup
|
22
20
|
path = relative_root.join(path).to_s if path.start_with?('.')
|
23
|
-
path.gsub
|
24
|
-
|
25
|
-
"
|
21
|
+
path = path.gsub(%r{^#{options[:root]}/}, '') if options[:root]
|
22
|
+
path = options[:prefix] + path if options[:prefix] && !path.include?(':')
|
23
|
+
# puts "require: #{before_path} -> #{path}"
|
24
|
+
"minispade.require('#{path}'"
|
25
|
+
end
|
26
|
+
code.gsub!(%r{(?<!\.)\brequireAll\s*\(([^)]+)\)}) do |m|
|
27
|
+
regex = $1
|
28
|
+
# puts "requireAll: #{regex}"
|
29
|
+
"minispade.requireAll(#{regex})"
|
26
30
|
end
|
27
31
|
output.write(code)
|
28
32
|
end
|
data/lib/embarista/helpers.rb
CHANGED
@@ -34,18 +34,18 @@ module Embarista
|
|
34
34
|
match pattern, &JavascriptPipeline.new(opts)
|
35
35
|
end
|
36
36
|
|
37
|
-
def sass_uncompressed(&block)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
def sass_compressed(&block)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
def sass_uncompressed(options={}, &block)
|
38
|
+
options[:additional_load_paths] ||= 'css'
|
39
|
+
options[:style] = :expanded
|
40
|
+
options[:line_comments] = true
|
41
|
+
sass(options, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def sass_compressed(options={}, &block)
|
45
|
+
options[:additional_load_paths] ||= 'css'
|
46
|
+
options[:style] = :compressed
|
47
|
+
options[:line_comments] = false
|
48
|
+
sass(options, &block)
|
49
49
|
end
|
50
50
|
|
51
51
|
# rename "qunit-*.css" => "qunit.css"
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'execjs'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Embarista
|
5
|
+
|
6
|
+
class PrecompilerError < StandardError
|
7
|
+
def initialize(template, error)
|
8
|
+
@template, @error = template, error
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"Pre-compilation failed for: #{@template}\n. Compiler said: #{@error}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class PrecompilerConfigurationError < StandardError
|
17
|
+
def initialize(message)
|
18
|
+
@message = message
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Precompiler
|
27
|
+
def initialize(opts)
|
28
|
+
throw PrecompilerConfigurationError.new('Must specify handlebars source path') unless opts[:handlebars]
|
29
|
+
throw PrecompilerConfigurationError.new('Must specify ember_template_compiler source path') unless opts[:ember_template_compiler]
|
30
|
+
@handlebars = File.new(opts[:handlebars])
|
31
|
+
@ember_template_precompiler = File.new(opts[:ember_template_compiler])
|
32
|
+
end
|
33
|
+
|
34
|
+
def compile(template)
|
35
|
+
context.call(
|
36
|
+
"EmberHandlebarsCompiler.precompile",
|
37
|
+
sanitize(template)
|
38
|
+
)
|
39
|
+
rescue ExecJS::ProgramError => ex
|
40
|
+
raise Embarista::PrecompilerError.new(template, ex)
|
41
|
+
end
|
42
|
+
|
43
|
+
def sources
|
44
|
+
[precompiler, handlebars, ember_template_precompiler]
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :handlebars, :ember_template_precompiler
|
48
|
+
|
49
|
+
def precompiler
|
50
|
+
@precompiler ||= StringIO.new(<<-JS)
|
51
|
+
var exports = this.exports || {};
|
52
|
+
function require() {
|
53
|
+
// ember-template-compiler only requires('handlebars')
|
54
|
+
return Handlebars;
|
55
|
+
}
|
56
|
+
// Precompiler
|
57
|
+
var EmberHandlebarsCompiler = {
|
58
|
+
precompile: function(string) {
|
59
|
+
return exports.precompile(string).toString();
|
60
|
+
}
|
61
|
+
};
|
62
|
+
JS
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def sanitize(template)
|
69
|
+
begin
|
70
|
+
JSON.load(%Q|{"template":#{template}}|)['template']
|
71
|
+
rescue JSON::ParserError
|
72
|
+
template
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def context
|
77
|
+
@context ||= ExecJS.compile(source)
|
78
|
+
end
|
79
|
+
|
80
|
+
def source
|
81
|
+
@source ||= sources.map(&:read).join("\n;\n")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Embarista
|
5
|
+
module Redis
|
6
|
+
def self.client
|
7
|
+
$redis ||= begin
|
8
|
+
uri = URI.parse(url)
|
9
|
+
require 'redis'
|
10
|
+
::Redis.new(
|
11
|
+
:host => uri.host,
|
12
|
+
:port => uri.port,
|
13
|
+
:password => uri.password
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.url
|
19
|
+
ENV['REDISTOGO_URL'] ||= fetch_url
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.fetch_url
|
23
|
+
if App.heroku_app
|
24
|
+
Bundler.with_clean_env do
|
25
|
+
Timeout::timeout(30) do
|
26
|
+
`heroku config:get REDISTOGO_URL --app #{App.heroku_app}`.chomp
|
27
|
+
end
|
28
|
+
end
|
29
|
+
else
|
30
|
+
'redis://0.0.0.0:6379/'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/embarista/s3.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
require 'zopfli'
|
3
|
+
require 'mime/types'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
module Embarista
|
7
|
+
# Simple interface for S3
|
8
|
+
class S3
|
9
|
+
SHOULD_ADD_CHARSET_UTF8 = %w(.js .css .html .json .xml)
|
10
|
+
SHOULD_GZIP_BINARY = %w(.ttf .eot .otf)
|
11
|
+
SHOULD_GZIP_ENCODING = %w(8bit 7bit quoted-printable)
|
12
|
+
DEFAULT_MIME_TYPE = MIME::Types['application/octet-stream'].first
|
13
|
+
|
14
|
+
attr_reader :bucket_name, :age
|
15
|
+
def initialize(bucket_name, opts={})
|
16
|
+
s3 = Aws::S3::Resource.new
|
17
|
+
@bucket = s3.bucket(bucket_name)
|
18
|
+
@root = Pathname.new(opts[:root] || '').expand_path
|
19
|
+
@age = opts[:age] || 31536000
|
20
|
+
end
|
21
|
+
|
22
|
+
def store(name, file_or_path=name, opts={})
|
23
|
+
opts[:acl] ||= 'public-read'
|
24
|
+
|
25
|
+
puts "#{bucket_name} -> #{name}"
|
26
|
+
s3_object = @bucket.object(name)
|
27
|
+
if file_or_path.is_a?(String)
|
28
|
+
opts[:cache_control] = "max-age=#{age.to_i}"
|
29
|
+
opts[:expires] = (Time.now + age).httpdate
|
30
|
+
path = @root + file_or_path
|
31
|
+
ext = path.extname
|
32
|
+
mime_type = MIME::Types.type_for(ext).first || DEFAULT_MIME_TYPE
|
33
|
+
opts[:content_type] = mime_type.to_s
|
34
|
+
if SHOULD_ADD_CHARSET_UTF8.include?(ext)
|
35
|
+
opts[:content_type] += '; charset=utf-8'
|
36
|
+
end
|
37
|
+
if SHOULD_GZIP_ENCODING.include?(mime_type.encoding) or SHOULD_GZIP_BINARY.include?(ext)
|
38
|
+
opts[:content_encoding] = 'gzip'
|
39
|
+
s3_object.put(opts.merge(body: Zopfli.deflate(path.read, format: :gzip)))
|
40
|
+
else
|
41
|
+
s3_object.put(opts.merge(body: path.read))
|
42
|
+
end
|
43
|
+
else
|
44
|
+
s3_object.put(opts.merge(body: file_or_path))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def read(name, &block)
|
49
|
+
url = @bucket.object(name).public_url
|
50
|
+
if block_given?
|
51
|
+
open(url, &block)
|
52
|
+
else
|
53
|
+
open(url) {|io| io.read }
|
54
|
+
end
|
55
|
+
rescue ::AWS::S3::Errors::NoSuchKey
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/embarista/s3sync.rb
CHANGED
@@ -1,44 +1,18 @@
|
|
1
|
+
require 'embarista/s3'
|
2
|
+
|
1
3
|
module Embarista
|
2
4
|
class S3sync
|
3
|
-
attr_reader :
|
4
|
-
|
5
|
-
def initialize(origin, options)
|
6
|
-
@bucket_name = options.fetch(:bucket_name)
|
7
|
-
@local_manifest_path = options[:local_manifest_path]
|
8
|
-
@remote_manifest_path = options[:remote_manifest_path]
|
9
|
-
aws_key = options.fetch(:aws_key)
|
10
|
-
aws_secret = options.fetch(:aws_secret)
|
11
|
-
|
12
|
-
@connection = AWS::S3::Base.establish_connection!(
|
13
|
-
:access_key_id => aws_key,
|
14
|
-
:secret_access_key => aws_secret
|
15
|
-
)
|
5
|
+
attr_reader :local_manifest_path, :remote_manifest_path, :s3
|
16
6
|
|
17
|
-
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
7
|
+
def initialize(root, opts={})
|
8
|
+
@local_manifest_path = opts[:local_manifest_path]
|
9
|
+
@remote_manifest_path = opts[:remote_manifest_path]
|
10
|
+
@root = Pathname.new(root).expand_path
|
11
|
+
@s3 = S3.new(opts.fetch(:bucket_name), root: root)
|
21
12
|
end
|
22
13
|
|
23
|
-
def self.sync(
|
24
|
-
new(
|
25
|
-
end
|
26
|
-
|
27
|
-
def store(name, file)
|
28
|
-
puts " -> #{name}"
|
29
|
-
|
30
|
-
opts = {
|
31
|
-
access: :public_read
|
32
|
-
}
|
33
|
-
|
34
|
-
if should_gzip?(name)
|
35
|
-
opts[:content_encoding] = 'gzip'
|
36
|
-
end
|
37
|
-
|
38
|
-
opts[:cache_control] = "max-age=#{@age.to_i}"
|
39
|
-
opts[:expires] = (Time.now + @age).httpdate
|
40
|
-
|
41
|
-
AWS::S3::S3Object.store(name, file, bucket_name, opts)
|
14
|
+
def self.sync(root, opts={})
|
15
|
+
new(root, opts).sync
|
42
16
|
end
|
43
17
|
|
44
18
|
def sync
|
@@ -50,43 +24,12 @@ module Embarista
|
|
50
24
|
end
|
51
25
|
|
52
26
|
delta_manifest.values.each do |file_name|
|
53
|
-
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
open(local_manifest_path) do |file|
|
59
|
-
store(remote_manifest_file_name, file)
|
60
|
-
store(local_manifest_file_name, file)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def compressed_open(file_name)
|
65
|
-
if should_gzip?(file_name)
|
66
|
-
str_io = StringIO.new
|
67
|
-
open(tmp_root.to_s + file_name) do |f|
|
68
|
-
streaming_deflate(f, str_io)
|
69
|
-
end
|
70
|
-
str_io.reopen(str_io.string, "r")
|
71
|
-
yield str_io
|
72
|
-
str_io.close
|
73
|
-
else
|
74
|
-
open(tmp_root.to_s + file_name) do |f|
|
75
|
-
yield f
|
76
|
-
end
|
27
|
+
file_name[0] = ''
|
28
|
+
s3.store(file_name)
|
77
29
|
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def streaming_deflate(source_io, target_io, buffer_size = 4 * 1024)
|
81
|
-
gz = Zlib::GzipWriter.new(target_io, Zlib::BEST_COMPRESSION)
|
82
|
-
while(string = source_io.read(buffer_size)) do
|
83
|
-
gz.write(string)
|
84
|
-
end
|
85
|
-
gz.close
|
86
|
-
end
|
87
30
|
|
88
|
-
|
89
|
-
|
31
|
+
s3.store(remote_manifest_file_name, local_manifest_path)
|
32
|
+
s3.store(local_manifest_file_name, local_manifest_path)
|
90
33
|
end
|
91
34
|
|
92
35
|
def remote_manifest_file_name
|
@@ -106,13 +49,18 @@ module Embarista
|
|
106
49
|
end
|
107
50
|
|
108
51
|
def remote_manifest
|
109
|
-
@remote_manifest ||=
|
110
|
-
|
111
|
-
|
52
|
+
@remote_manifest ||= begin
|
53
|
+
if remote_manifest = s3.read(remote_manifest_file_name)
|
54
|
+
YAML.load(remote_manifest)
|
55
|
+
else
|
56
|
+
puts 'no remote existing manifest, uploading everything'
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
112
60
|
end
|
113
61
|
|
114
62
|
def local_manifest
|
115
|
-
@local_manifest ||= YAML.load_file(local_manifest_path)
|
63
|
+
@local_manifest ||= YAML.load_file(@root + local_manifest_path)
|
116
64
|
end
|
117
65
|
end
|
118
66
|
end
|
@@ -2,7 +2,7 @@ module Embarista
|
|
2
2
|
module SassFunctions
|
3
3
|
def manifest_url(path)
|
4
4
|
digested_path = lookup_manifest_path(path.value)
|
5
|
-
Sass::Script::String.new("url(#{digested_path})")
|
5
|
+
Sass::Script::String.new("url(#{URI.encode(digested_path)})")
|
6
6
|
end
|
7
7
|
|
8
8
|
def manifest_path(path)
|
@@ -13,7 +13,7 @@ module Embarista
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def manifest
|
16
|
-
|
16
|
+
@@_manifest ||= begin
|
17
17
|
# TODO: some switch so that this doesn't run in dev?
|
18
18
|
manifest_path = 'public/manifest.yml'
|
19
19
|
return {} unless File.exist?(manifest_path)
|
@@ -22,12 +22,22 @@ module Embarista
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def lookup_manifest_path(path)
|
25
|
-
if
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
path
|
25
|
+
# take the anchor/querystring off if there is one
|
26
|
+
post_path_start = path.index(/[?#]/)
|
27
|
+
if post_path_start
|
28
|
+
post_path = path[post_path_start..-1]
|
29
|
+
path = path[0..post_path_start-1]
|
30
30
|
end
|
31
|
+
|
32
|
+
resolved_path = if digest?
|
33
|
+
raise ::Sass::SyntaxError.new "manifest-url(#{path.inspect}) missing manifest entry" unless manifest.key? path
|
34
|
+
manifest[path]
|
35
|
+
else
|
36
|
+
path
|
37
|
+
end
|
38
|
+
|
39
|
+
# put the anchor/querystring back on, if there is one
|
40
|
+
"#{resolved_path}#{post_path}"
|
31
41
|
end
|
32
42
|
|
33
43
|
def digest?
|