amiel-sprockets 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/Rakefile +26 -0
  2. data/ext/nph-sprockets.cgi +127 -0
  3. data/lib/sprockets.rb +42 -0
  4. data/lib/sprockets/concatenation.rb +36 -0
  5. data/lib/sprockets/environment.rb +52 -0
  6. data/lib/sprockets/error.rb +5 -0
  7. data/lib/sprockets/pathname.rb +37 -0
  8. data/lib/sprockets/preprocessor.rb +92 -0
  9. data/lib/sprockets/secretary.rb +110 -0
  10. data/lib/sprockets/source_file.rb +54 -0
  11. data/lib/sprockets/source_line.rb +86 -0
  12. data/lib/sprockets/version.rb +9 -0
  13. data/test/fixtures/assets/images/script_with_assets/one.png +1 -0
  14. data/test/fixtures/assets/images/script_with_assets/two.png +1 -0
  15. data/test/fixtures/assets/stylesheets/script_with_assets.css +1 -0
  16. data/test/fixtures/constants.yml +1 -0
  17. data/test/fixtures/double_slash_comments_that_are_not_requires_should_be_ignored_when_strip_comments_is_false.js +8 -0
  18. data/test/fixtures/double_slash_comments_that_are_not_requires_should_be_removed_by_default.js +2 -0
  19. data/test/fixtures/multiline_comments_should_be_removed_by_default.js +4 -0
  20. data/test/fixtures/requiring_a_file_after_it_has_already_been_required_should_do_nothing.js +5 -0
  21. data/test/fixtures/requiring_a_file_that_does_not_exist_should_raise_an_error.js +1 -0
  22. data/test/fixtures/requiring_a_file_with_css_style_comments_should_replace_the_require_comment_with_file_contents.css +4 -0
  23. data/test/fixtures/requiring_a_single_file_should_replace_the_require_comment_with_the_file_contents.js +3 -0
  24. data/test/fixtures/requiring_the_current_file_should_do_nothing.js +1 -0
  25. data/test/fixtures/src/constants.yml +3 -0
  26. data/test/fixtures/src/foo.css +1 -0
  27. data/test/fixtures/src/foo.js +1 -0
  28. data/test/fixtures/src/foo/bar.js +4 -0
  29. data/test/fixtures/src/foo/foo.js +1 -0
  30. data/test/fixtures/src/script_with_assets.js +3 -0
  31. data/test/test_concatenation.rb +28 -0
  32. data/test/test_environment.rb +64 -0
  33. data/test/test_helper.rb +55 -0
  34. data/test/test_pathname.rb +43 -0
  35. data/test/test_preprocessor.rb +116 -0
  36. data/test/test_secretary.rb +83 -0
  37. data/test/test_source_file.rb +34 -0
  38. data/test/test_source_line.rb +89 -0
  39. metadata +50 -4
