psyho_juicer 1.0.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/History.txt +58 -0
 - data/Manifest.txt +58 -0
 - data/Rakefile +96 -0
 - data/Readme.rdoc +313 -0
 - data/VERSION +1 -0
 - data/bin/juicer +6 -0
 - data/lib/juicer.rb +69 -0
 - data/lib/juicer/asset/path.rb +275 -0
 - data/lib/juicer/asset/path_resolver.rb +79 -0
 - data/lib/juicer/binary.rb +171 -0
 - data/lib/juicer/cache_buster.rb +131 -0
 - data/lib/juicer/chainable.rb +106 -0
 - data/lib/juicer/cli.rb +56 -0
 - data/lib/juicer/command/install.rb +61 -0
 - data/lib/juicer/command/list.rb +57 -0
 - data/lib/juicer/command/merge.rb +205 -0
 - data/lib/juicer/command/util.rb +32 -0
 - data/lib/juicer/command/verify.rb +60 -0
 - data/lib/juicer/css_cache_buster.rb +90 -0
 - data/lib/juicer/datafy/datafy.rb +20 -0
 - data/lib/juicer/dependency_resolver/css_dependency_resolver.rb +29 -0
 - data/lib/juicer/dependency_resolver/dependency_resolver.rb +101 -0
 - data/lib/juicer/dependency_resolver/javascript_dependency_resolver.rb +23 -0
 - data/lib/juicer/ext/logger.rb +5 -0
 - data/lib/juicer/ext/string.rb +47 -0
 - data/lib/juicer/ext/symbol.rb +15 -0
 - data/lib/juicer/image_embed.rb +129 -0
 - data/lib/juicer/install/base.rb +186 -0
 - data/lib/juicer/install/closure_compiler_installer.rb +69 -0
 - data/lib/juicer/install/jslint_installer.rb +51 -0
 - data/lib/juicer/install/rhino_installer.rb +53 -0
 - data/lib/juicer/install/yui_compressor_installer.rb +67 -0
 - data/lib/juicer/jslint.rb +90 -0
 - data/lib/juicer/merger/base.rb +74 -0
 - data/lib/juicer/merger/javascript_merger.rb +29 -0
 - data/lib/juicer/merger/stylesheet_merger.rb +110 -0
 - data/lib/juicer/minifyer/closure_compiler.rb +90 -0
 - data/lib/juicer/minifyer/java_base.rb +77 -0
 - data/lib/juicer/minifyer/yui_compressor.rb +96 -0
 - data/test/bin/jslint-1.0.js +523 -0
 - data/test/bin/jslint.js +523 -0
 - data/test/bin/rhino1_7R1.zip +0 -0
 - data/test/bin/rhino1_7R2-RC1.jar +0 -0
 - data/test/bin/rhino1_7R2-RC1.zip +0 -0
 - data/test/bin/yuicompressor +0 -0
 - data/test/bin/yuicompressor-2.3.5.zip +0 -0
 - data/test/bin/yuicompressor-2.4.2.jar +0 -0
 - data/test/bin/yuicompressor-2.4.2.zip +0 -0
 - data/test/fixtures/yui-download.html +425 -0
 - data/test/test_helper.rb +175 -0
 - data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
 - data/test/unit/juicer/asset/path_test.rb +370 -0
 - data/test/unit/juicer/cache_buster_test.rb +104 -0
 - data/test/unit/juicer/chainable_test.rb +94 -0
 - data/test/unit/juicer/command/install_test.rb +58 -0
 - data/test/unit/juicer/command/list_test.rb +81 -0
 - data/test/unit/juicer/command/merge_test.rb +162 -0
 - data/test/unit/juicer/command/util_test.rb +58 -0
 - data/test/unit/juicer/command/verify_test.rb +48 -0
 - data/test/unit/juicer/css_cache_buster_test.rb +71 -0
 - data/test/unit/juicer/datafy_test.rb +37 -0
 - data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
 - data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
 - data/test/unit/juicer/ext/string_test.rb +59 -0
 - data/test/unit/juicer/ext/symbol_test.rb +27 -0
 - data/test/unit/juicer/image_embed_test.rb +271 -0
 - data/test/unit/juicer/install/installer_base_test.rb +214 -0
 - data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
 - data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
 - data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
 - data/test/unit/juicer/jslint_test.rb +60 -0
 - data/test/unit/juicer/merger/base_test.rb +122 -0
 - data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
 - data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
 - data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
 - data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
 - data/test/unit/juicer_test.rb +1 -0
 - metadata +278 -0
 
