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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -1
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +2 -1
  5. data/Gemfile +4 -9
  6. data/embarista.gemspec +4 -5
  7. data/lib/embarista.rb +7 -0
  8. data/lib/embarista/app.rb +46 -0
  9. data/lib/embarista/dynamic_index.rb +3 -207
  10. data/lib/embarista/dynamic_index/context.rb +32 -0
  11. data/lib/embarista/dynamic_index/generator.rb +27 -0
  12. data/lib/embarista/dynamic_index/middleware.rb +25 -0
  13. data/lib/embarista/filters/manifest_url_filter.rb +12 -0
  14. data/lib/embarista/filters/precompile_handlebars_filter.rb +32 -5
  15. data/lib/embarista/filters/rewrite_minispade_requires_filter.rb +11 -7
  16. data/lib/embarista/helpers.rb +12 -12
  17. data/lib/embarista/precompiler.rb +84 -0
  18. data/lib/embarista/redis.rb +34 -0
  19. data/lib/embarista/s3.rb +59 -0
  20. data/lib/embarista/s3sync.rb +23 -75
  21. data/lib/embarista/sass_functions.rb +17 -7
  22. data/lib/embarista/tasks.rb +5 -0
  23. data/lib/embarista/tasks/assets.rb +41 -0
  24. data/lib/embarista/tasks/generate_index.rb +46 -0
  25. data/lib/embarista/tasks/generate_s3_index.rb +33 -0
  26. data/lib/embarista/tasks/set_current_index.rb +31 -0
  27. data/lib/embarista/tasks/updater.rb +114 -8
  28. data/lib/embarista/tasks/upload_index.rb +45 -0
  29. data/lib/embarista/version.rb +1 -1
  30. data/spec/manifest_builder_spec.rb +3 -3
  31. data/spec/manifest_url_filter_spec.rb +9 -3
  32. data/spec/precompile_handlebars_filter_spec.rb +6 -5
  33. data/spec/spec_helper.rb +3 -1
  34. data/vendor/ember-template-compiler.js +195 -0
  35. metadata +67 -98
  36. data/.rvmrc +0 -49
  37. data/lib/embarista/server.rb +0 -171
  38. 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= {}, &block)
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 = Barber::Ember::FilePrecompiler.call(input.read)
31
+ compiled = precompile(input.read, @handlebars_src, @ember_template_compiler_src)
25
32
 
26
- output.write "\nEmber.TEMPLATES['#{full_name}'] = #{compiled};\n"
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(All)?\s*\(\s*(["'/])([^"']+)\2\s*}) do |m|
18
- optional_all = $1
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!(%r{^#{options[:root]}/}, options[:prefix]) if options[:root]
24
- #puts "require#{optional_all}: #{before_path} -> #{path}"
25
- "minispade.require#{optional_all}(#{quote_char}#{path}#{quote_char}"
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
@@ -34,18 +34,18 @@ module Embarista
34
34
  match pattern, &JavascriptPipeline.new(opts)
35
35
  end
36
36
 
37
- def sass_uncompressed(&block)
38
- sass(:additional_load_paths => 'css',
39
- :style => :expanded,
40
- :line_comments => true,
41
- &block)
42
- end
43
-
44
- def sass_compressed(&block)
45
- sass(:additional_load_paths => 'css',
46
- :style => :compressed,
47
- :line_comments => false,
48
- &block)
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
@@ -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
@@ -1,44 +1,18 @@
1
+ require 'embarista/s3'
2
+
1
3
  module Embarista
2
4
  class S3sync
3
- attr_reader :origin, :bucket_name, :pwd, :tmp_root, :local_manifest_path, :remote_manifest_path
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
- @pwd = Pathname.new('').expand_path
18
- @origin = origin
19
- @tmp_root = @pwd + @origin
20
- @age = options[:age] || 31556900
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(origin, options)
24
- new(origin, options).sync
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
- compressed_open(file_name) do |file|
54
- store(file_name, file)
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
- def should_gzip?(name)
89
- name =~ /\.css|\.js\Z/
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 ||= YAML.load(AWS::S3::S3Object.find(remote_manifest_file_name, bucket_name).value)
110
- rescue AWS::S3::NoSuchKey
111
- puts 'no remote existing manifest, uploading everything'
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
- @_manifest ||= begin
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 digest?
26
- raise ::Sass::SyntaxError.new "manifest-url(#{path.inspect}) missing manifest entry" unless manifest.key? path
27
- manifest[path]
28
- else
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?