assette 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,24 +1,158 @@
1
- require 'yaml'
2
-
3
1
  module Assette
4
- attr_accessor :config
2
+ def config(optional_path=nil)
3
+ return @config if @config && !optional_path
4
+
5
+ f = %w{assets.rb config/assets.rb}
6
+ f.unshift(optional_path.to_s) if optional_path
7
+
8
+ p = f.find { |path| File.exist?(path) }
9
+
10
+ @config = Assette::Config.load(p)
11
+ end
5
12
 
6
13
  class Config
7
- OPTIONS = [
8
- :file_paths, :asset_path, :templates_path, :template_format
9
- ]
14
+ MULTIPLES = %w{file_path asset_host}.freeze
15
+ SINGLES = %w{asset_dir templates_path template_format cache_method
16
+ template_preloader template_partial}.freeze
17
+ BLOCKS = %w{after_compile}.freeze
18
+
19
+ OPTIONS = begin
20
+ arr = MULTIPLES.collect do |m|
21
+ "#{m}s"
22
+ end
23
+ arr += SINGLES
24
+ arr += BLOCKS
25
+ end.freeze
26
+
10
27
  attr_reader *OPTIONS
28
+ attr_accessor :compiling, :sha
29
+
30
+ DEFAULTS = {
31
+ :asset_dir => 'assets',
32
+ :asset_hosts => [],
33
+ :file_paths => %w{public},
34
+ :templates_path => 'app/templates',
35
+ :template_format => 'AT.t[{*path*}] = {*template*};'
36
+ }.freeze
11
37
 
12
38
  def initialize args = {}
39
+ args = DEFAULTS.merge(args||{})
40
+
13
41
  args.each do |k,v|
14
42
  instance_variable_set "@#{k}", v.dup.freeze
15
43
  end
16
44
  end
17
45
 