| 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "pathname"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Juicer
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Command
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Utilities for Juicer command objects
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                module Util
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # Returns an array of files from a variety of input. Input may be a single
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # file, a single glob pattern or multiple files and/or patterns. It may
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # even be an array of mixed input.
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def files(*args)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    args.flatten.collect { |file| Dir.glob(file) }.flatten
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  #
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # Uses Pathname to calculate the shortest relative path from +path+ to
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # +reference_path+ (default is +Dir.cwd+)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #
         
     | 
| 
      
 20 
     | 
    
         
            +
                  def relative(paths, reference_path = Dir.pwd)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    paths = [paths].flatten.collect do |path|
         
     | 
| 
      
 22 
     | 
    
         
            +
                      path = Pathname.new(File.expand_path(path))
         
     | 
| 
      
 23 
     | 
    
         
            +
                      reference_path = Pathname.new(File.expand_path(reference_path))
         
     | 
| 
      
 24 
     | 
    
         
            +
                      path.relative_path_from(reference_path).to_s
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    paths.length == 1 ? paths.first : paths
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,60 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "juicer/command/util"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "rubygems"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "cmdparse"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "pathname"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Juicer
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Command
         
     | 
| 
      
 8 
     | 
    
         
            +
                # Verifies problem-free-ness of source code (JavaScript and CSS)
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                class Verify < CmdParse::Command
         
     | 
| 
      
 11 
     | 
    
         
            +
                  include Juicer::Command::Util
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  # Initializes command
         
     | 
| 
      
 14 
     | 
    
         
            +
                  #
         
     | 
| 
      
 15 
     | 
    
         
            +
                  def initialize(log = nil)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    super('verify', false, true)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @log = log || Logger.new($STDIO)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    self.short_desc = "Verifies that the given JavaScript/CSS file is problem free"
         
     | 
| 
      
 19 
     | 
    
         
            +
                    self.description = <<-EOF
         
     | 
