amiel-sprockets 1.0.4 → 1.0.5

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 (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