46
+ def find_file_from_relative_path path
47
+ a = Assette.config.file_paths.find do |pp|
48
+ File.exist?(File.join(pp,path))
49
+ end
50
+ File.join(a,path)
51
+ end
52
+
53
+ def find_target_from_relative_path path
54
+ Assette.config.file_paths.find do |p|
55
+ Assette::Reader.possible_targets(File.join(p,path)).find do |pp|
56
+ File.exist?(pp)
57
+ end
58
+ end
59
+ end
60
+
61
+ def build_target
62
+ @asset_dir
63
+ end
64
+
65
+ def asset_hosts?
66
+ !@asset_hosts.empty?
67
+ end
68
+
69
+ def compiling?
70
+ !!compiling
71
+ end
72
+
73
+ def asset_host i
74
+ return '' unless asset_hosts?
75
+ @asset_hosts[i % @asset_hosts.size]
76
+ end
77
+
78
+ def compiled_path str
79
+ str = str.dup
80
+ if sha
81
+ case cache_method.to_s
82
+ when 'path'
83
+ str = File.join(sha,str)
84
+ when 'param'
85
+ str << (str.include?('?') ? '&' : '?')
86
+
87
+ str << 'v=' << sha
88
+ else
89
+ warn('No cache compile method set (param or path)')
90
+ end
91
+ end
92
+
93
+ str
94
+ end
95
+
96
+ class Builder
97
+
98
+ attr_reader :__hsh__
99
+ def initialize(file)
100
+ @__str = File.open(file).read
101
+
102
+ @__hsh__ = {}
103
+ end
104
+
105
+ def __run__
106
+ instance_eval @__str
107
+ end
108
+
109
+ MULTIPLES.each do |m|
110
+ eval <<-RB
111
+ def #{m} *args
112
+ @__hsh__[:#{m}s] ||= []
113
+ @__hsh__[:#{m}s] |= args.flatten
114
+ end
115
+
116
+ def #{m}s *args
117
+ @__hsh__[:#{m}s] = args.flatten
118
+ end
119
+ RB
120
+ end
121
+
122
+ SINGLES.each do |s|
123
+ eval <<-RB
124
+ def #{s} v
125
+ @__hsh__[:#{s}] = v
126
+ end
127
+ RB
128
+ end
129
+
130
+ BLOCKS.each do |b|
131
+ eval <<-RB
132
+ def #{b} val=nil, &blk
133
+ if val
134
+ @__hsh__[:#{b}] ||= {}
135
+ @__hsh__[:#{b}][val] = blk
136
+ else
137
+ @__hsh__[:#{b}] = blk
138
+ end
139
+ end
140
+ RB
141
+ end
142
+
143
+ def to_hash
144
+ @__hsh__
145
+ end
146
+
147
+ end
148
+
18
149
  class << self
19
150
 
20
151
  def load p
21
- new YAML.load_file(p)
152
+ b = Builder.new(p)
153
+ b.__run__
154
+
155
+ new b.to_hash
22
156
  end
23
157
 
24
158
  end
@@ -26,10 +160,3 @@ module Assette
26
160
  end
27
161
 
28
162
  end
29
-
30
- f = %w{assets.yml config/assets.yml}
31
- f << File.join( File.dirname(__FILE__), '..', '..', 'examples', 'defaults.yml' )
32
-
33
- p = f.find { |path| File.exist?(path) }
34
-
35
- Assette.config = Assette::Config.load(p)
data/lib/assette/file.rb CHANGED
@@ -6,7 +6,7 @@ class Assette::File < ::File
6
6
  end
7
7
 
8
8
  def extension
9
- m = path.match(/\.(\w+)$/)
9
+ m = filename.match(/\.(\w+)$/)
10
10
  m[1] if m
11
11
  end
12
12
 
@@ -67,8 +67,12 @@ class Assette::File < ::File
67
67
  File.dirname(path)
68
68
  end
69
69
 
70
+ def target_path
71
+ File.join(dirname,filename.gsub(reader_class.extension,target_class.extension))
72
+ end
73
+
70
74
  def filename
71
- path.gsub(dirname,'').gsub(/^\//,'')
75
+ File.basename(path)
72
76
  end
73
77
 
74
78
  def puts *args
@@ -0,0 +1,65 @@
1
+ module Assette
2
+ module PostProcessor
3
+ extend self
4
+ POST_PROCESSORS = Hash.new {|h,k| h[k] = []}
5
+
6
+ def s
7
+ POST_PROCESSORS
8
+ end
9
+
10
+ class Base
11
+
12
+ def initialize(str, args={})
13
+ @str = str; @args = args
14
+ end
15
+
16
+ def should_process?
17
+ true
18
+ end
19
+
20
+ def processor
21
+ raise Exception, "You must implement the processor method for #{self.class.inspect} (you can use the @str)"
22
+ end
23
+
24
+ def process
25
+ return @str unless should_process?
26
+
27
+ processor
28
+ end
29
+
30
+ class << self
31
+ def inherited subclass
32
+ return if subclass == Assette::PostProcessor::Base || subclass.inspect =~ /#<Class/
33
+
34
+ if outputs
35
+ Assette::PostProcessor::POST_PROCESSORS[outputs] |= [subclass]
36
+ end
37
+ end
38
+
39
+ def outputs
40
+
41
+ end
42
+
43
+ def set_outputs val
44
+ raise ArgumentError, 'must set outputs to a symbol' unless val.is_a?(Symbol)
45
+
46
+ instance_eval <<-RUBY
47
+ def outputs
48
+ #{val.inspect}
49
+ end
50
+ RUBY
51
+ val
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+
59
+
60
+ def self.PostProcessor(type)
61
+ c = Class.new(PostProcessor::Base)
62
+ c.set_outputs(type)
63
+ c
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ class Assette::PostProcessor::CacheBuster < Assette::PostProcessor(:css)
2
+ URL_MATCHER = /url\((?:["'])?(?!http)(?!\/\/)([\w\/\.\-\s\?=]+)(?:["'])?\)/i
3
+
4
+ def should_process?
5
+ Assette.config.compiling?
6
+ end
7
+
8
+ def processor
9
+ @@i ||= -1
10
+
11
+ @str.gsub(URL_MATCHER) do |s|
12
+ url = File.join(Assette.config.asset_host(@@i+=1),Assette.config.compiled_path($1))
13
+
14
+ %Q{url("#{url}")}
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,4 @@
1
+ fname = File.join(File.dirname(__FILE__),'post_processors','*.rb')
2
+ Dir[fname].each do |f|
3
+ require f
4
+ end
@@ -1,4 +1,4 @@
1
- require 'open3'
1
+ require 'coffee-script'
2
2
 
3
3
  class Assette::Reader::Coffee < Assette::Reader(:js)
4
4
 
@@ -6,26 +6,26 @@ class Assette::Reader::Coffee < Assette::Reader(:js)
6
6
  self.class.compile_str text
7
7
  end
8
8
 
9
- class << self
10
-
11
- def compile_str text
12
- out = nil
13
-
14
- Open3.popen3('coffee -sc') do |stdin, stdout, stderr|
15
- stdin.puts(text)
16
- stdin.close_write
17
- out = stdout.read
18
- end
19
-
20
- out
21
- end
22
-
23
- def comment_str
24
- '#'
25
- end
26
-
27
- def comment_str_end
28
- ''
29
- end
9
+ def self.comment_str
10
+ '# %s'
30
11
  end
31
- end
12
+
13
+ def self.compile_str text
14
+ CoffeeScript.compile text
15
+ end
16
+
17
+ end
18
+
19
+ # require 'open3'
20
+ # Compile coffeescript using coffee binary.
21
+ # def compile_str_external text
22
+ # out = nil
23
+ #
24
+ # Open3.popen3('coffee -sc') do |stdin, stdout, stderr|
25
+ # stdin.puts(text)
26
+ # stdin.close_write
27
+ # out = stdout.read
28
+ # end
29
+ #
30
+ # out
31
+ # end
@@ -7,7 +7,7 @@ class Assette::Reader::Css < Assette::Reader(:css)
7
7
  class << self
8
8
 
9
9
  def comment_str
10
- @comment_str ||= '/* %s */'
10
+ '/* %s */'
11
11
  end
12
12
 
13
13
  end
@@ -7,7 +7,7 @@ class Assette::Reader::Js < Assette::Reader(:js)
7
7
  class << self
8
8
 
9
9
  def comment_str
10
- @comment_str ||= "// %s"
10
+ '// %s'
11
11
  end
12
12
  end
13
13
  end
@@ -3,8 +3,7 @@ require 'sass'
3
3
  class Assette::Reader::Sass < Assette::Reader(:css)
4
4
 
5
5
  def compile
6
- engine = ::Sass::Engine.new(text,options)
7
- engine.to_css
6
+ ::Sass::Engine.new(text,options).to_css
8
7
  end
9
8
 
10
9
  private
@@ -0,0 +1,2 @@
1
+
2
+ run Assette::Server
@@ -2,9 +2,11 @@ module Assette
2
2
  # Methods for finding files are ugly, should fix someday
3
3
  class Server
4
4
  attr_reader :path
5
- def initialize(path)
6
- @path = path
7
- Assette.config.asset_path
5
+ def initialize(env)
6
+ @env = env
7
+ @path = env["PATH_INFO"]
8
+
9
+ # Assette.config.asset_path
8
10
  end
9
11
 
10
12
  def find_compiled_file
@@ -44,8 +46,8 @@ module Assette
44
46
  Assette.config.file_paths.each do |p|
45
47
  new_path = File.join(p,path)
46
48
  if File.exist?(new_path)
47
- f = File.open(new_path)
48
- # add in grabbing index.html files if no extension provided
49
+ new_path = File.join(Dir.pwd,new_path)
50
+ f = Rack::File.new(p).call(@env)
49
51
  end
50
52
 
51
53
  break if f
@@ -55,25 +57,25 @@ module Assette
55
57
  end
56
58
 
57
59
  def rack_resp
58
-
59
60
  if has_registered_reader? && (f = find_compiled_file)
60
61
  [200,{"Content-Type" => f.content_type},f]
61
- elsif (f = find_file) && path_mime_type
62
- puts content_type
63
- [200,{"Content-Type" => content_type},f]
62
+ elsif f = find_file
63
+ f
64
64
  else
65
- [404,{"Content-Type" => "text/plain"},["File Not Found"]]
65
+ possible_paths = Assette.config.file_paths.collect do |p|
66
+ File.join(Dir.pwd,p,path)
67
+ end
68
+
69
+ [404,{"Content-Type" => "text/plain"},["File Not Found\n#{possible_paths.join("\n")}"]]
66
70
  end
67
71
  end
68
72
 
69
73
  class << self
70
74
 
71
75
  def call(env)
72
- s = new(env["PATH_INFO"])
76
+ s = new(env)
73
77
 
74
78
  s.rack_resp
75
- # rescue => e
76
- # [500,{"Content-Type" => "text/plain"},[e.inspect]]
77
79
  end
78
80
 
79
81
  end
@@ -2,7 +2,11 @@ module Assette
2
2
 
3
3
  class Template < Assette::File
4
4
  def compile
5
- format = Assette.config.template_format.dup
5
+ if Assette.config.template_partial && filename =~ /^_/
6
+ format = Assette.config.template_partial.dup
7
+ else
8
+ format = Assette.config.template_format.dup
9
+ end
6
10
 
7
11
  format.gsub!('{*path*}',local_path.to_json)
8
12
  format.gsub!('{*template*}',stringify_body)
@@ -14,7 +18,9 @@ module Assette
14
18
 
15
19
  def local_path
16
20
  lp = path.gsub(Assette.config.templates_path,'')
17
- lp.gsub!(/((\.html)?\.\w+)$/,'')
21
+ lp.gsub!(/((\.html)?\.\w+)$/,'').gsub!(/^\//,'')
22
+ lp.gsub!(/\/_/,'/')
23
+ lp
18
24
  end
19
25
  end
20
26
 
@@ -36,6 +36,13 @@ module Assette
36
36
  end
37
37
  end
38
38
 
39
+ def insert_preloader
40
+ p = Assette.config.template_preloader
41
+ if p && pp = Assette.config.find_file_from_relative_path(p)
42
+ Assette::File.open(pp).all_code
43
+ end
44
+ end
45
+
39
46
  def compile
40
47
  coffee = Array.new
41
48
 
@@ -49,7 +56,7 @@ module Assette
49
56
  str = "window[#{var.to_json}] ||= {}"
50
57
  else
51
58
  str = used.join('.')
52
- str << " = (#{str} || {})"
59
+ str << " ||= {}"
53
60
  end
54
61
 
55
62
  coffee << str
@@ -59,7 +66,9 @@ module Assette
59
66
  coffee << template.compile
60
67
  end
61
68
 
62
- Assette::Reader::Coffee.compile_str(coffee.join("\n"))
69
+ t = Assette::Reader::Coffee.compile_str(coffee.join("\n"))
70
+ pre = insert_preloader
71
+ pre ? [pre,t].join("\n") : t
63
72
  end
64
73
 
65
74
  def storage_variable
data/lib/assette.rb CHANGED
@@ -17,8 +17,10 @@ MIME::Types.add coffee_type, sass_type
17
17
  module Assette
18
18
  extend self
19
19
  CONFIG_WRAPPER = 'ASSETTE CONFIG'
20
+ VERSION = File.open(File.expand_path(File.dirname(__FILE__)+'/../VERSION')).read
20
21
  end
21
22
 
22
- %w{config reader readers compiled_file file template_set template}.each do |f|
23
+ %w{config reader readers post_processor post_processors
24
+ compiled_file file template_set template}.each do |f|
23
25
  require File.dirname(__FILE__)+'/assette/'+f
24
26
  end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Assette::Config do
4
+
5
+ before do
6
+ Assette.instance_variable_set :@config, nil
7
+ end
8
+
9
+ it "should recieve correct file" do
10
+ Assette::Config.should_receive(:load).once.with('config/assets.rb')
11
+
12
+ Dir.chdir(File.dirname(__FILE__) + '/../examples') { Assette.config }
13
+ end
14
+
15
+ describe "default file" do
16
+ before(:all) do
17
+ @dir_before = Dir.pwd
18
+ Dir.chdir(File.dirname(__FILE__) + '/../examples')
19
+ end
20
+
21
+ after(:all) do
22
+ Dir.chdir(@dir_before)
23
+ end
24
+
25
+ subject {Assette.config}
26
+
27
+ it "should set file paths" do
28
+ subject.file_paths.should == %w{public foo}
29
+ end
30
+
31
+ it "should set asset hosts" do
32
+ subject.asset_hosts.should == %w{http://cdn1.gilt.com http://cdn2.gilt.com}
33
+ end
34
+
35
+ it "should set assets dir" do
36
+ subject.asset_dir.should == 'myassets'
37
+ end
38
+
39
+ it "should set templates path" do
40
+ subject.templates_path.should == 'myapp/templates'
41
+ end
42
+
43
+ it "should set templates format" do
44
+ subject.template_format.should == 'GC.foo.t[{*path*}] = Handlebars.compile({*template*});'
45
+ end
46
+
47
+ it "should set after compile" do
48
+ subject.after_compile.call.should == 3
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,63 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Assette::PostProcessor do
4
+
5
+ before(:all) do
6
+ @dir_before = Dir.pwd
7
+ Dir.chdir(File.dirname(__FILE__) + '/../examples')
8
+ end
9
+
10
+ after(:all) do
11
+ Dir.chdir(@dir_before)
12
+ end
13
+
14
+ describe Assette::PostProcessor::CacheBuster do
15
+
16
+ before(:all) do
17
+ Assette.config.compiling = true
18
+ Assette.config.sha = "1234567"
19
+ end
20
+
21
+ after(:all) do
22
+ Assette.config.compiling = nil
23
+ end
24
+
25
+ subject do
26
+ Assette::File.open('public/stylesheets/two2.scss')
27
+ end
28
+
29
+ describe "with asset cdn paths" do
30
+ before(:all) do
31
+ Assette.config.instance_variable_set :@asset_hosts, %w{http://cdn1.gilt.com http://cdn2.gilt.com}
32
+ end
33
+
34
+ describe "path changing" do
35
+ before(:all) do
36
+ Assette.config.instance_variable_set :@cache_method, 'path'
37
+ end
38
+
39
+ it "does something" do
40
+ subject.all_code.should include('gilt.com/1234567/images/mystuff/goat.se?test=1"')
41
+ subject.all_code.should include('gilt.com/1234567/images/mystuff/goa-t.se"')
42
+ subject.all_code.should include('gilt.com/1234567/images/mystuff/go_at.se"')
43
+ end
44
+ end
45
+
46
+
47
+ describe "param changing" do
48
+ before(:all) do
49
+ Assette.config.instance_variable_set :@cache_method, 'param'
50
+ end
51
+
52
+ it "does something" do
53
+ subject.all_code.should include('gilt.com/images/mystuff/goat.se?test=1&v=1234567"')
54
+ subject.all_code.should include('gilt.com/images/mystuff/goa-t.se?v=1234567"')
55
+ subject.all_code.should include('gilt.com/images/mystuff/go_at.se?v=1234567"')
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'assette/server'
3
+
4
+ describe Assette::Server do
5
+ before(:all) do
6
+ @dir_before = Dir.pwd
7
+ Dir.chdir(File.dirname(__FILE__) + '/../examples')
8
+ end
9
+
10
+ after(:all) do
11
+ Dir.chdir(@dir_before)
12
+ end
13
+
14
+ it "should be implemented"
15
+ end
@@ -14,18 +14,22 @@ describe "Templates" do
14
14
  describe Assette::TemplateSet do
15
15
 
16
16
  it "should get correct list of files" do
17
- f = Assette::Template.open("app/templates/foo/index.html.mustache")
17
+ f = Assette::Template.open("myapp/templates/foo/index.html.mustache")
18
18
 
19
19
  a = Assette::TemplateSet.new(:all)
20
20
  a.templates.should include f
21
-
21
+
22
+ p = Assette::Template.open("myapp/templates/foo/_partial.html.mustache")
22
23
  a = Assette::TemplateSet.new(:foo)
23
- a.templates.should == [f]
24
+ a.templates.should == [p,f]
24
25
  end
25
26
 
26
27
  it "should compile" do
27
28
  a = Assette::TemplateSet.new(:all)
28
- a.compile.should include('{{foo}}')
29
+ c = a.compile
30
+ c.should include('{{foo}}')
31
+ c.should include('Handlebars.registerPartial("foo/partial",')
32
+ c.should include('Handlebars.compile("<div>')
29
33
 
30
34
  a = Assette::TemplateSet.new(:foo)
31
35
  a.compile.should include('{{foo}}')
@@ -36,7 +40,7 @@ describe "Templates" do
36
40
  describe Assette::Template do
37
41
 
38
42
  subject do
39
- Assette::Template.open("app/templates/foo/index.html.mustache")
43
+ Assette::Template.open("myapp/templates/foo/index.html.mustache")
40
44
  end
41
45
 
42
46
  end