lib/jim/cli.rb in jim-0.2.3 vs lib/jim/cli.rb in jim-0.3.0.pre

- old
+ new

@@ -1,214 +1,252 @@ -require 'optparse' -require 'readline' +require 'thor' module Jim + # CLI handles the command line interface for the `jim` binary. It is a `Thor` + # application. + class CLI < ::Thor + include Thor::Actions - # CLI handles the command line interface for the `jim` binary. - # The layout is farily simple. Options are parsed using optparse.rb and - # the different public methods represent 1-1 the commands provided by the bin. - class CLI + attr_accessor :jimfile, :jimhome, :debug, :force - attr_accessor :jimfile, :jimhome, :force, :stdout + source_root File.dirname(__FILE__) + '/templates' + class_option "jimhome", + :type => :string, + :banner => "set the install path/JIMHOME dir (default ~/.jim)" + + class_option "jimfile", + :type => :string, + :aliases => '-j', + :banner => "load specific Jimfile at path (default ./Jimfile)" + + class_option "force", + :default => false, + :aliases => '-f', + :banner => "force file creation/overwrite" + + class_option "debug", + :default => false, + :aliases => '-d', + :banner => "set log level to debug" + + class_option "version", + :type => :boolean, + :aliases => '-v', + :banner => "print version and exit" + # create a new instance with the args passed from the command line i.e. ARGV - def initialize(args) - @output = "" + def initialize(*) + super + if options[:version] + say "jim #{Jim::VERSION}", :red + exit + end # set the default jimhome - self.jimhome = Pathname.new(ENV['JIMHOME'] || '~/.jim').expand_path + self.jimhome = Pathname.new(options[:jimhome] || ENV['JIMHOME'] || '~/.jim').expand_path # parse the options - self.jimfile = Pathname.new('Jimfile') - @args = parse_options(args) - ## try to run based on args + self.jimfile = Pathname.new(options[:jimfile] || 'Jimfile').expand_path + self.force = options[:force] + self.debug = options[:debug] + logger.level = Logger::DEBUG if debug end - # method called by the bin directly after initialization. - def run(reraise = false) - command = @args.shift - if command && respond_to?(command) - self.send(command, *@args) - elsif command.nil? || command.strip == '' - cheat - else - @output << "No action found for #{command}. Run -h for help." - end - @output - rescue ArgumentError => e - @output << "#{e.message} for #{command}" - raise e if reraise - rescue Jim::FileExists => e - @output << "#{e.message} already exists, bailing. Use --force if you're sure" - raise e if reraise - rescue => e - @output << e.message + " (#{e.class})" - raise e if reraise - end - - # list the possible commands to the logger - def commands - logger.info "Usage: jim [options] [command] [args]\n" - logger.info "Commands:" - logger.info template('commands') - end - - # list the possible commands without detailed descriptions - def cheat - logger.info "Usage: jim [options] [command] [args]\n" - logger.info "Commands:" - logger.info [*template('commands')].grep(/^\w/).join - logger.info "run commands for details" - end - alias :help :cheat - - # initialize the current dir with a new Jimfile + desc 'init [APPDIR]', + 'Create an example Jimfile at path or the current directory if path is omitted' def init(dir = nil) dir = Pathname.new(dir || '') jimfile_path = dir + 'Jimfile' - if jimfile_path.readable? && !force - raise Jim::FileExists.new(jimfile_path) - else - File.open(jimfile_path, 'w') do |f| - f << template('jimfile') - end - logger.info "wrote Jimfile to #{jimfile_path}" - end + template('jimfile', jimfile_path) end - # install the file/project `url` into `jimhome` + desc 'install <URL> [NAME] [VERSION]', + "Install the file(s) at url into the JIMHOME directory." + + long_desc <<-EOT + Install the file(s) at url into the JIMHOME directory. URL can be any path + or url that Downlow understands. This means: + + jim install http://code.jquery.com/jquery-1.4.1.js\n + jim install ../sammy/\n + jim install gh://quirkey/sammy\n + jim install git://github.com/jquery/jquery.git\n + EOT def install(url, name = false, version = false) Jim::Installer.new(url, jimhome, :force => force, :name => name, :version => version).install end - # bundle the files specified in Jimfile into `to` - def bundle(to = nil) - to = STDOUT if stdout - io = bundler.bundle!(to) - logger.info "Wrote #{File.size(io.path) / 1024}kb" if io.respond_to? :path + desc 'bundle [BUNDLE_NAME]', + "Bundle the files specified in Jimfile" + long_desc <<-EOT + Concatenate all the bundles listed in a Jimfile and save them to the bundle dir + specified in the options or in the Jimfile. + + If [BUNDLE_NAME] is specified, only bundles that specific bundle. + + If no Jimfile is set in the options, assumes ./Jimfile. + EOT + method_option "bundle_dir", + :type => :string, + :banner => "override the bundle_dir set in the Jimfile" + method_option "stdout", + :default => false, + :aliases => '-o', + :type => :boolean, + :banner => "write the bundle to STDOUT" + def bundle(bundle_name = nil) + make_bundle(bundle_name, false) end - # compress the files specified in Jimfile into `to` - def compress(to = nil) - to = STDOUT if stdout - io = bundler.compress!(to) - logger.info "Wrote #{File.size(io.path) / 1024}kb" if io.respond_to? :path + desc "compress [BUNDLE_NAME]", + "Bundle all the files listed in a Jimfile, run through the google closure " + + "compiler and save them to [COMPRESSED_PATH]." + long_desc <<-EOT + Concatenate all the bundles listed in a Jimfile, run them through the google + closure compiler and save them to the bundle dirspecified in the options or + in the Jimfile. + + If a [BUNDLE_NAME] is specified, only bundle and compress that bundle. + + If no Jimfile is set in the options, assumes ./Jimfile. + EOT + method_option "bundle_dir", + :type => :string, + :banner => "override the bundle_dir set in the Jimfile" + method_option "stdout", + :default => false, + :aliases => '-o', + :type => :boolean, + :banner => "write the bundle to STDOUT" + def compress(bundle_name = nil) + make_bundle(bundle_name, true) end - # copy/vendor all the files specified in Jimfile to `dir` + desc "vendor [VENDOR_DIR]", + "Copy all the files listed in Jimfile to the vendor_dir" def vendor(dir = nil) - bundler.vendor!(dir, force) + dir = bundler.vendor!(dir, force) + say("Vendored files to #{dir}", :green) + rescue Jim::Error => e + say e.message, :red end - # list the only the _installed_ projects and versions. - # Match names against `search` if supplied. + desc "list [SEARCH]", + "List all the installed packages and their versions, optionally limiting by [SEARCH]" def list(search = nil) - logger.info "Getting list of installed files in\n#{installed_index.directories.join(':')}" - logger.info "Searching for '#{search}'" if search + say "Getting list of installed files in" + say("#{installed_index.directories.join(':')}", :yellow) + say("Searching for '#{search}'", :yellow) if search list = installed_index.list(search) - logger.info "Installed:" + say "Installed:" print_version_list(list) end - alias :installed :list + map "installed" => "list" - # list all available projects and versions including those in the local path, or - # paths specified in a Jimfile. - # Match names against `search` if supplied. + desc "available [SEARCH]", + "List all available projects and versions " + + "including those in the local path, or paths specified in a Jimfile" def available(search = nil) - logger.info "Getting list of all available files in\n#{index.directories.join("\n")}" - logger.info "Searching for '#{search}'" if search + say "Getting list of all available files in\n#{index.directories.join("\n")}" + say "Searching for '#{search}'" if search list = index.list(search) - logger.info "Available:" + say "Available:" print_version_list(list) end - # Iterates over matching files and prompts for removal + desc "remove <NAME> [VERSION]", + "Iterate through the install files and prompt for the removal of those " + + "matching the supplied NAME and VERSION" def remove(name, version = nil) - logger.info "Looking for files matching #{name} #{version}" + say "Looking for files matching #{name} #{version}" files = installed_index.find_all(name, version) if files.length > 0 - logger.info "Found #{files.length} matching files" + say "Found #{files.length} matching files" removed = 0 files.each do |filename| - response = Readline.readline("Remove #{filename}? (y/n)\n") - if response.strip =~ /y/i - logger.info "Removing #{filename}" + do_remove = yes?("Remove #{filename}?", :red) + if do_remove + say "Removing #{filename}" filename.delete removed += 1 else - logger.info "Skipping #{filename}" + say "Skipping #{filename}", :yellow end end - logger.info "Removed #{removed} files." + say "Removed #{removed} files." else - logger.info "No installed files matched." + say "No installed files matched." end end - alias :uninstall :remove + map "uninstall" => "remove" - # list the files and their resolved paths specified in the Jimfile + desc "resolve", + "Resolve all the paths listed in a Jimfile and print them to STDOUT. " + + "If no Jimfile is set in the options, assumes ./Jimfile." def resolve resolved = bundler.resolve! - logger.info "Files:" - resolved.each do |r| - logger.info r.join(" | ") + say "Files:" + resolved.each do |bundle_name, requirements| + say bundle_name, :green + say "-----------------------", :green + requirements.each do |path, name, version| + say [name, version, path].join(" | ") + "\n" + end end resolved + rescue Jim::Error => e + say e.message, :red end - # vendor to dir, then bundle and compress the Jimfile contents + desc "pack [DIR]", + "Runs in order, vendor, bundle, compress. This command " + + "simplifies the common workflow of vendoring and re-bundling " + + "before committing or deploying changes to a project" def pack(dir = nil) - logger.info "packing the Jimfile for this project" - vendor(dir) - bundle - compress + say "Packing the Jimfile for this project" + invoke :vendor, [dir] + invoke :bundle + invoke :compress end - private - def parse_options(runtime_args) - OptionParser.new("", 24, ' ') do |opts| - opts.banner = "Usage: jim [options] [command] [args]" + desc "watch [DIR]", + "Watches your Jimfile and JS files and triggers `bundle` if something " + + "changes. Handy for development." + def watch(dir = nil) + require 'fssm' + run_update = lambda {|type, path| + unless bundler.bundle_paths.any? {|p| path.include?(p) } + say("--> #{path} #{type}") + system "jim bundle" + end + } + say "Now watching JS files..." + run_update["started", 'Jimfile'] + FSSM.monitor(Dir.pwd, [File.join('**','*.js'), 'Jimfile']) do + update do |base, relative| + run_update["changed", relative] + end - opts.separator "" - opts.separator "jim options:" + create do |base, relative| + run_update["created", relative] + end - opts.on("--jimhome path/to/home", "set the install path/JIMHOME dir (default ~/.jim)") {|h| - self.jimhome = Pathname.new(h) - } - - opts.on("-j", "--jimfile path/to/jimfile", "load specific Jimfile at path (default ./Jimfile)") { |j| - self.jimfile = Pathname.new(j) - } - - opts.on("-f", "--force", "force file creation/overwrite") {|f| - self.force = true - } - - opts.on("-d", "--debug", "set log level to debug") {|d| - logger.level = Logger::DEBUG - } - - opts.on("-o", "--stdout", "write output of commands (like bundle and compress to STDOUT)") {|o| - logger.level = Logger::ERROR - self.stdout = true - } - - opts.on("-v", "--version", "print version") {|d| - puts "jim #{Jim::VERSION}" - exit - } - - - opts.on_tail("-h", "--help", "Show this message. Run jim commands for list of commands.") do - puts opts.help - exit + delete do |base, relative| + run_update["deleted", relative] end + end + end - end.parse! runtime_args - rescue OptionParser::MissingArgument => e - logger.warn "#{e}, run -h for options" - exit + desc "update_jimfile [APP_DIR]", + "Converts a Jimfile from the old pre 0.3 format to the JSON format." + def update_jimfile(dir = nil) + dir = Pathname.new(dir || '') + bundler + copy_file(dir + 'Jimfile', dir + 'Jimfile.old') + create_file(dir + 'Jimfile', bundler.jimfile_to_json) end + private def index @index ||= Jim::Index.new(install_dir, Dir.pwd) end def installed_index @@ -221,21 +259,32 @@ def install_dir jimhome + 'lib' end - def template(path) - (Pathname.new(__FILE__).dirname + 'templates' + path).read - end - def logger Jim.logger end def print_version_list(list) list.each do |file, versions| - logger.info "#{file} (#{VersionSorter.rsort(versions.collect {|v| v[0] }).join(', ')})" + say "#{file} (#{VersionSorter.rsort(versions.collect {|v| v[0] }).join(', ')}\n" end + end + + def make_bundle(bundle_name, compress = false) + bundler.bundle_dir = options[:bundle_dir] if options[:bundle_dir] + bundler.bundle_dir = nil if options[:stdout] + result = bundler.bundle!(bundle_name, compress) + if options[:stdout] + puts result + else + result.each do |path| + say("Wrote #{path} #{File.size(path.to_s) / 1024}kb", :green) + end + end + rescue Jim::Error => e + say e.message, :red end end end