bin/jsus in jsus-0.2.6 vs bin/jsus in jsus-0.2.7
- old
+ new
@@ -18,177 +18,319 @@
require 'jsus'
require "fileutils"
require "optparse"
-start_time = Time.now
+module Jsus
+ class CLI
+ class <<self
+ attr_accessor :cli_options
-options = {}
-cli = OptionParser.new do |opts|
- opts.banner = "jsus #{Jsus.version}. Usage: jsus [options] <input_dir> <output_dir>"
+ def parse_command_line!
+ options = {}
+ cli = OptionParser.new do |opts|
+ opts.banner = "jsus #{Jsus.version}. Usage: jsus [options] <input_dir> <output_dir>"
- opts.on('-i', '--input-directory [DIR]', '[DEPRECATED] path to input directory ') do |dir|
- $stderr.puts "DEPRECATION NOTICE: please do not use -i command-line argument"
- options[:input_dir] = dir
- end
+ opts.on('-i', '--input-directory [DIR]', '[DEPRECATED] path to input directory ') do |dir|
+ $stderr.puts "DEPRECATION NOTICE: please do not use -i command-line argument"
+ options[:input_dir] = dir
+ end
- opts.on('-o', '--output-directory [DIR]', '[DEPRECATED] path to output directory ') do |dir|
- $stderr.puts "DEPRECATION NOTICE: please do not use -o command-line argument"
- options[:output_dir] = dir
- end
+ opts.on('-o', '--output-directory [DIR]', '[DEPRECATED] path to output directory ') do |dir|
+ $stderr.puts "DEPRECATION NOTICE: please do not use -o command-line argument"
+ options[:output_dir] = dir
+ end
- opts.on('-d', '--with-dependencies [DEPS]', 'path to directory containing dependency packages') do |dir|
- options[:deps_dir] = dir
- end
+ opts.on('-d', '--with-dependencies [DEPS]', 'path to directory containing dependency packages') do |dir|
+ options[:deps_dir] = dir
+ end
- opts.on('-g', '--generate-includes [ROOT]', 'generates includes.js file that you may use for ad-hoc requiring of dependencies, defaults to output directory') do |dir|
- options[:generate_includes] = true
- options[:includes_root] = dir
- end
+ opts.on('-g', '--generate-includes [ROOT]', 'generates includes.js file that you may use for ad-hoc requiring of dependencies, defaults to output directory') do |dir|
+ options[:generate_includes] = true
+ options[:includes_root] = dir
+ end
- opts.on('--generate-docs [*CLASSES]', Array, "generate docs for some of the sources. When given empty array, defaults to /**/*") do |docs|
- if !docs
- options[:documented_classes] = ["/**/*"]
- else
- options[:documented_classes] = docs
- end
- end
+ opts.on('--generate-docs [*CLASSES]', Array, "generate docs for some of the sources. When given empty array, defaults to /**/*") do |docs|
+ if !docs
+ options[:documented_classes] = ["/**/*"]
+ else
+ options[:documented_classes] = docs
+ end
+ end
- opts.on('--no-syntax-highlight', 'if you turned on docs generation, it will use syntax highlighting by default. This option prevents it') do
- options[:no_syntax_highlight] = true
- end
+ opts.on('--no-syntax-highlight', 'if you turned on docs generation, it will use syntax highlighting by default. This option prevents it') do
+ options[:no_syntax_highlight] = true
+ end
- opts.on('--validate-with [*VALIDATORS]', Array, 'performs a check against some of the validators. Available validators: mooforge') do |validators|
- options[:validators] = (validators || []).map {|v| v.downcase }
- end
+ opts.on('--validate-with [*VALIDATORS]', Array, 'performs a check against some of the validators. Available validators: mooforge') do |validators|
+ options[:validators] = (validators || []).map {|v| v.downcase }
+ end
- opts.on('--postproc [*PROCESSORS]', Array, 'performs postprocessing. Available postprocs:\n* moocompat12 -- removes mootools 1.2compat tags and their contents\n* mooltIE8 -- removes mootools ltIE8 compat tags and their contents') do |postprocs|
- options[:postproc] = postprocs
- end
+ opts.on('--postproc [*PROCESSORS]', Array, 'performs postprocessing. Available postprocs:\n* moocompat12 -- removes mootools 1.2compat tags and their contents\n* mooltIE8 -- removes mootools ltIE8 compat tags and their contents') do |postprocs|
+ options[:postproc] = postprocs
+ end
- opts.on_tail('-v', '--verbose', 'verbose mode, shows various debug messages') do
- options[:verbose] = true
- end
+ opts.on('--compress', 'compresses resulting file with YUI compressor') do
+ options[:compress] = true
+ end
- opts.on_tail('-b', '--benchmark', 'shows time spent on various stages') do
- options[:benchmark] = true
- end
+ opts.on_tail('-v', '--verbose', 'verbose mode, shows various debug messages') do
+ Jsus.verbose = true
+ end
- opts.on_tail('--without-scripts-info', 'do not generate scripts.json') do
- options[:without_scripts_info] = true
- end
+ opts.on_tail('-b', '--benchmark', 'shows time spent on various stages') do
+ options[:benchmark] = true
+ end
- opts.on_tail('--without-tree-info', 'do not generate tree.json') do
- options[:without_tree_info] = true
- end
+ opts.on_tail('--without-scripts-info', 'do not generate scripts.json') do
+ options[:without_scripts_info] = true
+ end
+ opts.on_tail('--without-tree-info', 'do not generate tree.json') do
+ options[:without_tree_info] = true
+ end
+ opts.on_tail('--watch', 'watch file system events for *.js files in subdirectories and rerun jsus with the same parameters') do
+ options[:watch] = true
+ end
- opts.on_tail('-h', '--help', 'Show this message') do
- puts opts
- exit
- end
-end
-cli.parse!
+ opts.on_tail('-h', '--help', 'Show this message') do
+ puts opts
+ exit
+ end
+ end
+ cli.parse!
-options[:input_dir] ||= ARGV[0]
-options[:output_dir] ||= ARGV[1]
+ options[:input_dir] ||= ARGV[0]
+ options[:output_dir] ||= ARGV[1]
-if !(options[:input_dir] && options[:output_dir])
- puts cli
- exit
-end
+ if !(options[:input_dir] && options[:output_dir])
+ puts cli
+ exit
+ end
+ options[:input_dir] = File.expand_path(options[:input_dir])
+ options[:output_dir] = File.expand_path(options[:output_dir])
+ self.cli_options = options
+ end
-compile_start_time = Time.now
+ def watch?
+ cli_options[:watch]
+ end
-Jsus.verbose = options[:verbose]
+ def launch!
+ new.launch
+ end
-pool_load_start_time = Time.now
-pool = if options[:deps_dir]
- Jsus::Pool.new(options[:deps_dir])
-else
- Jsus::Pool.new
-end
-pool_load_finish_time = Time.now
+ def run!
+ parse_command_line!
+ if watch?
+ watch do |base, match|
+ full_path = File.join(base, match)
+ unless full_path.include?(cli_options[:output_dir])
+ puts "#{match} has changed, relaunching jsus..."
+ launch!
+ puts "... done"
+ puts ""
+ end
+ end
+ else
+ launch!
+ end
+ end
-package = Jsus::Package.new(options[:input_dir], :pool => pool)
-package.include_dependencies!
-output_dir = options[:output_dir]
+ def watch
+ require 'fssm'
+ puts "Jsus enters watch mode, it will watch your files for changes and relaunch itself"
+ puts ""
+ start_directory = Dir.pwd
+ watched_dirs = [cli_options[:input_dir], cli_options[:deps_dir]].compact.map {|path| File.expand_path(path)}
+ FSSM.monitor do
+ watched_dirs.each do |dir|
+ path dir do
+ glob ["**/*.js", "**/package.yml", "**/package.json"]
+ update {|base, relative| yield base, relative }
+ delete {|base, relative| yield base, relative }
+ create {|base, relative| yield base, relative }
+ end
+ end
+ end
-package_content = package.compile(nil)
+ rescue LoadError => e
+ puts "You need to install fssm gem for --watch option."
+ puts "You may also want to install rb-fsevent for OS X"
+ raise e
+ end
-if options[:postproc]
- options[:postproc].each do |processor|
- case processor.strip
- when /^moocompat12$/i
- package_content.gsub!(/\/\/<1.2compat>.*?\/\/<\/1.2compat>/m, '')
- package_content.gsub!(/\/\*<1.2compat>\*\/.*?\/\*<\/1.2compat>\*\//m, '')
- when /^mooltie8$/i
- package_content.gsub!(/\/\/<ltIE8>.*?\/\/<\/ltIE8>/m, '')
- package_content.gsub!(/\/\*<ltIE8>\*\/.*?\/\*<\/ltIE8>\*\//m, '')
- else
- $stderr.puts "Unknown post-processor: #{processor}"
end
- end
-end
-FileUtils.mkdir_p(output_dir)
-package_filename = File.join(output_dir, package.filename)
-File.open(package_filename, 'w') {|f| f << package_content }
-package.generate_scripts_info(output_dir) unless options[:without_scripts_info]
-package.generate_tree(output_dir) unless options[:without_tree_info]
+ attr_accessor :options
-# Validations
-validators_map = {"mooforge" => Jsus::Util::Validator::Mooforge}
-(options[:validators] || []).each do |validator_name|
- if validator = validators_map[validator_name]
- errors = validator.new(pool.sources.to_a & package.source_files.to_a).validation_errors
- unless errors.empty?
- puts "Validator #{validator_name} found errors: "
- errors.each {|e| puts " * #{e}"}
+ def initialize(options = Jsus::CLI.cli_options)
+ @options = options
end
- else
- puts "No such validator: #{validator_name}"
- end
-end
-# Postprocs, sort of hack
+ def launch
+ checkpoint(:start)
+ setup_output_directory
+ preload_pool
+ load_package
+ compile_package
+ post_process if options[:postproc]
+ compress_package if options[:compress]
+ package_filename = File.join(@output_dir, @package.filename)
+ File.open(package_filename, 'w') {|f| f << @package_content }
+ generate_supplemental_files
+ validate_sources
+ generate_includes if options[:generate_includes]
+ generate_docs if options[:documented_classes] && !options[:documented_classes].empty?
+ output_benchmarks
+ end
-# Hack, hack, hack >:E
-if options[:generate_includes]
- includes_root = options[:includes_root] || output_dir
- File.open(File.join(output_dir, "includes.js"), "w") do |f|
- c = Jsus::Container.new(*(package.source_files.to_a + package.linked_external_dependencies.to_a))
- script = %{
- (function(prefix, loader) {
- var sources = %sources%;
- if (!loader) loader = function(path) {
- document.write('<scr' + 'ipt src="' + (prefix || '') + path + '"></script>');
- }
- for (var i = 0, j = sources.length; i < j; i++) loader(sources[i]);
- })(window.prefix, window.loader);}.sub("%sources%", JSON.pretty_generate(c.required_files(includes_root)))
- f.puts script
- end
-end
+ def setup_output_directory
+ @output_dir = options[:output_dir]
+ FileUtils.mkdir_p(@output_dir)
+ end
-docs_start_time = Time.now
-# Generate documentation
-if options[:documented_classes] && !options[:documented_classes].empty?
- documenter = Jsus::Util::Documenter.new(:highlight_source => !options[:no_syntax_highlight])
- package.source_files.each {|source| documenter << source }
- pool.sources.each {|source| documenter << source } if pool
- documenter.only(options[:documented_classes]).generate(output_dir + "/docs")
-end
-docs_finish_time = Time.now
+ def preload_pool
+ @pool = if options[:deps_dir]
+ Jsus::Pool.new(options[:deps_dir])
+ else
+ Jsus::Pool.new
+ end
+ checkpoint(:pool)
+ end
-finish_time = Time.now
+ def load_package
+ @package = Jsus::Package.new(options[:input_dir], :pool => @pool)
+ @package.include_dependencies!
+ checkpoint(:dependencies)
+ end
+ def compile_package
+ @package_content = @package.compile(nil)
+ checkpoint(:compilation)
+ end
-if options[:benchmark]
- puts "Benchmarking results:"
- puts "Total execution time: #{format("%.3f" ,finish_time - start_time)}s"
- puts ""
- puts "Of them:"
- puts "Pool preloading time: #{format("%.3f", pool_load_finish_time - pool_load_start_time)}s"
- puts "Docs generation time: #{format("%.3f", docs_finish_time - docs_start_time)}s" if options[:documented_classes]
- puts "Total compilation time: #{format("%.3f", finish_time - compile_start_time - (pool_load_finish_time - pool_load_start_time))}s"
+ def post_process
+ options[:postproc].each do |processor|
+ case processor.strip
+ when /^moocompat12$/i
+ @package_content.gsub!(/\/\/<1.2compat>.*?\/\/<\/1.2compat>/m, '')
+ @package_content.gsub!(/\/\*<1.2compat>\*\/.*?\/\*<\/1.2compat>\*\//m, '')
+ when /^mooltie8$/i
+ @package_content.gsub!(/\/\/<ltIE8>.*?\/\/<\/ltIE8>/m, '')
+ @package_content.gsub!(/\/\*<ltIE8>\*\/.*?\/\*<\/ltIE8>\*\//m, '')
+ else
+ $stderr.puts "Unknown post-processor: #{processor}"
+ end
+ end
+ checkpoint(:postproc)
+ end
+
+ def compress_package
+ require 'yui/compressor'
+ compressor = YUI::JavaScriptCompressor.new(:munge => true)
+ compressed_content = compressor.compress(@package_content)
+ if compressed_content != ""
+ compression_ratio = compressed_content.size.to_f / @package_content.size.to_f
+ @package_content = compressed_content
+ else
+ compression_ratio = 1.00
+ puts "ERROR: YUI compressor could not parse input. Falling back to uncompressed version"
+ puts "Compressor command used: #{compressor.command.join(' ')}"
+ end
+ puts "Compression ratio: #{sprintf("%.2f%%", compression_ratio * 100)}" if Jsus.verbose?
+ checkpoint(:compress)
+ rescue LoadError
+ puts 'ERROR: You need "yui-compressor" gem in order to use --compress option'
+ end
+
+ def generate_supplemental_files
+ @package.generate_scripts_info(@output_dir) unless options[:without_scripts_info]
+ @package.generate_tree(@output_dir) unless options[:without_tree_info]
+ checkpoint(:supplemental_files)
+ end
+
+ def generate_includes
+ includes_root = options[:includes_root] || @output_dir
+ File.open(File.join(@output_dir, "includes.js"), "w") do |f|
+ c = Jsus::Container.new(*(@package.source_files.to_a + @package.linked_external_dependencies.to_a))
+ script = %{
+ (function(prefix, loader) {
+ var sources = %sources%;
+ if (!loader) loader = function(path) {
+ document.write('<scr' + 'ipt src="' + (prefix || '') + path + '"></script>');
+ }
+ for (var i = 0, j = sources.length; i < j; i++) loader(sources[i]);
+ })(window.prefix, window.loader);}.sub("%sources%", JSON.pretty_generate(c.required_files(includes_root)))
+ f.puts script
+ end
+ checkpoint(:includes)
+ end
+
+ def generate_docs
+ documenter = Jsus::Util::Documenter.new(:highlight_source => !options[:no_syntax_highlight])
+ @package.source_files.each {|source| documenter << source }
+ @pool.sources.each {|source| documenter << source }
+ documenter.only(options[:documented_classes]).generate(@output_dir + "/docs")
+ checkpoint(:documentation)
+ end
+
+ def validate_sources
+ validators_map = {"mooforge" => Jsus::Util::Validator::Mooforge}
+ (options[:validators] || []).each do |validator_name|
+ if validator = validators_map[validator_name]
+ errors = validator.new(@pool.sources.to_a & @package.source_files.to_a).validation_errors
+ unless errors.empty?
+ puts "Validator #{validator_name} found errors: "
+ errors.each {|e| puts " * #{e}"}
+ end
+ else
+ puts "No such validator: #{validator_name}"
+ end
+ end
+ checkpoint(:validators)
+ end
+
+ def output_benchmarks
+ if options[:benchmark]
+ puts "Benchmarking results:"
+ puts "Total execution time: #{formatted_time_for(:all)}"
+ puts ""
+ puts "Of them:"
+ puts "Pool preloading time: #{formatted_time_for(:pool)}"
+ puts "Docs generation time: #{formatted_time_for(:documentation)}" if options[:documented_classes] && !options[:documented_classes].empty?
+ puts "Total compilation time: #{formatted_time_for(:compilation)}"
+ puts "Post-processing time: #{formatted_time_for(:postproc)}" if options[:postproc]
+ puts "Compression time: #{formatted_time_for(:compress)}" if options[:compress]
+ end
+ end
+
+ def checkpoint(checkpoint_name)
+ @checkpoints ||= {}
+ @time_for ||= {}
+ @checkpoints[checkpoint_name] = Time.now
+ if @last_checkpoint
+ @time_for[checkpoint_name] = @checkpoints[checkpoint_name] - @last_checkpoint
+ end
+ @last_checkpoint = Time.now
+ end
+
+ def checkpoint?(checkpoint_name)
+ @checkpoints[checkpoint_name]
+ end
+
+ def time_for(checkpoint_name)
+ if checkpoint_name == :all
+ @last_checkpoint - @checkpoints[:start]
+ else
+ @time_for[checkpoint_name]
+ end
+ end
+
+ def formatted_time_for(checkpoint_name)
+ "#{format("%.3f", time_for(checkpoint_name))}s"
+ end
+ end
end
+
+Jsus::CLI.run!
\ No newline at end of file