Permalink
Browse files

included subcommands library wrapper to optparse

  • Loading branch information...
1 parent 386a2d2 commit fad894bd11cd1239a226b079c1c0294e8dabdc4d @schacon committed Feb 18, 2011
Showing with 308 additions and 19 deletions.
  1. +1 −1 bin/git-scribe
  2. +1 −1 git-scribe.gemspec
  3. +40 −17 lib/git-scribe.rb
  4. +266 −0 lib/subcommand.rb
View
2 bin/git-scribe
@@ -3,4 +3,4 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
require 'git-scribe'
-GitScribe.start(ARGV)
+GitScribe.start
View
2 git-scribe.gemspec
@@ -2,7 +2,7 @@ $LOAD_PATH.unshift 'lib'
Gem::Specification.new do |s|
s.name = "git-scribe"
- s.version = "0.0.1"
+ s.version = "0.0.3"
s.date = Time.now.strftime('%Y-%m-%d')
s.summary = "git-scribe is an authors toolkit for writing and publishing books"
s.homepage = "http://github.com/schacon/git-scribe"
View
57 lib/git-scribe.rb
@@ -1,21 +1,50 @@
require 'rubygems'
require 'nokogiri'
require 'liquid'
+require 'subcommand'
require 'fileutils'
require 'pp'
class GitScribe
+ include Subcommands
+
+ BOOK_FILE = 'book.asc'
+ OUTPUT_TYPES = ['pdf', 'epub', 'mobi', 'html', 'site']
SCRIBE_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
- def initialize(args)
- @command = args.shift
- @args = args
+ def initialize
+
+ @options = {}
+ global_options do |opts|
+ opts.banner = "Usage: #{$0} [options] [subcommand [options]]"
+ opts.description = "git-scribe helps you write books with the power of Git"
+ opts.separator ""
+ opts.separator "Global options are:"
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
+ @options[:verbose] = v
+ end
+ end
+
+ command :init do |opts|
+ opts.banner = "Usage: git scribe init (directory)"
+ opts.description = "initialize a new book layout"
+ opts.on("-l", "--lang", "choose a default language (en)") do |v|
+ @options[:lang] = lang || 'en'
+ end
+ end
+
+ command :gen do |opts|
+ opts.banner = "Usage: git scribe gen [options]"
+ opts.description = "generate digital formats"
+ end
+
+ @command = opt_parse
end
- def self.start(args)
- GitScribe.new(args).run
+ def self.start
+ GitScribe.new.run
end
def run
@@ -28,14 +57,14 @@ def run
## COMMANDS ##
- def help
- puts "No command: #{@command}"
- puts "TODO: tons of help"
+
+ def die(message)
+ raise message
end
# start a new scribe directory with skeleton structure
def init
- name = @args.shift
+ name = ARGV.shift
die("needs a directory name") if !name
die("directory already exists") if File.exists?(name)
@@ -79,14 +108,9 @@ def check_can_run(command)
`#{command} 2>&1`
$?.exitstatus == 0
end
-
- BOOK_FILE = 'book.asc'
-
- OUTPUT_TYPES = ['pdf', 'epub', 'mobi', 'html', 'site']
-
# generate the new media
def gen
- type = @args.shift || 'all'
+ type = ARGV.shift || 'all'
prepare_output_dir
gather_and_process
@@ -104,8 +128,7 @@ def gen
end
end
# clean up
- # `rm #{BOOK_FILE}`
- # TODO: open media (?)
+ `rm #{BOOK_FILE}`
end
end
View
266 lib/subcommand.rb
@@ -0,0 +1,266 @@
+#!/usr/bin/env ruby -w
+######################################
+# A tiny wrapper over optparse that gives easy subcommand facility.
+# It also neatly prints help for global and subcommands
+# as well as summarizes subcommands in global help.
+#
+# Thanks to Robert Klemme for his idea on lazy loading the subcommand option parsers.
+#
+# @author Rahul Kumar, Jun 2010
+# @date 2010-06-20 22:33
+#
+# @examples
+# if a program has subcommands foo and baz
+#
+# ruby subcommand.rb help
+# ruby subcommand.rb --help
+# ruby subcommand.rb help foo
+# ruby subcommand.rb foo --help
+# ruby subcommand.rb baz --quiet "some text"
+# ruby subcommand.rb --verbose foo --force file.zzz
+#
+# == STEPS
+# 1. define global_options (optional)
+#
+# global_options do |opts|
+# opts.banner = "Usage: #{$0} [options] [subcommand [options]]"
+# opts.description = "Stupid program that does something"
+# opts.separator ""
+# opts.separator "Global options are:"
+# opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
+# options[:verbose] = v
+# end
+# end
+#
+# 2. define commands using command().
+# command :foo do |opts|
+# opts.banner = "Usage: foo [options]"
+# opts.description = "desc for foo"
+# opts.on("-f", "--[no-]force", "force verbosely") do |v|
+# options[:force] = v
+# end
+# end
+#
+# 3. call opt_parse()
+#
+# 4. As before, handle ARGS and options hash.
+#
+# TODO: add aliases for commands
+######################################
+require 'optparse'
+
+# Allow command to have a description to generate help
+class OptionParser
+ attr_accessor :description
+ #attr_accessor :action
+end
+
+module Subcommands
+ ##
+ # specify a single command and all its options
+ # If multiple names are given, they are treated as aliases.
+ # Do repeatedly for each command
+ # Yields the optionparser
+ def command *names
+ name = names.shift
+ @commands ||= {}
+ @aliases ||= {}
+ if names.length > 0
+ names.each do |n|
+ #puts "aliases #{n} => #{name} "
+ @aliases[n.to_s] = name.to_s
+ end
+ end
+ # Thanks to Robert Klemme for the lazy loading idea.
+ opt = lambda { OptionParser.new do |opts|
+ yield opts
+ # append desc to banner in next line
+ opts.banner << "\n#{opts.description}\n" if opts.description
+ end }
+ @commands[name.to_s] = opt
+ end
+ # specify global options and banner and description
+ # Yields the optionparser
+ def global_options
+ if !defined? @global
+ @global = OptionParser.new do |opts|
+ yield opts
+ end
+ else
+ yield @global
+ end
+ end
+ def print_actions
+ cmdtext = "Commands are:"
+ @commands.each_pair do |c, opt|
+ #puts "inside opt.call loop"
+ desc = opt.call.description
+ cmdtext << "\n #{c} : #{desc}"
+ end
+
+ # print aliases
+ unless @aliases.empty?
+ cmdtext << "\n\nAliases: \n"
+ @aliases.each_pair { |name, val| cmdtext << " #{name} - #{val}\n" }
+ end
+
+ cmdtext << "\n\nSee '#{$0} help COMMAND' for more information on a specific command."
+ end
+ ## add text of subcommands in help and --help option
+ def add_subcommand_help
+ # user has defined some, but lets add subcommand information
+
+ cmdtext = print_actions
+
+ global_options do |opts|
+ # lets add the description user gave into banner
+ opts.banner << "\n#{opts.description}\n" if opts.description
+ opts.separator ""
+ opts.separator cmdtext
+ end
+ end
+ # this is so that on pressing --help he gets same subcommand help as when doing help.
+ # This is to be added in your main program, after defining global options
+ # if you want detailed help on --help. This is since optionparser's default
+ # --help will not print your actions/commands
+ def add_help_option
+ global_options do |opts|
+ opts.on("-h", "--help", "Print this help") do |v|
+ add_subcommand_help
+ puts @global
+ exit
+ end
+ end
+ end
+ # first parse global optinos
+ # then parse subcommand options if valid subcommand
+ # special case of "help command" so we print help of command - git style (3)
+ # in all invalid cases print global help
+ # @return command name if relevant
+ def opt_parse
+ # if user has not defined global, we need to create it
+ @command_name = nil
+ if !defined? @global
+ global_options do |opts|
+ opts.banner = "Usage: #{$0} [options] [subcommand [options]]"
+ opts.separator ""
+ opts.separator "Global options are:"
+ opts.on("-h", "--help", "Print this help") do |v|
+ add_subcommand_help
+ puts @global
+ exit
+ end
+ opts.separator ""
+ #opts.separator subtext # FIXME: no such variable supposed to have subcommand help
+ end
+ else
+ end
+ @global.order!
+ cmd = ARGV.shift
+ if cmd
+ #$stderr.puts "Command: #{cmd}, args:#{ARGV}, #{@commands.keys} "
+ sc = @commands[cmd]
+ #puts "sc: #{sc}: #{@commands}"
+ unless sc
+ # see if an alias exists
+ sc, cmd = _check_alias cmd
+ end
+ # if valid command parse the args
+ if sc
+ @command_name = cmd
+ sc.call.order!
+ else
+ # else if help <command> then print its help GIT style (3)
+ if !ARGV.empty? && cmd == "help"
+ cmd = ARGV.shift
+ #$stderr.puts " 110 help #{cmd}"
+ sc = @commands[cmd]
+ # if valid command print help, else print global help
+ unless sc
+ sc, cmd = _check_alias cmd
+ end
+ if sc
+ #puts " 111 help #{cmd}"
+ puts sc.call
+ else
+ # no help for this command XXX check for alias
+ puts "Invalid command: #{cmd}."
+ add_subcommand_help
+ puts @global
+ end
+ else
+ # invalid command
+ puts "Invalid command: #{cmd}" unless cmd == "help"
+ add_subcommand_help
+ puts @global
+ end
+ exit 0
+ end
+ end
+ return @command_name
+ end
+ def alias_command name, *args
+ @aliases[name.to_s] = args
+ end
+ def _check_alias cmd
+ alas = @aliases[cmd]
+ #$stderr.puts "195 alas: #{alas} "
+ if alas
+ case alas
+ when Array
+ cmd = alas.shift
+ #$stderr.puts "Array cmd: #{cmd} "
+ ARGV.unshift alas.shift unless alas.empty?
+ #$stderr.puts "ARGV #{ARGV} "
+ else
+ cmd = alas
+ end
+ end
+ sc = @commands[cmd] if cmd
+ return sc, cmd
+ end
+end
+
+if __FILE__ == $PROGRAM_NAME
+ include Subcommands
+ options = {}
+ appname = File.basename($0)
+ # global is optional
+ global_options do |opts|
+ opts.banner = "Usage: #{appname} [options] [subcommand [options]]"
+ opts.description = "Stupid program that does something"
+ opts.separator ""
+ opts.separator "Global options are:"
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
+ options[:verbose] = v
+ end
+ end
+ add_help_option
+ # define a command
+ command :foo, :goo do |opts|
+ opts.banner = "Usage: foo [options]"
+ opts.description = "desc for foo"
+ opts.on("-f", "--[no-]force", "force verbosely") do |v|
+ options[:force] = v
+ end
+ end
+ command :baz do |opts|
+ opts.banner = "Usage: baz [options]"
+ opts.description = "desc for baz"
+ opts.on("-q", "--[no-]quiet", "quietly run ") do |v|
+ options[:quiet] = v
+ end
+ end
+ alias_command :bar, 'baz'
+ alias_command :boo, 'foo', '--force'
+ alias_command :zoo, 'foo', 'ruby'
+
+ # do the parsing.
+ cmd = opt_parse()
+
+ puts "cmd: #{cmd}"
+ puts "options ......"
+ p options
+ puts "ARGV:"
+ p ARGV
+end

0 comments on commit fad894b

Please sign in to comment.