From acb81bb87410ee4a5b3efa72278ad4353b2b397e Mon Sep 17 00:00:00 2001 From: yan Date: Wed, 7 Dec 2011 20:34:25 -0800 Subject: [PATCH] Adding 'yadr' command for managing plugins (wip) --- bin/yadr/default_libs.rb | 2 + .../git-style-binaries-0.1.11/README.markdown | 280 +++++ .../lib/git-style-binaries-0.1.11/Rakefile | 65 + .../lib/git-style-binaries-0.1.11/VERSION.yml | 4 + .../lib/ext/colorize.rb | 198 +++ .../git-style-binaries-0.1.11/lib/ext/core.rb | 16 + .../lib/git-style-binary.rb | 88 ++ .../lib/git-style-binary/autorunner.rb | 21 + .../lib/git-style-binary/command.rb | 204 +++ .../lib/git-style-binary/commands/help.rb | 32 + .../git-style-binary/helpers/name_resolver.rb | 78 ++ .../lib/git-style-binary/helpers/pager.rb | 37 + .../lib/git-style-binary/parser.rb | 223 ++++ .../test/fixtures/flickr | 4 + .../test/fixtures/flickr-download | 17 + .../test/fixtures/wordpress | 42 + .../test/fixtures/wordpress-categories | 18 + .../test/fixtures/wordpress-list | 18 + .../test/fixtures/wordpress-post | 26 + .../test/git-style-binary/command_test.rb | 17 + .../test/git_style_binary_test.rb | 21 + .../test/running_binaries_test.rb | 224 ++++ .../test/shoulda_macros/matching_stdio.rb | 13 + .../test/test_helper.rb | 28 + bin/yadr/lib/trollop-1.16.2/FAQ.txt | 84 ++ bin/yadr/lib/trollop-1.16.2/History.txt | 116 ++ bin/yadr/lib/trollop-1.16.2/README.txt | 52 + bin/yadr/lib/trollop-1.16.2/lib/trollop.rb | 781 ++++++++++++ .../lib/trollop-1.16.2/release-script.txt | 15 + .../lib/trollop-1.16.2/test/test_trollop.rb | 1094 +++++++++++++++++ bin/yadr/yadr | 13 + bin/yadr/yadr-vim-add-plugin | 23 + bin/yadr/yadr-vim-update-plugins | 13 + util/addvim | 13 - zsh/aliases | 7 +- zsh/zshrc | 2 +- 36 files changed, 3874 insertions(+), 15 deletions(-) create mode 100755 bin/yadr/default_libs.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/README.markdown create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/Rakefile create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/VERSION.yml create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/colorize.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/core.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/autorunner.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/command.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/commands/help.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/name_resolver.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/pager.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/parser.rb create mode 100755 bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr create mode 100755 bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr-download create mode 100755 bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress create mode 100755 bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-categories create mode 100755 bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-list create mode 100755 bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-post create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/test/git-style-binary/command_test.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/test/git_style_binary_test.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/test/running_binaries_test.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/test/shoulda_macros/matching_stdio.rb create mode 100644 bin/yadr/lib/git-style-binaries-0.1.11/test/test_helper.rb create mode 100644 bin/yadr/lib/trollop-1.16.2/FAQ.txt create mode 100644 bin/yadr/lib/trollop-1.16.2/History.txt create mode 100644 bin/yadr/lib/trollop-1.16.2/README.txt create mode 100644 bin/yadr/lib/trollop-1.16.2/lib/trollop.rb create mode 100644 bin/yadr/lib/trollop-1.16.2/release-script.txt create mode 100644 bin/yadr/lib/trollop-1.16.2/test/test_trollop.rb create mode 100755 bin/yadr/yadr create mode 100755 bin/yadr/yadr-vim-add-plugin create mode 100755 bin/yadr/yadr-vim-update-plugins delete mode 100755 util/addvim diff --git a/bin/yadr/default_libs.rb b/bin/yadr/default_libs.rb new file mode 100755 index 0000000..900e095 --- /dev/null +++ b/bin/yadr/default_libs.rb @@ -0,0 +1,2 @@ +Dir[File.join(File.dirname(__FILE__),"lib/**/lib")].each {|dir| $LOAD_PATH << dir} +require 'git-style-binary/command' diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/README.markdown b/bin/yadr/lib/git-style-binaries-0.1.11/README.markdown new file mode 100644 index 0000000..b63fa39 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/README.markdown @@ -0,0 +1,280 @@ +git-style-binaries +================== + +Ridiculously easy git-style binaries. + +This gem uses [`trollop`](http://trollop.rubyforge.org/) for option parsing + +## Installation + + gem install jashmenn-git-style-binaries --source=http://gems.github.com + +## Screencast + +Checkout the new screencast! + + + +## Try it out + + cd `gem env gemdir`/gems/jashmenn-git-style-binaries-0.1.4/test/fixtures + ./wordpress -h + ./wordpress help post + +## Goal + +Lets use the imaginary `wordpress` gem. Let's say we have three different +actions we want to specify: + +* categories +* list +* post + +Each command has its own binary in a directory structure like this: + + bin/ + |-- wordpress + |-- wordpress-categories + |-- wordpress-list + `-- wordpress-post + +The goal is to be able to call commands in this manner: + + wordpress -h # gives help summary of all commands + wordpress-list -h # gives long help of wordpress-list + wordpress list -h # ditto + echo "about me" | wordpress-post --title="new post" # posts a new post with that title + +## Example code +Our `bin/wordpress` binary is called the *primary* . Our primary only needs to contain the following line: + + #!/usr/bin/env ruby + require 'git-style-binary/command' + +`git-style-binary` will automatically make this command the primary. + +The `bin/wordpress-post` binary could contain the following: + + #!/usr/bin/env ruby + require 'git-style-binary/command' + + GitStyleBinary.command do + short_desc "create a blog post" + banner <<-EOS + Usage: #{command.full_name} #{all_options_string} {content|STDIN} + + Posts content to a wordpress blog + + EOS + opt :blog, "short name of the blog to use", :default => 'default' + opt :category, "tag/category. specify multiple times for multiple categories", :type => String, :multi => true + opt :title, "title for the post", :required => true, :type => String + opt :type, "type of the content [html|xhtml|text]", :default => 'html', :type => String + + run do |command| + command.die :type, "type must be one of [html|xhtml|text]" unless command.opts[:type] =~ /^(x?html|text)$/i + + puts "Subcommand name: #{command.name.inspect}" + puts "Options: #{command.opts.inspect}" + puts "Remaining arguments: #{command.argv.inspect}" + end + end + +And so on with the other binaries. + +## Running the binaries + +Now if we run `wordpress -h` we get the following output: + + NAME + wordpress + + VERSION + 0.0.1 (c) 2009 Nate Murray - local + + SYNOPSIS + wordpress [--version] [--test-primary] [--help] [--verbose] COMMAND [ARGS] + + SUBCOMMANDS + wordpress-categories + do something with categories + + wordpress-help + get help for a specific command + + wordpress-list + list blog postings + + wordpress-post + create a blog post + + + See 'wordpress help COMMAND' for more information on a specific command. + + OPTIONS + -v, --verbose + verbose + + + -t, --test-primary= + test an option on the primary + + + -e, --version + Print version and exit + + + -h, --help + Show this message + + + +Default **options**, **version string**, and **usage banner** are automatically selected for you. +The subcommands and their short descriptions are loaded automatically! + +You can pass the `-h` flag to any one of the subcommands (with or without the +connecting `-`) or use the built-in `help` subcommand for the same effect. For instance: + + $ wordpress help post + + NAME + wordpress-post - create a blog post + + VERSION + 0.0.1 (c) 2009 Nate Murray - local + + SYNOPSIS + wordpress-post [--type] [--version] [--test-primary] [--blog] [--help] [--verbose] [--category] + [--title] COMMAND [ARGS] {content|STDIN} + + OPTIONS + -v, --verbose + verbose + + + -t, --test-primary= + test an option on the primary + + + -b, --blog= + short name of the blog to use (default: default) + + + -c, --category= + tag/category. specify multiple times for multiple + categories + + + -i, --title= + title for the post + + + -y, --type= + type of the content [html|xhtml|text] (default: html) + + + -e, --version + Print version and exit + + + -h, --help + Show this message + + +For more examples, see the binaries in `test/fixtures/`. + +## Primary options + +Often you may *want* the primary to have its own set of options. Simply call `GitStyleBinary.primary` with a block like so: + + #!/usr/bin/env ruby + require 'git-style-binary/command' + GitStyleBinary.primary do + version "#{command.full_name} 0.0.1 (c) 2009 Nate Murray - local" + opt :test_primary, "a primary string option", :type => String + + run do |command| + puts "Primary Options: #{command.opts.inspect}" + end + end + +Primary options are **inherited** by all subcommands. That means in this case +all subcommands will now get the `--test-primary` option available to them as +well as this new `version` string. + +## Option parsing + +Option parsing is done by [trollop](http://trollop.rubyforge.org/). +`git-style-binary` uses this more-or-less exactly. See the [trollop +documentation](http://trollop.rubyforge.org/) for information on how to setup +the options and flags. + +## Callbacks + +Callbacks are available on the primary and subcommands. Available callbacks currently +are before/after_run. These execute before the run block of the command parser and take +take one argument, which is the command itself + +## The `run` block + +To get the 'introspection' on the individual binaries every binary is `load`ed +on `primary help`. We need a way to get that information while not running +every command when calling `primary help`. To achieve that you need to put what +will be run in the `run` block. + +`run` `yields` a `Command` object which contains a number of useful options +such as `name`, `full_name`, `opts`, and `argv`. + +* `command.opts` is a hash of the options parsed +* `command.argv` is an array of the remaining arguments + +## Features +* automatic colorization +* automatic paging + +## To Learn more + +Play with the examples in the `test/fixtures` directory. + +## Credits +* `git-style-binary` was written by Nate Murray `` +* `trollop` was written by [William Morgan](http://trollop.rubyforge.org/) +* Inspiration comes from Ari Lerner's [git-style-binaries](http://blog.xnot.org/2008/12/16/git-style-binaries/) for [PoolParty.rb](http://poolpartyrb.com) +* [`colorize.rb`](http://colorize.rubyforge.org) by Michal Kalbarczyk +* Automatic less paging by [Nathan Weizenbaum](http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby) +* Color inspiration from [Brian Henderson](http://xcombinator.com) teaching me how to get `man git` colors using `less` on MacOSX + +## TODO +* automagic tab completion - Automatic for subcommands and options for any library that uses this + +## Known Bugs/Problems +* Young +* A few places of really ugly code +* A feeling that this could be done in 1/2 lines of code + +## Authors +By Nate Murray and Ari Lerner + +## Copyright + +The MIT License + +Copyright (c) 2009 Nate Murray. See LICENSE for details. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/Rakefile b/bin/yadr/lib/git-style-binaries-0.1.11/Rakefile new file mode 100644 index 0000000..2d0d50b --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/Rakefile @@ -0,0 +1,65 @@ +require 'rubygems' +require 'rake' + +begin + require 'jeweler' + Jeweler::Tasks.new do |gem| + gem.name = "git-style-binaries" + gem.description = %Q{Ridiculously easy git-style binaries} + gem.summary =<<-EOF + Add git-style binaries to your project easily. + EOF + gem.email = "nate@natemurray.com" + gem.homepage = "http://github.com/jashmenn/git-style-binaries" + gem.authors = ["Nate Murray"] + gem.add_dependency 'trollop' + gem.add_dependency 'shoulda' # for running the tests + + excludes = /(README\.html)/ + gem.files = (FileList["[A-Z]*.*", "{bin,examples,generators,lib,rails,spec,test,vendor}/**/*", 'Rakefile', 'LICENSE*']).delete_if{|f| f =~ excludes} + gem.extra_rdoc_files = FileList["README*", "ChangeLog*", "LICENSE*"].delete_if{|f| f =~ excludes} + end +rescue LoadError + puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" +end + +require 'rake/testtask' +Rake::TestTask.new(:test) do |test| + test.libs << 'lib' << 'test' + test.pattern = 'test/**/*_test.rb' + test.verbose = true +end + +begin + require 'rcov/rcovtask' + Rcov::RcovTask.new do |test| + test.libs << 'test' + test.pattern = 'test/**/*_test.rb' + test.verbose = true + end +rescue LoadError + task :rcov do + abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" + end +end + + +task :default => :test + +require 'rake/rdoctask' +require 'yaml' +Rake::RDocTask.new do |rdoc| + if File.exist?('VERSION.yml') + config = YAML.load(File.read('VERSION.yml')) + version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}" + else + version = "" + end + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "git-style-binaries #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +task :bump => ['version:bump:patch', 'gemspec', 'build'] diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/VERSION.yml b/bin/yadr/lib/git-style-binaries-0.1.11/VERSION.yml new file mode 100644 index 0000000..43ec547 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/VERSION.yml @@ -0,0 +1,4 @@ +--- +:patch: 10 +:major: 0 +:minor: 1 diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/colorize.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/colorize.rb new file mode 100644 index 0000000..c4d695a --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/colorize.rb @@ -0,0 +1,198 @@ +# +# Colorize String class extension. +# +class String + + # + # Version string + # + COLORIZE_VERSION = '0.5.6' + + # + # Colors Hash + # + COLORS = { + :black => 0, + :red => 1, + :green => 2, + :yellow => 3, + :blue => 4, + :magenta => 5, + :cyan => 6, + :white => 7, + :default => 9, + + :light_black => 10, + :light_red => 11, + :light_green => 12, + :light_yellow => 13, + :light_blue => 14, + :light_magenta => 15, + :light_cyan => 16, + :light_white => 17 + } + + # + # Modes Hash + # + MODES = { + :default => 0, # Turn off all attributes + #:bright => 1, # Set bright mode + :underline => 4, # Set underline mode + :blink => 5, # Set blink mode + :swap => 7, # Exchange foreground and background colors + :hide => 8 # Hide text (foreground color would be the same as background) + } + + protected + + # + # Set color values in new string intance + # + def set_color_parameters( params ) + if (params.instance_of?(Hash)) + @color = params[:color] + @background = params[:background] + @mode = params[:mode] + @uncolorized = params[:uncolorized] + self + else + nil + end + end + + public + + # + # Change color of string + # + # Examples: + # + # puts "This is blue".colorize( :blue ) + # puts "This is light blue".colorize( :light_blue ) + # puts "This is also blue".colorize( :color => :blue ) + # puts "This is blue with red background".colorize( :color => :light_blue, :background => :red ) + # puts "This is blue with red background".colorize( :light_blue ).colorize( :background => :red ) + # puts "This is blue text on red".blue.on_red + # puts "This is red on blue".colorize( :red ).on_blue + # puts "This is red on blue and underline".colorize( :red ).on_blue.underline + # puts "This is blue text on red".blue.on_red.blink + # + def colorize( params ) + + unless STDOUT.use_color + return self unless STDOUT.isatty + end + return self if ENV['NO_COLOR'] + + begin + require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32/ + rescue LoadError + raise 'You must gem install win32console to use color on Windows' + end + + color_parameters = {} + + if (params.instance_of?(Hash)) + color_parameters[:color] = COLORS[params[:color]] + color_parameters[:background] = COLORS[params[:background]] + color_parameters[:mode] = MODES[params[:mode]] + elsif (params.instance_of?(Symbol)) + color_parameters[:color] = COLORS[params] + end + + color_parameters[:color] ||= @color || 9 + color_parameters[:background] ||= @background || 9 + color_parameters[:mode] ||= @mode || 0 + + color_parameters[:uncolorized] ||= @uncolorized || self.dup + + # calculate bright mode + color_parameters[:color] += 50 if color_parameters[:color] > 10 + + color_parameters[:background] += 50 if color_parameters[:background] > 10 + + return "\033[#{color_parameters[:mode]};#{color_parameters[:color]+30};#{color_parameters[:background]+40}m#{color_parameters[:uncolorized]}\033[0m".set_color_parameters( color_parameters ) + end + + + # + # Return uncolorized string + # + def uncolorize + return @uncolorized || self + end + + # + # Return true if sting is colorized + # + def colorized? + return !@uncolorized.nil? + end + + # + # Make some color and on_color methods + # + COLORS.each_key do | key | + eval <<-"end_eval" + def #{key.to_s} + return self.colorize( :color => :#{key.to_s} ) + end + def on_#{key.to_s} + return self.colorize( :background => :#{key.to_s} ) + end + end_eval + end + + # + # Methods for modes + # + MODES.each_key do | key | + eval <<-"end_eval" + def #{key.to_s} + return self.colorize( :mode => :#{key.to_s} ) + end + end_eval + end + + class << self + + # + # Return array of available modes used by colorize method + # + def modes + keys = [] + MODES.each_key do | key | + keys << key + end + keys + end + + # + # Return array of available colors used by colorize method + # + def colors + keys = [] + COLORS.each_key do | key | + keys << key + end + keys + end + + # + # Display color matrix with color names. + # + def color_matrix( txt = "[X]" ) + size = String.colors.length + String.colors.each do | color | + String.colors.each do | back | + print txt.colorize( :color => color, :background => back ) + end + puts " < #{color}" + end + String.colors.reverse.each_with_index do | back, index | + puts "#{"|".rjust(txt.length)*(size-index)} < #{back}" + end + end + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/core.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/core.rb new file mode 100644 index 0000000..70bbf3d --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/ext/core.rb @@ -0,0 +1,16 @@ +class Object + def returning(value) + yield(value) + value + end unless Object.respond_to?(:returning) +end + +class Symbol + def to_proc + Proc.new { |*args| args.shift.__send__(self, *args) } + end +end + +class IO + attr_accessor :use_color +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary.rb new file mode 100644 index 0000000..fa853c9 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary.rb @@ -0,0 +1,88 @@ +$:.unshift(File.dirname(__FILE__)) +require 'rubygems' + +# Load the vendor gems +$:.unshift(File.dirname(__FILE__) + "/../vendor/gems") +%w(trollop).each do |library| + begin + require "#{library}/lib/#{library}" + rescue LoadError + begin + require 'trollop' + rescue LoadError + puts "There was an error loading #{library}. Try running 'gem install #{library}' to correct the problem" + end + end +end + +require 'ext/core' +require 'ext/colorize' +require 'git-style-binary/autorunner' +Dir[File.dirname(__FILE__) + "/git-style-binary/helpers/*.rb"].each {|f| require f} + +module GitStyleBinary + + class << self + include Helpers::NameResolver + attr_accessor :current_command + attr_accessor :primary_command + attr_writer :known_commands + + # If set to false GitStyleBinary will not automatically run at exit. + attr_writer :run + + # Automatically run at exit? + def run? + @run ||= false + end + + def parser + @p ||= Parser.new + end + + def known_commands + @known_commands ||= {} + end + + def load_primary + unless @loaded_primary + @loaded_primary = true + primary_file = File.join(binary_directory, basename) + load primary_file + + if !GitStyleBinary.primary_command # you still dont have a primary load a default + GitStyleBinary.primary do + run do |command| + educate + end + end + end + end + end + + def load_subcommand + unless @loaded_subcommand + @loaded_subcommand = true + cmd_file = GitStyleBinary.binary_filename_for(GitStyleBinary.current_command_name) + load cmd_file + end + end + + def load_command_file(name, file) + self.name_of_command_being_loaded = name + load file + self.name_of_command_being_loaded = nil + end + + # UGLY eek + attr_accessor :name_of_command_being_loaded + + end +end + +at_exit do + unless $! || GitStyleBinary.run? + command = GitStyleBinary::AutoRunner.run + exit 0 + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/autorunner.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/autorunner.rb new file mode 100644 index 0000000..be769c9 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/autorunner.rb @@ -0,0 +1,21 @@ +require 'git-style-binary/parser' + +module GitStyleBinary +class AutoRunner + + def self.run(argv=ARGV) + r = new + r.run + end + + def run + unless GitStyleBinary.run? + if !GitStyleBinary.current_command + GitStyleBinary.load_primary + end + GitStyleBinary.current_command.run + end + end + +end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/command.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/command.rb new file mode 100644 index 0000000..296588f --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/command.rb @@ -0,0 +1,204 @@ +require 'git-style-binary' + +module GitStyleBinary + def self.command(&block) + returning Command.new(:constraints => [block]) do |c| + c.name ||= (GitStyleBinary.name_of_command_being_loaded || GitStyleBinary.current_command_name) + GitStyleBinary.known_commands[c.name] = c + + if !GitStyleBinary.current_command || GitStyleBinary.current_command.is_primary? + GitStyleBinary.current_command = c + end + end + end + + def self.primary(&block) + returning Primary.new(:constraints => [block]) do |c| + c.name ||= (GitStyleBinary.name_of_command_being_loaded || GitStyleBinary.current_command_name) + GitStyleBinary.known_commands[c.name] = c + + GitStyleBinary.primary_command = c unless GitStyleBinary.primary_command + GitStyleBinary.current_command = c unless GitStyleBinary.current_command + end + end + + class Command + class << self + def defaults + lambda do + name_desc "#{command.full_name}\#{command.short_desc ? ' - ' + command.short_desc : ''}" # eval jit + version_string = defined?(VERSION) ? VERSION : "0.0.1" + version "#{version_string} (c) #{Time.now.year}" + banner <<-EOS +#{"SYNOPSIS".colorize(:red)} + #{command.full_name.colorize(:light_blue)} #{all_options_string} + +#{"SUBCOMMANDS".colorize(:red)} + \#{GitStyleBinary.pretty_known_subcommands.join("\n ")} + + See '#{command.full_name} help COMMAND' for more information on a specific command. + EOS + + opt :verbose, "verbose", :default => false + end + end + end + + attr_reader :constraints + attr_reader :opts + attr_accessor :name + + def initialize(o={}) + o.each do |k,v| + eval "@#{k.to_s}= v" + end + end + + def parser + @parser ||= begin + p = Parser.new + p.command = self + p + end + end + + def constraints + @constraints ||= [] + end + + def run + GitStyleBinary.load_primary unless is_primary? + GitStyleBinary.load_subcommand if is_primary? && running_subcommand? + load_all_parser_constraints + @opts = process_args_with_subcmd + call_parser_run_block + self + end + + def running_subcommand? + GitStyleBinary.valid_subcommand?(GitStyleBinary.current_command_name) + end + + def load_all_parser_constraints + @loaded_all_parser_constraints ||= begin + load_parser_default_constraints + load_parser_primary_constraints + load_parser_local_constraints + true + end + end + + def load_parser_default_constraints + parser.consume_all([self.class.defaults]) + end + + def load_parser_primary_constraints + parser.consume_all(GitStyleBinary.primary_command.constraints) + end + + def load_parser_local_constraints + cur = GitStyleBinary.current_command # see, why isn't 'this' current_command? + + unless self.is_primary? && cur == self + # TODO TODO - the key lies in this function. figure out when you hav emore engergy + # soo UGLY. see #process_parser! unify with that method + # parser.consume_all(constraints) rescue ArgumentError + parser.consume_all(cur.constraints) + end + end + + def call_parser_run_block + runs = GitStyleBinary.current_command.parser.runs + + parser.run_callbacks(:before_run, self) + parser.runs.last.call(self) # ... not too happy with this + parser.run_callbacks(:after_run, self) + end + + def process_args_with_subcmd(args = ARGV, *a, &b) + cmd = GitStyleBinary.current_command_name + vals = process_args(args, *a, &b) + parser.leftovers.shift if parser.leftovers[0] == cmd + vals + end + + # TOOooootally ugly! why? bc load_parser_local_constraints doesn't work + # when loading the indivdual commands because it depends on + # #current_command. This really sucks and is UGLY. + # the todo is to put in 'load_all_parser_constraints' and this works + def process_parser! + # load_all_parser_constraints + + load_parser_default_constraints + load_parser_primary_constraints + # load_parser_local_constraints + parser.consume_all(constraints) + + # hack + parser.consume { + opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"] + opt :help, "Show this message" unless @specs[:help] || @long["help"] + resolve_default_short_options + } # hack + end + + def process_args(args = ARGV, *a, &b) + p = parser + begin + vals = p.parse args + args.clear + p.leftovers.each { |l| args << l } + vals # ugly todo + rescue Trollop::CommandlineError => e + $stderr.puts "Error: #{e.message}." + $stderr.puts "Try --help for help." + exit(-1) + rescue Trollop::HelpNeeded + p.educate + exit + rescue Trollop::VersionNeeded + puts p.version + exit + end + end + + def is_primary? + false + end + + def argv + parser.leftovers + end + + def short_desc + parser.short_desc + end + + def full_name + # ugly, should be is_primary? + GitStyleBinary.primary_name == name ? GitStyleBinary.primary_name : GitStyleBinary.primary_name + "-" + name + end + + def die arg, msg=nil + p = parser # create local copy + Trollop.instance_eval { @p = p } + Trollop::die(arg, msg) + end + + # Helper to return the option + def [](k) + opts[k] + end + + end + + class Primary < Command + def is_primary? + true + end + def primary + self + end + end + +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/commands/help.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/commands/help.rb new file mode 100644 index 0000000..5c1f70b --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/commands/help.rb @@ -0,0 +1,32 @@ +module GitStyleBinary + module Commands + class Help + # not loving this syntax, but works for now + GitStyleBinary.command do + short_desc "get help for a specific command" + run do |command| + + # this is slightly ugly b/c it has to muck around in the internals to + # get information about commands other than itself. This isn't a + # typical case + self.class.send :define_method, :educate_about_command do |name| + load_all_commands + if GitStyleBinary.known_commands.has_key?(name) + cmd = GitStyleBinary.known_commands[name] + cmd.process_parser! + cmd.parser.educate + else + puts "Unknown command '#{name}'" + end + end + + if command.argv.size > 0 + command.argv.first == "help" ? educate : educate_about_command(command.argv.first) + else + educate + end + end + end + end + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/name_resolver.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/name_resolver.rb new file mode 100644 index 0000000..e6edbd6 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/name_resolver.rb @@ -0,0 +1,78 @@ +module GitStyleBinary +module Helpers + module NameResolver + + def basename(filename=zero) + File.basename(filename).match(/(.*?)(\-|$)/).captures.first + end + alias_method :primary_name, :basename + + # checks the bin directory for all files starting with +basename+ and + # returns an array of strings specifying the subcommands + def subcommand_names(filename=zero) + subfiles = Dir[File.join(binary_directory, basename + "-*")] + cmds = subfiles.collect{|file| File.basename(file).sub(/^#{basename}-/, '')}.sort + cmds += built_in_command_names + cmds.uniq + end + + def binary_directory(filename=zero) + File.dirname(filename) + end + + def built_in_commands_directory + File.dirname(__FILE__) + "/../commands" + end + + def built_in_command_names + Dir[built_in_commands_directory + "/*.rb"].collect{|f| File.basename(f.sub(/\.rb$/,''))} + end + + def list_subcommands(filename=zero) + subcommand_names(filename).join(", ") + end + + # load first from users binary directory. then load built-in commands if + # available + def binary_filename_for(name) + user_file = File.join(binary_directory, "#{basename}-#{name}") + return user_file if File.exists?(user_file) + built_in = File.join(built_in_commands_directory, "#{name}.rb") + return built_in if File.exists?(built_in) + user_file + end + + def current_command_name(filename=zero,argv=ARGV) + current = File.basename(zero) + first_arg = ARGV[0] + return first_arg if valid_subcommand?(first_arg) + return basename if basename == current + current.sub(/^#{basename}-/, '') + end + + # returns the command name with the prefix if needed + def full_current_command_name(filename=zero,argv=ARGV) + cur = current_command_name(filename, argv) + subcmd = cur == basename(filename) ? false : true # is this a subcmd? + "%s%s%s" % [basename(filename), subcmd ? "-" : "", subcmd ? current_command_name(filename, argv) : ""] + end + + def valid_subcommand?(name) + subcommand_names.include?(name) + end + + def zero + $0 + end + + def pretty_known_subcommands(theme=:long) + GitStyleBinary.known_commands.collect do |k,cmd| + next if k == basename + cmd.process_parser! + ("%-s%s%-10s" % [basename, '-', k]).colorize(:light_blue) + ("%s " % [theme == :long ? "\n" : ""]) + ("%s" % [cmd.short_desc]) + "\n" + end.compact.sort + end + + end +end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/pager.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/pager.rb new file mode 100644 index 0000000..1fa1cff --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/helpers/pager.rb @@ -0,0 +1,37 @@ +module GitStyleBinary + module Helpers + module Pager + + # by Nathan Weizenbaum - http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby + def run_pager + return if RUBY_PLATFORM =~ /win32/ + return unless STDOUT.tty? + STDOUT.use_color = true + + read, write = IO.pipe + + unless Kernel.fork # Child process + STDOUT.reopen(write) + STDERR.reopen(write) if STDERR.tty? + read.close + write.close + return + end + + # Parent process, become pager + STDIN.reopen(read) + read.close + write.close + + ENV['LESS'] = 'FSRX' # Don't page if the input is short enough + + Kernel.select [STDIN] # Wait until we have input before we start the pager + pager = ENV['PAGER'] || 'less -erXF' + exec pager rescue exec "/bin/sh", "-c", pager + end + + module_function :run_pager + + end + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/parser.rb b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/parser.rb new file mode 100644 index 0000000..4e03d5c --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/lib/git-style-binary/parser.rb @@ -0,0 +1,223 @@ +module GitStyleBinary +class Parser < Trollop::Parser + attr_reader :runs, :callbacks + attr_reader :short_desc + attr_accessor :command + + def initialize *a, &b + super + @runs = [] + setup_callbacks + end + + def setup_callbacks + @callbacks = {} + %w(run).each do |event| + %w(before after).each do |time| + @callbacks["#{time}_#{event}".to_sym] = [] + instance_eval "def #{time}_#{event}(&block);@callbacks[:#{time}_#{event}] << block;end" + end + end + end + + def run_callbacks(at, from) + @callbacks[at].each {|c| c.call(from) } + end + + def banner s=nil; @banner = s if s; @banner end + def short_desc s=nil; @short_desc = s if s; @short_desc end + def name_desc s=nil; @name_desc = s if s; @name_desc end + + # Set the theme. Valid values are +:short+ or +:long+. Default +:long+ + attr_writer :theme + + def theme + @theme ||= :long + end + + ## Adds text to the help display. + def text s; @order << [:text, s] end + + def spec_names + @specs.collect{|name, spec| spec[:long]} + end + + # should probably be somewhere else + def load_all_commands + GitStyleBinary.subcommand_names.each do |name| + cmd_file = GitStyleBinary.binary_filename_for(name) + GitStyleBinary.load_command_file(name, cmd_file) + end + end + + ## Print the help message to 'stream'. + def educate(stream=$stdout) + load_all_commands + width # just calculate it now; otherwise we have to be careful not to + # call this unless the cursor's at the beginning of a line. + GitStyleBinary::Helpers::Pager.run_pager + self.send("educate_#{theme}", stream) + end + + def educate_long(stream=$stdout) + left = {} + + @specs.each do |name, spec| + left[name] = + ((spec[:short] ? "-#{spec[:short]}, " : "") + + "--#{spec[:long]}" + + case spec[:type] + when :flag; "" + when :int; "=" + when :ints; "=" + when :string; "=" + when :strings; "=" + when :float; "=" + when :floats; "=" + end).colorize(:red) + end + + leftcol_width = left.values.map { |s| s.length }.max || 0 + rightcol_start = leftcol_width + 6 # spaces + leftcol_start = 6 + leftcol_spaces = " " * leftcol_start + + unless @order.size > 0 && @order.first.first == :text + + if @name_desc + stream.puts "NAME".colorize(:red) + stream.puts "#{leftcol_spaces}"+ colorize_known_words(eval(%Q["#{@name_desc}"])) + "\n" + stream.puts + end + + if @version + stream.puts "VERSION".colorize(:red) + stream.puts "#{leftcol_spaces}#@version\n" + end + + stream.puts + + banner = colorize_known_words_array(wrap(eval(%Q["#{@banner}"]) + "\n", :prefix => leftcol_start)) if @banner # lazy banner + stream.puts banner + + stream.puts + stream.puts "OPTIONS".colorize(:red) + else + stream.puts "#@banner\n" if @banner + end + + @order.each do |what, opt| + if what == :text + stream.puts wrap(opt) + next + end + + spec = @specs[opt] + stream.printf " %-#{leftcol_width}s\n", left[opt] + desc = spec[:desc] + + if spec[:default] + if spec[:desc] =~ /\.$/ + " (Default: #{spec[:default]})" + else + " (default: #{spec[:default]})" + end + else + "" + end + stream.puts wrap(" %s" % [desc], :prefix => leftcol_start, :width => width - rightcol_start - 1 ) + stream.puts + stream.puts + end + + end + + def educate_short(stream=$stdout) + left = {} + + @specs.each do |name, spec| + left[name] = "--#{spec[:long]}" + + (spec[:short] ? ", -#{spec[:short]}" : "") + + case spec[:type] + when :flag; "" + when :int; " " + when :ints; " " + when :string; " " + when :strings; " " + when :float; " " + when :floats; " " + end + end + + leftcol_width = left.values.map { |s| s.length }.max || 0 + rightcol_start = leftcol_width + 6 # spaces + leftcol_start = 0 + + unless @order.size > 0 && @order.first.first == :text + stream.puts "#@version\n" if @version + stream.puts colorize_known_words_array(wrap(eval(%Q["#{@banner}"]) + "\n", :prefix => leftcol_start)) if @banner # jit banner + stream.puts "Options:" + else + stream.puts "#@banner\n" if @banner + end + + @order.each do |what, opt| + if what == :text + stream.puts wrap(opt) + next + end + + spec = @specs[opt] + stream.printf " %#{leftcol_width}s: ", left[opt] + desc = spec[:desc] + + if spec[:default] + if spec[:desc] =~ /\.$/ + " (Default: #{spec[:default]})" + else + " (default: #{spec[:default]})" + end + else + "" + end + stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start) + end + + end + + + def colorize_known_words_array(txts) + txts.collect{|txt| colorize_known_words(txt)} + end + + def colorize_known_words(txt) + txt = txt.gsub(/^([A-Z]+\s*)$/, '\1'.colorize(:red)) # all caps words on their own line + txt = txt.gsub(/\b(#{bin_name})\b/, '\1'.colorize(:light_blue)) # the current command name + txt = txt.gsub(/\[([^\s]+)\]/, "[".colorize(:magenta) + '\1'.colorize(:green) + "]".colorize(:magenta)) # synopsis options + end + + def consume(&block) + cloaker(&block).bind(self).call + end + + def consume_all(blocks) + blocks.each {|b| consume(&b)} + end + + def bin_name + GitStyleBinary.full_current_command_name + end + + def all_options_string + # '#{spec_names.collect(&:to_s).collect{|name| "[".colorize(:magenta) + "--" + name + "]".colorize(:magenta)}.join(" ")} COMMAND [ARGS]' + '#{spec_names.collect(&:to_s).collect{|name| "[" + "--" + name + "]"}.join(" ")} COMMAND [ARGS]' + end + + def run(&block) + @runs << block + end + + def action(name = :action, &block) + block.call(self) if block + end + +end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr new file mode 100755 index 0000000..8eca1b9 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +$:.unshift(File.dirname(__FILE__) + "/../../lib") +VERSION="0.0.2" # just to test it +require 'git-style-binary/command' diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr-download b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr-download new file mode 100755 index 0000000..aff4128 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/flickr-download @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +$:.unshift(File.dirname(__FILE__) + "/../../lib") +require 'git-style-binary/command' + +GitStyleBinary.command do + short_desc "download a flickr image" + banner <<-EOS +SYNOPSIS + #{command.full_name} #{all_options_string} url + +Downloads an image from flickr + +EOS + run do |command| + puts "would download: #{command.argv.inspect}" + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress new file mode 100755 index 0000000..90a1cd6 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby +$:.unshift(File.dirname(__FILE__) + "/../../lib") + +require 'git-style-binary/command' +GitStyleBinary.primary do + version "0.0.1 (c) 2009 Nate Murray - local" + opt :test_primary, "test an option on the primary", :type => String + + action do + @categories = ["sports", "news"] + end + + before_run do |cmd| + puts "before_run command #{cmd}" + end + + after_run do |cmd| + puts "after_run command #{cmd}" + end + + run do |command| + puts "Primary Options: #{command.opts.inspect}" + end +end + +# OR + +# require 'git-style-binary/primary' +# command = GitStyleBinary::primary("wordpress") do +# version "#{$0} 0.0.1 (c) 2009 Nate Murray" +# banner <<-EOS +# usage: #{$0} #{all_options.collect(:&to_s).join(" ")} COMMAND [ARGS] +# +# The wordpress subcommands commands are: +# {subcommand_names.pretty_print} +# +# See 'wordpress help COMMAND' for more information on a specific command. +# EOS +# opt :verbose, "verbose", :default => false +# opt :dry, "dry run", :default => false +# opt :test_global, "a basic global string option", :type => String +# end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-categories b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-categories new file mode 100755 index 0000000..66c44a4 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-categories @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby +$:.unshift(File.dirname(__FILE__) + "/../../lib") +require 'git-style-binary/command' + +GitStyleBinary.command do + short_desc "do something with categories" + banner <<-EOS +SYNOPSIS + #{command.full_name} #{all_options_string} + +Does something with categories + +EOS + run do |command| + puts "does something with categories" + puts @categories.join(" ") + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-list b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-list new file mode 100755 index 0000000..dbe82a7 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-list @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby +$:.unshift(File.dirname(__FILE__) + "/../../lib") +require 'git-style-binary/command' + +GitStyleBinary.command do + short_desc "list blog postings" + banner <<-EOS +SYNOPSIS + #{command.full_name} #{all_options_string} + +Lists the posts on the blog + +EOS + run do |command| + puts "listing blog posts" + end +end + diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-post b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-post new file mode 100755 index 0000000..61f08f4 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/fixtures/wordpress-post @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby +$:.unshift(File.dirname(__FILE__) + "/../../lib") +require 'git-style-binary/command' + +GitStyleBinary.command do + short_desc "create a blog post" + banner <<-EOS +SYNOPSIS + #{command.full_name} #{all_options_string} {content|STDIN} + +EOS + opt :blog, "short name of the blog to use", :default => 'default' + opt :category, "tag/category. specify multiple times for multiple categories", :type => String, :multi => true + opt :title, "title for the post", :required => true, :type => String + opt :type, "type of the content [html|xhtml|text]", :default => 'html', :type => String + + run do |command| + command.die :type, "type must be one of [html|xhtml|text]" unless command.opts[:type] =~ /^(x?html|text)$/i + + puts "Subcommand name: #{command.name.inspect}" + puts "Options: #{command.opts.inspect}" + puts "Remaining arguments: #{command.argv.inspect}" + end +end + + diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/git-style-binary/command_test.rb b/bin/yadr/lib/git-style-binaries-0.1.11/test/git-style-binary/command_test.rb new file mode 100644 index 0000000..0b35c98 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/git-style-binary/command_test.rb @@ -0,0 +1,17 @@ +require File.dirname(__FILE__) + "/../test_helper.rb" +require 'git-style-binary/command' + +class CommandTest < Test::Unit::TestCase + context "cmd" do + setup do + @c = GitStyleBinary::Command.new + end + + should "be able to easily work with constraints" do + assert_equal @c.constraints, [] + @c.constraints << "foo" + assert_equal @c.constraints, ["foo"] + end + + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/git_style_binary_test.rb b/bin/yadr/lib/git-style-binaries-0.1.11/test/git_style_binary_test.rb new file mode 100644 index 0000000..aa715d8 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/git_style_binary_test.rb @@ -0,0 +1,21 @@ +require File.dirname(__FILE__) + "/test_helper.rb" + +class GitStyleBinariesTest < Test::Unit::TestCase + context "parsing basenames" do + should "accurately parse basenames" do + assert_equal "wordpress", GitStyleBinary.basename("bin/wordpress") + assert_equal "wordpress", GitStyleBinary.basename("bin/wordpress-post") + assert_equal "wordpress", GitStyleBinary.basename("wordpress-post") + end + + should "get the current command name" do + # doesn't really apply any more b/c it calls 'current' which is never the + # current when your running rake_test_loader.rb + # + # assert_equal "wordpress", GitStyleBinary.current_command_name("bin/wordpress", ["--help"]) + # assert_equal "post", GitStyleBinary.current_command_name("bin/wordpress-post", ["--help"]) + # assert_equal "post", GitStyleBinary.current_command_name("bin/wordpress post", ["--help"]) + #assert_equal "post", GitStyleBinary.current_command_name("bin/wordpress post", []) + end + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/running_binaries_test.rb b/bin/yadr/lib/git-style-binaries-0.1.11/test/running_binaries_test.rb new file mode 100644 index 0000000..887b651 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/running_binaries_test.rb @@ -0,0 +1,224 @@ +require File.dirname(__FILE__) + "/test_helper.rb" + +THIS_YEAR=Time.now.year # todo + +class RunningBinariesTest < Test::Unit::TestCase + include RunsBinaryFixtures + + context "when running primary" do + ["wordpress -h", "wordpress help"].each do |format| + context "and getting help as a '#{format}'" do + setup { @stdout, @stderr = bin(format) } + + should "have the command name and short description" do + unless format == "wordpress -h" # doesn't apply to wordpress -h + output_matches /NAME\n\s*wordpress\-help \- get help for a specific command/m + end + end + + should "have a local (not default) version string" do + output_matches /0\.0\.1 \(c\) 2009 Nate Murray - local/ + end + + should "get a list of subcommands" do + output_matches /subcommands/mi + end + + should "have subcommand short descriptions" do + output_matches /post\s*create a blog post/ + output_matches /categories\s*do something with categories/ + output_matches /help\s*get help for a specific command/ + output_matches /list\s*list blog postings/ + end + + should "have a usage" do + output_matches /SYNOPSIS/i + output_matches /wordpress(\-help)? \[/ + end + + should "be able to ask for help about help" + end + end + + context "and getting help as subcommand" do + # ["wordpress -h", "wordpress help"].each do |format| + ["wordpress help"].each do |format| + context "'#{format}'" do + should "get help on subcommand post" + end + end + end + + context "with no options" do + setup { @stdout, @stderr = bin("wordpress") } + + should "output the options" do + output_matches /Primary Options:/ + end + + should "have the test_primary option" do + output_matches /test_primary=>nil/ + end + end + should "be able to require 'primary' and run just fine" + end + + context "when running with an action" do + # should be the same for both formats + ["wordpress-categories", "wordpress categories"].each do |bin_format| + context "#{bin_format}" do + + context "with action block" do + setup { @stdout, @stderr = bin("#{bin_format}") } + should "have the parsed action items in the help output" do + output_matches /sports news/m + end + end + end + end + end + + context "callbacks" do + context "on a binary" do + setup { @stdout, @stderr = bin("wordpress") } + + %w(before after).each do |time| + should "run the callback #{time}_run}" do + assert @stdout.match(/#{time}_run command/) + end + end + end + + context "on help" do + setup { @stdout, @stderr = bin("wordpress -h") } + + %w(before after).each do |time| + should "not run the callback #{time}_run" do + assert_nil @stdout.match(/#{time}_run command/) + end + end + end + + end + + + context "when running the subcommand" do + # should be the same for both formats + ["wordpress-post", "wordpress post"].each do |bin_format| + context "#{bin_format}" do + + context "with no options" do + setup { @stdout, @stderr = bin("#{bin_format}") } + should "fail because title is required" do + output_matches /Error: option 'title' must be specified.\s*Try --help for help/m + end + end + + context "with options" do + setup { @stdout, @stderr = bin("#{bin_format} --title='glendale'") } + should "be running the subcommand's run block" do + output_matches /Subcommand name/ + end + should "have some default options" do + output_matches /version=>false/ + output_matches /help=>false/ + end + should "have some primary options" do + output_matches /test_primary=>nil/ + end + should "have some local options" do + output_matches /title=>"glendale"/ + output_matches /type=>"html"/ + end + end + + context "testing die statements" do + setup { @stdout, @stderr = bin("#{bin_format} --title='glendale' --type=yaml") } + + should "die on invalid options" do + output_matches /argument \-\-type type must be one of \[html\|xhtml\|text\]/ + end + end + + end # end bin_format + end # end #each + end + + ["wordpress help post", "wordpress post -h"].each do |format| + context "when calling '#{format}'" do + + setup { @stdout, @stderr = bin(format) } + should "have a description" do + output_matches /create a blog post/ + end + + should "have the proper usage line" do + output_matches /SYNOPSIS\n\s*wordpress\-post/m + output_matches /\[--title\]/ + end + + should "have option flags" do + output_matches /\-\-title(.*)/ + end + + should "have primary option flags" do + output_matches /\-\-test-primary(.*)/ + end + + should "have default option flags" do + output_matches /\-\-verbose/ + end + + should "have trollop default option flags" do + output_matches /\-e, \-\-version/ + end + + should "have the correct binary name and short description" do + output_matches /NAME\n\s*wordpress\-post \- create a blog post/m + end + + should "have a the primaries version string" do + output_matches /0\.0\.1 \(c\) 2009 Nate Murray - local/ + end + + should "have options" do + output_matches /Options/i + + output_matches /\-b, \-\-blog=/ + output_matches /short name of the blog to use/ + + output_matches /-i, \-\-title=/ + output_matches /title for the post/ + end + + end + end + + context "when running a bare primary" do + ["flickr -h", "flickr help"].each do |format| + context format do + setup { @stdout, @stderr = bin(format) } + + should "have the name and short description" do + unless format == "flickr -h" # hmm + output_matches /NAME\n\s*flickr\-help \- get help for a specific command/m + end + end + + should "have a local (not default) version string" do + output_matches /0\.0\.2 \(c\) #{Time.now.year}/ + end + end + end + ["flickr-download -h", "flickr download -h"].each do |format| + context format do + setup { @stdout, @stderr = bin(format) } + + should "match on usage" do + output_matches /SYNOPSIS\n\s*flickr\-download/m + end + end + end + end + +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/shoulda_macros/matching_stdio.rb b/bin/yadr/lib/git-style-binaries-0.1.11/test/shoulda_macros/matching_stdio.rb new file mode 100644 index 0000000..e8e90e5 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/shoulda_macros/matching_stdio.rb @@ -0,0 +1,13 @@ +class Test::Unit::TestCase + def output_should_match(regexp) + assert_match regexp, @stdout + @stderr + end + alias_method :output_matches, :output_should_match + + def stdout_should_match(regexp) + assert_match regexp, @stdout + end + def stderr_should_match(regexp) + assert_match regexp, @stderr + end +end diff --git a/bin/yadr/lib/git-style-binaries-0.1.11/test/test_helper.rb b/bin/yadr/lib/git-style-binaries-0.1.11/test/test_helper.rb new file mode 100644 index 0000000..7fcb576 --- /dev/null +++ b/bin/yadr/lib/git-style-binaries-0.1.11/test/test_helper.rb @@ -0,0 +1,28 @@ +require 'rubygems' +require 'test/unit' +require 'shoulda' +begin require 'redgreen'; rescue LoadError; end +require 'open3' + +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +$LOAD_PATH.unshift(File.dirname(__FILE__)) +Dir[File.join(File.dirname(__FILE__), "shoulda_macros", "*.rb")].each {|f| require f} +ENV['NO_COLOR'] = "true" + +require 'git-style-binary' +GitStyleBinary.run = true + +class Test::Unit::TestCase + def fixtures_dir + File.join(File.dirname(__FILE__), "fixtures") + end +end + +module RunsBinaryFixtures + # run the specified cmd returning the string values of [stdout,stderr] + def bin(cmd) + stdin, stdout, stderr = Open3.popen3("#{fixtures_dir}/#{cmd}") + [stdout.read, stderr.read] + end +end + diff --git a/bin/yadr/lib/trollop-1.16.2/FAQ.txt b/bin/yadr/lib/trollop-1.16.2/FAQ.txt new file mode 100644 index 0000000..809b35d --- /dev/null +++ b/bin/yadr/lib/trollop-1.16.2/FAQ.txt @@ -0,0 +1,84 @@ +Trollop FAQ +----------- + +Q: Why is it called "Trollop"? +A: No reason. + +Q: Why should I use Trollop? +A: Because it will take you FEWER LINES OF CODE to do reasonable option parsing + than any other option parser out there. + + Look at this: + + opts = Trollop::options do + opt :monkey, "Use monkey mode" + opt :goat, "Use goat mode", :default => true + opt :num_limbs, "Set number of limbs", :default => 4 + end + + That's it. And opts is a hash and you do whatever you want with it. + Trivial. You don't have to mix option processing code blocks with the + declarations. You don't have to make a class for every option (what is this, + Java?). You don't have to write more than 1 line of code per option. + + Plus, you get a beautiful help screen that detects your terminal width and + wraps appropriately. C'mon, that's hot. + +Q: What is the philosophy behind Trollop? +A: Must a commandline option processor have a philosophy? + +Q: Seriously now. What is it? +A: Ok, it's this: Trollop *just* does the parsing and gives you a hash table + of the result. So whatever fancy logic or constraints you need, you can + implement by operating on that hash table. Options that disable other + options, fancy constraints involving multiple sets of values across multiple + sets of options, etc. are all left for you to do manually. + + (Trollop does support limited constraint setting (see #conflicts and + #depends), but any non-trivial program will need to get fancier.) + + The result is that using Trollop is pretty simple, and whatever bizarre + logic you want, you can write yourself. And don't forget, you can call + Trollop::die to abort the program and give a fancy options-related error + message. + +Q: What happens to the other stuff on the commandline? +A: Anything Trollop doesn't recognize as an option or as an option parameter is + left in ARGV for you to process. + +Q: Does Trollop support multiple-value arguments? +A: Yes. If you set the :type of an option to something plural, like ":ints", + ":strings", ":doubles", ":floats", ":ios", it will accept multiple arguments + on the commandline and the value will be an array of these. + +Q: Does Trollop support arguments that can be given multiple times? +A: Yes. If you set :multi to true, then the argument can appear multiple times + on the commandline, and the value will be an array of the parameters. + +Q: Does Trollop support subcommands? +A: Yes. You get subcommand support by adding a call #stop_on within the options + block, and passing the names of the subcommands to it. (See the third + example on the webpage.) When Trollop encounters one of these subcommands on + the commandline, it stops processing and returns. + + ARGV at that point will contain the subcommand followed by any subcommand + options, since Trollop has contained the rest. So you can consume the + subcommand and call Trollop.options again with the particular options set + for that subcommand. + + If you don't know the subcommands ahead of time, you can call + #stop_on_unknown, which will cause Trollop to stop when it encounters any + unknown token. This might be more trouble than its worth if you're also + passing filenames on the commandline. + + It's probably easier to see the example on the webpage than to read all + that. + +Q: Why does Trollop disallow numeric short argument names, like '-1' and '-9'? +A: Because it's ambiguous whether these are arguments or negative integer or + floating-point parameters to arguments. E.g., what about "-f -3". Is that a + negative three parameter to -f, or two separate parameters? + + I could be very clever and detect when there are no arguments that require + floating-point parameters, and allow such short option names in those cases, + but opted for simplicity and consistency. diff --git a/bin/yadr/lib/trollop-1.16.2/History.txt b/bin/yadr/lib/trollop-1.16.2/History.txt new file mode 100644 index 0000000..bb2c8df --- /dev/null +++ b/bin/yadr/lib/trollop-1.16.2/History.txt @@ -0,0 +1,116 @@ +== 1.16.2 / 2010-04-06 +* Bugfix in Trollop::options. Thanks to Brian C. Thomas for pointing it out. + +== 1.16.1 / 2010-04-05 +* Bugfix in Trollop::die method introduced in last release. + +== 1.16 / 2010-04-01 +* Add Trollop::with_standard_exception_handling method for easing the use of Parser directly. +* Handle scientific notation in float arguments, thanks to Will Fitzgerald. +* Drop hoe dependency. + +== 1.15 / 2009-09-30 +* Don't raise an exception when out of short arguments (thanks to Rafael + Sevilla for pointing out how dumb this behavior was). + +== 1.14 / 2009-06-19 +* Make :multi arguments default to [], not nil, when not set on the commandline. +* Minor commenting and error message improvements + +== 1.13 / 2009-03-16 +* Fix parsing of "--longarg=". + +== 1.12 / 2009-01-30 +* Fix some unit test failures in the last release. Should be more careful. +* Make default short options only be assigned *after* all user-specified + short options. Now there's a little less juggling to do when you just + want to specify a few short options. + +== 1.11 / 2009-01-29 +* Set _given keys in the results hash for options that were specified + on the commandline. + +== 1.10.2 / 2008-10-23 +* No longer try `stty size` for screen size detection. Just use curses, and + screen users will have to deal with the screen clearing. + +== 1.10.1 / 2008-10-22 +* Options hash now responds to method calls as well as standard hash lookup. +* Default values for multi-occurrence parameters now autoboxed. +* The relationship between multi-value, multi-occurrence, and default values + improved and explained. +* Documentation improvements. + +== 1.10 / 2008-10-21 +* Added :io type for parameters that point to IO streams (filenames, URIs, etc). +* For screen size detection, first try `stty size` before loading Curses. +* Improved documentation. + +== 1.9 / 2008-08-20 +* Added 'stop_on_unknown' command to stop parsing on any unknown argument. + This is useful for handling sub-commands when you don't know the entire + set of commands up front. (E.g. if the initial arguments can change it.) +* Added a :multi option for parameters, signifying that they can be specified + multiple times. +* Added :ints, :strings, :doubles, and :floats option types, which can take + multiple arguments. + +== 1.8.2 / 2008-06-25 +* Bugfix for #conflicts and #depends error messages + +== 1.8.1 / 2008-06-24 +* Bugfix for short option autocreation +* More aggressive documentation + +== 1.8 / 2008-06-16 +* Sub-command support via Parser#stop_on + +== 1.7.2 / 2008-01-16 +* Ruby 1.9-ify. Apparently this means replacing :'s with ;'s. + +== 1.7.1 / 2008-01-07 +* Documentation improvements + +== 1.7 / 2007-06-17 +* Fix incorrect error message for multiple missing required arguments + (thanks to Neill Zero) + +== 1.6 / 2007-04-01 +* Don't attempt curses screen-width magic unless running on a terminal. + +== 1.5 / 2007-03-31 +* --help and --version do the right thing even if the rest of the + command line is incorrect. +* Added #conflicts and #depends to model dependencies and exclusivity + between arguments. +* Minor bugfixes. + +== 1.4 / 2007-03-26 +* Disable short options with :short => :none. +* Minor bugfixes and error message improvements. + +== 1.3 / 2007-01-31 +* Wrap at (screen width - 1) instead of screen width. +* User can override --help and --version. +* Bugfix in handling of -v and -h. +* More tests to confirm the above. + +== 1.2 / 2007-01-31 +* Minor documentation tweaks. +* Removed hoe dependency. + +== 1.1 / 2007-01-30 +* Trollop::options now passes any arguments as block arguments. Since + instance variables are not properly captured by the block, this + makes it slightly less noisy to pass them in as local variables. + (A real-life use for _why's cloaker!) +* Help display now preserves original argument order. +* Trollop::die now also has a single string form in case death is not + due to a single argument. +* Parser#text now an alias for Parser#banner, and can be called + multiple times, with the output being placed in the right position + in the help text. +* Slightly more indicative formatting for parameterized arguments. + +== 1.0 / 2007-01-29 +* Initial release. diff --git a/bin/yadr/lib/trollop-1.16.2/README.txt b/bin/yadr/lib/trollop-1.16.2/README.txt new file mode 100644 index 0000000..7258008 --- /dev/null +++ b/bin/yadr/lib/trollop-1.16.2/README.txt @@ -0,0 +1,52 @@ +== trollop + +by William Morgan (http://masanjin.net/) + +Main page: http://trollop.rubyforge.org + +Release announcements and comments: http://all-thing.net/label/trollop + +Documentation quickstart: See Trollop.options and then Trollop::Parser#opt. +Also see the examples at http://trollop.rubyforge.org/. + +== DESCRIPTION + +Trollop is a commandline option parser for Ruby that just gets out of your +way. One line of code per option is all you need to write. For that, you get a +nice automatically-generated help page, robust option parsing, command +subcompletion, and sensible defaults for everything you don't specify. + +== FEATURES/PROBLEMS + +- Dirt-simple usage. +- Sensible defaults. No tweaking necessary, much tweaking possible. +- Support for long options, short options, short option bundling, and + automatic type validation and conversion. +- Support for subcommands. +- Automatic help message generation, wrapped to current screen width. +- Lots of unit tests. + +== REQUIREMENTS + +* A burning desire to write less code. + +== INSTALL + +* gem install trollop + +== SYNOPSIS + + require 'trollop' + opts = Trollop::options do + opt :monkey, "Use monkey mode" # flag --monkey, default false + opt :goat, "Use goat mode", :default => true # flag --goat, default true + opt :num_limbs, "Number of limbs", :default => 4 # integer --num-limbs , default to 4 + opt :num_thumbs, "Number of thumbs", :type => :int # integer --num-thumbs , default nil + end + + p opts # a hash: { :monkey => false, :goat => true, :num_limbs => 4, :num_thumbs => nil } + +== LICENSE + +Copyright (c) 2008--2009 William Morgan. Trollop is distributed under the same +terms as Ruby. diff --git a/bin/yadr/lib/trollop-1.16.2/lib/trollop.rb b/bin/yadr/lib/trollop-1.16.2/lib/trollop.rb new file mode 100644 index 0000000..06ee2af --- /dev/null +++ b/bin/yadr/lib/trollop-1.16.2/lib/trollop.rb @@ -0,0 +1,781 @@ +## lib/trollop.rb -- trollop command-line processing library +## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net) +## Copyright:: Copyright 2007 William Morgan +## License:: the same terms as ruby itself + +require 'date' + +module Trollop + +VERSION = "1.16.2" + +## Thrown by Parser in the event of a commandline error. Not needed if +## you're using the Trollop::options entry. +class CommandlineError < StandardError; end + +## Thrown by Parser if the user passes in '-h' or '--help'. Handled +## automatically by Trollop#options. +class HelpNeeded < StandardError; end + +## Thrown by Parser if the user passes in '-h' or '--version'. Handled +## automatically by Trollop#options. +class VersionNeeded < StandardError; end + +## Regex for floating point numbers +FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/ + +## Regex for parameters +PARAM_RE = /^-(-|\.$|[^\d\.])/ + +## The commandline parser. In typical usage, the methods in this class +## will be handled internally by Trollop::options. In this case, only the +## #opt, #banner and #version, #depends, and #conflicts methods will +## typically be called. +## +## If you want to instantiate this class yourself (for more complicated +## argument-parsing logic), call #parse to actually produce the output hash, +## and consider calling it from within +## Trollop::with_standard_exception_handling. +class Parser + + ## The set of values that indicate a flag option when passed as the + ## +:type+ parameter of #opt. + FLAG_TYPES = [:flag, :bool, :boolean] + + ## The set of values that indicate a single-parameter (normal) option when + ## passed as the +:type+ parameter of #opt. + ## + ## A value of +io+ corresponds to a readable IO resource, including + ## a filename, URI, or the strings 'stdin' or '-'. + SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date] + + ## The set of values that indicate a multiple-parameter option (i.e., that + ## takes multiple space-separated values on the commandline) when passed as + ## the +:type+ parameter of #opt. + MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates] + + ## The complete set of legal values for the +:type+ parameter of #opt. + TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES + + INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc: + + ## The values from the commandline that were not interpreted by #parse. + attr_reader :leftovers + + ## The complete configuration hashes for each option. (Mainly useful + ## for testing.) + attr_reader :specs + + ## Initializes the parser, and instance-evaluates any block given. + def initialize *a, &b + @version = nil + @leftovers = [] + @specs = {} + @long = {} + @short = {} + @order = [] + @constraints = [] + @stop_words = [] + @stop_on_unknown = false + + #instance_eval(&b) if b # can't take arguments + cloaker(&b).bind(self).call(*a) if b + end + + ## Define an option. +name+ is the option name, a unique identifier + ## for the option that you will use internally, which should be a + ## symbol or a string. +desc+ is a string description which will be + ## displayed in help messages. + ## + ## Takes the following optional arguments: + ## + ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s. + ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+. + ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given. + ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the the commandline the value will be +false+. + ## [+:required+] If set to +true+, the argument must be provided on the commandline. + ## [+:multi+] If set to +true+, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.) + ## + ## Note that there are two types of argument multiplicity: an argument + ## can take multiple values, e.g. "--arg 1 2 3". An argument can also + ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2". + ## + ## Arguments that take multiple values should have a +:type+ parameter + ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+ + ## value of an array of the correct type (e.g. [String]). The + ## value of this argument will be an array of the parameters on the + ## commandline. + ## + ## Arguments that can occur multiple times should be marked with + ## +:multi+ => +true+. The value of this argument will also be an array. + ## In contrast with regular non-multi options, if not specified on + ## the commandline, the default value will be [], not nil. + ## + ## These two attributes can be combined (e.g. +:type+ => +:strings+, + ## +:multi+ => +true+), in which case the value of the argument will be + ## an array of arrays. + ## + ## There's one ambiguous case to be aware of: when +:multi+: is true and a + ## +:default+ is set to an array (of something), it's ambiguous whether this + ## is a multi-value argument as well as a multi-occurrence argument. + ## In thise case, Trollop assumes that it's not a multi-value argument. + ## If you want a multi-value, multi-occurrence argument with a default + ## value, you must specify +:type+ as well. + + def opt name, desc="", opts={} + raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name + + ## fill in :type + opts[:type] = # normalize + case opts[:type] + when :boolean, :bool; :flag + when :integer; :int + when :integers; :ints + when :double; :float + when :doubles; :floats + when Class + case opts[:type].name + when 'TrueClass', 'FalseClass'; :flag + when 'String'; :string + when 'Integer'; :int + when 'Float'; :float + when 'IO'; :io + when 'Date'; :date + else + raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'" + end + when nil; nil + else + raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type]) + opts[:type] + end + + ## for options with :multi => true, an array default doesn't imply + ## a multi-valued argument. for that you have to specify a :type + ## as well. (this is how we disambiguate an ambiguous situation; + ## see the docs for Parser#opt for details.) + disambiguated_default = + if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type] + opts[:default].first + else + opts[:default] + end + + type_from_default = + case disambiguated_default + when Integer; :int + when Numeric; :float + when TrueClass, FalseClass; :flag + when String; :string + when IO; :io + when Date; :date + when Array + if opts[:default].empty? + raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'" + end + case opts[:default][0] # the first element determines the types + when Integer; :ints + when Numeric; :floats + when String; :strings + when IO; :ios + when Date; :dates + else + raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'" + end + when nil; nil + else + raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'" + end + + raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default + + opts[:type] = opts[:type] || type_from_default || :flag + + ## fill in :long + opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-") + opts[:long] = + case opts[:long] + when /^--([^-].*)$/ + $1 + when /^[^-]/ + opts[:long] + else + raise ArgumentError, "invalid long option name #{opts[:long].inspect}" + end + raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]] + + ## fill in :short + opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none + opts[:short] = case opts[:short] + when /^-(.)$/; $1 + when nil, :none, /^.$/; opts[:short] + else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'" + end + + if opts[:short] + raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]] + raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX + end + + ## fill in :default for flags + opts[:default] = false if opts[:type] == :flag && opts[:default].nil? + + ## autobox :default for :multi (multi-occurrence) arguments + opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array) + + ## fill in :multi + opts[:multi] ||= false + + opts[:desc] ||= desc + @long[opts[:long]] = name + @short[opts[:short]] = name if opts[:short] && opts[:short] != :none + @specs[name] = opts + @order << [:opt, name] + end + + ## Sets the version string. If set, the user can request the version + ## on the commandline. Should probably be of the form " + ## ". + def version s=nil; @version = s if s; @version end + + ## Adds text to the help display. Can be interspersed with calls to + ## #opt to build a multi-section help page. + def banner s; @order << [:text, s] end + alias :text :banner + + ## Marks two (or more!) options as requiring each other. Only handles + ## undirected (i.e., mutual) dependencies. Directed dependencies are + ## better modeled with Trollop::die. + def depends *syms + syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] } + @constraints << [:depends, syms] + end + + ## Marks two (or more!) options as conflicting. + def conflicts *syms + syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] } + @constraints << [:conflicts, syms] + end + + ## Defines a set of words which cause parsing to terminate when + ## encountered, such that any options to the left of the word are + ## parsed as usual, and options to the right of the word are left + ## intact. + ## + ## A typical use case would be for subcommand support, where these + ## would be set to the list of subcommands. A subsequent Trollop + ## invocation would then be used to parse subcommand options, after + ## shifting the subcommand off of ARGV. + def stop_on *words + @stop_words = [*words].flatten + end + + ## Similar to #stop_on, but stops on any unknown word when encountered + ## (unless it is a parameter for an argument). This is useful for + ## cases where you don't know the set of subcommands ahead of time, + ## i.e., without first parsing the global options. + def stop_on_unknown + @stop_on_unknown = true + end + + ## Parses the commandline. Typically called by Trollop::options, + ## but you can call it directly if you need more control. + ## + ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions. + def parse cmdline=ARGV + vals = {} + required = {} + + opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"] + opt :help, "Show this message" unless @specs[:help] || @long["help"] + + @specs.each do |sym, opts| + required[sym] = true if opts[:required] + vals[sym] = opts[:default] + vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil + end + + resolve_default_short_options + + ## resolve symbols + given_args = {} + @leftovers = each_arg cmdline do |arg, params| + sym = case arg + when /^-([^-])$/ + @short[$1] + when /^--([^-]\S*)$/ + @long[$1] + else + raise CommandlineError, "invalid argument syntax: '#{arg}'" + end + raise CommandlineError, "unknown argument '#{arg}'" unless sym + + if given_args.include?(sym) && !@specs[sym][:multi] + raise CommandlineError, "option '#{arg}' specified multiple times" + end + + given_args[sym] ||= {} + + given_args[sym][:arg] = arg + given_args[sym][:params] ||= [] + + # The block returns the number of parameters taken. + num_params_taken = 0 + + unless params.nil? + if SINGLE_ARG_TYPES.include?(@specs[sym][:type]) + given_args[sym][:params] << params[0, 1] # take the first parameter + num_params_taken = 1 + elsif MULTI_ARG_TYPES.include?(@specs[sym][:type]) + given_args[sym][:params] << params # take all the parameters + num_params_taken = params.size + end + end + + num_params_taken + end + + ## check for version and help args + raise VersionNeeded if given_args.include? :version + raise HelpNeeded if given_args.include? :help + + ## check constraint satisfaction + @constraints.each do |type, syms| + constraint_sym = syms.find { |sym| given_args[sym] } + next unless constraint_sym + + case type + when :depends + syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym } + when :conflicts + syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) } + end + end + + required.each do |sym, val| + raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym + end + + ## parse parameters + given_args.each do |sym, given_data| + arg = given_data[:arg] + params = given_data[:params] + + opts = @specs[sym] + raise CommandlineError, "option '#{arg}' needs a parameter" if params.empty? && opts[:type] != :flag + + vals["#{sym}_given".intern] = true # mark argument as specified on the commandline + + case opts[:type] + when :flag + vals[sym] = !opts[:default] + when :int, :ints + vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } } + when :float, :floats + vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } } + when :string, :strings + vals[sym] = params.map { |pg| pg.map { |p| p.to_s } } + when :io, :ios + vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } } + when :date, :dates + vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } } + end + + if SINGLE_ARG_TYPES.include?(opts[:type]) + unless opts[:multi] # single parameter + vals[sym] = vals[sym][0][0] + else # multiple options, each with a single parameter + vals[sym] = vals[sym].map { |p| p[0] } + end + elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi] + vals[sym] = vals[sym][0] # single option, with multiple parameters + end + # else: multiple options, with multiple parameters + end + + ## modify input in place with only those + ## arguments we didn't process + cmdline.clear + @leftovers.each { |l| cmdline << l } + + ## allow openstruct-style accessors + class << vals + def method_missing(m, *args) + self[m] || self[m.to_s] + end + end + vals + end + + def parse_date_parameter param, arg #:nodoc: + begin + begin + time = Chronic.parse(param) + rescue NameError + # chronic is not available + end + time ? Date.new(time.year, time.month, time.day) : Date.parse(param) + rescue ArgumentError => e + raise CommandlineError, "option '#{arg}' needs a date" + end + end + + ## Print the help message to +stream+. + def educate stream=$stdout + width # just calculate it now; otherwise we have to be careful not to + # call this unless the cursor's at the beginning of a line. + + left = {} + @specs.each do |name, spec| + left[name] = "--#{spec[:long]}" + + (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") + + case spec[:type] + when :flag; "" + when :int; " " + when :ints; " " + when :string; " " + when :strings; " " + when :float; " " + when :floats; " " + when :io; " " + when :ios; " " + when :date; " " + when :dates; " " + end + end + + leftcol_width = left.values.map { |s| s.length }.max || 0 + rightcol_start = leftcol_width + 6 # spaces + + unless @order.size > 0 && @order.first.first == :text + stream.puts "#@version\n" if @version + stream.puts "Options:" + end + + @order.each do |what, opt| + if what == :text + stream.puts wrap(opt) + next + end + + spec = @specs[opt] + stream.printf " %#{leftcol_width}s: ", left[opt] + desc = spec[:desc] + begin + default_s = case spec[:default] + when $stdout; "" + when $stdin; "" + when $stderr; "" + when Array + spec[:default].join(", ") + else + spec[:default].to_s + end + + if spec[:default] + if spec[:desc] =~ /\.$/ + " (Default: #{default_s})" + else + " (default: #{default_s})" + end + else + "" + end + end + stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start) + end + end + + def width #:nodoc: + @width ||= if $stdout.tty? + begin + require 'curses' + Curses::init_screen + x = Curses::cols + Curses::close_screen + x + rescue Exception + 80 + end + else + 80 + end + end + + def wrap str, opts={} # :nodoc: + if str == "" + [""] + else + str.split("\n").map { |s| wrap_line s, opts }.flatten + end + end + + ## The per-parser version of Trollop::die (see that for documentation). + def die arg, msg + if msg + $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}." + else + $stderr.puts "Error: #{arg}." + end + $stderr.puts "Try --help for help." + exit(-1) + end + +private + + ## yield successive arg, parameter pairs + def each_arg args + remains = [] + i = 0 + + until i >= args.length + if @stop_words.member? args[i] + remains += args[i .. -1] + return remains + end + case args[i] + when /^--$/ # arg terminator + remains += args[(i + 1) .. -1] + return remains + when /^--(\S+?)=(.*)$/ # long argument with equals + yield "--#{$1}", [$2] + i += 1 + when /^--(\S+)$/ # long argument + params = collect_argument_parameters(args, i + 1) + unless params.empty? + num_params_taken = yield args[i], params + unless num_params_taken + if @stop_on_unknown + remains += args[i + 1 .. -1] + return remains + else + remains += params + end + end + i += 1 + num_params_taken + else # long argument no parameter + yield args[i], nil + i += 1 + end + when /^-(\S+)$/ # one or more short arguments + shortargs = $1.split(//) + shortargs.each_with_index do |a, j| + if j == (shortargs.length - 1) + params = collect_argument_parameters(args, i + 1) + unless params.empty? + num_params_taken = yield "-#{a}", params + unless num_params_taken + if @stop_on_unknown + remains += args[i + 1 .. -1] + return remains + else + remains += params + end + end + i += 1 + num_params_taken + else # argument no parameter + yield "-#{a}", nil + i += 1 + end + else + yield "-#{a}", nil + end + end + else + if @stop_on_unknown + remains += args[i .. -1] + return remains + else + remains << args[i] + i += 1 + end + end + end + + remains + end + + def parse_integer_parameter param, arg + raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/ + param.to_i + end + + def parse_float_parameter param, arg + raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE + param.to_f + end + + def parse_io_parameter param, arg + case param + when /^(stdin|-)$/i; $stdin + else + require 'open-uri' + begin + open param + rescue SystemCallError => e + raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}" + end + end + end + + def collect_argument_parameters args, start_at + params = [] + pos = start_at + while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do + params << args[pos] + pos += 1 + end + params + end + + def resolve_default_short_options + @order.each do |type, name| + next unless type == :opt + opts = @specs[name] + next if opts[:short] + + c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) } + if c # found a character to use + opts[:short] = c + @short[c] = name + end + end + end + + def wrap_line str, opts={} + prefix = opts[:prefix] || 0 + width = opts[:width] || (self.width - 1) + start = 0 + ret = [] + until start > str.length + nextt = + if start + width >= str.length + str.length + else + x = str.rindex(/\s/, start + width) + x = str.index(/\s/, start) if x && x < start + x || str.length + end + ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt] + start = nextt + 1 + end + ret + end + + ## instance_eval but with ability to handle block arguments + ## thanks to why: http://redhanded.hobix.com/inspect/aBlockCostume.html + def cloaker &b + (class << self; self; end).class_eval do + define_method :cloaker_, &b + meth = instance_method :cloaker_ + remove_method :cloaker_ + meth + end + end +end + +## The easy, syntactic-sugary entry method into Trollop. Creates a Parser, +## passes the block to it, then parses +args+ with it, handling any errors or +## requests for help or version information appropriately (and then exiting). +## Modifies +args+ in place. Returns a hash of option values. +## +## The block passed in should contain zero or more calls to +opt+ +## (Parser#opt), zero or more calls to +text+ (Parser#text), and +## probably a call to +version+ (Parser#version). +## +## The returned block contains a value for every option specified with +## +opt+. The value will be the value given on the commandline, or the +## default value if the option was not specified on the commandline. For +## every option specified on the commandline, a key "