psyho_juicer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/History.txt +58 -0
  2. data/Manifest.txt +58 -0
  3. data/Rakefile +96 -0
  4. data/Readme.rdoc +313 -0
  5. data/VERSION +1 -0
  6. data/bin/juicer +6 -0
  7. data/lib/juicer.rb +69 -0
  8. data/lib/juicer/asset/path.rb +275 -0
  9. data/lib/juicer/asset/path_resolver.rb +79 -0
  10. data/lib/juicer/binary.rb +171 -0
  11. data/lib/juicer/cache_buster.rb +131 -0
  12. data/lib/juicer/chainable.rb +106 -0
  13. data/lib/juicer/cli.rb +56 -0
  14. data/lib/juicer/command/install.rb +61 -0
  15. data/lib/juicer/command/list.rb +57 -0
  16. data/lib/juicer/command/merge.rb +205 -0
  17. data/lib/juicer/command/util.rb +32 -0
  18. data/lib/juicer/command/verify.rb +60 -0
  19. data/lib/juicer/css_cache_buster.rb +90 -0
  20. data/lib/juicer/datafy/datafy.rb +20 -0
  21. data/lib/juicer/dependency_resolver/css_dependency_resolver.rb +29 -0
  22. data/lib/juicer/dependency_resolver/dependency_resolver.rb +101 -0
  23. data/lib/juicer/dependency_resolver/javascript_dependency_resolver.rb +23 -0
  24. data/lib/juicer/ext/logger.rb +5 -0
  25. data/lib/juicer/ext/string.rb +47 -0
  26. data/lib/juicer/ext/symbol.rb +15 -0
  27. data/lib/juicer/image_embed.rb +129 -0
  28. data/lib/juicer/install/base.rb +186 -0
  29. data/lib/juicer/install/closure_compiler_installer.rb +69 -0
  30. data/lib/juicer/install/jslint_installer.rb +51 -0
  31. data/lib/juicer/install/rhino_installer.rb +53 -0
  32. data/lib/juicer/install/yui_compressor_installer.rb +67 -0
  33. data/lib/juicer/jslint.rb +90 -0
  34. data/lib/juicer/merger/base.rb +74 -0
  35. data/lib/juicer/merger/javascript_merger.rb +29 -0
  36. data/lib/juicer/merger/stylesheet_merger.rb +110 -0
  37. data/lib/juicer/minifyer/closure_compiler.rb +90 -0
  38. data/lib/juicer/minifyer/java_base.rb +77 -0
  39. data/lib/juicer/minifyer/yui_compressor.rb +96 -0
  40. data/test/bin/jslint-1.0.js +523 -0
  41. data/test/bin/jslint.js +523 -0
  42. data/test/bin/rhino1_7R1.zip +0 -0
  43. data/test/bin/rhino1_7R2-RC1.jar +0 -0
  44. data/test/bin/rhino1_7R2-RC1.zip +0 -0
  45. data/test/bin/yuicompressor +0 -0
  46. data/test/bin/yuicompressor-2.3.5.zip +0 -0
  47. data/test/bin/yuicompressor-2.4.2.jar +0 -0
  48. data/test/bin/yuicompressor-2.4.2.zip +0 -0
  49. data/test/fixtures/yui-download.html +425 -0
  50. data/test/test_helper.rb +175 -0
  51. data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
  52. data/test/unit/juicer/asset/path_test.rb +370 -0
  53. data/test/unit/juicer/cache_buster_test.rb +104 -0
  54. data/test/unit/juicer/chainable_test.rb +94 -0
  55. data/test/unit/juicer/command/install_test.rb +58 -0
  56. data/test/unit/juicer/command/list_test.rb +81 -0
  57. data/test/unit/juicer/command/merge_test.rb +162 -0
  58. data/test/unit/juicer/command/util_test.rb +58 -0
  59. data/test/unit/juicer/command/verify_test.rb +48 -0
  60. data/test/unit/juicer/css_cache_buster_test.rb +71 -0
  61. data/test/unit/juicer/datafy_test.rb +37 -0
  62. data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
  63. data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
  64. data/test/unit/juicer/ext/string_test.rb +59 -0
  65. data/test/unit/juicer/ext/symbol_test.rb +27 -0
  66. data/test/unit/juicer/image_embed_test.rb +271 -0
  67. data/test/unit/juicer/install/installer_base_test.rb +214 -0
  68. data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
  69. data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
  70. data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
  71. data/test/unit/juicer/jslint_test.rb +60 -0
  72. data/test/unit/juicer/merger/base_test.rb +122 -0
  73. data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
  74. data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
  75. data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
  76. data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
  77. data/test/unit/juicer_test.rb +1 -0
  78. metadata +278 -0
