ktheory-juicer 1.0.0.ktheory1

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 (98) hide show
  1. data/History.txt +30 -0
  2. data/Manifest.txt +58 -0
  3. data/Rakefile +96 -0
  4. data/Readme.rdoc +312 -0
  5. data/VERSION +1 -0
  6. data/bin/juicer +8 -0
  7. data/lib/juicer.rb +70 -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 +130 -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 +80 -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 +136 -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/data/Changelog.txt +10 -0
  50. data/test/data/a.css +3 -0
  51. data/test/data/a.js +5 -0
  52. data/test/data/a1.css +5 -0
  53. data/test/data/b.css +1 -0
  54. data/test/data/b.js +5 -0
  55. data/test/data/b1.css +5 -0
  56. data/test/data/c1.css +3 -0
  57. data/test/data/css/2.gif +1 -0
  58. data/test/data/css/test.css +11 -0
  59. data/test/data/css/test2.css +1 -0
  60. data/test/data/d1.css +3 -0
  61. data/test/data/images/1.png +1 -0
  62. data/test/data/my_app.js +2 -0
  63. data/test/data/not-ok.js +2 -0
  64. data/test/data/ok.js +3 -0
  65. data/test/data/path_test.css +5 -0
  66. data/test/data/path_test2.css +14 -0
  67. data/test/data/pkg/module/moda.js +2 -0
  68. data/test/data/pkg/module/modb.js +3 -0
  69. data/test/data/pkg/pkg.js +1 -0
  70. data/test/test_helper.rb +169 -0
  71. data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
  72. data/test/unit/juicer/asset/path_test.rb +370 -0
  73. data/test/unit/juicer/cache_buster_test.rb +104 -0
  74. data/test/unit/juicer/chainable_test.rb +94 -0
  75. data/test/unit/juicer/command/install_test.rb +58 -0
  76. data/test/unit/juicer/command/list_test.rb +81 -0
  77. data/test/unit/juicer/command/merge_test.rb +162 -0
  78. data/test/unit/juicer/command/util_test.rb +58 -0
  79. data/test/unit/juicer/command/verify_test.rb +48 -0
  80. data/test/unit/juicer/css_cache_buster_test.rb +71 -0
  81. data/test/unit/juicer/datafy_test.rb +37 -0
  82. data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
  83. data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
  84. data/test/unit/juicer/ext/string_test.rb +59 -0
  85. data/test/unit/juicer/ext/symbol_test.rb +27 -0
  86. data/test/unit/juicer/image_embed_test.rb +271 -0
  87. data/test/unit/juicer/install/installer_base_test.rb +214 -0
  88. data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
  89. data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
  90. data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
  91. data/test/unit/juicer/jslint_test.rb +60 -0
  92. data/test/unit/juicer/merger/base_test.rb +122 -0
  93. data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
  94. data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
  95. data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
  96. data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
  97. data/test/unit/juicer_test.rb +1 -0
  98. metadata +265 -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>:web_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
+ @web_root = options[:web_root]
27
+ @web_root = File.expand_path(@web_root).sub(/\/?$/, "") if @web_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[:web_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[:web_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\s("|')(.*)("|')\;?/, '')
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 :web_root option") if !@web_root
77
+ path = Pathname.new(File.join(@web_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 :web_root option") if !@web_root
84
+ path = File.expand_path(File.join(dir, url)).sub(@web_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