cjohansen-juicer 0.2.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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *~
2
+ test/data
data/History.txt ADDED
@@ -0,0 +1,10 @@
1
+ == 0.2.0 / 2009-xx-xx
2
+
3
+ * Refactored the minifyers execute method from compress to save
4
+ * Refactored Juicer::Merger::FileMerger -> Juicer::Merger::Base
5
+ * Refactored the mergers and minifyers to be chainable commands.
6
+ * Added Chainable module
7
+
8
+ == 0.1.0 / 2008-12-17
9
+
10
+ * Dug up old project and set it up with Mr Bones
data/Manifest.txt ADDED
@@ -0,0 +1,40 @@
1
+ .gitignore
2
+ History.txt
3
+ Manifest.txt
4
+ Rakefile
5
+ Readme.rdoc
6
+ bin/juicer
7
+ juicer.gemspec
8
+ lib/juicer.rb
9
+ lib/juicer/chainable.rb
10
+ lib/juicer/cli.rb
11
+ lib/juicer/command/merge.rb
12
+ lib/juicer/merger/base.rb
13
+ lib/juicer/merger/css_dependency_resolver.rb
14
+ lib/juicer/merger/dependency_resolver.rb
15
+ lib/juicer/merger/javascript_dependency_resolver.rb
16
+ lib/juicer/merger/javascript_merger.rb
17
+ lib/juicer/merger/stylesheet_merger.rb
18
+ lib/juicer/minifyer/compressor.rb
19
+ lib/juicer/minifyer/yui_compressor.rb
20
+ test/data/Changelog.txt
21
+ test/data/a.css
22
+ test/data/a.js
23
+ test/data/b.css
24
+ test/data/b.js
25
+ test/data/mappe/a.css
26
+ test/data/mappe/c.css
27
+ test/data/mappe/enda_en_mappe/a.css
28
+ test/data/version-test.txt
29
+ test/data/version.txt
30
+ test/data/version2.txt
31
+ test/juicer/merger/test_base.rb
32
+ test/juicer/merger/test_css_dependency_resolver.rb
33
+ test/juicer/merger/test_javascript_dependency_resolver.rb
34
+ test/juicer/merger/test_javascript_merger.rb
35
+ test/juicer/merger/test_stylesheet_merger.rb
36
+ test/juicer/minifyer/test_compressor.rb
37
+ test/juicer/minifyer/test_yui_compressor.rb
38
+ test/juicer/test_chainable.rb
39
+ test/test_helper.rb
40
+ test/test_juicer.rb
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ load 'tasks/setup.rb'
10
+ end
11
+
12
+ ensure_in_path 'lib'
13
+ require 'juicer'
14
+
15
+ task :default => 'test:run'
16
+
17
+ PROJ.name = 'juicer'
18
+ PROJ.authors = 'Christian Johansen'
19
+ PROJ.email = 'christian@cjohansen.no'
20
+ PROJ.url = 'http://www.cjohansen.no/en/projects/juicer'
21
+ PROJ.version = Juicer::VERSION
22
+ PROJ.rubyforge.name = 'juicer'
23
+ PROJ.readme_file = 'Readme.rdoc'
24
+
25
+ PROJ.spec.opts << '--color'
26
+
27
+ depend_on 'cmdparse'
data/Readme.rdoc ADDED
@@ -0,0 +1,108 @@
1
+ = Juicer
2
+ Christian Johansen (http://www.cjohansen.no)
3
+
4
+ == DESCRIPTION:
5
+
6
+ Juicer is a command line tool aimed at easing JavaScript and CSS development.
7
+ Currently it only provides a wrapper to YUI Compressor along with a module that
8
+ can dynamically link together files, but there are plans for more functionality.
9
+
10
+ == FEATURES:
11
+
12
+ Juicer can read @import statements in CSS files and use them to combine all your
13
+ stylesheets into a single file. This file may be minified using the YUI
14
+ Compressor. Eventually it will support other minifying tools too.
15
+
16
+ Juicer can treat your JavaScript files much the same way too, parsing a comment
17
+ switch @depends, as this example shows:
18
+
19
+ /**
20
+ * My script file
21
+ *
22
+ * @depend jquery-1.2.0.js
23
+ */
24
+ var myNS = {
25
+ myObject = {}
26
+ };
27
+
28
+ Running <tt>juicer merge</tt> on this file will result in a minified file
29
+ <tt>filename.min.js</tt> containing the file jquery-1.2.0.js (located in the
30
+ same directory) and the code above.
31
+
32
+ == 0.1.0 STATUS:
33
+
34
+ Juicer is currently in a very early stage, please expect it to be unstable,
35
+ poorly documented and otherwise unfinished. Some of this code is a bit old, and
36
+ a brush up is planned that will likely result in several API changes.
37
+
38
+ == PLANNED FEATURES:
39
+
40
+ There are several things on the pipeline:
41
+
42
+ * Support more minifiers, JsMin (Ruby port), Packer and possibly others
43
+ * Wrap code quality assurance tools, such as JsLint and CSS Tidy
44
+ juicer check design/*
45
+ * A tool to build a whole proect based on a configuration file or something
46
+ like that
47
+
48
+ As you see, Juicer will mostly rely heavily on other peoples great work. Juicers
49
+ strength will be that it provides a single simple to use interface to run these
50
+ tools on your project, as well as sewing it all together.
51
+
52
+ If you have any ideas, feature requests, want to contribute or whatever, fork
53
+ the proect on github, or get in touch through christian (at) cjohansen.no.
54
+
55
+ == SYNOPSIS:
56
+
57
+ juicer merge myfile.css
58
+ -> Produces myfile.min.css which may contain several CSS files, minified
59
+
60
+ juicer merge myfile.js
61
+ -> Produces myfile.min.js, minified and combined
62
+
63
+ == REQUIREMENTS:
64
+
65
+ In order to use YUI Compressor you need Java installed and the java executable
66
+ available on your path.
67
+
68
+ == INSTALL:
69
+
70
+ $ wget http://www.cjohansen.no/juicer-0.1.0.gem
71
+ $ gem install juicer-0.1.0.gem
72
+
73
+ You need Java installed, and you need the YUI Compressor jar file. Get it from
74
+ http://developer.yahoo.com/yui/compressor/
75
+
76
+ Download the YUI Compressor package and unzip on your file system. Either create
77
+ an environment variable <tt>$YUIC_HOME</tt> that points to where the file is
78
+ found, or provide the path like so:
79
+ juicer merge --path ~/sources/yuicompressor/build [...]
80
+
81
+ Alternatively you can keep a copy of the jar file in the same directory that you
82
+ run the <tt>juicer</tt> command from. I imagine that would only make sense in a
83
+ testing scenario.
84
+
85
+ == LICENSE:
86
+
87
+ (The MIT License)
88
+
89
+ Copyright (c) 2008 Christian Johansen
90
+
91
+ Permission is hereby granted, free of charge, to any person obtaining
92
+ a copy of this software and associated documentation files (the
93
+ 'Software'), to deal in the Software without restriction, including
94
+ without limitation the rights to use, copy, modify, merge, publish,
95
+ distribute, sublicense, and/or sell copies of the Software, and to
96
+ permit persons to whom the Software is furnished to do so, subject to
97
+ the following conditions:
98
+
99
+ The above copyright notice and this permission notice shall be
100
+ included in all copies or substantial portions of the Software.
101
+
102
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
103
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
104
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
105
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
106
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
107
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
108
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/juicer ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ dir = File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)
3
+ base = File.expand_path(File.join(dir, %w[.. lib juicer]))
4
+ require base
5
+ require File.join(base, "cli")
6
+
7
+ Juicer::Cli.run(ARGV)
data/juicer.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{juicer}
3
+ s.version = "0.2.0"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["Christian Johansen"]
7
+ s.date = %q{2009-01-04}
8
+ s.default_executable = %q{juicer}
9
+ s.description = %q{Juicer is a command line tool aimed at easing JavaScript and CSS development. Currently it only provides a wrapper to YUI Compressor along with a module that can dynamically link together files, but there are plans for more functionality.}
10
+ s.email = %q{christian@cjohansen.no}
11
+ s.executables = ["juicer"]
12
+ s.extra_rdoc_files = ["History.txt", "Readme.rdoc", "bin/juicer", "test/data/Changelog.txt", "test/data/version-test.txt", "test/data/version.txt", "test/data/version2.txt"]
13
+ s.files = [".gitignore", "History.txt", "Manifest.txt", "Rakefile", "Readme.rdoc", "bin/juicer", "juicer.gemspec", "lib/juicer.rb", "lib/juicer/chainable.rb", "lib/juicer/cli.rb", "lib/juicer/command/merge.rb", "lib/juicer/merger/base.rb", "lib/juicer/merger/css_dependency_resolver.rb", "lib/juicer/merger/dependency_resolver.rb", "lib/juicer/merger/javascript_dependency_resolver.rb", "lib/juicer/merger/javascript_merger.rb", "lib/juicer/merger/stylesheet_merger.rb", "lib/juicer/minifyer/compressor.rb", "lib/juicer/minifyer/yui_compressor.rb", "test/data/Changelog.txt", "test/data/a.css", "test/data/a.js", "test/data/b.css", "test/data/b.js", "test/data/mappe/a.css", "test/data/mappe/c.css", "test/data/mappe/enda_en_mappe/a.css", "test/data/version-test.txt", "test/data/version.txt", "test/data/version2.txt", "test/juicer/merger/test_base.rb", "test/juicer/merger/test_css_dependency_resolver.rb", "test/juicer/merger/test_javascript_dependency_resolver.rb", "test/juicer/merger/test_javascript_merger.rb", "test/juicer/merger/test_stylesheet_merger.rb", "test/juicer/minifyer/test_compressor.rb", "test/juicer/minifyer/test_yui_compressor.rb", "test/juicer/test_chainable.rb", "test/test_helper.rb", "test/test_juicer.rb"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://www.cjohansen.no/en/projects/juicer}
16
+ s.rdoc_options = ["--main", "Readme.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{juicer}
19
+ s.rubygems_version = %q{1.2.0}
20
+ s.summary = %q{Juicer is a command line tool aimed at easing JavaScript and CSS development}
21
+ s.test_files = ["test/test_helper.rb", "test/juicer/test_chainable.rb", "test/juicer/merger/test_javascript_dependency_resolver.rb", "test/juicer/merger/test_css_dependency_resolver.rb", "test/juicer/merger/test_base.rb", "test/juicer/merger/test_javascript_merger.rb", "test/juicer/merger/test_stylesheet_merger.rb", "test/juicer/minifyer/test_compressor.rb", "test/juicer/minifyer/test_yui_compressor.rb", "test/test_juicer.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if current_version >= 3 then
28
+ s.add_runtime_dependency(%q<cmdparse>, [">= 2.0.2"])
29
+ s.add_development_dependency(%q<bones>, [">= 2.2.0"])
30
+ else
31
+ s.add_dependency(%q<cmdparse>, [">= 2.0.2"])
32
+ s.add_dependency(%q<bones>, [">= 2.2.0"])
33
+ end
34
+ else
35
+ s.add_dependency(%q<cmdparse>, [">= 2.0.2"])
36
+ s.add_dependency(%q<bones>, [">= 2.2.0"])
37
+ end
38
+ end
@@ -0,0 +1,105 @@
1
+ module Juicer
2
+ #
3
+ # Facilitates the chain of responsibility pattern. Wraps given methods and
4
+ # calls them in a chain.
5
+ #
6
+ # To make an object chainable, simply include the module and call the class
7
+ # method chain_method for each method that should be chained.
8
+ #
9
+ # Example is a simplified version of the Wikipedia one
10
+ # (http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern)
11
+ #
12
+ # class Logger
13
+ # include Juicer::Chainable
14
+ #
15
+ # ERR = 3
16
+ # NOTICE = 5
17
+ # DEBUG = 7
18
+ #
19
+ # def initialize(level)
20
+ # @level = level
21
+ # end
22
+ #
23
+ # def log(str, level)
24
+ # if level <= @level
25
+ # write str
26
+ # else
27
+ # abort_chain
28
+ # end
29
+ # end
30
+ #
31
+ # def write(str)
32
+ # puts str
33
+ # end
34
+ #
35
+ # chain_method :message
36
+ # end
37
+ #
38
+ # class EmailLogger < Logger
39
+ # def write(str)
40
+ # p "Logging by email"
41
+ # # ...
42
+ # end
43
+ # end
44
+ #
45
+ # logger = Logger.new(Logger::NOTICE)
46
+ # logger.next_in_chain = EmailLogger.new(Logger::ERR)
47
+ #
48
+ # logger.log("Some message", Logger::DEBUG) # Ignored
49
+ # logger.log("A warning", Logger::NOTICE) # Logged to console
50
+ # logger.log("An error", Logger::ERR) # Logged to console and email
51
+ #
52
+ module Chainable
53
+
54
+ #
55
+ # Add the chain_method to classes that includes the module
56
+ #
57
+ def self.included(base)
58
+ base.extend(ClassMethods)
59
+ end
60
+
61
+ #
62
+ # Sets the next command in the chain
63
+ #
64
+ def next_in_chain=(next_obj)
65
+ @_next_in_chain = next_obj
66
+ end
67
+
68
+ alias_method :set_next, :next_in_chain=
69
+
70
+ #
71
+ # Get next command in chain
72
+ #
73
+ def next_in_chain
74
+ @_next_in_chain ||= nil
75
+ @_next_in_chain
76
+ end
77
+
78
+ private
79
+ #
80
+ # Abort the chain for the current message
81
+ #
82
+ def abort_chain
83
+ @_abort_chain = true
84
+ end
85
+
86
+ module ClassMethods
87
+ #
88
+ # Sets up a method for chaining
89
+ #
90
+ def chain_method(method)
91
+ original_method = "execute_#{method}".to_sym
92
+ alias_method original_method, method
93
+
94
+ self.class_eval <<-RUBY
95
+ def #{method}(*args, &block)
96
+ @_abort_chain = false
97
+ #{original_method}(*args, &block)
98
+ next_in_chain.#{method}(*args, &block) if !@_abort_chain && next_in_chain
99
+ @_abort_chain = false
100
+ end
101
+ RUBY
102
+ end
103
+ end
104
+ end
105
+ end
data/lib/juicer/cli.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'cmdparse'
3
+
4
+ # Command line interpreter for Juicer
5
+ #
6
+ module Juicer
7
+ class Cli
8
+ def initialize
9
+ $verbose = false
10
+ end
11
+
12
+ # Set up command parser and parse arguments
13
+ #
14
+ def parse(arguments = ARGV)
15
+ @cmd = CmdParse::CommandParser.new(true, true)
16
+ @cmd.program_name = "juicer"
17
+ @cmd.program_version = Juicer.version.split(".")
18
+
19
+ # @cmd.options = CmdParse::OptionParserWrapper.new do |opt|
20
+ # opt.separator "Global options:"
21
+ # opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true }
22
+ # end
23
+
24
+ add_commands
25
+ @cmd.parse(arguments)
26
+ end
27
+
28
+ # Run CLI
29
+ #
30
+ def self.run(arguments = ARGV)
31
+ juicer = self.new
32
+ juicer.parse(arguments)
33
+ end
34
+
35
+ private
36
+ # Adds commands supported by juicer. Instantiates all classes in the
37
+ # Juicer::Command namespace.
38
+ #
39
+ def add_commands
40
+ @cmd.add_command(CmdParse::HelpCommand.new)
41
+ @cmd.add_command(CmdParse::VersionCommand.new)
42
+
43
+ if Juicer.const_defined?("Command")
44
+ Juicer::Command.constants.each do |const|
45
+ const = Juicer::Command.const_get(const)
46
+ @cmd.add_command(const.new) if const.kind_of?(Class)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,90 @@
1
+ require 'rubygems'
2
+ require 'cmdparse'
3
+ require 'tempfile'
4
+
5
+ module Juicer
6
+ module Command
7
+ # The compress command combines and minifies CSS and JavaScript files
8
+ #
9
+ class Merge < CmdParse::Command
10
+ # Initializes compress command
11
+ #
12
+ def initialize
13
+ super('merge', false, true)
14
+ @types = { :js => Juicer::Merger::JavaScriptMerger,
15
+ :css => Juicer::Merger::StylesheetMerger }
16
+ @output = nil
17
+ @force = false
18
+ @minifyer = "yui_compressor"
19
+ @opts = {}
20
+ self.short_desc = "Combines and minifies CSS and JavaScript files"
21
+ self.description = <<-EOF
22
+ Each file provided as input will be checked for dependencies to other files,
23
+ and those files will be added to the final output
24
+
25
+ For CSS files the dependency checking is done through regular @import
26
+ statements.
27
+
28
+ For JavaScript files you can tell Juicer about dependencies through special
29
+ comment switches. These should appear inside a multi-line comment, specifically
30
+ inside the first multi-line comment. The switch is @depend or @depends, your
31
+ choice.
32
+
33
+ The -m --minifyer switch can be used to select which minifyer to use. Currently
34
+ only YUI Compressor is supported, ie -m yui_compressor (default). When using
35
+ the YUI Compressor the path should be the path to where the jar file is found.
36
+ EOF
37
+
38
+ self.options = CmdParse::OptionParserWrapper.new do |opt|
39
+ opt.on( '-o', '--output [OUTPUT]', 'Output filename' ) { |filename| @output = filename }
40
+ opt.on( '-p', '--path [PATH]', 'Path to compressor binary' ) { |path| @opts[:bin_path] = path }
41
+ opt.on( '-m', '--minifyer [MINIFYER]', 'Which minifer to use. Currently only supports YUI Compressor' ) { |name| @minifyer = name }
42
+ opt.on( '-f', '--force', 'Force overwrite of target file' ) { @force = true }
43
+ end
44
+ end
45
+
46
+ # Execute command
47
+ #
48
+ def execute(args)
49
+ if args.length == 0
50
+ raise OptionParser::ParseError.new('Please provide atleast one input file')
51
+ end
52
+
53
+ # If no file name is provided, use name of first input with .min
54
+ # prepended to suffix
55
+ @output = @output || args[0].sub(/\.([^\.]+)$/, '.min.\1')
56
+
57
+ if File.exists?(@output) && !@force
58
+ puts "Unable to continue, #{@output} exists. Run again with --force to overwrite"
59
+ exit
60
+ end
61
+
62
+ merger = @types[@output.split(/\.([^\.]*)$/)[1].to_sym].new(args)
63
+ merger.set_next(minifyer)
64
+ merger.save(@output)
65
+
66
+ # Print report
67
+ puts "Produced #{@output}"
68
+ end
69
+
70
+ private
71
+ #
72
+ # Resolve and load minifyer
73
+ #
74
+ def minifyer
75
+ begin
76
+ minifyer = @minifyer.split("_").collect { |p| p.capitalize! }.join
77
+ compressor = Juicer::Minifyer.const_get(minifyer).new(@opts)
78
+ rescue NameError
79
+ puts "No such minifyer '#{minifyer}', aborting"
80
+ exit
81
+ rescue Exception => e
82
+ puts e.message
83
+ exit
84
+ end
85
+
86
+ compressor
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "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
+ @dependency_resolver ||= nil
14
+ self.append files
15
+ end
16
+
17
+ #
18
+ # Append contents to output. Resolves dependencies and adds
19
+ # required files recursively
20
+ # file = A file to add to merged content
21
+ #
22
+ def append(file)
23
+ return file.each { |f| self << f } if file.class == Array
24
+ return if @files.include?(file)
25
+
26
+ if !@dependency_resolver.nil?
27
+ path = File.expand_path(file)
28
+ resolve_dependencies(path)
29
+ elsif !@files.include?(file)
30
+ @files << file
31
+ end
32
+ end
33
+
34
+ alias_method :<<, :append
35
+
36
+ #
37
+ # Save the merged contents. If a filename is given the new file is
38
+ # written. If a stream is provided, contents are written to it.
39
+ #
40
+ def save(file_or_stream)
41
+ output = file_or_stream
42
+ output = File.open(output, 'w') if output.is_a? String
43
+
44
+ @files.each { |f| output.puts(merge(f)) }
45
+ output.close if file_or_stream.is_a? String
46
+ end
47
+
48
+ chain_method :save
49
+
50
+ private
51
+ def resolve_dependencies(file)
52
+ @dependency_resolver.resolve(file) do |f|
53
+ if @files.include?(f)
54
+ false
55
+ else
56
+ @files << f
57
+ resolve_dependencies(f)
58
+ true
59
+ end
60
+ end
61
+
62
+ @files
63
+ end
64
+
65
+ # Fetch contents of a single file. May be overridden in subclasses to provide
66
+ # custom content filtering
67
+ def merge(file)
68
+ IO.read(file) + "\n"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "dependency_resolver"))
2
+
3
+ module Juicer
4
+ module Merger
5
+ # Resolves @import statements in CSS files and builds a list of all
6
+ # files, in order.
7
+ #
8
+ class CssDependencyResolver < DependencyResolver
9
+ # Regexp borrowed from similar project:
10
+ # http://github.com/cgriego/front-end-blender/tree/master/lib/front_end_architect/blender.rb
11
+ @@import_pattern = /^\s*@import(?: url\(| )(['"]?)([^\?'"\)\s]+)(\?(?:[^'"\)]+)?)?\1\)?(?:[^?;]+)?;?/im
12
+
13
+ private
14
+ def parse(line, imported_file = nil)
15
+ return $2 if line =~ @@import_pattern
16
+
17
+ # At first sight of actual CSS rules we abort (TODO: This does not take
18
+ # into account the fact that rules may be commented out and that more
19
+ # imports may follow)
20
+ throw :done if imported_file && line =~ %r{/*}
21
+ throw :done if line =~ /^[\.\#a-zA-Z\:]/
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ module Juicer
2
+ module Merger
3
+ class DependencyResolver
4
+ attr_reader :files
5
+
6
+ # Constructor
7
+ def initialize(options = {})
8
+ @files = []
9
+ @options = options
10
+ end
11
+
12
+ #
13
+ # Resolve dependencies.
14
+ # This method accepts an optional block. The block will receive each
15
+ # file in succession. The file is included in the returned collection
16
+ # if the block is true for the given file. Without a block every found
17
+ # file is returned.
18
+ #
19
+ def resolve(file)
20
+ imported_file = nil
21
+ @files = []
22
+
23
+ catch(:done) do
24
+ IO.foreach(file) do |line|
25
+ imported_file = parse(line, imported_file)
26
+
27
+ if imported_file
28
+ imported_file = resolve_path(imported_file, file)
29
+ @files << imported_file if !block_given? || yield(imported_file)
30
+ end
31
+ end
32
+ end
33
+
34
+ file = File.expand_path(file)
35
+ @files << file if !block_given? || yield(file)
36
+ end
37
+
38
+ #
39
+ # Resolves a path relative to another. If the path is absolute (ie it
40
+ # starts with a protocol or /) the <tt>:web_root</tt> options has to be
41
+ # set as well.
42
+ #
43
+ def resolve_path(path, reference)
44
+ # Absolute URL
45
+ if path =~ %r{^(/|[a-z]+:)}
46
+ if @options[:web_root].nil?
47
+ msg = "Cannot resolve absolute path '#{path}' without web root option"
48
+ raise ArgumentError.new(msg)
49
+ end
50
+
51
+ path.sub!(%r{^[a-z]+://[^/]+/}, '')
52
+ return File.expand_path(File.join(@options[:web_root], path))
53
+ end
54
+
55
+ File.expand_path(File.join(File.dirname(reference), path))
56
+ end
57
+
58
+ private
59
+ def parse(line)
60
+ raise NotImplementedError.new
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "dependency_resolver"))
2
+
3
+ module Juicer
4
+ module Merger
5
+ # Resolves @depends and @depend statements in comments in JavaScript files.
6
+ # Only the first comment in a JavaScript file is parsed
7
+ #
8
+ class JavaScriptDependencyResolver < DependencyResolver
9
+ @@depends_pattern = /\@depends?\s+([^\s\'\"\;]+)/
10
+
11
+ private
12
+ def parse(line, imported_file = nil)
13
+ return $1 if line =~ @@depends_pattern
14
+
15
+ # If we have already skimmed through some @depend/@depends or a
16
+ # closing comment we're done.
17
+ throw :done unless imported_file.nil? || !(line =~ /\*\//)
18
+ end
19
+ end
20
+ end
21
+ end