| 
      
 20 
     | 
    
         
            +
            Uses JsLint (http://www.jslint.com) to check that code adheres to good coding
         
     | 
| 
      
 21 
     | 
    
         
            +
            practices to avoid potential bugs, and protect against introducing bugs by
         
     | 
| 
      
 22 
     | 
    
         
            +
            minifying.
         
     | 
| 
      
 23 
     | 
    
         
            +
                    EOF
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  # Execute command
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #
         
     | 
| 
      
 28 
     | 
    
         
            +
                  def execute(args)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    # Need atleast one file
         
     | 
| 
      
 30 
     | 
    
         
            +
                    raise ArgumentError.new('Please provide atleast one input file/pattern') if args.length == 0
         
     | 
| 
      
 31 
     | 
    
         
            +
                    Juicer::Command::Verify.check_all(files(args), @log)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def self.check_all(files, log = nil)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    log ||= Logger.new($stdio)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    jslint = Juicer::JsLint.new(:bin_path => Juicer.home)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    problems = false
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    # Check that JsLint is installed
         
     | 
| 
      
 40 
     | 
    
         
            +
                    raise FileNotFoundError.new("Missing 3rd party library JsLint, install with\njuicer install jslint") if jslint.locate_lib.nil?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    # Verify all files
         
     | 
| 
      
 43 
     | 
    
         
            +
                    files.each do |file|
         
     | 
| 
      
 44 
     | 
    
         
            +
                      log.info "Verifying #{file} with JsLint"
         
     | 
| 
      
 45 
     | 
    
         
            +
                      report = jslint.check(file)
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                      if report.ok?
         
     | 
| 
      
 48 
     | 
    
         
            +
                        log.info "  OK!"
         
     | 
| 
      
 49 
     | 
    
         
            +
                      else
         
     | 
| 
      
 50 
     | 
    
         
            +
                        problems = true
         
     | 
| 
      
 51 
     | 
    
         
            +
                        log.warn "  Problems detected"
         
     | 
| 
      
 52 
     | 
    
         
            +
                        log.warn "  #{report.errors.join("\n").gsub(/\n/, "\n  ")}\n"
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    !problems
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,90 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "juicer/chainable"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "juicer/cache_buster"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "juicer/asset/path_resolver"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Juicer
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # The CssCacheBuster is a tool that can parse a CSS file and substitute all
         
     | 
| 
      
 8 
     | 
    
         
            +
              # referenced URLs by a URL appended with a timestamp denoting it's last change.
         
     | 
| 
      
 9 
     | 
    
         
            +
              # This causes the URLs to be unique every time they've been modified, thus
         
     | 
| 
      
 10 
     | 
    
         
            +
              # facilitating using a far future expires header on your web server.
         
     | 
| 
      
 11 
     | 
    
         
            +
              #
         
     | 
| 
      
 12 
     | 
    
         
            +
              # See Juicer::CacheBuster for more information on how the cache buster URLs
         
     | 
| 
      
 13 
     | 
    
         
            +
              # work.
         
     | 
| 
      
 14 
     | 
    
         
            +
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              # When dealing with CSS files that reference absolute URLs like /images/1.png
         
     | 
| 
      
 16 
     | 
    
         
            +
              # you must specify the :document_root option that these URLs should be resolved
         
     | 
| 
      
 17 
     | 
    
         
            +
              # against.
         
     | 
| 
      
 18 
     | 
    
         
            +
              #
         
     | 
| 
      
 19 
     | 
    
         
            +
              # When dealing with full URLs (ie including hosts) you can optionally specify
         
     | 
| 
      
 20 
     | 
    
         
            +
              # an array of hosts to recognize as "local", meaning they serve assets from
         
     | 
| 
      
 21 
     | 
    
         
            +
              # the :document_root directory. This way even asset host cycling can benefit from
         
     | 
| 
      
 22 
     | 
    
         
            +
              # cache busters.
         
     | 
| 
      
 23 
     | 
    
         
            +
              #
         
     | 
| 
      
 24 
     | 
    
         
            +
              class CssCacheBuster
         
     | 
| 
      
 25 
     | 
    
         
            +
                include Juicer::Chainable
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @document_root = options[:document_root]
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @document_root.sub!(%r{/?$}, "") if @document_root
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @type = options[:type] || :soft
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @hosts = (options[:hosts] || []).collect { |h| h.sub!(%r{/?$}, "") }
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @contents = @base = nil
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                #
         
     | 
| 
      
 36 
     | 
    
         
            +
                # Update file. If no +output+ is provided, the input file is overwritten
         
     | 
| 
      
 37 
     | 
    
         
            +
                #
         
     | 
| 
      
 38 
     | 
    
         
            +
                def save(file, output = nil)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @contents = File.read(file)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  self.base = File.dirname(file)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  used = []
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  urls(file).each do |asset|
         
     | 
| 
      
 44 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 45 
     | 
    
         
            +
                      next if used.include?(asset.path)          
         
     | 
| 
      
 46 
     | 
    
         
            +
                      @contents.gsub!(asset.path, asset.path(:cache_buster_type => @type))
         
     | 
| 
      
 47 
     | 
    
         
            +
                    rescue Errno::ENOENT
         
     | 
| 
      
 48 
     | 
    
         
            +
                      puts "Unable to locate file #{asset.path}, skipping cache buster"
         
     | 
| 
      
 49 
     | 
    
         
            +
                    rescue ArgumentError => e
         
     | 
| 
      
 50 
     | 
    
         
            +
                      if e.message =~ /No document root/
         
     | 
| 
      
 51 
     | 
    
         
            +
                        raise FileNotFoundError.new("Unable to resolve path #{asset.path} without :document_root option")
         
     | 
| 
      
 52 
     | 
    
         
            +
                      else
         
     | 
| 
      
 53 
     | 
    
         
            +
                        raise e
         
     | 
| 
      
 54 
     | 
    
         
            +
                      end
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  File.open(output || file, "w") { |f| f.puts @contents }
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @contents = nil
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                chain_method :save
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                #
         
     | 
| 
      
 65 
     | 
    
         
            +
                # Returns all referenced URLs in +file+. Returned paths are absolute (ie,
         
     | 
| 
      
 66 
     | 
    
         
            +
                # they're resolved relative to the +file+ path.
         
     | 
| 
      
 67 
     | 
    
         
            +
                #
         
     | 
| 
      
 68 
     | 
    
         
            +
                def urls(file)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @contents = File.read(file) unless @contents
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  @contents.scan(/url\([\s"']*([^\)"'\s]*)[\s"']*\)/m).collect do |match|
         
     | 
| 
      
 72 
     | 
    
         
            +
                    path_resolver.resolve(match.first)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                protected
         
     | 
| 
      
 77 
     | 
    
         
            +
                def base=(base)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  @prev_base = @base
         
     | 
| 
      
 79 
     | 
    
         
            +
                  @base = base
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                def path_resolver
         
     | 
| 
      
 83 
     | 
    
         
            +
                  return @path_resolver if @path_resolver && @base == @prev_base
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  @path_resolver = Juicer::Asset::PathResolver.new(:document_root => @document_root,
         
     | 
| 
      
 86 
     | 
    
         
            +
                                                                   :hosts => @hosts,
         
     | 
| 
      
 87 
     | 
    
         
            +
                                                                   :base => @base)
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby -w
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # Datafy code lifted from http://segment7.net/projects/ruby/datafy/
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            require 'base64'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'cgi'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Datafy
         
     | 
| 
      
 9 
     | 
    
         
            +
              def Datafy::make_data_uri(content, content_type)
         
     | 
| 
      
 10 
     | 
    
         
            +
                outuri = 'data:' + content_type
         
     | 
| 
      
 11 
     | 
    
         
            +
                unless content_type =~ /^text/i # base64 encode if not text
         
     | 
| 
      
 12 
     | 
    
         
            +
                  outuri += ';base64'
         
     | 
| 
      
 13 
     | 
    
         
            +
                  content = Base64.encode64(content).gsub("\n", '')
         
     | 
| 
      
 14 
     | 
    
         
            +
                else
         
     | 
| 
      
 15 
     | 
    
         
            +
                  content = CGI::escape(content)
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
                outuri += ",#{content}"
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
              
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "juicer/dependency_resolver/dependency_resolver"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Juicer
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 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(?:\surl\(|\s)(['"]?)([^\?'"\)\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 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def extension
         
     | 
| 
      
 25 
     | 
    
         
            +
                  ".css"
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,101 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Juicer
         
     | 
| 
      
 2 
     | 
    
         
            +
              class DependencyResolver
         
     | 
| 
      
 3 
     | 
    
         
            +
                include Enumerable
         
     | 
| 
      
 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, &block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @files = []
         
     | 
| 
      
 21 
     | 
    
         
            +
                  _resolve(file, &block)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                #
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Yield files recursively. Resolve dependencies first, then call each, or
         
     | 
| 
      
 26 
     | 
    
         
            +
                # any other enumerable methods.
         
     | 
| 
      
 27 
     | 
    
         
            +
                #
         
     | 
| 
      
 28 
     | 
    
         
            +
                def each(&block)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @files.each(&block)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                #
         
     | 
| 
      
 33 
     | 
    
         
            +
                # Resolves a path relative to another. If the path is absolute (ie it
         
     | 
| 
      
 34 
     | 
    
         
            +
                # starts with a protocol or /) the <tt>:document_root</tt> options has to be
         
     | 
| 
      
 35 
     | 
    
         
            +
                # set as well.
         
     | 
| 
      
 36 
     | 
    
         
            +
                #
         
     | 
| 
      
 37 
     | 
    
         
            +
                def resolve_path(path, reference)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  # Absolute URL
         
     | 
| 
      
 39 
     | 
    
         
            +
                  if path =~ %r{^(/|[a-z]+:)}
         
     | 
| 
      
 40 
     | 
    
         
            +
                    if @options[:document_root].nil?
         
     | 
| 
      
 41 
     | 
    
         
            +
                      msg = "Cannot resolve absolute path '#{path}' without web root option"
         
     | 
| 
      
 42 
     | 
    
         
            +
                      raise ArgumentError.new(msg)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    path.sub!(%r{^[a-z]+://[^/]+/}, '')
         
     | 
| 
      
 46 
     | 
    
         
            +
                    return File.expand_path(File.join(@options[:document_root], path))
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  File.expand_path(File.join(File.dirname(reference), path))
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                private
         
     | 
| 
      
 53 
     | 
    
         
            +
                def parse(line)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  raise NotImplementedError.new
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def extension
         
     | 
| 
      
 58 
     | 
    
         
            +
                  raise NotImplementedError.new
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                #
         
     | 
| 
      
 62 
     | 
    
         
            +
                # Carries out the actual work of resolve. resolve resets the internal
         
     | 
| 
      
 63 
     | 
    
         
            +
                # file list and yields control to _resolve for rebuilding the file list.
         
     | 
| 
      
 64 
     | 
    
         
            +
                #
         
     | 
| 
      
 65 
     | 
    
         
            +
                def _resolve(file)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  imported_path = nil
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  IO.foreach(file) do |line|
         
     | 
| 
      
 69 
     | 
    
         
            +
                    # Implementing subclasses may throw :done from the parse method when
         
     | 
| 
      
 70 
     | 
    
         
            +
                    # the file is exhausted for dependency declaration possibilities.
         
     | 
| 
      
 71 
     | 
    
         
            +
                    catch(:done) do
         
     | 
| 
      
 72 
     | 
    
         
            +
                      imported_path = parse(line, imported_path)
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                      # If a dependency declaration was found
         
     | 
| 
      
 75 
     | 
    
         
            +
                      if imported_path
         
     | 
| 
      
 76 
     | 
    
         
            +
                        # Resolves a path relative to the file that imported it
         
     | 
| 
      
 77 
     | 
    
         
            +
                        imported_path = resolve_path(imported_path, file)
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                        if File.directory?(imported_path)
         
     | 
| 
      
 80 
     | 
    
         
            +
                          imported_files = Dir.glob(File.join(imported_path, "**", "*#{extension}"))
         
     | 
| 
      
 81 
     | 
    
         
            +
                        else
         
     | 
| 
      
 82 
     | 
    
         
            +
                          imported_files = [imported_path]
         
     | 
| 
      
 83 
     | 
    
         
            +
                        end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                        imported_files.each do |imported_file|
         
     | 
| 
      
 86 
     | 
    
         
            +
                          # Only keep processing file if it's not already included.
         
     | 
| 
      
 87 
     | 
    
         
            +
                          # Yield to block to allow caller to ignore file
         
     | 
| 
      
 88 
     | 
    
         
            +
                          if !@files.include?(imported_file) && (!block_given? || yield(imported_file))
         
     | 
| 
      
 89 
     | 
    
         
            +
                            # Check this file for imports before adding it to get order right
         
     | 
| 
      
 90 
     | 
    
         
            +
                            _resolve(imported_file) { |f| f != File.expand_path(file) }
         
     | 
| 
      
 91 
     | 
    
         
            +
                          end
         
     | 
| 
      
 92 
     | 
    
         
            +
                        end
         
     | 
| 
      
 93 
     | 
    
         
            +
                      end
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  file = File.expand_path(file)
         
     | 
| 
      
 98 
     | 
    
         
            +
                  @files << file if !@files.include?(file) && (!block_given? || yield(file))
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
              end
         
     | 
| 
      
 101 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "juicer/dependency_resolver/dependency_resolver"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Juicer
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Resolves @depends and @depend statements in comments in JavaScript files.
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Only the first comment in a JavaScript file is parsed
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              class JavaScriptDependencyResolver < DependencyResolver
         
     | 
| 
      
 8 
     | 
    
         
            +
                @@depends_pattern = /\@depends?\s+([^\s\'\"\;]+)/
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                private
         
     | 
| 
      
 11 
     | 
    
         
            +
                def parse(line, imported_file = nil)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  return $1 if line =~ @@depends_pattern
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  # If we have already skimmed through some @depend/@depends or a
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # closing comment we're done.
         
     | 
| 
      
 16 
     | 
    
         
            +
                  throw :done unless imported_file.nil? || !(line =~ /\*\//)
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def extension
         
     | 
| 
      
 20 
     | 
    
         
            +
                  ".js"
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Additions to core Ruby objects
         
     | 
| 
      
 3 
     | 
    
         
            +
            #
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            class String
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              unless String.method_defined?(:camel_case)
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Turn an underscored string into camel case, ie this_becomes -> ThisBecomes
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                def camel_case
         
     | 
| 
      
 12 
     | 
    
         
            +
                  self.split("_").inject("") { |str, piece| str + piece.capitalize }
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              unless String.method_defined?(:to_class)
         
     | 
| 
      
 17 
     | 
    
         
            +
                #
         
     | 
| 
      
 18 
     | 
    
         
            +
                # Treat a string as a class name and return the class. Optionally provide a
         
     | 
| 
      
 19 
     | 
    
         
            +
                # module to look up the class in.
         
     | 
| 
      
 20 
     | 
    
         
            +
                #
         
     | 
| 
      
 21 
     | 
    
         
            +
                def to_class(mod = nil)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  res = "#{mod}::#{self}".sub(/^::/, "").split("::").inject(Object) do |mod, obj|
         
     | 
| 
      
 23 
     | 
    
         
            +
                    raise "No such class/module" unless mod.const_defined?(obj)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    mod = mod.const_get(obj)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              unless String.method_defined?(:classify)
         
     | 
| 
      
 30 
     | 
    
         
            +
                #
         
     | 
| 
      
 31 
     | 
    
         
            +
                # Turn a string in either underscore or camel case form into a class directly
         
     | 
| 
      
 32 
     | 
    
         
            +
                #
         
     | 
| 
      
 33 
     | 
    
         
            +
                def classify(mod = nil)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  self.camel_case.to_class(mod)
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              unless String.method_defined?(:underscore)
         
     | 
| 
      
 39 
     | 
    
         
            +
                #
         
     | 
| 
      
 40 
     | 
    
         
            +
                # Turn a camelcase string into underscore string
         
     | 
| 
      
 41 
     | 
    
         
            +
                #
         
     | 
| 
      
 42 
     | 
    
         
            +
                def underscore
         
     | 
| 
      
 43 
     | 
    
         
            +
                  self.split(/([A-Z][^A-Z]*)/).find_all { |str| str != "" }.join("_").downcase
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Symbol
         
     | 
| 
      
 2 
     | 
    
         
            +
              #
         
     | 
| 
      
 3 
     | 
    
         
            +
              # Converts symbol to string and calls String#camel_case
         
     | 
| 
      
 4 
     | 
    
         
            +
              #
         
     | 
| 
      
 5 
     | 
    
         
            +
              def camel_case
         
     | 
| 
      
 6 
     | 
    
         
            +
                self.to_s.camel_case
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              # Converts symbol to string and calls String#classify
         
     | 
| 
      
 11 
     | 
    
         
            +
              #
         
     | 
| 
      
 12 
     | 
    
         
            +
              def classify(mod = nil)
         
     | 
| 
      
 13 
     | 
    
         
            +
                self.to_s.classify(mod)
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,129 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "juicer/chainable"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "juicer/cache_buster"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "juicer/asset/path_resolver"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Juicer
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # The ImageEmbed is a tool that can parse a CSS file and substitute all
         
     | 
| 
      
 8 
     | 
    
         
            +
              # referenced URLs by a data uri
         
     | 
| 
      
 9 
     | 
    
         
            +
              # 
         
     | 
| 
      
 10 
     | 
    
         
            +
              # - data uri (http://en.wikipedia.org/wiki/Data_URI_scheme)
         
     | 
| 
      
 11 
     | 
    
         
            +
              # 
         
     | 
| 
      
 12 
     | 
    
         
            +
              # Only local resources will be processed this way, external resources referenced
         
     | 
| 
      
 13 
     | 
    
         
            +
              # by absolute urls will be left alone
         
     | 
| 
      
 14 
     | 
    
         
            +
              # 
         
     | 
| 
      
 15 
     | 
    
         
            +
              class ImageEmbed
         
     | 
| 
      
 16 
     | 
    
         
            +
                include Juicer::Chainable
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # The maximum supported limit for modern browsers, See the Readme.rdoc for details
         
     | 
| 
      
 19 
     | 
    
         
            +
                SIZE_LIMIT = 32768
         
     | 
| 
      
 20 
     | 
    
         
            +
                
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                # Returns the size limit
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                def size_limit
         
     | 
| 
      
 25 
     | 
    
         
            +
                  SIZE_LIMIT
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @document_root = options[:document_root]
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @document_root.sub!(%r{/?$}, "") if @document_root # Remove trailing slash
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @type = options[:type] || :none
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @contents = nil
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @hosts = options[:hosts]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @path_resolver = Juicer::Asset::PathResolver.new(:document_root => options[:document_root],
         
     | 
| 
      
 35 
     | 
    
         
            +
                                                                   :hosts => options[:hosts])
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                #
         
     | 
| 
      
 39 
     | 
    
         
            +
                # Update file. If no +output+ is provided, the input file is overwritten
         
     | 
| 
      
 40 
     | 
    
         
            +
                #
         
     | 
| 
      
 41 
     | 
    
         
            +
                def save(file, output = nil)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  return unless @type == :data_uri
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  output_file = output || file
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @contents = File.read(file)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  used = []
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  @path_resolver = Juicer::Asset::PathResolver.new(:document_root => @document_root,
         
     | 
| 
      
 49 
     | 
    
         
            +
                                                                   :hosts => @hosts,
         
     | 
| 
      
 50 
     | 
    
         
            +
                                                                   :base => File.dirname(file))
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  assets = urls(file)
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  # TODO: Remove "?embed=true" from duplicate urls
         
     | 
| 
      
 55 
     | 
    
         
            +
                  duplicates = duplicate_urls(assets)
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  if duplicates.length > 0
         
     | 
| 
      
 58 
     | 
    
         
            +
                    Juicer::LOGGER.warn("Duplicate image urls detected, these images will not be embedded: #{duplicates.collect { |v| v.gsub('?embed=true', '') }.inspect}") 
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  assets.each do |asset|
         
     | 
| 
      
 62 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 63 
     | 
    
         
            +
                      next if used.include?(asset) || duplicates.include?(asset.path)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      used << asset
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                      # make sure we do not exceed SIZE_LIMIT
         
     | 
| 
      
 67 
     | 
    
         
            +
                      new_path = embed_data_uri(asset.filename)
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                      if new_path.length < SIZE_LIMIT
         
     | 
| 
      
 70 
     | 
    
         
            +
                        # replace the url in the css file with the data uri
         
     | 
| 
      
 71 
     | 
    
         
            +
                        @contents.gsub!(asset.path, embed_data_uri(asset.path))
         
     | 
| 
      
 72 
     | 
    
         
            +
                      else
         
     | 
| 
      
 73 
     | 
    
         
            +
                        Juicer::LOGGER.warn("The final data uri for the image located at #{asset.path.gsub('?embed=true', '')} exceeds #{SIZE_LIMIT} and will not be embedded to maintain compatability.") 
         
     | 
| 
      
 74 
     | 
    
         
            +
                      end
         
     | 
| 
      
 75 
     | 
    
         
            +
                    rescue Errno::ENOENT
         
     | 
| 
      
 76 
     | 
    
         
            +
                      puts "Unable to locate file #{asset.path}, skipping image embedding"
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  File.open(output || file, "w") { |f| f.puts @contents }
         
     | 
| 
      
 81 
     | 
    
         
            +
                  @contents = nil
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                chain_method :save
         
     | 
| 
      
 85 
     | 
    
         
            +
                        
         
     | 
| 
      
 86 
     | 
    
         
            +
                def embed_data_uri( path )
         
     | 
| 
      
 87 
     | 
    
         
            +
                  new_path = path
         
     | 
| 
      
 88 
     | 
    
         
            +
                  
         
     | 
| 
      
 89 
     | 
    
         
            +
                  if path.match( /\?embed=true$/ )
         
     | 
| 
      
 90 
     | 
    
         
            +
                    supported_file_matches = path.match( /(?:\.)(png|gif|jpg|jpeg)(?:\?embed=true)$/i )
         
     | 
| 
      
 91 
     | 
    
         
            +
                    filetype = supported_file_matches[1] if supported_file_matches
         
     | 
| 
      
 92 
     | 
    
         
            +
                    
         
     | 
| 
      
 93 
     | 
    
         
            +
                    if ( filetype )        
         
     | 
| 
      
 94 
     | 
    
         
            +
                      filename = path.gsub('?embed=true','')      
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                      # check if file exists, throw an error if it doesn't exist
         
     | 
| 
      
 97 
     | 
    
         
            +
                      if File.exist?( filename )
         
     | 
| 
      
 98 
     | 
    
         
            +
                        
         
     | 
| 
      
 99 
     | 
    
         
            +
                        # read contents of file into memory              
         
     | 
| 
      
 100 
     | 
    
         
            +
                        content = File.read( filename )
         
     | 
| 
      
 101 
     | 
    
         
            +
                        content_type = "image/#{filetype}"
         
     | 
| 
      
 102 
     | 
    
         
            +
                        
         
     | 
| 
      
 103 
     | 
    
         
            +
                        # encode the url
         
     | 
| 
      
 104 
     | 
    
         
            +
                        new_path = Datafy::make_data_uri( content, content_type )
         
     | 
| 
      
 105 
     | 
    
         
            +
                      else
         
     | 
| 
      
 106 
     | 
    
         
            +
                        puts "Unable to locate file #{filename} on local file system, skipping image embedding"
         
     | 
| 
      
 107 
     | 
    
         
            +
                      end
         
     | 
| 
      
 108 
     | 
    
         
            +
                    end
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
                  return new_path
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                #
         
     | 
| 
      
 114 
     | 
    
         
            +
                # Returns all referenced URLs in +file+.
         
     | 
| 
      
 115 
     | 
    
         
            +
                #
         
     | 
| 
      
 116 
     | 
    
         
            +
                def urls(file)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  @contents = File.read(file) unless @contents
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                  @contents.scan(/url\([\s"']*([^\)"'\s]*)[\s"']*\)/m).collect do |match|
         
     | 
| 
      
 120 
     | 
    
         
            +
                    @path_resolver.resolve(match.first)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                private
         
     | 
| 
      
 125 
     | 
    
         
            +
                def duplicate_urls(urls)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  urls.inject({}) { |h,v| h[v.path] = h[v.path].to_i+1; h }.reject{ |k,v| v == 1 }.keys
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
            end
         
     |