asset_packager 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,212 @@
1
+ module Synthesis
2
+ class AssetPackage
3
+
4
+ # class variables
5
+ @@asset_packages_yml = $asset_packages_yml ||
6
+ (File.exists?("#{RAILS_ROOT}/config/asset_packages.yml") ? YAML.load_file("#{RAILS_ROOT}/config/asset_packages.yml") : nil)
7
+
8
+ # singleton methods
9
+ class << self
10
+
11
+ def merge_environments=(environments)
12
+ @@merge_environments = environments
13
+ end
14
+
15
+ def merge_environments
16
+ @@merge_environments ||= ["production"]
17
+ end
18
+
19
+ def parse_path(path)
20
+ /^(?:(.*)\/)?([^\/]+)$/.match(path).to_a
21
+ end
22
+
23
+ def find_by_type(asset_type)
24
+ @@asset_packages_yml[asset_type].map { |p| self.new(asset_type, p) }
25
+ end
26
+
27
+ def find_by_target(asset_type, target)
28
+ package_hash = @@asset_packages_yml[asset_type].find {|p| p.keys.first == target }
29
+ package_hash ? self.new(asset_type, package_hash) : nil
30
+ end
31
+
32
+ def find_by_source(asset_type, source)
33
+ path_parts = parse_path(source)
34
+ package_hash = @@asset_packages_yml[asset_type].find do |p|
35
+ key = p.keys.first
36
+ p[key].include?(path_parts[2]) && (parse_path(key)[1] == path_parts[1])
37
+ end
38
+ package_hash ? self.new(asset_type, package_hash) : nil
39
+ end
40
+
41
+ def targets_from_sources(asset_type, sources)
42
+ package_names = Array.new
43
+ sources.each do |source|
44
+ package = find_by_target(asset_type, source) || find_by_source(asset_type, source)
45
+ package_names << (package ? package.current_file : source)
46
+ end
47
+ package_names.uniq
48
+ end
49
+
50
+ def sources_from_targets(asset_type, targets)
51
+ source_names = Array.new
52
+ targets.each do |target|
53
+ package = find_by_target(asset_type, target)
54
+ source_names += (package ? package.sources.collect do |src|
55
+ package.target_dir.gsub(/^(.+)$/, '\1/') + src
56
+ end : target.to_a)
57
+ end
58
+ source_names.uniq
59
+ end
60
+
61
+ def build_all
62
+ @@asset_packages_yml.keys.each do |asset_type|
63
+ @@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).build }
64
+ end
65
+ end
66
+
67
+ def delete_all
68
+ @@asset_packages_yml.keys.each do |asset_type|
69
+ @@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).delete_previous_build }
70
+ end
71
+ end
72
+
73
+ def create_yml
74
+ unless File.exists?("#{RAILS_ROOT}/config/asset_packages.yml")
75
+ asset_yml = Hash.new
76
+
77
+ asset_yml['javascripts'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/javascripts", "js")}]
78
+ asset_yml['stylesheets'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/stylesheets", "css")}]
79
+
80
+ File.open("#{RAILS_ROOT}/config/asset_packages.yml", "w") do |out|
81
+ YAML.dump(asset_yml, out)
82
+ end
83
+
84
+ log "config/asset_packages.yml example file created!"
85
+ log "Please reorder files under 'base' so dependencies are loaded in correct order."
86
+ else
87
+ log "config/asset_packages.yml already exists. Aborting task..."
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ # instance methods
94
+ attr_accessor :asset_type, :target, :target_dir, :sources
95
+
96
+ def initialize(asset_type, package_hash)
97
+ target_parts = self.class.parse_path(package_hash.keys.first)
98
+ @target_dir = target_parts[1].to_s
99
+ @target = target_parts[2].to_s
100
+ @sources = package_hash[package_hash.keys.first]
101
+ @asset_type = asset_type
102
+ @asset_path = ($asset_base_path ? "#{$asset_base_path}/" : "#{RAILS_ROOT}/public/") +
103
+ "#{@asset_type}#{@target_dir.gsub(/^(.+)$/, '/\1')}"
104
+ @extension = get_extension
105
+ @file_name = "#{@target}_packaged.#{@extension}"
106
+ @full_path = File.join(@asset_path, @file_name)
107
+ end
108
+
109
+ def package_exists?
110
+ File.exists?(@full_path)
111
+ end
112
+
113
+ def current_file
114
+ build unless package_exists?
115
+
116
+ path = @target_dir.gsub(/^(.+)$/, '\1/')
117
+ "#{path}#{@target}_packaged"
118
+ end
119
+
120
+ def build
121
+ delete_previous_build
122
+ create_new_build
123
+ end
124
+
125
+ def delete_previous_build
126
+ File.delete(@full_path) if File.exists?(@full_path)
127
+ end
128
+
129
+ private
130
+ def create_new_build
131
+ new_build_path = "#{@asset_path}/#{@target}_packaged.#{@extension}"
132
+ if File.exists?(new_build_path)
133
+ log "Latest version already exists: #{new_build_path}"
134
+ else
135
+ File.open(new_build_path, "w") {|f| f.write(compressed_file) }
136
+ log "Created #{new_build_path}"
137
+ end
138
+ end
139
+
140
+ def merged_file
141
+ merged_file = ""
142
+ @sources.each {|s|
143
+ File.open("#{@asset_path}/#{s}.#{@extension}", "r") { |f|
144
+ merged_file += f.read + "\n"
145
+ }
146
+ }
147
+ merged_file
148
+ end
149
+
150
+ def compressed_file
151
+ case @asset_type
152
+ when "javascripts" then compress_js(merged_file)
153
+ when "stylesheets" then compress_css(merged_file)
154
+ end
155
+ end
156
+
157
+ def compress_js(source)
158
+ jsmin_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/lib"
159
+ tmp_path = "#{RAILS_ROOT}/tmp/#{@target}_packaged"
160
+
161
+ # write out to a temp file
162
+ File.open("#{tmp_path}_uncompressed.js", "w") {|f| f.write(source) }
163
+
164
+ # compress file with JSMin library
165
+ `ruby #{jsmin_path}/jsmin.rb <#{tmp_path}_uncompressed.js >#{tmp_path}_compressed.js \n`
166
+
167
+ # read it back in and trim it
168
+ result = ""
169
+ File.open("#{tmp_path}_compressed.js", "r") { |f| result += f.read.strip }
170
+
171
+ # delete temp files if they exist
172
+ File.delete("#{tmp_path}_uncompressed.js") if File.exists?("#{tmp_path}_uncompressed.js")
173
+ File.delete("#{tmp_path}_compressed.js") if File.exists?("#{tmp_path}_compressed.js")
174
+
175
+ result
176
+ end
177
+
178
+ def compress_css(source)
179
+ source.gsub!(/\s+/, " ") # collapse space
180
+ source.gsub!(/\/\*(.*?)\*\//, "") # remove comments - caution, might want to remove this if using css hacks
181
+ source.gsub!(/\} /, "}\n") # add line breaks
182
+ source.gsub!(/\n$/, "") # remove last break
183
+ source.gsub!(/ \{ /, " {") # trim inside brackets
184
+ source.gsub!(/; \}/, "}") # trim inside brackets
185
+ source
186
+ end
187
+
188
+ def get_extension
189
+ case @asset_type
190
+ when "javascripts" then "js"
191
+ when "stylesheets" then "css"
192
+ end
193
+ end
194
+
195
+ def log(message)
196
+ self.class.log(message)
197
+ end
198
+
199
+ def self.log(message)
200
+ puts message
201
+ end
202
+
203
+ def self.build_file_list(path, extension)
204
+ re = Regexp.new(".#{extension}\\z")
205
+ file_list = Dir.new(path).entries.delete_if { |x| ! (x =~ re) }.map {|x| x.chomp(".#{extension}")}
206
+ # reverse javascript entries so prototype comes first on a base rails app
207
+ file_list.reverse! if extension == "js"
208
+ file_list
209
+ end
210
+
211
+ end
212
+ end
@@ -0,0 +1,39 @@
1
+ module Synthesis
2
+ module AssetPackageHelper
3
+
4
+ def should_merge?
5
+ AssetPackage.merge_environments.include?(RAILS_ENV)
6
+ end
7
+
8
+ def javascript_include_merged(*sources)
9
+ options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
10
+
11
+ if sources.include?(:defaults)
12
+ sources = sources[0..(sources.index(:defaults))] +
13
+ ['prototype', 'effects', 'dragdrop', 'controls'] +
14
+ (File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") ? ['application'] : []) +
15
+ sources[(sources.index(:defaults) + 1)..sources.length]
16
+ sources.delete(:defaults)
17
+ end
18
+
19
+ sources.collect!{|s| s.to_s}
20
+ sources = (should_merge? ?
21
+ AssetPackage.targets_from_sources("javascripts", sources) :
22
+ AssetPackage.sources_from_targets("javascripts", sources))
23
+
24
+ sources.collect {|source| javascript_include_tag(source, options) }.join("\n")
25
+ end
26
+
27
+ def stylesheet_link_merged(*sources)
28
+ options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
29
+
30
+ sources.collect!{|s| s.to_s}
31
+ sources = (should_merge? ?
32
+ AssetPackage.targets_from_sources("stylesheets", sources) :
33
+ AssetPackage.sources_from_targets("stylesheets", sources))
34
+
35
+ sources.collect { |source| stylesheet_link_tag(source, options) }.join("\n")
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ require 'yaml'
2
+ require File.dirname(__FILE__) + '/../lib/synthesis/asset_package'
3
+
4
+ namespace :asset do
5
+ namespace :packager do
6
+
7
+ desc "Merge and compress assets"
8
+ task :build_all do
9
+ Synthesis::AssetPackage.build_all
10
+ end
11
+
12
+ desc "Delete all asset builds"
13
+ task :delete_all do
14
+ Synthesis::AssetPackage.delete_all
15
+ end
16
+
17
+ desc "Generate asset_packages.yml from existing assets"
18
+ task :create_yml do
19
+ Synthesis::AssetPackage.create_yml
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,100 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ ENV['RAILS_ENV'] = "development"
4
+ require File.dirname(__FILE__) + '/../../../../config/environment'
5
+ require 'test/unit'
6
+ require 'rubygems'
7
+ require 'mocha'
8
+
9
+ require 'action_controller/test_process'
10
+
11
+ ActionController::Base.logger = nil
12
+ ActionController::Routing::Routes.reload rescue nil
13
+
14
+ $asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
15
+ $asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
16
+
17
+ class AssetPackageHelperDevelopmentTest < Test::Unit::TestCase
18
+ include ActionView::Helpers::TagHelper
19
+ include ActionView::Helpers::AssetTagHelper
20
+ include Synthesis::AssetPackageHelper
21
+
22
+ def setup
23
+ Synthesis::AssetPackage.any_instance.stubs(:log)
24
+
25
+ @controller = Class.new do
26
+ def request
27
+ @request ||= ActionController::TestRequest.new
28
+ end
29
+ end.new
30
+ end
31
+
32
+ def build_js_expected_string(*sources)
33
+ sources.map {|s| javascript_include_tag(s) }.join("\n")
34
+ end
35
+
36
+ def build_css_expected_string(*sources)
37
+ sources.map {|s| stylesheet_link_tag(s) }.join("\n")
38
+ end
39
+
40
+ def test_js_basic
41
+ assert_dom_equal build_js_expected_string("prototype"),
42
+ javascript_include_merged("prototype")
43
+ end
44
+
45
+ def test_js_multiple_packages
46
+ assert_dom_equal build_js_expected_string("prototype", "foo"),
47
+ javascript_include_merged("prototype", "foo")
48
+ end
49
+
50
+ def test_js_unpackaged_file
51
+ assert_dom_equal build_js_expected_string("prototype", "foo", "not_part_of_a_package"),
52
+ javascript_include_merged("prototype", "foo", "not_part_of_a_package")
53
+ end
54
+
55
+ def test_js_multiple_from_same_package
56
+ assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "not_part_of_a_package", "foo"),
57
+ javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo")
58
+ end
59
+
60
+ def test_js_by_package_name
61
+ assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop"),
62
+ javascript_include_merged(:base)
63
+ end
64
+
65
+ def test_js_multiple_package_names
66
+ assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop", "foo", "bar", "application"),
67
+ javascript_include_merged(:base, :secondary)
68
+ end
69
+
70
+ def test_css_basic
71
+ assert_dom_equal build_css_expected_string("screen"),
72
+ stylesheet_link_merged("screen")
73
+ end
74
+
75
+ def test_css_multiple_packages
76
+ assert_dom_equal build_css_expected_string("screen", "foo", "subdir/bar"),
77
+ stylesheet_link_merged("screen", "foo", "subdir/bar")
78
+ end
79
+
80
+ def test_css_unpackaged_file
81
+ assert_dom_equal build_css_expected_string("screen", "foo", "not_part_of_a_package", "subdir/bar"),
82
+ stylesheet_link_merged("screen", "foo", "not_part_of_a_package", "subdir/bar")
83
+ end
84
+
85
+ def test_css_multiple_from_same_package
86
+ assert_dom_equal build_css_expected_string("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar"),
87
+ stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar")
88
+ end
89
+
90
+ def test_css_by_package_name
91
+ assert_dom_equal build_css_expected_string("screen", "header"),
92
+ stylesheet_link_merged(:base)
93
+ end
94
+
95
+ def test_css_multiple_package_names
96
+ assert_dom_equal build_css_expected_string("screen", "header", "foo", "bar", "subdir/foo", "subdir/bar"),
97
+ stylesheet_link_merged(:base, :secondary, "subdir/styles")
98
+ end
99
+
100
+ end
@@ -0,0 +1,140 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require File.dirname(__FILE__) + '/../../../../config/environment'
4
+ require 'test/unit'
5
+ require 'rubygems'
6
+ require 'mocha'
7
+
8
+ require 'action_controller/test_process'
9
+
10
+ ActionController::Base.logger = nil
11
+ ActionController::Routing::Routes.reload rescue nil
12
+
13
+ $asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
14
+ $asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
15
+
16
+ class AssetPackageHelperProductionTest < Test::Unit::TestCase
17
+ include ActionView::Helpers::TagHelper
18
+ include ActionView::Helpers::AssetTagHelper
19
+ include Synthesis::AssetPackageHelper
20
+
21
+ cattr_accessor :packages_built
22
+
23
+ def setup
24
+ Synthesis::AssetPackage.any_instance.stubs(:log)
25
+ self.stubs(:should_merge?).returns(true)
26
+
27
+ @controller = Class.new do
28
+ def request
29
+ @request ||= ActionController::TestRequest.new
30
+ end
31
+ end.new
32
+
33
+ build_packages_once
34
+ end
35
+
36
+ def build_packages_once
37
+ unless @@packages_built
38
+ Synthesis::AssetPackage.build_all
39
+ @@packages_built = true
40
+ end
41
+ end
42
+
43
+ def build_js_expected_string(*sources)
44
+ sources.map {|s| javascript_include_tag(s) }.join("\n")
45
+ end
46
+
47
+ def build_css_expected_string(*sources)
48
+ sources.map {|s| stylesheet_link_tag(s) }.join("\n")
49
+ end
50
+
51
+ def test_js_basic
52
+ current_file = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
53
+ assert_dom_equal build_js_expected_string(current_file),
54
+ javascript_include_merged("prototype")
55
+ end
56
+
57
+ def test_js_multiple_packages
58
+ current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
59
+ current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
60
+
61
+ assert_dom_equal build_js_expected_string(current_file1, current_file2),
62
+ javascript_include_merged("prototype", "foo")
63
+ end
64
+
65
+ def test_js_unpackaged_file
66
+ current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
67
+ current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
68
+
69
+ assert_dom_equal build_js_expected_string(current_file1, current_file2, "not_part_of_a_package"),
70
+ javascript_include_merged("prototype", "foo", "not_part_of_a_package")
71
+ end
72
+
73
+ def test_js_multiple_from_same_package
74
+ current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
75
+ current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
76
+
77
+ assert_dom_equal build_js_expected_string(current_file1, "not_part_of_a_package", current_file2),
78
+ javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo")
79
+ end
80
+
81
+ def test_js_by_package_name
82
+ package_name = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file
83
+ assert_dom_equal build_js_expected_string(package_name),
84
+ javascript_include_merged(:base)
85
+ end
86
+
87
+ def test_js_multiple_package_names
88
+ package_name1 = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file
89
+ package_name2 = Synthesis::AssetPackage.find_by_target("javascripts", "secondary").current_file
90
+ assert_dom_equal build_js_expected_string(package_name1, package_name2),
91
+ javascript_include_merged(:base, :secondary)
92
+ end
93
+
94
+ def test_css_basic
95
+ current_file = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
96
+ assert_dom_equal build_css_expected_string(current_file),
97
+ stylesheet_link_merged("screen")
98
+ end
99
+
100
+ def test_css_multiple_packages
101
+ current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
102
+ current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
103
+ current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file
104
+
105
+ assert_dom_equal build_css_expected_string(current_file1, current_file2, current_file3),
106
+ stylesheet_link_merged("screen", "foo", "subdir/bar")
107
+ end
108
+
109
+ def test_css_unpackaged_file
110
+ current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
111
+ current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
112
+
113
+ assert_dom_equal build_css_expected_string(current_file1, current_file2, "not_part_of_a_package"),
114
+ stylesheet_link_merged("screen", "foo", "not_part_of_a_package")
115
+ end
116
+
117
+ def test_css_multiple_from_same_package
118
+ current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
119
+ current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
120
+ current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file
121
+
122
+ assert_dom_equal build_css_expected_string(current_file1, "not_part_of_a_package", current_file2, current_file3),
123
+ stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar")
124
+ end
125
+
126
+ def test_css_by_package_name
127
+ package_name = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file
128
+ assert_dom_equal build_css_expected_string(package_name),
129
+ stylesheet_link_merged(:base)
130
+ end
131
+
132
+ def test_css_multiple_package_names
133
+ package_name1 = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file
134
+ package_name2 = Synthesis::AssetPackage.find_by_target("stylesheets", "secondary").current_file
135
+ package_name3 = Synthesis::AssetPackage.find_by_target("stylesheets", "subdir/styles").current_file
136
+ assert_dom_equal build_css_expected_string(package_name1, package_name2, package_name3),
137
+ stylesheet_link_merged(:base, :secondary, "subdir/styles")
138
+ end
139
+
140
+ end