asset_packager 0.1.0

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.
@@ -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