@@ -0,0 +1,26 @@
1
+ require "rubygems"
2
+ require "rake/testtask"
3
+ require "rake/gempackagetask"
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList["test/test_*.rb"]
10
+ t.verbose = true
11
+ end
12
+
13
+ Rake::GemPackageTask.new(eval(IO.read(File.join(File.dirname(__FILE__), "sprockets.gemspec")))) do |pkg|
14
+ require File.join(File.dirname(__FILE__), "lib", "sprockets", "version")
15
+ raise "Error: Sprockets::Version doesn't match gemspec" if Sprockets::Version::STRING != pkg.version.to_s
16
+
17
+ pkg.need_zip = true
18
+ pkg.need_tar = true
19
+ end
20
+
21
+ namespace :manifest do
22
+ task :generate do
23
+ files = Dir["Rakefile", "bin/**/*", "lib/**/*", "test/**/*", "ext/**/*"]
24
+ File.open("MANIFEST.txt", "w"){|f| f.puts *files }
25
+ end
26
+ end
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This is a simple CGI wrapper around Sprockets.
4
+ #
5
+ # Copy it into a directory on your site with CGI enabled. When invoked, the
6
+ # script will search its directory and parent directories for a YAML file named
7
+ # "config/sprockets.yml" in order to load configuration information.
8
+ #
9
+ # If you set the environment variable "sprockets_generate_output_file" to
10
+ # "true" the concatenation will be cached to disk. Use it in conjunction with
11
+ # URL rewriting to cache your Sprockets output on the first request.
12
+ #
13
+ # Assuming a site layout like this:
14
+ #
15
+ # mysite/
16
+ # config/
17
+ # sprockets.yml
18
+ # javascripts/
19
+ # mysite.js
20
+ # ...
21
+ # public/
22
+ # index.html
23
+ # nph-sprockets.cgi (this file)
24
+ # vendor/
25
+ # sprockets/
26
+ # prototype/ -> ...
27
+ # scriptaculous/ -> ...
28
+ #
29
+ # mysite/config/sprockets.yml might look like this:
30
+ #
31
+ # :load_path:
32
+ # - javascripts
33
+ # - vendor/sprockets/*/src
34
+ # :source_files:
35
+ # - javascripts/mysite.js
36
+ # - javascripts/*.js
37
+ # :output_file: public/sprockets.js
38
+ #
39
+ # The <script> tag in mysite/public/index.html could look like this:
40
+ #
41
+ # <script type="text/javascript" src="/sprockets.js"></script>
42
+ #
43
+ # And you might have the following Apache configuration:
44
+ #
45
+ # <VirtualHost ...>
46
+ # ServerName mysite.example.org
47
+ # DocumentRoot "/path/to/mysite/public"
48
+ #
49
+ # <Directory "/path/to/mysite/public">
50
+ # Options +ExecCGI +FollowSymLinks
51
+ # AddHandler cgi-script .cgi
52
+ #
53
+ # RewriteEngine on
54
+ # RewriteCond /sprockets.js !-f
55
+ # RewriteRule ^sprockets\.js /nph-sprockets.cgi [P,L]
56
+ # </Directory>
57
+ # </VirtualHost>
58
+ #
59
+ # All requests to /sprockets.js will transparently proxy /nph-sprockets.cgi if
60
+ # mysite/public/sprockets.js does not exist. In production, you can add
61
+ #
62
+ # SetEnv sprockets_generate_output_file true
63
+ #
64
+ # to your Apache configuration and mysites/public/sprockets.js will be cached
65
+ # on the first request to /sprockets.js.
66
+
67
+ require "yaml"
68
+ require "fileutils"
69
+
70
+ def respond_with(options = {})
71
+ options = { :code => 200, :content => "", :type => "text/plain" }.merge(options)
72
+ print "HTTP/1.0 #{options[:code]}\r\n"
73
+ print "Content-Type: #{options[:type]}\r\n"
74
+ print "Content-Length: #{options[:content].length}\r\n"
75
+ print "\r\n#{options[:content]}"
76
+ $stdout.flush
77
+ exit!
78
+ end
79
+
80
+ def search_upwards_for(filename)
81
+ pwd = original_pwd = Dir.pwd
82
+ loop do
83
+ return File.expand_path(filename) if File.file?(filename)
84
+ Dir.chdir("..")
85
+ respond_with(:code => 500, :content => "couldn't find config/sprockets.yml") if Dir.pwd == pwd
86
+ pwd = Dir.pwd
87
+ end
88
+ ensure
89
+ Dir.chdir(original_pwd)
90
+ end
91
+
92
+ def generate_output_file?
93
+ (ENV["REDIRECT_sprockets_generate_output_file"] || ENV["sprockets_generate_output_file"]) =~ /true/i
94
+ end
95
+
96
+ configuration_file = search_upwards_for("config/sprockets.yml")
97
+ sprockets_root = File.dirname(File.dirname(configuration_file))
98
+ configuration = YAML.load(IO.read(configuration_file))
99
+
100
+ begin
101
+ if File.directory?(sprockets_dir = File.join(sprockets_root, "vendor/gems/sprockets/lib"))
102
+ $:.unshift sprockets_dir
103
+ elsif File.directory?(sprockets_dir = File.join(sprockets_root, "vendor/sprockets/lib"))
104
+ $:.unshift sprockets_dir
105
+ else
106
+ require "rubygems"
107
+ end
108
+
109
+ require "sprockets"
110
+
111
+ rescue Exception => e
112
+ respond_with(:code => 500, :content => "couldn't find sprockets: #{e}")
113
+ end
114
+
115
+ begin
116
+ secretary = Sprockets::Secretary.new(
117
+ :root => sprockets_root,
118
+ :load_path => configuration[:load_path],
119
+ :source_files => configuration[:source_files]
120
+ )
121
+
122
+ secretary.concatenation.save_to(File.join(sprockets_root, configuration[:output_file])) if generate_output_file?
123
+ respond_with(:content => secretary.concatenation.to_s, :type => "text/javascript")
124
+
125
+ rescue Exception => e
126
+ respond_with(:code => 500, :content => "couldn't generate concatenated javascript: #{e}")
127
+ end
@@ -0,0 +1,42 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Sprockets
7
+ class << self
8
+ def running_on_windows?
9
+ RUBY_PLATFORM =~ /(win|w)32$/
10
+ end
11
+
12
+ def absolute?(location)
13
+ same_when_expanded?(location) || platform_absolute_path?(location)
14
+ end
15
+
16
+ protected
17
+ def same_when_expanded?(location)
18
+ location[0, 1] == File.expand_path(location)[0, 1]
19
+ end
20
+
21
+ def platform_absolute_path?(location)
22
+ false
23
+ end
24
+
25
+ if Sprockets.running_on_windows?
26
+ def platform_absolute_path?(location)
27
+ location[0, 1] == File::SEPARATOR && File.expand_path(location) =~ /[A-Za-z]:[\/\\]/
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ require "sprockets/version"
34
+ require "sprockets/error"
35
+ require "sprockets/environment"
36
+ require "sprockets/pathname"
37
+ require "sprockets/source_line"
38
+ require "sprockets/source_file"
39
+ require "sprockets/concatenation"
40
+ require "sprockets/preprocessor"
41
+ require "sprockets/secretary"
42
+
@@ -0,0 +1,36 @@
1
+ module Sprockets
2
+ class Concatenation
3
+ attr_reader :source_lines
4
+
5
+ def initialize
6
+ @source_lines = []
7
+ @source_file_mtimes = {}
8
+ end
9
+
10
+ def record(source_line)
11
+ source_lines << source_line
12
+ record_mtime_for(source_line.source_file)
13
+ source_line
14
+ end
15
+
16
+ def to_s
17
+ source_lines.join
18
+ end
19
+
20
+ def mtime
21
+ @source_file_mtimes.values.max
22
+ end
23
+
24
+ def save_to(filename)
25
+ timestamp = mtime
26
+ File.open(filename, "w") { |file| file.write(to_s) }
27
+ File.utime(timestamp, timestamp, filename)
28
+ true
29
+ end
30
+
31
+ protected
32
+ def record_mtime_for(source_file)
33
+ @source_file_mtimes[source_file] ||= source_file.mtime
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,52 @@
1
+ module Sprockets
2
+ class Environment
3
+ attr_reader :root, :load_path
4
+
5
+ def initialize(root, load_path = [])
6
+ @load_path = [@root = Pathname.new(self, root)]
7
+
8
+ load_path.reverse_each do |location|
9
+ register_load_location(location)
10
+ end
11
+ end
12
+
13
+ def pathname_from(location)
14
+ Pathname.new(self, absolute_location_from(location))
15
+ end
16
+
17
+ def register_load_location(location)
18
+ pathname = pathname_from(location)
19
+ load_path.delete(pathname)
20
+ load_path.unshift(pathname)
21
+ location
22
+ end
23
+
24
+ def find(location)
25
+ if Sprockets.absolute?(location) && File.exists?(location)
26
+ pathname_from(location)
27
+ else
28
+ find_all(location).first
29
+ end
30
+ end
31
+
32
+ def constants(reload = false)
33
+ @constants = nil if reload
34
+ @constants ||= find_all("constants.yml").inject({}) do |constants, pathname|
35
+ contents = YAML.load(pathname.contents) rescue nil
36
+ contents = {} unless contents.is_a?(Hash)
37
+ constants.merge(contents)
38
+ end
39
+ end
40
+
41
+ protected
42
+ def absolute_location_from(location)
43
+ location = location.to_s
44
+ location = File.join(root.absolute_location, location) unless Sprockets.absolute?(location)
45
+ File.expand_path(location)
46
+ end
47
+
48
+ def find_all(location)
49
+ load_path.map { |pathname| pathname.find(location) }.compact
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ module Sprockets
2
+ class Error < ::StandardError; end
3
+ class LoadError < Error; end
4
+ class UndefinedConstantError < Error; end
5
+ end
@@ -0,0 +1,37 @@
1
+ module Sprockets
2
+ class Pathname
3
+ attr_reader :environment, :absolute_location
4
+
5
+ def initialize(environment, absolute_location)
6
+ @environment = environment
7
+ @absolute_location = File.expand_path(absolute_location)
8
+ end
9
+
10
+ # Returns a Pathname for the location relative to this pathname's absolute location.
11
+ def find(location, kind = :file)
12
+ location = File.join(absolute_location, location)
13
+ File.send("#{kind}?", location) ? Pathname.new(environment, location) : nil
14
+ end
15
+
16
+ def parent_pathname
17
+ Pathname.new(environment, File.dirname(absolute_location))
18
+ end
19
+
20
+ def source_file
21
+ SourceFile.new(environment, self)
22
+ end
23
+
24
+ def contents
25
+ IO.read(absolute_location)
26
+ end
27
+
28
+ def ==(pathname)
29
+ environment == pathname.environment &&
30
+ absolute_location == pathname.absolute_location
31
+ end
32
+
33
+ def to_s
34
+ absolute_location
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ module Sprockets
2
+ class Preprocessor
3
+ attr_reader :environment, :concatenation, :source_files, :asset_paths
4
+
5
+ def initialize(environment, options = {})
6
+ @environment = environment
7
+ @concatenation = Concatenation.new
8
+ @source_files = []
9
+ @asset_paths = []
10
+ @options = options
11
+ end
12
+
13
+ def require(source_file)
14
+ return if source_files.include?(source_file)
15
+ source_files << source_file
16
+
17
+ source_file.each_source_line do |source_line|
18
+ if source_line.require?
19
+ require_from_source_line(source_line)
20
+ elsif source_line.provide?
21
+ provide_from_source_line(source_line)
22
+ else
23
+ record_source_line(source_line)
24
+ end
25
+ end
26
+ end
27
+
28
+ def provide(asset_path)
29
+ return if !asset_path || asset_paths.include?(asset_path)
30
+ asset_paths << asset_path
31
+ end
32
+
33
+ protected
34
+ attr_reader :options
35
+
36
+ def require_from_source_line(source_line)
37
+ require pathname_from(source_line).source_file
38
+ end
39
+
40
+ def provide_from_source_line(source_line)
41
+ provide asset_path_from(source_line)
42
+ end
43
+
44
+ def record_source_line(source_line)
45
+ unless source_line.comment? && strip_comments?
46
+ concatenation.record(source_line)
47
+ end
48
+ end
49
+
50
+ def strip_comments?
51
+ options[:strip_comments] != false
52
+ end
53
+
54
+ def pathname_from(source_line)
55
+ pathname = send(pathname_finder_from(source_line), source_line)
56
+ raise_load_error_for(source_line) unless pathname
57
+ pathname
58
+ end
59
+
60
+ def pathname_for_require_from(source_line)
61
+ environment.find(location_from(source_line))
62
+ end
63
+
64
+ def pathname_for_relative_require_from(source_line)
65
+ source_line.source_file.find(location_from(source_line))
66
+ end
67
+
68
+ def pathname_finder_from(source_line)
69
+ "pathname_for_#{kind_of_require_from(source_line)}_from"
70
+ end
71
+
72
+ def kind_of_require_from(source_line)
73
+ source_line.require[/^(.)/, 1] == '"' ? :relative_require : :require
74
+ end
75
+
76
+ def location_from(source_line)
77
+ location = source_line.require[/^.(.*).$/, 1]
78
+ ext = File.extname source_line.source_file.pathname.to_s
79
+ File.join(File.dirname(location), File.basename(location, ext) + ext)
80
+ end
81
+
82
+ def asset_path_from(source_line)
83
+ source_line.source_file.find(source_line.provide, :exists)
84
+ end
85
+
86
+ def raise_load_error_for(source_line)
87
+ kind = kind_of_require_from(source_line).to_s.tr("_", " ")
88
+ file = File.split(location_from(source_line)).last
89
+ raise LoadError, "can't find file for #{kind} `#{file}' (#{source_line.inspect})"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,110 @@
1
+ module Sprockets
2
+ class Secretary
3
+ DEFAULT_OPTIONS = {
4
+ :root => ".",
5
+ :load_path => [],
6
+ :source_files => [],
7
+ :expand_paths => true
8
+ }
9
+
10
+ attr_reader :environment, :preprocessor
11
+
12
+ def initialize(options = {})
13
+ reset!(options)
14
+ end
15
+
16
+ def reset!(options = @options)
17
+ @options = DEFAULT_OPTIONS.merge(options)
18
+ @environment = Sprockets::Environment.new(@options[:root])
19
+ @preprocessor = Sprockets::Preprocessor.new(@environment)
20
+
21
+ add_load_locations(@options[:load_path])
22
+ add_source_files(@options[:source_files])
23
+ end
24
+
25
+ def add_load_location(load_location, options = {})
26
+ add_load_locations([load_location], options)
27
+ end
28
+
29
+ def add_load_locations(load_locations, options = {})
30
+ expand_paths(load_locations, options).each do |load_location|
31
+ environment.register_load_location(load_location)
32
+ end
33
+ end
34
+
35
+ def add_source_file(source_file, options = {})
36
+ add_source_files([source_file], options)
37
+ end
38
+
39
+ def add_source_files(source_files, options = {})
40
+ expand_paths(source_files, options).each do |source_file|
41
+ if pathname = environment.find(source_file)
42
+ preprocessor.require(pathname.source_file)
43
+ else
44
+ raise Sprockets::LoadError, "no such file `#{source_file}'"
45
+ end
46
+ end
47
+ end
48
+
49
+ def concatenation
50
+ preprocessor.concatenation
51
+ end
52
+
53
+ def install_assets
54
+ if @options[:asset_root]
55
+ preprocessor.asset_paths.each do |asset_path|
56
+ copy_assets_from(asset_path.absolute_location)
57
+ end
58
+ end
59
+ end
60
+
61
+ def source_last_modified
62
+ preprocessor.source_files.map { |s| s.mtime }.max
63
+ end
64
+
65
+ protected
66
+ def expand_paths(paths, options = {})
67
+ if options.has_key?(:expand_paths) ? options[:expand_paths] : @options[:expand_paths]
68
+ paths.map { |path| Dir[from_root(path)].sort }.flatten.compact
69
+ else
70
+ paths.map { |path| from_root(path) }
71
+ end
72
+ end
73
+
74
+ def from_root(path)
75
+ if Sprockets.absolute?(path)
76
+ path
77
+ else
78
+ File.join(@options[:root], path)
79
+ end
80
+ end
81
+
82
+ def copy_assets_from(asset_path)
83
+ if File.directory?(asset_path)
84
+ relative_file_paths_beneath(asset_path).each do |filename|
85
+ source, destination = File.join(asset_path, filename), File.join(asset_root, File.dirname(filename))
86
+ if !File.directory?(source)
87
+ FileUtils.mkdir_p(destination)
88
+ FileUtils.cp(source, destination)
89
+ end
90
+ end
91
+ else
92
+ FileUtils.cp(asset_path, asset_root)
93
+ end
94
+ end
95
+
96
+ def relative_file_paths_beneath(path)
97
+ Dir[File.join(path, "**", "*")].map do |filename|
98
+ File.join(*path_pieces(filename)[path_pieces(path).length..-1])
99
+ end
100
+ end
101
+
102
+ def asset_root
103
+ from_root(@options[:asset_root])
104
+ end
105
+
106
+ def path_pieces(path)
107
+ path.split(File::SEPARATOR)
108
+ end
109
+ end
110
+ end