@@ -0,0 +1,74 @@
1
+ require "juicer/chainable"
2
+
3
+ # Merge several files into one single output file
4
+ module Juicer
5
+ module Merger
6
+ class Base
7
+ include Chainable
8
+ attr_accessor :dependency_resolver
9
+ attr_reader :files
10
+
11
+ def initialize(files = [], options = {})
12
+ @files = []
13
+ @root = nil
14
+ @options = options
15
+ @dependency_resolver ||= nil
16
+ self.append files
17
+ end
18
+
19
+ #
20
+ # Append contents to output. Resolves dependencies and adds
21
+ # required files recursively
22
+ # file = A file to add to merged content
23
+ #
24
+ def append(file)
25
+ return file.each { |f| self << f } if file.class == Array
26
+ return if @files.include?(file)
27
+
28
+ if !@dependency_resolver.nil?
29
+ path = File.expand_path(file)
30
+ resolve_dependencies(path)
31
+ elsif !@files.include?(file)
32
+ @files << file
33
+ end
34
+ end
35
+
36
+ alias_method :<<, :append
37
+
38
+ #
39
+ # Save the merged contents. If a filename is given the new file is
40
+ # written. If a stream is provided, contents are written to it.
41
+ #
42
+ def save(file_or_stream)
43
+ output = file_or_stream
44
+
45
+ if output.is_a? String
46
+ @root = Pathname.new(File.dirname(File.expand_path(output)))
47
+ output = File.open(output, 'w')
48
+ else
49
+ @root = Pathname.new(File.expand_path("."))
50
+ end
51
+
52
+ @files.each do |f|
53
+ output.puts(merge(f))
54
+ end
55
+
56
+ output.close if file_or_stream.is_a? String
57
+ end
58
+
59
+ chain_method :save
60
+
61
+ private
62
+ def resolve_dependencies(file)
63
+ @files.concat @dependency_resolver.resolve(file)
64
+ @files.uniq!
65
+ end
66
+
67
+ # Fetch contents of a single file. May be overridden in subclasses to provide
68
+ # custom content filtering
69
+ def merge(file)
70
+ IO.read(file) + "\n"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ require "juicer/merger/base"
3
+ require "juicer/dependency_resolver/javascript_dependency_resolver"
4
+
5
+ module Juicer
6
+ module Merger
7
+ # Merge several files into one single output file. Resolves and adds in files from @depend comments
8
+ class JavaScriptMerger < Base
9
+
10
+ # Constructor
11
+ def initialize(files = [], options = {})
12
+ @dependency_resolver = JavaScriptDependencyResolver.new
13
+ super(files, options)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # Run file from command line
20
+ # TODO: Refactor to testable Juicer::Merger::JavaScript::FileMerger.cli method
21
+ # or similar.
22
+ #
23
+ if $0 == __FILE__
24
+ puts("Usage: javascript_merger.rb file[...] output") and exit if $*.length < 2
25
+
26
+ fm = JavaScriptMerger.new()
27
+ fm << $*[0..-2]
28
+ fm.save($*[-1])
29
+ end
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ require "juicer/merger/base"
3
+ require "juicer/dependency_resolver/css_dependency_resolver"
4
+ require 'pathname'
5
+
6
+ module Juicer
7
+ module Merger
8
+ # Merge several files into one single output file. Resolves and adds in files
9
+ # from @import statements
10
+ #
11
+ class StylesheetMerger < Base
12
+
13
+ # Constructor
14
+ #
15
+ # Options:
16
+ # * <tt>:document_root</tt> - Path to web root if there is any @import statements
17
+ # using absolute URLs
18
+ #
19
+ def initialize(files = [], options = {})
20
+ @dependency_resolver = CssDependencyResolver.new(options)
21
+ super(files || [], options)
22
+ @hosts = options[:hosts] || []
23
+ @host_num = 0
24
+ @use_absolute = options.key?(:absolute_urls) ? options[:absolute_urls] : false
25
+ @use_relative = options.key?(:relative_urls) ? options[:relative_urls] : false
26
+ @document_root = options[:document_root]
27
+ @document_root = File.expand_path(@document_root).sub(/\/?$/, "") if @document_root # Make sure path doesn't end in a /
28
+ end
29
+
30
+ private
31
+ #
32
+ # Takes care of removing any import statements. This avoids importing the
33
+ # file that was just merged into the current file.
34
+ #
35
+ # +merge+ also recalculates any referenced URLs. Relative URLs are adjusted
36
+ # to be relative to the resulting merged file. Absolute URLs are left alone
37
+ # by default. If the :hosts option is set, the absolute URLs will cycle
38
+ # through these. This may help in concurrent downloads.
39
+ #
40
+ # The options hash decides how Juicer recalculates referenced URLs:
41
+ #
42
+ # options[:absolute_urls] When true, all paths are converted to absolute
43
+ # URLs. Requires options[:document_root] to define
44
+ # root directory to resolve absolute URLs from.
45
+ # options[:relative_urls] When true, all paths are converted to relative
46
+ # paths. Requires options[:document_root] to define
47
+ # root directory to resolve absolute URLs from.
48
+ #
49
+ # If none if these are set then relative URLs are recalculated to match
50
+ # location of merged target while absolute URLs are left absolute.
51
+ #
52
+ # If options[:hosts] is set to an array of hosts, then they will be cycled
53
+ # for all absolute URLs regardless of absolute/relative URL strategy.
54
+ #
55
+ def merge(file)
56
+ content = super.gsub(/^\s*@import(?:\surl\(|\s)(['"]?)([^\?'"\)\s]+)(\?(?:[^'"\)]+)?)?\1\)?(?:[^?;]+)?;?/i, "")
57
+ dir = File.expand_path(File.dirname(file))
58
+
59
+ content.scan(/url\([\s"']*([^\)"'\s]*)[\s"']*\)/m).uniq.collect do |url|
60
+ url = url.first
61
+ path = resolve_path(url, dir)
62
+ content.gsub!(/\(#{url}\)/m, "(#{path})") unless path == url
63
+ end
64
+
65
+ content
66
+ end
67
+
68
+ #
69
+ # Resolves a path relative to a directory
70
+ #
71
+ def resolve_path(url, dir)
72
+ path = url
73
+
74
+ # Absolute URLs
75
+ if url =~ %r{^/} && @use_relative
76
+ raise ArgumentError.new("Unable to handle absolute URLs without :document_root option") if !@document_root
77
+ path = Pathname.new(File.join(@document_root, url)).relative_path_from(@root).to_s
78
+ end
79
+
80
+ # All URLs that don't start with a protocol
81
+ if url !~ %r{^/} && url !~ %r{^[a-z]+://}
82
+ if @use_absolute
83
+ raise ArgumentError.new("Unable to handle absolute URLs without :document_root option") if !@document_root
84
+ path = File.expand_path(File.join(dir, url)).sub(@document_root, "") # Make absolute
85
+ else
86
+ path = Pathname.new(File.join(dir, url)).relative_path_from(@root).to_s # ...or redefine relative ref
87
+ end
88
+ end
89
+
90
+ # Cycle hosts, if any
91
+ if url =~ %r{^/} && @hosts.length > 0
92
+ path = File.join(@hosts[@host_num % @hosts.length], url)
93
+ @host_num += 1
94
+ end
95
+
96
+ path
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ # Run file from command line
103
+ #
104
+ if $0 == __FILE__
105
+ puts("Usage: stylesheet_merger.rb file[...] output") and exit if $*.length < 2
106
+
107
+ fm = Juicer::Merger::StylesheetMerger.new()
108
+ fm << $*[0..-2]
109
+ fm.save($*[-1])
110
+ end
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tempfile'
3
+ require 'juicer/minifyer/java_base'
4
+ require 'juicer/chainable'
5
+
6
+ module Juicer
7
+ module Minifyer
8
+
9
+ # Provides an interface to the Closure compiler library using
10
+ # Juicer::Shell::Binary. The Closure compiler library is implemented
11
+ # using Java, and as such Java is required when running this code. Also, the
12
+ # compiler jar file has to be provided.
13
+ #
14
+ # The Closure Compiler is invoked using the java binary and the compiler
15
+ # jar file.
16
+ #
17
+ # Providing the Jar file (usually compiler.jar) can be done in
18
+ # several ways. The following directories are searched (in preferred order)
19
+ #
20
+ # 1. The directory specified by the option :bin_path
21
+ # 2. The directory specified by the environment variable $CLOSUREC_HOME, if set
22
+ # 3. Current working directory
23
+ #
24
+ # For more information on how the Jar is located, see
25
+ # +Juicer::Minify::ClosureCompiler.locate_jar+
26
+ #
27
+ # Author:: Christian Johansen (christian@cjohansen.no), Pavel Valodzka (pavel@valodzka.name)
28
+ # Copyright:: Copyright (c) 2008-2009 Christian Johansen, (c) 2009 Pavel Valodzka
29
+ # License:: MIT
30
+ #
31
+ # = Usage example =
32
+ # closure = Juicer::Minifyer::ClosureCompiler.new
33
+ # closure.java = "/usr/local/bin/java" # If 'java' is not on path
34
+ # closure.path << "/home/user/java/yui_compressor/"
35
+ # closure.save("", "")
36
+ #
37
+ class ClosureCompiler
38
+ include Juicer::Minifyer::JavaBase
39
+ include Juicer::Chainable
40
+
41
+ # Compresses a file using the YUI Compressor. Note that the :bin_path
42
+ # option needs to be set in order for YuiCompressor to find and use the
43
+ # YUI jar file. Please refer to the class documentation for how to set
44
+ # this.
45
+ #
46
+ # file = The file to compress
47
+ # output = A file or stream to save the results to. If not provided the
48
+ # original file will be overwritten
49
+ # type = Either :js or :css. If this parameter is not provided, the type
50
+ # is guessed from the suffix on the input file name
51
+ def save(file, output = nil, type = nil)
52
+ type = type.nil? ? file.split('.')[-1].to_sym : type
53
+
54
+ use_tmp = unless output
55
+ output = file
56
+ file = File.join(Dir::tmpdir, File.basename(file) + '.min.tmp.' + type.to_s)
57
+ FileUtils.mkdir_p(File.dirname(file))
58
+ FileUtils.move(output, file)
59
+
60
+ true
61
+ end
62
+
63
+ out_dir = File.dirname(output)
64
+ FileUtils.mkdir_p(out_dir) unless File.exists?(out_dir)
65
+ result = execute(%Q{-jar "#{locate_jar}"#{jar_args} -js_output_file "#{output}" -js "#{file}"})
66
+
67
+ File.delete(file) if use_tmp
68
+ end
69
+
70
+ chain_method :save
71
+
72
+ def self.bin_base_name
73
+ "*compiler"
74
+ end
75
+
76
+ def self.env_name
77
+ "CLOSUREC_HOME"
78
+ end
79
+
80
+ private
81
+
82
+ # Some class level options may be set:
83
+ # :bin_path (defaults to Dir.cwd)
84
+ # :java (Java command, defaults to 'java')
85
+ def default_options
86
+ { }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tempfile'
3
+ require 'juicer/binary'
4
+
5
+ module Juicer
6
+ module Minifyer
7
+
8
+ # Provides an interface to Java based compressor libraries using
9
+ # Juicer::Shell::Binary.
10
+ #
11
+ # The compressor is invoked using the java binary and the compressor jar
12
+ # file.
13
+ #
14
+ # Providing the Jar file can be done in several ways. The following
15
+ # directories are searched (in preferred order)
16
+ #
17
+ # 1. The directory specified by the option :bin_path
18
+ # 2. The directory specified by the environment variable, if set
19
+ # 3. Current working directory
20
+ #
21
+ # Name of environment variable is decided by including classes self.env_name
22
+ # constant.
23
+ #
24
+ # For more information on how the Jar is located, see
25
+ # +Juicer::Minify::JavaMinifyer.locate_jar+
26
+ #
27
+ # Author:: Christian Johansen (christian@cjohansen.no)
28
+ # Copyright:: Copyright (c) 2008-2009 Christian Johansen
29
+ # License:: MIT
30
+ #
31
+ module JavaBase
32
+ include Juicer::Binary
33
+
34
+ def initialize(options = {})
35
+ bin = options.delete(:java) || "java"
36
+ bin_path = options.delete(:bin_path) || nil
37
+ @jar = nil
38
+ @jar_args = nil
39
+
40
+ super(bin, options)
41
+ path << bin_path if bin_path
42
+ end
43
+
44
+ # Overrides set_opts called from binary class
45
+ # This avoids sending illegal options to the java binary
46
+ #
47
+ def set_opts(args)
48
+ @jar_args = " #{args}"
49
+ end
50
+
51
+ def jar_args
52
+ @jar_args
53
+ end
54
+
55
+ private
56
+
57
+ # Locates the Jar file by searching directories.
58
+ # The following directories are searched (in preferred order)
59
+ #
60
+ # 1. The directory specified by the option :bin_path
61
+ # 2. The directory specified by the environment variable, if set
62
+ # 3. Current working directory
63
+ #
64
+ # If any of these folders contain one or more files named like
65
+ # [self.class.bin_base_name].jar or [self.class.bin_base_name]-x.y.z.jar
66
+ # the method willpick the last file in the list returned by
67
+ # +Dir.glob("#{dir}/[self.class.bin_base_name]*.jar").sort. This means that
68
+ # higher version numbers will be preferred with the default naming for the
69
+ # jars
70
+ #
71
+ def locate_jar
72
+ files = locate("#{self.class.bin_base_name}*.jar", self.class.env_name)
73
+ !files || files.empty? ? nil : files.sort.last
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tempfile'
3
+ require 'juicer/minifyer/java_base'
4
+ require 'juicer/chainable'
5
+
6
+ module Juicer
7
+ module Minifyer
8
+
9
+ # Provides an interface to the YUI compressor library using
10
+ # Juicer::Shell::Binary. The YUI compressor library is implemented
11
+ # using Java, and as such Java is required when running this code. Also, the
12
+ # YUI jar file has to be provided.
13
+ #
14
+ # The YUI Compressor is invoked using the java binary and the YUI Compressor
15
+ # jar file.
16
+ #
17
+ # Providing the Jar file (usually yuicompressor-x.y.z.jar) can be done in
18
+ # several ways. The following directories are searched (in preferred order)
19
+ #
20
+ # 1. The directory specified by the option :bin_path
21
+ # 2. The directory specified by the environment variable $YUIC_HOME, if set
22
+ # 3. Current working directory
23
+ #
24
+ # For more information on how the Jar is located, see
25
+ # +Juicer::Minify::YuiCompressor.locate_jar+
26
+ #
27
+ # Author:: Christian Johansen (christian@cjohansen.no)
28
+ # Copyright:: Copyright (c) 2008-2009 Christian Johansen
29
+ # License:: MIT
30
+ #
31
+ # = Usage example =
32
+ # yuic = Juicer::Minifyer::YuiCompressor.new
33
+ # yuic.java = "/usr/local/bin/java" # If 'java' is not on path
34
+ # yuic.path << "/home/user/java/yui_compressor/"
35
+ # yuic.save("", "")
36
+ #
37
+ #
38
+ class YuiCompressor
39
+ include Juicer::Minifyer::JavaBase
40
+ include Juicer::Chainable
41
+
42
+ # Compresses a file using the YUI Compressor. Note that the :bin_path
43
+ # option needs to be set in order for YuiCompressor to find and use the
44
+ # YUI jar file. Please refer to the class documentation for how to set
45
+ # this.
46
+ #
47
+ # file = The file to compress
48
+ # output = A file or stream to save the results to. If not provided the
49
+ # original file will be overwritten
50
+ # type = Either :js or :css. If this parameter is not provided, the type
51
+ # is guessed from the suffix on the input file name
52
+ def save(file, output = nil, type = nil)
53
+ type = type.nil? ? file.split('.')[-1].to_sym : type
54
+
55
+ output ||= file
56
+ use_tmp = !output.is_a?(String)
57
+ output = File.join(Dir::tmpdir, File.basename(file) + '.min.tmp.' + type.to_s) if use_tmp
58
+ FileUtils.mkdir_p(File.dirname(output))
59
+
60
+ result = execute(%Q{-jar "#{locate_jar}"#{jar_args} -o "#{output}" "#{file}"})
61
+
62
+ if use_tmp # If no output file is provided, YUI compressor will
63
+ output.puts IO.read(output) # compress to a temp file. This file should be cleared
64
+ File.delete(output) # out after we fetch its contents.
65
+ end
66
+ end
67
+
68
+ chain_method :save
69
+
70
+ def self.bin_base_name
71
+ "yuicompressor"
72
+ end
73
+
74
+ def self.env_name
75
+ "YUIC_HOME"
76
+ end
77
+
78
+ private
79
+ # Returns a map of options accepted by YUI Compressor, currently:
80
+ #
81
+ # :charset
82
+ # :line_break
83
+ # :no_munge (JavaScript only)
84
+ # :preserve_semi
85
+ # :disable_optimizations
86
+ #
87
+ # In addition, some class level options may be set:
88
+ # :bin_path (defaults to Dir.cwd)
89
+ # :java (Java command, defaults to 'java')
90
+ def default_options
91
+ { :charset => nil, :line_break => nil, :nomunge => nil,
92
+ :preserve_semi => nil, :disable_optimizations => nil }
93
+ end
94
+ end
95
+ end
96
+ end