Class: Juicer::Command::Merge

Inherits:
CmdParse::Command
  • Object
show all
Includes:
Util
Defined in:
lib/juicer/command/merge.rb

Overview

The compress command combines and minifies CSS and JavaScript files

Instance Method Summary collapse

Methods included from Util

#files, #relative

Constructor Details

#initialize(log = nil) ⇒ Merge

Initializes compress command



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/juicer/command/merge.rb', line 15

def initialize(log = nil)
  super('merge', false, true)
  @types = { :js => Juicer::Merger::JavaScriptMerger,
             :css => Juicer::Merger::StylesheetMerger }
  @output = nil                   # File to write to
  @force = false                  # Overwrite existing file if true
  @type = nil                     # "css" or "js" - for minifyer
  @minifyer = "yui_compressor"    # Which minifyer to use
  @opts = {}                      # Path to minifyer binary
  @arguments = nil                # Minifyer arguments
  @ignore = false                 # Ignore syntax problems if true
  @cache_buster = :soft           # What kind of cache buster to use, :soft or :hard
  @hosts = nil                    # Hosts to use when replacing URLs in stylesheets
  @document_root = nil                 # Used to understand absolute paths
  @relative_urls = false          # Make the merger use relative URLs
  @absolute_urls = false          # Make the merger use absolute URLs
  @local_hosts = []               # Host names that are served from :document_root
  @verify = true                  # Verify js files with JsLint
  @image_embed_type = :none       # Embed images in css files, options are :none, :data_uri
  @force_image_embed = false

  @log = log || Logger.new(STDOUT)

  self.short_desc = "Combines and minifies CSS and JavaScript files"
  self.description = <<-EOF
Each file provided as input will be checked for dependencies to other files,
and those files will be added to the final output

For CSS files the dependency checking is done through regular @import
statements.

For JavaScript files you can tell Juicer about dependencies through special
comment switches. These should appear inside a multi-line comment, specifically
inside the first multi-line comment. The switch is @depend or @depends, your
choice.

The -m --minifyer switch can be used to select which minifyer to use. Currently
only YUI Compressor and Google Closure Compiler is supported, ie -m yui_compressor (default) or -m closure_compiler. When using
the compressor the path should be the path to where the jar file is found.
  EOF

  self.options = CmdParse::OptionParserWrapper.new do |opt|
    opt.on("-o", "--output file", "Output filename") { |filename| @output = filename }
    opt.on("-p", "--path path", "Path to compressor binary") { |path| @opts[:bin_path] = path }
    opt.on("-m", "--minifyer name", "Which minifer to use. Currently only supports yui_compressor and closure compiler") { |name| @minifyer = name }
    opt.on("-f", "--force", "Force overwrite of target file") { @force = true }
    opt.on("-a", "--arguments arguments", "Arguments to minifyer, escape with quotes") { |arguments|
      @arguments = arguments.to_s.gsub(/(^['"]|["']$)/, "")
    }
    opt.on("-i", "--ignore-problems", "Merge and minify even if verifyer finds problems") { @ignore = true }
    opt.on("-s", "--skip-verification", "Skip JsLint verification (js files only). Not recomended!") { @verify = false }
    opt.on("-t", "--type type", "Juicer can only guess type when files have .css or .js extensions. Specify js or\n" +
                     (" " * 37) + "css with this option in cases where files have other extensions.") { |type| @type = type.to_sym }
    opt.on("-h", "--hosts hosts", "Cycle asset hosts for referenced urls. Comma separated") { |hosts| @hosts = hosts.split(",") }
    opt.on("-l", "--local-hosts hosts", "Host names that are served from --document-root (can be given cache busters). Comma separated") do |hosts|
      @local_hosts = hosts.split(",")
    end
    opt.on("", "--all-hosts-local", "Treat all hosts as local (ie served from --document-root)") { @all_hosts_local = true }
    opt.on("-r", "--relative-urls", "Convert all referenced URLs to relative URLs. Requires --document-root if\n" +
                     (" " * 37) + "absolute URLs are used. Only valid for CSS files") { |t| @relative_urls = true }
    opt.on("-b", "--absolute-urls", "Convert all referenced URLs to absolute URLs. Requires --document-root.\n" +
                     (" " * 37) + "Works with cycled asset hosts. Only valid for CSS files") { |t| @absolute_urls = true }
    opt.on("-d", "--document-root dir", "Path to resolve absolute URLs relative to") { |path| @document_root = path }
    opt.on("-c", "--cache-buster type", "none, soft, rails, hard or md5. Default is soft, which adds timestamps to\n" +
                     (" " * 37) + "reference URLs as query parameters. None leaves URLs untouched, rails adds\n" +
                     (" " * 37) + "timestamps in the same format as Rails' image_tag helper. Hard and md5 alters\n" +
                     (" " * 37) + "file names") do |type|
      @cache_buster = [:soft, :hard, :rails, :md5].include?(type.to_sym) ? type.to_sym : nil
    end
    opt.on("-e", "--embed-images type", "none or data_uri. Default is none. Data_uri embeds images using Base64 encoding\n" +
                     (" " * 37) + "None leaves URLs untouched. Candiate images must be flagged with '?embed=true to be considered") do |embed|
      @image_embed_type = [:none, :data_uri].include?(embed.to_sym) ? embed.to_sym : nil
    end
    opt.on("", "--force-image-embed", "Will force all images to embed without having to tag with embed parameter") { @force_image_embed = true }
  end
end

Instance Method Details

#execute(args) ⇒ Object

Execute command



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/juicer/command/merge.rb', line 94

def execute(args)
  if (files = files(args)).length == 0
    @log.fatal "Please provide atleast one input file"
    raise SystemExit.new("Please provide atleast one input file")
  end

  # Copy hosts to local_hosts if --all-hosts-local was specified
  @local_hosts = @hosts if @all_hosts_local

  # Figure out which file to output to
  output = output(files.first)

  # Warn if file already exists
  if File.exists?(output) && !@force
    msg = "Unable to continue, #{output} exists. Run again with --force to overwrite"
    @log.fatal msg
    raise SystemExit.new(msg)
  end

  # Set up merger to resolve imports and so on. Do not touch URLs now, if
  # asset host cycling is added at this point, the cache buster WILL be
  # confused
  merger = merger(output).new(files, :relative_urls => @relative_urls,
                                     :absolute_urls => @absolute_urls,
                                     :document_root => @document_root,
                                     :hosts => @hosts)

  # Fail if syntax trouble (js only)
  if @verify && !Juicer::Command::Verify.check_all(merger.files.reject { |f| f =~ /\.css$/ }, @log)
    @log.error "Problems were detected during verification"
    raise SystemExit.new("Input files contain problems") unless @ignore
    @log.warn "Ignoring detected problems"
  end

  # Set command chain and execute
  merger.set_next(image_embed(output)).set_next(cache_buster(output)).set_next(minifyer)
  merger.save(output)

  # Print report
  @log.info "Produced #{relative output} from"
  merger.files.each { |file| @log.info "  #{relative file}" }
rescue FileNotFoundError => err
  # Handle missing document-root option
  puts err.message.sub(/:document_root/, "--document-root")
end