├── Gemfile ├── lib ├── cli_base.rb └── cli │ ├── config.rb │ ├── core_ext.rb │ ├── errors.rb │ ├── version.rb │ ├── utils.rb │ ├── source.rb │ ├── base.rb │ ├── parser.rb │ └── help.rb ├── demo ├── 00_introduction.rdoc ├── applique │ └── compare.rb ├── samples │ └── help.txt ├── 01_single_command.rdoc ├── 04_help_text.rdoc ├── 02_multiple_commands.rdoc └── 03_optparse_example.rdoc ├── .gitignore ├── .yardopts ├── .travis.yml ├── Rakefile ├── try ├── man │ ├── example2.1.ronn │ ├── example2.1 │ └── example2.1.html ├── example2 └── example1 ├── MANIFEST.txt ├── work ├── concepts │ ├── r5_subclasses │ │ ├── qed │ │ │ ├── samples │ │ │ │ └── help.txt │ │ │ ├── 01_single_command.rdoc │ │ │ ├── 04_help_text.rdoc │ │ │ ├── 02_multiple_commands.rdoc │ │ │ └── 03_rdoc_example.rdoc │ │ └── lib │ │ │ ├── subcommand │ │ │ └── help.rb │ │ │ └── subcommand.rb │ ├── r0_underscore │ │ ├── test_command.rb │ │ └── command.rb │ ├── r4_allinone │ │ └── cliclass.rb │ ├── r3_executable │ │ ├── README.rdoc │ │ └── executable.rb │ ├── r2_subcommand │ │ ├── command3.rb │ │ ├── command2.rb │ │ └── command.rb │ └── r1_option_mixins │ │ └── command.rb └── NOTES.rdoc ├── COPYING.rdoc ├── Assembly ├── HISTORY.rdoc ├── PROFILE ├── test └── test_cli_base.rb ├── .ruby ├── README.rdoc └── .gemspec /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /lib/cli_base.rb: -------------------------------------------------------------------------------- 1 | require 'cli/base' 2 | 3 | -------------------------------------------------------------------------------- /demo/00_introduction.rdoc: -------------------------------------------------------------------------------- 1 | = CLI::Base 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | log 4 | pkg 5 | tmp 6 | pages 7 | DEMO.rdoc 8 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "CLI Base" 2 | --readme README.rdoc 3 | --protected 4 | --private 5 | lib 6 | - 7 | [A-Z]*.* 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec qed" 3 | rvm: 4 | - 1.9.2 5 | - 1.9.3 6 | - rbx-2.0 7 | - jruby 8 | - ree 9 | 10 | -------------------------------------------------------------------------------- /demo/applique/compare.rb: -------------------------------------------------------------------------------- 1 | When 'should be clearly laid out as follows' do |text| 2 | text = text.sub('$0', File.basename($0)) 3 | @out.strip.assert == text.strip 4 | end 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => [:test] 2 | 3 | desc "run tests" 4 | task :test do 5 | sh "ruby-test -Ilib test/*.rb" 6 | end 7 | 8 | desc "convert README to site/readme.html" 9 | task :readme do 10 | sh "malt README.rdoc > site/readme.html" 11 | end 12 | -------------------------------------------------------------------------------- /try/man/example2.1.ronn: -------------------------------------------------------------------------------- 1 | subcmds.rb(1) example cli using manpage 2 | ======================================= 3 | 4 | ## SUBCOMMANDS 5 | 6 | * foo - foo does waht foo does 7 | 8 | ## OPTIONS 9 | 10 | * `--verbose` - do it loudly 11 | 12 | ## SEE ALSO 13 | 14 | exec(1) 15 | 16 | -------------------------------------------------------------------------------- /lib/cli/config.rb: -------------------------------------------------------------------------------- 1 | module CLI 2 | 3 | # Config is essentially an OpenHash. 4 | class Config < Hash 5 | def method_missing(s, *a, &b) 6 | name = s.to_s 7 | case name 8 | when /=$/ 9 | self[name.chomp('=')] = a.first 10 | else 11 | self[name] 12 | end 13 | end 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /try/man/example2.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "EXAMPLE2" "1" "November 2011" "" "" 5 | . 6 | .SH "SUBCOMMANDS" 7 | . 8 | .IP "\(bu" 4 9 | foo \- foo does waht foo does 10 | . 11 | .IP "" 0 12 | . 13 | .SH "OPTIONS" 14 | . 15 | .IP "\(bu" 4 16 | \fB\-\-verbose\fR \- do it loudly 17 | . 18 | .IP "" 0 19 | . 20 | .SH "SEE ALSO" 21 | exec(1) 22 | -------------------------------------------------------------------------------- /MANIFEST.txt: -------------------------------------------------------------------------------- 1 | #!mast .ruby .yardopts bin lib spec test [A-Z]*.* 2 | .ruby 3 | .yardopts 4 | lib/cli/base.rb 5 | lib/cli/config.rb 6 | lib/cli/core_ext.rb 7 | lib/cli/errors.rb 8 | lib/cli/help.rb 9 | lib/cli/parser.rb 10 | lib/cli/utils.rb 11 | lib/cli/version.rb 12 | lib/cli_base.rb 13 | test/test_cli_base.rb 14 | HISTORY.rdoc 15 | DEMO.rdoc 16 | SPEC.rdoc 17 | README.rdoc 18 | ROADMAP.rdoc 19 | NOTES.rdoc 20 | COPYING.rdoc 21 | -------------------------------------------------------------------------------- /demo/samples/help.txt: -------------------------------------------------------------------------------- 1 | qed 2 | 3 | COMMANDS: 4 | 5 | c1 This does c1. 6 | c2 This does c2. 7 | 8 | OPTIONS FOR c1: 9 | 10 | --o1 This is option --o1 for c1. 11 | --o2 This is option --o2 for c1. 12 | 13 | OPTIONS FOR c2: 14 | 15 | --o1 This is option --o1 for c2. 16 | --o2 This is option --o2 for c2. 17 | 18 | COMMON OPTIONS: 19 | 20 | -g This is global option -g. 21 | 22 | -------------------------------------------------------------------------------- /work/concepts/r5_subclasses/qed/samples/help.txt: -------------------------------------------------------------------------------- 1 | qed 2 | 3 | COMMANDS: 4 | 5 | c1 This does c1. 6 | c2 This does c2. 7 | 8 | OPTIONS FOR c1: 9 | 10 | --o1 This is option --o1 for c1. 11 | --o2 This is option --o2 for c1. 12 | 13 | OPTIONS FOR c2: 14 | 15 | --o1 This is option --o1 for c2. 16 | --o2 This is option --o2 for c2. 17 | 18 | COMMON OPTIONS: 19 | 20 | -g This is global option -g. 21 | 22 | -------------------------------------------------------------------------------- /try/example2: -------------------------------------------------------------------------------- 1 | require 'cli/base' 2 | 3 | # This is an example command-line with subcommands. 4 | class Example2 < CLI::Base 5 | 6 | # Do it loudly. 7 | attr_accessor :verbose 8 | def verbose?; @verbose; end 9 | 10 | # Apply the fooey. 11 | class Foo < self 12 | 13 | # Force it ot happen. 14 | attr_accessor :force 15 | def force?; @force; end 16 | end 17 | 18 | def help! 19 | cli_help.show_help 20 | end 21 | 22 | def main 23 | end 24 | 25 | end 26 | 27 | Example2.execute 28 | -------------------------------------------------------------------------------- /lib/cli/core_ext.rb: -------------------------------------------------------------------------------- 1 | class UnboundMethod 2 | if !method_defined?(:source_location) 3 | if Proc.method_defined? :__file__ # /ree/ 4 | def source_location 5 | [__file__, __line__] rescue nil 6 | end 7 | elsif defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/ 8 | require 'java' 9 | def source_location 10 | to_java.source_location(Thread.current.to_java.getContext()) 11 | end 12 | end 13 | end 14 | 15 | # 16 | def comment 17 | Source.get_above_comment(*source_location) 18 | end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /lib/cli/errors.rb: -------------------------------------------------------------------------------- 1 | module CLI 2 | 3 | class Base 4 | 5 | class NoOptionError < ::NoMethodError # ArgumentError ? 6 | def initialize(name, *arg) 7 | super("unknown option -- #{name}", name, *args) 8 | end 9 | end 10 | 11 | #class NoCommandError < ::NoMethodError 12 | # def initialize(name, *args) 13 | # super("unknown command -- #{name}", name, *args) 14 | # end 15 | #end 16 | 17 | class NoCommandError < ::NoMethodError 18 | def initialize(*args) 19 | super("missing command", *args) 20 | end 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | -------------------------------------------------------------------------------- /lib/cli/version.rb: -------------------------------------------------------------------------------- 1 | module CLI 2 | 3 | class Base 4 | # 5 | DIRECTORY = File.dirname(__FILE__) 6 | 7 | # 8 | def self.version 9 | @package ||= ( 10 | require 'yaml' 11 | YAML.load(File.new(DIRECTORY + '/version.yml')) 12 | ) 13 | end 14 | 15 | # 16 | #def self.profile 17 | # @profile ||= ( 18 | # require 'yaml' 19 | # YAML.load(File.new(DIRECTORY + '/profile.yml')) 20 | # ) 21 | #end 22 | 23 | # 24 | def self.const_missing(name) 25 | key = name.to_s.downcase 26 | #version[key] || profile[key] || super(name) 27 | version[key] super(name) 28 | end 29 | 30 | # becuase Ruby 1.8~ gets in the way 31 | remove_const(:VERSION) if const_defined?(:VERSION) 32 | end 33 | 34 | end 35 | 36 | -------------------------------------------------------------------------------- /COPYING.rdoc: -------------------------------------------------------------------------------- 1 | = COPYRIGHT NOTICES 2 | 3 | == Executable 4 | 5 | Copyright (c) 2009 Thomas Sawyer 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this program except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | 19 | --- 20 | 21 | You can find the full text of all cited licenses in the `meta/license` 22 | directory distributed with this program. 23 | 24 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | gh_pages: pages 4 | 5 | qed: 6 | files: demo 7 | 8 | qedoc: 9 | title : "CLI::Base Demonstrandum" 10 | files : demo 11 | output: DEMO.rdoc 12 | 13 | email: 14 | mailto: 15 | - ruby-talk@ruby-lang.org 16 | - rubyworks-mailinglist@googlegroups.com 17 | 18 | gem: 19 | active: true 20 | 21 | #ruth: 22 | # tests: test/*.rb 23 | # loadpath: lib 24 | 25 | dnote: 26 | title: Developer's Notes 27 | labels: ~ 28 | output: NOTES.rdoc 29 | 30 | #locat: 31 | # output: log/locat.html 32 | # active: true 33 | 34 | vclog: 35 | output: 36 | - log/CHANGES.rdoc 37 | - log/HISTORY.rdoc 38 | active: false 39 | 40 | #rubyforge do 41 | # unixname metadata.name 42 | # groupid nil 43 | # package metadata.name 44 | # sitemap 'doc/rdoc' => metadata.name 45 | # active false 46 | #end 47 | 48 | -------------------------------------------------------------------------------- /work/concepts/r0_underscore/test_command.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'clio/command' 3 | 4 | class TestCommand < Test::Unit::TestCase 5 | 6 | class ExampleCommand < ::Clio::Command 7 | 8 | def setup 9 | @check = {} 10 | end 11 | 12 | # No arguments and no options. 13 | def _a 14 | @check['a'] = true 15 | end 16 | 17 | # Takes only option. 18 | def _b(opts) 19 | @check['b'] = opts 20 | end 21 | 22 | # Takes multiple arguments and options. (Ruby 1.9 only) 23 | #def c(*args, opts) 24 | #end 25 | 26 | # opt 'a', :bolean, 'example option a' 27 | 28 | # Takes one argument and options. 29 | def call(args, opts) 30 | @check['args'] = args 31 | @check['opts'] = opts 32 | end 33 | 34 | end 35 | 36 | 37 | def test_one 38 | assert(true) 39 | end 40 | 41 | end 42 | 43 | -------------------------------------------------------------------------------- /HISTORY.rdoc: -------------------------------------------------------------------------------- 1 | = RELEASE HISTORY 2 | 3 | == 0.5.0 / 2011-10-08 4 | 5 | The library has been rename to CLI::Base (gem name `cli_base`). 6 | 7 | Changes: 8 | 9 | * Renamed to CLI::Base. 10 | 11 | 12 | == 0.4.0 / 2010-10-12 13 | 14 | Ooo... I found an even better API. Rather than use methods to denote commands, 15 | the new API uses nested classes. 16 | 17 | Changes: 18 | 19 | * New API! 20 | 21 | 22 | == 0.3.0 / 2010-09-12 23 | 24 | The most significant change is the use bang methods (foo!) for option flags. 25 | 26 | Changes: 27 | 28 | * Allow bang methods for option flags. 29 | 30 | 31 | == 0.2.0 / 2010-03-02 32 | 33 | Initial (non-public) release. Executioner is a rebranding of older library 34 | called TieClip. TieClip has moved in a completely different direction, 35 | so Executioner was create to preserve and continue development of it's 36 | design elements. 37 | 38 | Changes: 39 | 40 | * Happy Birthday! 41 | 42 | -------------------------------------------------------------------------------- /PROFILE: -------------------------------------------------------------------------------- 1 | --- 2 | name : cli_base 3 | version: 0.5.0 4 | 5 | title : CLI::Base 6 | summary: Command line tools, meet your Executioner! 7 | contact: trans 8 | created: 2008-08-08 9 | 10 | description: 11 | Think of CLI::Base as a COM, a Commandline Object Mapper, 12 | in much the same way that ActiveRecord::Base is an ORM, 13 | an Object Relational Mapper. A subclass of the CLI::Base 14 | can define a complete command line tool using nothing more 15 | than Ruby's own method definitions. 16 | 17 | resources: 18 | home: http://rubyworks.github.com/cli_base 19 | code: http://github.com/rubyworks/cli_base 20 | mail: http://groups.google.com/group/rubyworks-mailinglist 21 | 22 | repositories: 23 | upstream: git://github.com/rubyworks/cli_base.git 24 | 25 | requirements: 26 | - detroit (build) 27 | - qed (test) 28 | 29 | organization: RubyWorks 30 | 31 | authors: 32 | - trans 33 | 34 | copyrights: 35 | - (c) 2008 Rubyworks (BSD-2-Clause) 36 | 37 | -------------------------------------------------------------------------------- /demo/01_single_command.rdoc: -------------------------------------------------------------------------------- 1 | == No Subcommmands 2 | 3 | This example demonstrates using CLI::Base to create a simple command line 4 | interface without subcommands. 5 | 6 | require 'cli_base' 7 | 8 | class NoSubCommandCLI < CLI::Base 9 | 10 | attr :result 11 | 12 | def o? 13 | @o 14 | end 15 | 16 | def o=(flag) 17 | @o = flag 18 | end 19 | 20 | def main 21 | if o? 22 | @result = "with" 23 | else 24 | @result = "without" 25 | end 26 | end 27 | 28 | end 29 | 30 | Execute the CLI on an example command line. 31 | 32 | cli = NoSubCommandCLI.run('') 33 | cli.result.assert == 'without' 34 | 35 | Execute the CLI on an example command line. 36 | 37 | cli = NoSubCommandCLI.run('-o') 38 | cli.result.assert == 'with' 39 | 40 | There are two important things to notices heres. Frist, that #main is being 41 | called in each case. It is the method called with no other subcommands are 42 | defined. And second, the fact the a `o?` method is defined to compliment the 43 | `o=` writer, informs CLI::Base that `-o` is an option _flag_, not taking 44 | any parameters. 45 | 46 | -------------------------------------------------------------------------------- /demo/04_help_text.rdoc: -------------------------------------------------------------------------------- 1 | == Command Help 2 | 3 | Require Executor library. 4 | 5 | require 'cli_base' 6 | 7 | Setup an example CLI subclass. 8 | 9 | class MyCLI < CLI::Base 10 | 11 | # This is global option -g. 12 | def g=(val) 13 | end 14 | 15 | # This does c1. 16 | class C1 < self 17 | 18 | #help.header "This does c1." 19 | 20 | # This is option --o1 for c1. 21 | def o1=(value) 22 | end 23 | 24 | # This is option --o2 for c1. 25 | def o2=(value) 26 | end 27 | 28 | end 29 | 30 | # This does c2. 31 | class C2 < self 32 | 33 | # This is option --o1 for c2. 34 | def o1=(value) 35 | end 36 | 37 | # This is option --o2 for c2. 38 | def o2=(bool) 39 | end 40 | 41 | end 42 | 43 | end 44 | 45 | The help output, 46 | 47 | @out = MyCLI::C1.help.to_s 48 | 49 | should be clearly laid out as follows: 50 | 51 | Usage: $0 [options...] [subcommand] 52 | 53 | This does c1. 54 | 55 | OPTIONS 56 | -g This is global option -g. 57 | --o1 This is option --o1 for c1. 58 | --o2 This is option --o2 for c1. 59 | 60 | -------------------------------------------------------------------------------- /work/concepts/r5_subclasses/qed/01_single_command.rdoc: -------------------------------------------------------------------------------- 1 | = Scenario - No Subcommmands 2 | 3 | This example demonstrates using Executioner to create a simple command line 4 | interface without subcommands. 5 | 6 | require 'executioner' 7 | 8 | class NoSubCommandCLI < Executioner 9 | 10 | attr :result 11 | 12 | def o? 13 | @o 14 | end 15 | 16 | def o=(flag) 17 | @o = flag 18 | end 19 | 20 | def main 21 | if o? 22 | @result = "with" 23 | else 24 | @result = "without" 25 | end 26 | end 27 | 28 | end 29 | 30 | Execute the CLI on an example command line. 31 | 32 | cli = NoSubCommandCLI.run 33 | cli.result.assert == 'without' 34 | 35 | Execute the CLI on an example command line. 36 | 37 | cli = NoSubCommandCLI.run('-o') 38 | cli.result.assert == 'with' 39 | 40 | There are two important things to notices heres. Frist, that #main is being 41 | called in each case. It is the method called with no other subcommands are 42 | defined. And second, the fact the a `o?` method is defined to compliment the 43 | `o=` writer, informs Executioner that `-o` is an option _flag_, not taking 44 | any parameters. 45 | 46 | -------------------------------------------------------------------------------- /test/test_cli_base.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__)+'/../lib') 2 | 3 | require 'microtest' 4 | require 'ae' 5 | 6 | require 'cli_base' 7 | 8 | 9 | class CLIBaseTestCase < MicroTest::TestCase 10 | 11 | class MyCommand < CLI::Base 12 | attr_reader :size, :quiet, :file 13 | 14 | def initialize 15 | @file = 'hey.txt' # default 16 | end 17 | 18 | def quiet=(bool) 19 | @quiet = bool 20 | end 21 | 22 | def quiet? 23 | @quiet 24 | end 25 | 26 | def size=(integer) 27 | @size = integer.to_i 28 | end 29 | 30 | def file=(fname) 31 | @file = fname 32 | end 33 | 34 | # 35 | def main 36 | end 37 | 38 | #def call(*args) 39 | # @args = args 40 | #end 41 | end 42 | 43 | def test_boolean_optiion 44 | mc = MyCommand.execute('--quiet') 45 | mc.assert.quiet? 46 | end 47 | 48 | def test_integer_optiion 49 | mc = MyCommand.execute('--size=4') 50 | mc.size.assert == 4 51 | end 52 | 53 | def test_default_value 54 | mc = MyCommand.execute('') 55 | mc.file.assert == 'hey.txt' 56 | end 57 | 58 | #def usage_output 59 | # MyCommand.help.usage.assert == "{$0} [options] [subcommand]" 60 | #end 61 | 62 | end 63 | 64 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | --- 2 | authors: 3 | - name: Thomas Sawyer 4 | email: transfire@gmail.com 5 | copyrights: 6 | - holder: Thomas Sawyer 7 | year: '2008' 8 | license: BSD-2-Clause 9 | replacements: [] 10 | conflicts: [] 11 | requirements: 12 | - name: qed 13 | groups: 14 | - test 15 | development: true 16 | dependencies: [] 17 | repositories: 18 | - uri: git://github.com/rubyworks/cli_base.git 19 | scm: git 20 | name: upstream 21 | resources: 22 | home: http://rubyworks.github.com/cli_base 23 | code: http://github.com/rubyworks/cli_base 24 | mail: http://groups.google.com/group/rubyworks-mailinglist 25 | load_path: 26 | - lib 27 | extra: 28 | contact: trans 29 | manifest: MANIFEST.txt 30 | source: [] 31 | alternatives: [] 32 | revision: 0 33 | name: cli_base 34 | title: CLI::Base 35 | version: 0.5.0 36 | summary: Command line tools, meet your Executioner! 37 | created: '2008-08-08' 38 | description: Think of CLI::Base as a COM, a Commandline Object Mapper, in much the 39 | same way that ActiveRecord::Base is an ORM, an Object Relational Mapper. A subclass 40 | of the CLI::Base can define a complete command line tool using nothing more than 41 | Ruby's own method definitions. 42 | organization: RubyWorks 43 | date: '2011-10-08' 44 | -------------------------------------------------------------------------------- /lib/cli/utils.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CLI 4 | 5 | # Some handy-dandy CLI utility methods. 6 | # 7 | module Utils 8 | extend self 9 | 10 | # TODO: Maybe #ask chould serve all purposes depending on degfault? 11 | # e.g. `ask?("ok?", default=>true)`, would be same as `yes?("ok?")`. 12 | 13 | # Strings to interprest as boolean values. 14 | BOOLEAN_MAP = {"y"=>true, "yes"=>true, "n"=>false, "no"=>false} 15 | 16 | # Query the user for a yes/no answer, defaulting to yes. 17 | def yes?(question, options={}) 18 | print "#{question} [Y/n] " 19 | input = STDIN.readline.chomp.downcase 20 | BOOLEAN_MAP[input] || true 21 | end 22 | 23 | # Query the user for a yes/no answer, defaulting to no. 24 | def no?(question, options={}) 25 | print "#{question} [y/N] " 26 | input = STDIN.readline.chomp.downcase 27 | BOOLEAN_MAP[input] || false 28 | end 29 | 30 | # Query the user for an answer. 31 | def ask(question, options={}) 32 | print "#{question} [default: #{options[:default]}] " 33 | reply = STDIN.readline.chomp 34 | if reply.empty? 35 | options[:default] 36 | else 37 | reply 38 | end 39 | end 40 | 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /work/concepts/r5_subclasses/qed/04_help_text.rdoc: -------------------------------------------------------------------------------- 1 | Require Executor library. 2 | 3 | require 'executioner' 4 | 5 | Setup an example CLI subclass. 6 | 7 | class MyCLI < Executioner 8 | 9 | help "This does c1." 10 | 11 | def c1 12 | end 13 | 14 | help "This is option --o1 for c1." 15 | 16 | def c1__o1=(value) 17 | end 18 | 19 | help "This is option --o2 for c1." 20 | 21 | def c1__o2=(value) 22 | end 23 | 24 | help "This does c2." 25 | 26 | def c2 27 | end 28 | 29 | help "This is option --o1 for c2." 30 | 31 | def c2__o1=(value) 32 | end 33 | 34 | help "This is option --o2 for c2." 35 | 36 | def c2__o2=(bool) 37 | end 38 | 39 | help "This is global option -g." 40 | 41 | def __g 42 | end 43 | end 44 | 45 | The help output, 46 | 47 | MyCLI.to_s 48 | 49 | should be clearly laid out as follows: 50 | 51 | qed 52 | 53 | COMMANDS: 54 | 55 | c1 This does c1. 56 | c2 This does c2. 57 | 58 | OPTIONS FOR c1: 59 | 60 | --o1 This is option --o1 for c1. 61 | --o2 This is option --o2 for c1. 62 | 63 | OPTIONS FOR c2: 64 | 65 | --o1 This is option --o1 for c2. 66 | --o2 This is option --o2 for c2. 67 | 68 | COMMON OPTIONS: 69 | 70 | -g This is global option -g. 71 | 72 | -------------------------------------------------------------------------------- /work/NOTES.rdoc: -------------------------------------------------------------------------------- 1 | = Developer's Notes 2 | 3 | == TODO 4 | 5 | === file://lib/cli/base.rb 6 | 7 | * TODO: Should #main be called #call instead? (83) 8 | * TODO: fix help (87) 9 | 10 | === file://lib/cli/help.rb 11 | 12 | * TODO: Maybe default description should always come from `main` 13 | instead of the the class comment ? (147) 14 | 15 | === file://lib/cli/parser.rb 16 | 17 | * TODO: This needs some thought concerning character spliting and arguments. (143) 18 | * TODO: Sort alphabetically? (174) 19 | 20 | === file://lib/cli/utils.rb 21 | 22 | * TODO: Maybe #ask chould serve all purposes depending on degfault? 23 | e.g. `ask?("ok?", default=>true)`, would be same as `yes?("ok?")`. (10) 24 | 25 | === file://work/command-underscore.rb 26 | 27 | * TODO: Support passing a string or *args, opts in place of ARGV. (79) 28 | * TODO: use clio/option (201) 29 | 30 | === file://work/command.f3.rb 31 | 32 | * TODO: (25) 33 | 34 | === file://work/executable.rb 35 | 36 | * TODO: to_b if 'true' or 'false' ? 37 | if obj.respond_to?("#{x}=") (102) 38 | 39 | === file://work/renditions/command.f3.rb 40 | 41 | * TODO: (25) 42 | 43 | === file://work/subcommand/lib/subcommand.rb 44 | 45 | * TODO: to_b if 'true' or 'false' ? 46 | obj.send("#{x}=",v) 47 | else 48 | obj.option_missing(x, v) # argv? 49 | end (274) 50 | * TODO: this needs some thought concerning character spliting and arguments. (311) 51 | * TODO: Sort alphabetically? (348) 52 | 53 | 54 | == FIXME 55 | 56 | === file://work/command-facets.rb 57 | 58 | * FIXME: rename call to [] ? (234) 59 | 60 | === file://work/renditions/command-facets.rb 61 | 62 | * FIXME: rename call to [] ? (234) 63 | 64 | -------------------------------------------------------------------------------- /work/concepts/r4_allinone/cliclass.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | class MyCline < CliClass 5 | 6 | # Example of subcommand. 7 | def show(*args) 8 | # ... 9 | end 10 | 11 | def run(file) 12 | # ... 13 | end 14 | 15 | # example '--loadpath' option for run 16 | def run_loadpath(paths) 17 | 18 | end 19 | 20 | # example of '-I' option for run 21 | alias_method :run_I, :run_loadpath 22 | 23 | # example '--help' option for run 24 | def run_help 25 | # ... 26 | end 27 | 28 | # example of a global '--help' option. 29 | def _help 30 | puts "RTFM"; exit 31 | end 32 | 33 | # example of a global '--verbose' option. 34 | def _verbose 35 | @verbose = true 36 | end 37 | 38 | alias_method :_v, :_verbose 39 | 40 | end 41 | 42 | 43 | # OR 44 | 45 | # This is basically comandable. 46 | 47 | class MyCline < CliClass 48 | 49 | # Example of subcommand. 50 | def show(*args) 51 | # ... 52 | end 53 | 54 | # Example of another subcommand. 55 | def run(file) 56 | # ... 57 | end 58 | 59 | # example '--loadpath' option for run 60 | def run_loadpath=(paths) 61 | 62 | end 63 | 64 | # example of '-I' option for run 65 | alias_method :run_I=, :run_loadpath= 66 | 67 | # example '--help' option for run 68 | def run_help=(value=true) 69 | # ... 70 | end 71 | 72 | # example of a global '--verbose' option. 73 | def help=(value) 74 | if value 75 | puts "RTFM"; exit 76 | end 77 | end 78 | 79 | # example of a global '--verbose' option. 80 | def verbose=(value) 81 | @verbose = value 82 | end 83 | 84 | alias_method :v=, :verbose= 85 | 86 | end 87 | 88 | -------------------------------------------------------------------------------- /work/concepts/r5_subclasses/lib/subcommand/help.rb: -------------------------------------------------------------------------------- 1 | class Executioner 2 | 3 | # Encpsulates help output with code to display well formated help 4 | # output and manpages output. 5 | class Help 6 | 7 | attr :exe_class 8 | 9 | # 10 | def initialize(exe_class) 11 | @exe_class = exe_class 12 | end 13 | 14 | # 15 | def to_s 16 | help_text 17 | end 18 | 19 | # 20 | def to_manpage 21 | end 22 | 23 | # 24 | def help_text 25 | commands = [] 26 | command_options = Hash.new{|h,k| h[k]=[]} 27 | global_options = [] 28 | 29 | descriptions = exe_class.descriptions 30 | 31 | descs = descriptions.to_a.sort{ |a,b| a[0] <=> b[0] } 32 | descs.each do |(meth, desc)| 33 | case meth 34 | when /_(.*?)[\!\=]$/ 35 | command_options[$`] << [$1, meth] 36 | when /^(.*?)[\!\=]$/ 37 | global_options << [$1, meth] 38 | else 39 | commands << meth 40 | end 41 | end 42 | 43 | s = '' 44 | s << File.basename($0) 45 | 46 | if !commands.empty? 47 | s << "\n\nCOMMANDS:\n\n" 48 | commands.each do |cmd| 49 | s << " %-15s %s\n" % [cmd, descriptions[cmd]] 50 | end 51 | end 52 | 53 | command_options.each do |cmd, opts| 54 | s << "\nOPTIONS FOR #{cmd}:\n\n" 55 | opts.each do |(name, meth)| 56 | if name.size == 1 57 | s << " -%-15s %s\n" % [name, descriptions[meth]] 58 | else 59 | s << " --%-15s %s\n" % [name, descriptions[meth]] 60 | end 61 | end 62 | end 63 | 64 | s << "\nCOMMON OPTIONS:\n\n" 65 | global_options.each do |(name, meth)| 66 | if name.size == 1 67 | s << " -%-15s %s\n" % [name, descriptions[meth]] 68 | else 69 | s << " --%-15s %s\n" % [name, descriptions[meth]] 70 | end 71 | end 72 | s << "\n" 73 | s 74 | end 75 | 76 | end 77 | 78 | end 79 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = CLI::Base 2 | 3 | {Website}[http://rubyworks.github.com/cli_base] | 4 | {Source Code}[http://github.com/rubyworrks/cli_base] | 5 | {Report Issue}[http://github.com/rubyworrks/cli_base/features] | 6 | {#rubyworks}[irc://irc.freenode.org/rubyworks] 7 | 8 | {}[http://travis-ci.org/rubyworks/cli_base] 9 | 10 | 11 | == DESCRIPTION 12 | 13 | CLI::Base is a very striaght-forward CLI framework for Ruby. CLI::Base is 14 | a *COM* Command-to-Object Mapping library. A command line tool can be defined 15 | using nothing more than Ruby's own standard syntax. No special DSL is required. 16 | 17 | IMPORTANT! CLI::Base's help feature is Ruby 1.9+ only. It does NOT support 18 | 1.8.7 or older. 19 | 20 | 21 | == SYNOPSIS 22 | 23 | Using CLI::Base is straight-forward. Simply subclass the `CLI::Base` base 24 | class and add methods to handle command-line options. Writer methods 25 | (those ending in '=') coorepsond to an option and a query method (ending in '?') 26 | marks an option a flag. For example, here is a simple commandline tool to run 27 | a Ruby script. 28 | 29 | require 'cli/base' 30 | 31 | class RunCLI < CLI::Base 32 | # Require LIBRARY before executing your script. 33 | def require=(lib) 34 | require lib 35 | end 36 | alias :r= :require= 37 | 38 | # Include PATH in $LOAD_PATH. 39 | def include=(path) 40 | $:.unshift path 41 | end 42 | alias :I= :incude 43 | 44 | # Run in DEBUG mode. 45 | def debug? 46 | $DEBUG = true 47 | end 48 | 49 | # Show this message. 50 | def help? 51 | puts self 52 | exit 53 | end 54 | alias :h? :help? 55 | 56 | # Run the command. 57 | def main(script) 58 | load(script) 59 | end 60 | end 61 | 62 | For a more detail example see EXAMPLE.md. 63 | 64 | 65 | == COPYRIGHTS 66 | 67 | Copyright (c) 2009 Rubyworks. 68 | 69 | This program is distributed under the terms of the **BSD-2-Clause** license. 70 | 71 | Please see COPYING.rdoc file for details. 72 | -------------------------------------------------------------------------------- /work/concepts/r3_executable/README.rdoc: -------------------------------------------------------------------------------- 1 | = Executable 2 | 3 | == DESCRIPTION 4 | 5 | The Executable mixin is a very quick and and easy 6 | way to make almost any class usable via a command 7 | line interface. It simply uses writer methods as 8 | option setters, and the first command line argument 9 | as the method to call, with the subsequent arguments 10 | passed to the method. 11 | 12 | 13 | == FEATURES 14 | 15 | * Super easy to use, just mixin. 16 | * Public writers become options. 17 | * Public methods become subcommands. 18 | 19 | 20 | == RESOURCES 21 | 22 | * http://rubyworks.github.com/executable 23 | * http://github.com/rubyworks/executable 24 | 25 | 26 | == RELEASE NOTES 27 | 28 | Please see HISTORY file. 29 | 30 | 31 | == SYNOPSIS 32 | 33 | Simply mixin Executable, then call #execute_command. 34 | 35 | class Example 36 | include Executable 37 | 38 | attr_accessor :quiet 39 | 40 | attr_accessor :size 41 | 42 | def bread(*args) 43 | ["bread", quiet, size, *args] 44 | end 45 | 46 | def butter(*args) 47 | ["butter", quiet, size, *args] 48 | end 49 | end 50 | 51 | example = Example.new 52 | 53 | example.execute!("butter yum") 54 | => ["butter", nil, nil, "yum"] 55 | 56 | example.execute!("bread --quiet --size=big") 57 | => ["bread", true, "big"] 58 | 59 | 60 | Notice that Executable requires an equal-sign (=) be used 61 | when specifying values for non-boolean attributes. 62 | 63 | To make the command available on the command line, add an executable 64 | to your project passing ARGV to the #execute! method. 65 | 66 | #!usr/bin/env ruby 67 | require 'example' 68 | example = Example.new 69 | example.execute!(ARGV) 70 | 71 | 72 | == INSTALL 73 | 74 | $ gem install executable 75 | 76 | 77 | == LEGAL 78 | 79 | (Apache 2.0) 80 | 81 | Copyright (c) 2009 Thomas Sawyer 82 | 83 | Licensed under the Apache License, Version 2.0 (the "License"); 84 | you may not use this program except in compliance with the License. 85 | You may obtain a copy of the License at 86 | 87 | http://www.apache.org/licenses/LICENSE-2.0 88 | 89 | Unless required by applicable law or agreed to in writing, software 90 | distributed under the License is distributed on an "AS IS" BASIS, 91 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 92 | See the License for the specific language governing permissions and 93 | limitations under the License. 94 | 95 | -------------------------------------------------------------------------------- /lib/cli/source.rb: -------------------------------------------------------------------------------- 1 | # Manage source lookup. 2 | module Source 3 | extend self 4 | 5 | # Read and cache file. 6 | # 7 | # @param file [String] filename, should be full path 8 | # 9 | # @return [Array] file content in array of lines 10 | def read(file) 11 | @read ||= {} 12 | @read[file] ||= File.readlines(file) 13 | end 14 | 15 | # Get comment from file searching up from given line number. 16 | # 17 | # @param file [String] filename, should be full path 18 | # @param line [Integer] line number in file 19 | # 20 | def get_above_comment(file, line) 21 | get_above_comment_lines(file, line).join("\n").strip 22 | end 23 | 24 | # Get comment from file searching up from given line number. 25 | # 26 | # @param file [String] filename, should be full path 27 | # @param line [Integer] line number in file 28 | # 29 | def get_above_comment_lines(file, line) 30 | text = read(file) 31 | index = line - 1 32 | while index >= 0 && text[index] !~ /^\s*\#/ 33 | return [] if text[index] =~ /^\s*end/ 34 | index -= 1 35 | end 36 | rindex = index 37 | while text[index] =~ /^\s*\#/ 38 | index -= 1 39 | end 40 | result = text[index..rindex] 41 | result = result.map{ |s| s.strip } 42 | result = result.reject{ |s| s[0,1] != '#' } 43 | result = result.map{ |s| s.sub(/^#/,'').strip } 44 | #result = result.reject{ |s| s == "" } 45 | result 46 | end 47 | 48 | # Get comment from file searching down from given line number. 49 | # 50 | # @param file [String] filename, should be full path 51 | # @param line [Integer] line number in file 52 | # 53 | def get_following_comment(file, line) 54 | get_following_comment_lines(file, line).join("\n").strip 55 | end 56 | 57 | # Get comment from file searching down from given line number. 58 | # 59 | # @param file [String] filename, should be full path 60 | # @param line [Integer] line number in file 61 | # 62 | def get_following_comment_lines(file, line) 63 | text = read(file) 64 | index = line || 0 65 | while text[index] !~ /^\s*\#/ 66 | return nil if text[index] =~ /^\s*(class|module)/ 67 | index += 1 68 | end 69 | rindex = index 70 | while text[rindex] =~ /^\s*\#/ 71 | rindex += 1 72 | end 73 | result = text[index..(rindex-2)] 74 | result = result.map{ |s| s.strip } 75 | result = result.reject{ |s| s[0,1] != '#' } 76 | result = result.map{ |s| s.sub(/^#/,'').strip } 77 | result.join("\n").strip 78 | end 79 | 80 | end 81 | 82 | -------------------------------------------------------------------------------- /work/concepts/r5_subclasses/qed/02_multiple_commands.rdoc: -------------------------------------------------------------------------------- 1 | Require Executor class. 2 | 3 | require 'executioner' 4 | 5 | Setup an example CLI subclass. 6 | 7 | class MyCLI < Executioner 8 | 9 | attr :result 10 | 11 | def initialize 12 | @result = [] 13 | end 14 | 15 | def g=(value) 16 | @result << "g" if value 17 | end 18 | 19 | def g? 20 | @result.include?("g") 21 | end 22 | 23 | class C1 < subcommand 24 | 25 | def main 26 | @result << "c1" 27 | end 28 | 29 | def o1=(value) 30 | @result << "c1_o1 #{value}" 31 | end 32 | 33 | def o2=(value) 34 | @result << "c1_o2 #{value}" 35 | end 36 | 37 | end 38 | 39 | class C2 < subcommand 40 | 41 | def main 42 | @result << "c2" 43 | end 44 | 45 | def o1=(value) 46 | @result << "c2_o1 #{value}" 47 | end 48 | 49 | def o2=(value) 50 | @result << "c2_o2" if value 51 | end 52 | 53 | def o2? 54 | @result.include?("c2_o2") 55 | end 56 | 57 | end 58 | 59 | end 60 | 61 | Instantiate and run the class on an example command line. 62 | 63 | Just a command. 64 | 65 | cli = MyCLI.run('c1') 66 | cli.result.assert == ['c1'] 67 | 68 | Command with global option. 69 | 70 | cli = MyCLI.run('c1 -g') 71 | cli.result.assert == ['g', 'c1'] 72 | 73 | Command with an option. 74 | 75 | cli = MyCLI.run('c1 --o1 A') 76 | cli.result.assert == ['c1_o1 A', 'c1'] 77 | 78 | Command with two options. 79 | 80 | cli = MyCLI.run('c1 --o1 A --o2 B') 81 | cli.result.assert == ['c1_o1 A', 'c1_o2 B', 'c1'] 82 | 83 | Try out the second command. 84 | 85 | cli = MyCLI.run('c2') 86 | cli.result.assert == ['c2'] 87 | 88 | Seoncd command with an option. 89 | 90 | cli = MyCLI.run('c2 --o1 A') 91 | cli.result.assert == ['c2_o1 A', 'c2'] 92 | 93 | Second command with two options. 94 | 95 | cli = MyCLI.run('c2 --o1 A --o2') 96 | cli.result.assert == ['c2_o1 A', 'c2_o2', 'c2'] 97 | 98 | Since C1#main takes not arguments, if we try to issue a command 99 | that will have left over arguments, then an ArgumentError will be raised. 100 | 101 | expect ArgumentError do 102 | cli = MyCLI.run('c1 a') 103 | end 104 | 105 | How about a non-existenct subcommand. 106 | 107 | expect Executioner::NoCommandError do 108 | cli = MyCLI.run('q') 109 | cli.result.assert == ['q'] 110 | end 111 | 112 | How about an option only. 113 | 114 | expect Executioner::NoCommandError do 115 | cli = MyCLI.run('-g') 116 | cli.result.assert == ['-g'] 117 | end 118 | 119 | How about a non-existant options. 120 | 121 | expect Executioner::NoOptionError do 122 | cli = MyCLI.run('c1 --foo') 123 | end 124 | 125 | -------------------------------------------------------------------------------- /demo/02_multiple_commands.rdoc: -------------------------------------------------------------------------------- 1 | == Multiple Subcommmands 2 | 3 | Require CLI::Base class. 4 | 5 | require 'cli_base' 6 | 7 | Setup an example CLI subclass. 8 | 9 | class MyCLI < CLI::Base 10 | 11 | attr :result 12 | 13 | def initialize 14 | @result = [] 15 | end 16 | 17 | def g=(value) 18 | @result << "g" if value 19 | end 20 | 21 | def g? 22 | @result.include?("g") 23 | end 24 | 25 | class C1 < self 26 | 27 | def main 28 | @result << "c1" 29 | end 30 | 31 | def o1=(value) 32 | @result << "c1_o1 #{value}" 33 | end 34 | 35 | def o2=(value) 36 | @result << "c1_o2 #{value}" 37 | end 38 | 39 | end 40 | 41 | class C2 < CLI::Base 42 | 43 | attr :result 44 | 45 | def initialize 46 | @result = [] 47 | end 48 | 49 | def main 50 | @result << "c2" 51 | end 52 | 53 | def o1=(value) 54 | @result << "c2_o1 #{value}" 55 | end 56 | 57 | def o2=(value) 58 | @result << "c2_o2" if value 59 | end 60 | 61 | def o2? 62 | @result.include?("c2_o2") 63 | end 64 | 65 | end 66 | 67 | end 68 | 69 | Instantiate and run the class on an example command line. 70 | 71 | Just a command. 72 | 73 | cli = MyCLI.run('c1') 74 | cli.result.assert == ['c1'] 75 | 76 | Command with global option. 77 | 78 | cli = MyCLI.run('c1 -g') 79 | cli.result.assert == ['g', 'c1'] 80 | 81 | Command with an option. 82 | 83 | cli = MyCLI.run('c1 --o1 A') 84 | cli.result.assert == ['c1_o1 A', 'c1'] 85 | 86 | Command with two options. 87 | 88 | cli = MyCLI.run('c1 --o1 A --o2 B') 89 | cli.result.assert == ['c1_o1 A', 'c1_o2 B', 'c1'] 90 | 91 | Try out the second command. 92 | 93 | cli = MyCLI.run('c2') 94 | cli.result.assert == ['c2'] 95 | 96 | Seoncd command with an option. 97 | 98 | cli = MyCLI.run('c2 --o1 A') 99 | cli.result.assert == ['c2_o1 A', 'c2'] 100 | 101 | Second command with two options. 102 | 103 | cli = MyCLI.run('c2 --o1 A --o2') 104 | cli.result.assert == ['c2_o1 A', 'c2_o2', 'c2'] 105 | 106 | Since C1#main takes not arguments, if we try to issue a command 107 | that will have left over arguments, then an ArgumentError will be raised. 108 | 109 | expect ArgumentError do 110 | cli = MyCLI.run('c1 a') 111 | end 112 | 113 | How about a non-existenct subcommand. 114 | 115 | expect CLI::Base::NoCommandError do 116 | cli = MyCLI.run('q') 117 | cli.result.assert == ['q'] 118 | end 119 | 120 | How about an option only. 121 | 122 | expect CLI::Base::NoCommandError do 123 | cli = MyCLI.run('-g') 124 | cli.result.assert == ['-g'] 125 | end 126 | 127 | How about a non-existant options. 128 | 129 | expect CLI::Base::NoOptionError do 130 | MyCLI.run('c1 --foo') 131 | end 132 | 133 | -------------------------------------------------------------------------------- /try/man/example2.1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | example2(1) - subcmds.rb(1) example cli using manpage 7 | 44 | 45 | 52 | 53 |
54 | 55 | 60 | 61 |
    62 |
  1. example2(1)
  2. 63 |
  3. 64 |
  4. example2(1)
  5. 65 |
66 | 67 |

subcmds.rb(1) example cli using manpage

68 |

SUBCOMMANDS

69 | 70 |
    71 |
  • foo - foo does waht foo does
  • 72 |
73 | 74 | 75 |

OPTIONS

76 | 77 |
    78 |
  • --verbose - do it loudly
  • 79 |
80 | 81 | 82 |

SEE ALSO

83 | 84 |

exec(1)

85 | 86 | 87 |
    88 |
  1. 89 |
  2. November 2011
  3. 90 |
  4. example2(1)
  5. 91 |
92 | 93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /try/example1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'cli/base' 4 | require 'ostruct' 5 | 6 | # Example command-line interface given by OptionParser. 7 | # 8 | # Here is an example CLI::Base subclass that mimics the example 9 | # provided in documentation for Ruby built-in OptionParser. 10 | # 11 | # The only signifficant difference in capability between OptionParser and 12 | # CLI::Base is that CLI::Base does not support optional switch arguments. 13 | # These can easily lead to malformed command-lines, so they were let out 14 | # of CLI::Base's specifcation. 15 | # 16 | # @see http://ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html 17 | 18 | class Example1 < CLI::Base 19 | 20 | CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] 21 | CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } 22 | 23 | attr :options 24 | 25 | def initialize 26 | super 27 | reset 28 | end 29 | 30 | def reset 31 | @options = OpenStruct.new 32 | @options.library = [] 33 | @options.inplace = false 34 | @options.encoding = "utf8" 35 | @options.transfer_type = :auto 36 | @options.verbose = false 37 | end 38 | 39 | # Require the LIBRARY before executing your script. 40 | def require=(lib) 41 | options.library << lib 42 | end 43 | alias :r= :require= 44 | 45 | # Edit ARGV files in place (make backup if EXTENSION supplied). 46 | def inplace=(ext) 47 | options.inplace = true 48 | options.extension = ext 49 | options.extension.sub!(/\A\.?(?=.)/, ".") # ensure extension begins with dot. 50 | end 51 | alias :i= :inplace= 52 | 53 | # Delay N seconds before executing. 54 | # Casts 'delay' argument to a Float. 55 | def delay=(n) 56 | options.delay = n.to_float 57 | end 58 | 59 | # Begin execution at given time 60 | # Casts 'time' argument to a Time object. 61 | def time=(time) 62 | options.time = Time.parse(time) 63 | end 64 | alias :t= :time= 65 | 66 | # Specify record separator (default \\0). 67 | # Cast to octal integer. 68 | def irs=(octal) 69 | options.record_separator = octal.to_i(8) 70 | end 71 | alias :F= :irs= 72 | 73 | # Example 'list' of arguments. 74 | def list=(args) 75 | options.list = list.split(',') 76 | end 77 | 78 | # Keyword completion. We are specifying a specific set of arguments (CODES 79 | # and CODE_ALIASES - notice the latter is a Hash), and the user may provide 80 | # the shortest unambiguous text. 81 | CODE_LIST = (CODE_ALIASES.keys + CODES) 82 | 83 | # This is how to override default help text. 84 | help.option(:code, "Select encoding (#{CODE_LIST})") 85 | 86 | # Select encoding (#{CODE_LIST}). 87 | def code=(code) 88 | codes = CODE_LIST.select{ |x| /^#{code}/ =~ x } 89 | codes = codes.map{ |x| CODE_ALIASES.key?(x) ? CODE_ALIASES[x] : x }.uniq 90 | raise ArgumentError unless codes.size == 1 91 | options.encoding = codes.first 92 | end 93 | 94 | # Select transfer type (text, binary, auto). 95 | # Optional argument with keyword completion. 96 | def type=(type) 97 | raise ArgumentError unless %w{text binary auto}.include(type.downcase) 98 | options.transfer_type = type.downcase 99 | end 100 | 101 | # Run verbosely. 102 | def verbose=(bool) 103 | options.verbose = bool 104 | end 105 | def verbose? 106 | options.verbose 107 | end 108 | alias :v= :verbose= 109 | alias :v? :verbose? 110 | 111 | # Show this message. 112 | # No argument, shows at tail. This will print an options summary. 113 | def help! 114 | puts cli_help.text(__FILE__) 115 | exit 116 | end 117 | 118 | # Show version. 119 | # Another typical switch to print the version. 120 | def version! 121 | puts VERSION 122 | exit 123 | end 124 | 125 | # Add a footer to built-in help. 126 | help.footer "Copyright (c) Someone Else. All Rights Reserved." 127 | 128 | # 129 | def main 130 | # ... main procedure here ... 131 | p options 132 | end 133 | end 134 | 135 | Example1.execute 136 | -------------------------------------------------------------------------------- /lib/cli/base.rb: -------------------------------------------------------------------------------- 1 | module CLI 2 | 3 | # = CLI::Base 4 | # 5 | # class MyCLI < CLI::Base 6 | # 7 | # # cmd --debug 8 | # 9 | # def debug? 10 | # $DEBUG 11 | # end 12 | # 13 | # def debug=(bool) 14 | # $DEBUG = bool 15 | # end 16 | # 17 | # # $ foo remote 18 | # 19 | # class Remote < CLI::Base 20 | # 21 | # # $ foo remote --verbose 22 | # 23 | # def verbose? 24 | # @verbose 25 | # end 26 | # 27 | # def verbose=(bool) 28 | # @verbose = bool 29 | # end 30 | # 31 | # # $ foo remote --force 32 | # 33 | # def force? 34 | # @force 35 | # end 36 | # 37 | # def force=(bool) 38 | # @force = bool 39 | # end 40 | # 41 | # # $ foo remote --output 42 | # 43 | # def output=(path) 44 | # @path = path 45 | # end 46 | # 47 | # # $ foo remote -o 48 | # 49 | # alias_method :o=, :output= 50 | # 51 | # # $ foo remote add 52 | # 53 | # class Add < self 54 | # 55 | # def main(name, branch) 56 | # # ... 57 | # end 58 | # 59 | # end 60 | # 61 | # # $ foo remote show 62 | # 63 | # class Show < self 64 | # 65 | # def main(name) 66 | # # ... 67 | # end 68 | # 69 | # end 70 | # 71 | # end 72 | # 73 | # end 74 | # 75 | class Base 76 | 77 | require 'cli/errors' 78 | require 'cli/parser' 79 | require 'cli/help' 80 | require 'cli/config' 81 | require 'cli/utils' 82 | 83 | # TODO: Should #main be called #call instead? 84 | 85 | # 86 | def main(*args) 87 | #puts self.class # TODO: fix help 88 | raise NoCommandError 89 | end 90 | 91 | # Override option_missing if needed. This receives the name of the option 92 | # and the remaining argumentslist. It must consume any arguments it uses 93 | # from the begining of the list (i.e. in-place manipulation). 94 | # 95 | def option_missing(opt, argv) 96 | raise NoOptionError, opt 97 | end 98 | 99 | # Access the help instance of the class of the command object. 100 | def cli_help 101 | self.class.help 102 | end 103 | 104 | class << self 105 | 106 | # Helper method for creating switch attributes. 107 | # 108 | # This is equivalent to: 109 | # 110 | # def name=(val) 111 | # @name = val 112 | # end 113 | # 114 | # def name? 115 | # @name 116 | # end 117 | # 118 | def attr_switch(name) 119 | attr_writer name 120 | module_eval %{ 121 | def #{name}? 122 | @#{name} 123 | end 124 | } 125 | end 126 | 127 | # Run the command. 128 | # 129 | # @param argv [Array] command-line arguments 130 | # 131 | def execute(argv=ARGV) 132 | cli, args = parser.parse(argv) 133 | cli.main(*args) 134 | return cli 135 | end 136 | 137 | # CLI::Base classes don't run, they execute! But... 138 | alias_method :run, :execute 139 | 140 | # Command configuration options. 141 | # 142 | # @todo: This isn't used yet. Eventually the idea is to allow 143 | # some additional flexibility in the parser behavior. 144 | def config 145 | @config ||= Config.new 146 | end 147 | 148 | # The parser for this command. 149 | def parser 150 | @parser ||= Parser.new(self) 151 | end 152 | 153 | # List of subcommands. 154 | def subcommands 155 | parser.subcommands 156 | end 157 | 158 | # Interface with cooresponding help object. 159 | def help 160 | @help ||= Help.new(self) 161 | end 162 | 163 | # 164 | def inspect 165 | name 166 | end 167 | 168 | # When inherited, setup up the +file+ and +line+ of the 169 | # subcommand via +caller+. If for some odd reason this 170 | # does not work then manually use +setup+ method. 171 | # 172 | def inherited(subclass) 173 | file, line, _ = *caller.first.split(':') 174 | file = File.expand_path(file) 175 | subclass.help.setup(file,line.to_i) 176 | end 177 | 178 | end 179 | 180 | end 181 | 182 | end 183 | -------------------------------------------------------------------------------- /demo/03_optparse_example.rdoc: -------------------------------------------------------------------------------- 1 | == OptionParser Example 2 | 3 | This example mimics the one given in optparse.rb documentation. 4 | 5 | require 'cli_base' 6 | require 'ostruct' 7 | require 'time' 8 | 9 | class ExampleCLI < CLI::Base 10 | 11 | CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] 12 | CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } 13 | 14 | attr :options 15 | 16 | def initialize 17 | super 18 | reset 19 | end 20 | 21 | def reset 22 | @options = OpenStruct.new 23 | @options.library = [] 24 | @options.inplace = false 25 | @options.encoding = "utf8" 26 | @options.transfer_type = :auto 27 | @options.verbose = false 28 | end 29 | 30 | # Require the LIBRARY before executing your script 31 | def require=(lib) 32 | options.library << lib 33 | end 34 | alias :r= :require= 35 | 36 | # Edit ARGV files in place (make backup if EXTENSION supplied) 37 | def inplace=(ext) 38 | options.inplace = true 39 | options.extension = ext 40 | options.extension.sub!(/\A\.?(?=.)/, ".") # ensure extension begins with dot. 41 | end 42 | alias :i= :inplace= 43 | 44 | # Delay N seconds before executing 45 | # Cast 'delay' argument to a Float. 46 | def delay=(n) 47 | options.delay = n.to_float 48 | end 49 | 50 | # Begin execution at given time 51 | # Cast 'time' argument to a Time object. 52 | def time=(time) 53 | options.time = Time.parse(time) 54 | end 55 | alias :t= :time= 56 | 57 | # Specify record separator (default \\0) 58 | # Cast to octal integer. 59 | def irs=(octal) 60 | options.record_separator = octal.to_i(8) 61 | end 62 | alias :F= :irs= 63 | 64 | # Example 'list' of arguments 65 | # List of arguments. 66 | def list=(args) 67 | options.list = list.split(',') 68 | end 69 | 70 | # Keyword completion. We are specifying a specific set of arguments (CODES 71 | # and CODE_ALIASES - notice the latter is a Hash), and the user may provide 72 | # the shortest unambiguous text. 73 | CODE_LIST = (CODE_ALIASES.keys + CODES) 74 | 75 | help.option(:code, "Select encoding (#{CODE_LIST})") 76 | 77 | # Select encoding 78 | def code=(code) 79 | codes = CODE_LIST.select{ |x| /^#{code}/ =~ x } 80 | codes = codes.map{ |x| CODE_ALIASES.key?(x) ? CODE_ALIASES[x] : x }.uniq 81 | raise ArgumentError unless codes.size == 1 82 | options.encoding = codes.first 83 | end 84 | 85 | # Select transfer type (text, binary, auto) 86 | # Optional argument with keyword completion. 87 | def type=(type) 88 | raise ArgumentError unless %w{text binary auto}.include(type.downcase) 89 | options.transfer_type = type.downcase 90 | end 91 | 92 | # Run verbosely 93 | # Boolean switch. 94 | def verbose=(bool) 95 | options.verbose = bool 96 | end 97 | def verbose? 98 | @options.verbose 99 | end 100 | alias :v= :verbose= 101 | alias :v? :verbose? 102 | 103 | # Show this message 104 | # No argument, shows at tail. This will print an options summary. 105 | def help! 106 | puts help_text 107 | exit 108 | end 109 | alias :h! :help! 110 | 111 | # Show version 112 | # Another typical switch to print the version. 113 | def version? 114 | puts Executor::VERSION 115 | exit 116 | end 117 | 118 | # 119 | def main 120 | # ... main procedure here ... 121 | end 122 | end 123 | 124 | We will run some scenarios on this example to make sure it works. 125 | 126 | cli = ExampleCLI.execute('-r=facets') 127 | cli.options.library.assert == ['facets'] 128 | 129 | Make sure time option parses. 130 | 131 | cli = ExampleCLI.execute('--time=2010-10-10') 132 | cli.options.time.assert == Time.parse('2010-10-10') 133 | 134 | Make sure code lookup words and is limted to the selections provided. 135 | 136 | cli = ExampleCLI.execute('--code=ji') 137 | cli.options.encoding.assert == 'iso-2022-jp' 138 | 139 | expect ArgumentError do 140 | ExampleCLI.execute('--code=xxx') 141 | end 142 | 143 | Ensure +irs+ is set to an octal number. 144 | 145 | cli = ExampleCLI.execute('-F 32') 146 | cli.options.record_separator.assert == 032 147 | 148 | Ensure extension begins with dot and inplace is set to true. 149 | 150 | cli = ExampleCLI.execute('--inplace txt') 151 | cli.options.extension.assert == '.txt' 152 | cli.options.inplace.assert == true 153 | 154 | -------------------------------------------------------------------------------- /work/concepts/r5_subclasses/qed/03_rdoc_example.rdoc: -------------------------------------------------------------------------------- 1 | = Example 2 | 3 | This example mimics the one given in optparse.rb documentation. 4 | 5 | require 'executioner' 6 | require 'ostruct' 7 | 8 | class ExampleCLI < Executioner 9 | 10 | CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] 11 | CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } 12 | 13 | attr :options 14 | 15 | def initialize 16 | super 17 | reset 18 | end 19 | 20 | def reset 21 | @options = OpenStruct.new 22 | @options.library = [] 23 | @options.inplace = false 24 | @options.encoding = "utf8" 25 | @options.transfer_type = :auto 26 | @options.verbose = false 27 | end 28 | 29 | help "Require the LIBRARY before executing your script" 30 | 31 | def require=(lib) 32 | options.library << lib 33 | end 34 | alias :r= :require= 35 | 36 | help "Edit ARGV files in place (make backup if EXTENSION supplied)" 37 | 38 | def inplace=(ext) 39 | options.inplace = true 40 | options.extension = ext 41 | options.extension.sub!(/\A\.?(?=.)/, ".") # ensure extension begins with dot. 42 | end 43 | alias :i= :inplace= 44 | 45 | help "Delay N seconds before executing" 46 | 47 | # Cast 'delay' argument to a Float. 48 | def delay=(n) 49 | options.delay = n.to_float 50 | end 51 | 52 | help "Begin execution at given time" 53 | 54 | # Cast 'time' argument to a Time object. 55 | def time=(time) 56 | options.time = Time.parse(time) 57 | end 58 | alias :t= :time= 59 | 60 | help "Specify record separator (default \\0)" 61 | 62 | # Cast to octal integer. 63 | def irs=(octal) 64 | options.record_separator = octal.to_i(8) 65 | end 66 | alias :F= :irs= 67 | 68 | help "Example 'list' of arguments" 69 | 70 | # List of arguments. 71 | def list=(args) 72 | options.list = list.split(',') 73 | end 74 | 75 | # Keyword completion. We are specifying a specific set of arguments (CODES 76 | # and CODE_ALIASES - notice the latter is a Hash), and the user may provide 77 | # the shortest unambiguous text. 78 | CODE_LIST = (CODE_ALIASES.keys + CODES) 79 | 80 | help "Select encoding (#{CODE_LIST})" 81 | 82 | def code=(code) 83 | codes = CODE_LIST.select{ |x| /^#{code}/ =~ x } 84 | codes = codes.map{ |x| CODE_ALIASES.key?(x) ? CODE_ALIASES[x] : x }.uniq 85 | raise ArgumentError unless codes.size == 1 86 | options.encoding = codes.first 87 | end 88 | 89 | help "Select transfer type (text, binary, auto)" 90 | 91 | # Optional argument with keyword completion. 92 | def type=(type) 93 | raise ArgumentError unless %w{text binary auto}.include(type.downcase) 94 | options.transfer_type = type.downcase 95 | end 96 | 97 | help "Run verbosely" 98 | 99 | # Boolean switch. 100 | def verbose=(bool) 101 | options.verbose = bool) 102 | end 103 | def verbose? 104 | @options.verbose 105 | end 106 | alias :v= :verbose= 107 | alias :v? :verbose? 108 | 109 | help "Show this message" 110 | 111 | # No argument, shows at tail. This will print an options summary. 112 | def help! 113 | puts help_text 114 | exit 115 | end 116 | alias :h! :help! 117 | 118 | help "Show version" 119 | 120 | # Another typical switch to print the version. 121 | def version? 122 | puts Executor::VERSION 123 | exit 124 | end 125 | 126 | def main 127 | # ... main procedure here ... 128 | end 129 | end 130 | 131 | We will run some scenarios on this example to make sure it works. 132 | 133 | cli = ExampleCLI.new 134 | 135 | cli.execute_command('-r=facets') 136 | cli.options.library.assert == ['facets'] 137 | 138 | Make sure time option parses. 139 | 140 | cli.execute_command('--time=2010-10-10') 141 | cli.options.time.assert == Time.parse('2010-10-10') 142 | 143 | Make sure code lookup words and is limted to the selections provided. 144 | 145 | cli.execute_command('--code=ji') 146 | cli.options.encoding.assert == 'iso-2022-jp' 147 | 148 | expect ArgumentError do 149 | cli.execute_command('--code=xxx') 150 | end 151 | 152 | Ensure +irs+ is set to an octal number. 153 | 154 | cli.execute_command('-F 32') 155 | cli.options.record_separator.assert == 032 156 | 157 | Ensure extension begins with dot and inplace is set to true. 158 | 159 | cli.execute_command('--inplace txt') 160 | cli.options.extension.assert == '.txt' 161 | cli.options.inplace.assert == true 162 | 163 | -------------------------------------------------------------------------------- /work/concepts/r3_executable/executable.rb: -------------------------------------------------------------------------------- 1 | # = Executable Mixin 2 | # 3 | # The Executable mixin is a very quick and and easy way to make almost 4 | # any class usable via a command line interface. It simply uses writer 5 | # methods as option setters, and the first command line argument as a 6 | # method to call with the subsequent arguments passed to the method. 7 | # 8 | # The only limitation of this approach is that non-boolean options must 9 | # be specified with `key=value` notation. 10 | # 11 | # class Example 12 | # include Executable 13 | # 14 | # attr_accessor :quiet 15 | # 16 | # def bread(*args) 17 | # ["bread", quiet, *args] 18 | # end 19 | # 20 | # def butter(*args) 21 | # ["butter", quiet, *args] 22 | # end 23 | # end 24 | # 25 | # ex = Example.new 26 | # 27 | # ex.execute!("butter yum") 28 | # => ["butter", nil, "yum"] 29 | # 30 | # ex.execute!("bread --quiet") 31 | # => ["butter", true] 32 | # 33 | # Executable also provides #option_missing, which you can overriden to provide 34 | # suitable results when a given command line option has no corresponding 35 | # writer method. 36 | # 37 | module Executable 38 | 39 | # Used the #excute! method to invoke the command. 40 | def execute!(argv=ARGV) 41 | Executable.execute(self, argv) 42 | end 43 | 44 | ## When no attribute write exists for an option that has been given on 45 | ## the command line #option_missing is called. Override #option_missing 46 | ## to handle these cases, if needed. Otherwise a NoMethodArgument will be 47 | ## raised. This callback method receives the name and value of the option. 48 | #def option_missing(opt, arg) 49 | # raise NoMethodError, "undefined option `#{opt}=' for #{self}" 50 | #end 51 | 52 | class << self 53 | 54 | # Process the arguments as an exectuable against the given object. 55 | def execute(obj, argv=ARGV) 56 | args = parse(obj, argv) 57 | subcmd = args.first 58 | if subcmd && !obj.respond_to?("#{subcmd}=") 59 | obj.send(*args) 60 | else 61 | obj.method_missing(*args) 62 | end 63 | end 64 | 65 | # The original name for #execute. 66 | alias_method :run, :execute 67 | 68 | # Parse command line with respect to +obj+. 69 | def parse(obj, argv) 70 | case argv 71 | when String 72 | require 'shellwords' 73 | argv = Shellwords.shellwords(argv) 74 | else 75 | argv = argv.dup 76 | end 77 | 78 | argv = argv.dup 79 | args, opts, i = [], {}, 0 80 | while argv.size > 0 81 | case opt = argv.shift 82 | when /=/ 83 | parse_equal(obj, opt, argv) 84 | when /^--/ 85 | parse_option(obj, opt, argv) 86 | when /^-/ 87 | parse_flags(obj, opt, argv) 88 | else 89 | args << opt 90 | end 91 | end 92 | return args 93 | end 94 | 95 | # Parse a setting option. 96 | def parse_equal(obj, opt, argv) 97 | if md = /^[-]*(.*?)=(.*?)$/.match(opt) 98 | x, v = md[1], md[2] 99 | else 100 | raise ArgumentError, "#{x}" 101 | end 102 | # TODO: to_b if 'true' or 'false' ? 103 | #if obj.respond_to?("#{x}=") 104 | obj.send("#{x}=", v) 105 | #else 106 | # obj.option_missing(x, v) 107 | #end 108 | end 109 | 110 | # Parse a named boolean option. 111 | def parse_option(obj, opt, argv) 112 | x = opt.sub(/^--/, '') 113 | #if obj.respond_to?("#{x}=") 114 | obj.send("#{x}=", true) 115 | #else 116 | # obj.option_missing(x, true) 117 | #end 118 | end 119 | 120 | # Parse flags. Each character of a flag set is treated as a separate option. 121 | # For example: 122 | # 123 | # $ foo -abc 124 | # 125 | # Would be parsed the same as: 126 | # 127 | # $ foo -a -b -c 128 | # 129 | def parse_flags(obj, opt, args) 130 | x = opt.sub(/^-/, '') 131 | #c = 0 132 | x.split(//).each do |k| 133 | #if obj.respond_to?("#{k}=") 134 | obj.send("#{k}=", true) 135 | #else 136 | # obj.option_missing(x, true) 137 | #end 138 | end 139 | end 140 | 141 | end #class << self 142 | 143 | # 144 | module Help 145 | 146 | def self.included(base) 147 | base.extend Domain 148 | super(base) 149 | end 150 | 151 | # Class-level domain. 152 | module Domain 153 | 154 | # 155 | def help(text=nil) 156 | return(@help ||= {}) unless text 157 | singleton_class = (class << self; self; end) 158 | singleton_class.class_eval do 159 | define_method(:method_added) do |name| 160 | @help ||= {} 161 | @help[name] = text 162 | singleton_class.send(:remove_method, :method_added) 163 | end 164 | end 165 | end 166 | 167 | end 168 | 169 | end 170 | 171 | end 172 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module DotRuby 6 | 7 | # 8 | class GemSpec 9 | 10 | # For which revision of .ruby is this gemspec intended? 11 | REVISION = 0 12 | 13 | # 14 | PATTERNS = { 15 | :bin_files => 'bin/*', 16 | :lib_files => 'lib/{**/}*.rb', 17 | :ext_files => 'ext/{**/}extconf.rb', 18 | :doc_files => '*.{txt,rdoc,md,markdown,tt,textile}', 19 | :test_files => '{test/{**/}*_test.rb,spec/{**/}*_spec.rb}' 20 | } 21 | 22 | # 23 | def self.instance 24 | new.to_gemspec 25 | end 26 | 27 | attr :metadata 28 | 29 | attr :manifest 30 | 31 | # 32 | def initialize 33 | @metadata = YAML.load_file('.ruby') 34 | @manifest = Dir.glob('manifest{,.txt}', File::FNM_CASEFOLD).first 35 | 36 | if @metadata['revision'].to_i != REVISION 37 | warn "You have the wrong revision. Trying anyway..." 38 | end 39 | end 40 | 41 | # 42 | def scm 43 | @scm ||= \ 44 | case 45 | when File.directory?('.git') 46 | :git 47 | end 48 | end 49 | 50 | # 51 | def files 52 | @files ||= \ 53 | #glob_files[patterns[:files]] 54 | case 55 | when manifest 56 | File.readlines(manifest). 57 | map{ |line| line.strip }. 58 | reject{ |line| line.empty? || line[0,1] == '#' } 59 | when scm == :git 60 | `git ls-files -z`.split("\0") 61 | else 62 | Dir.glob('{**/}{.*,*}') # TODO: be more specific using standard locations ? 63 | end.select{ |path| File.file?(path) } 64 | end 65 | 66 | # 67 | def glob_files(pattern) 68 | Dir.glob(pattern).select { |path| 69 | File.file?(path) && files.include?(path) 70 | } 71 | end 72 | 73 | # 74 | def patterns 75 | PATTERNS 76 | end 77 | 78 | # 79 | def executables 80 | @executables ||= \ 81 | glob_files(patterns[:bin_files]).map do |path| 82 | File.basename(path) 83 | end 84 | end 85 | 86 | def extensions 87 | @extensions ||= \ 88 | glob_files(patterns[:ext_files]).map do |path| 89 | File.basename(path) 90 | end 91 | end 92 | 93 | # 94 | def name 95 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 96 | end 97 | 98 | # 99 | def to_gemspec 100 | Gem::Specification.new do |gemspec| 101 | gemspec.name = name 102 | gemspec.version = metadata['version'] 103 | gemspec.summary = metadata['summary'] 104 | gemspec.description = metadata['description'] 105 | 106 | metadata['authors'].each do |author| 107 | gemspec.authors << author['name'] 108 | 109 | if author.has_key?('email') 110 | if gemspec.email 111 | gemspec.email << author['email'] 112 | else 113 | gemspec.email = [author['email']] 114 | end 115 | end 116 | end 117 | 118 | gemspec.licenses = metadata['copyrights'].map{ |c| c['license'] }.compact 119 | 120 | metadata['requirements'].each do |req| 121 | name = req['name'] 122 | version = req['version'] 123 | groups = req['groups'] || [] 124 | 125 | case version 126 | when /^(.*?)\+$/ 127 | version = ">= #{$1}" 128 | when /^(.*?)\-$/ 129 | version = "< #{$1}" 130 | when /^(.*?)\~$/ 131 | version = "~> #{$1}" 132 | end 133 | 134 | if groups.empty? or groups.include?('runtime') 135 | # populate runtime dependencies 136 | if gemspec.respond_to?(:add_runtime_dependency) 137 | gemspec.add_runtime_dependency(name,*version) 138 | else 139 | gemspec.add_dependency(name,*version) 140 | end 141 | else 142 | # populate development dependencies 143 | if gemspec.respond_to?(:add_development_dependency) 144 | gemspec.add_development_dependency(name,*version) 145 | else 146 | gemspec.add_dependency(name,*version) 147 | end 148 | end 149 | end 150 | 151 | # convert external dependencies into a requirements 152 | if metadata['external_dependencies'] 153 | ##gemspec.requirements = [] unless metadata['external_dependencies'].empty? 154 | metadata['external_dependencies'].each do |req| 155 | gemspec.requirements << req.to_s 156 | end 157 | end 158 | 159 | # determine homepage from resources 160 | homepage = metadata['resources'].find{ |key, url| key =~ /^home/ } 161 | gemspec.homepage = homepage.last if homepage 162 | 163 | gemspec.require_paths = metadata['load_path'] || ['lib'] 164 | gemspec.post_install_message = metadata['install_message'] 165 | 166 | # RubyGems specific metadata 167 | gemspec.files = files 168 | gemspec.extensions = extensions 169 | gemspec.executables = executables 170 | 171 | if Gem::VERSION < '1.7.' 172 | gemspec.default_executable = gemspec.executables.first 173 | end 174 | 175 | gemspec.test_files = glob_files(patterns[:test_files]) 176 | 177 | unless gemspec.files.include?('.document') 178 | gemspec.extra_rdoc_files = glob_files(patterns[:doc_files]) 179 | end 180 | end 181 | end 182 | 183 | end #class GemSpec 184 | 185 | end 186 | 187 | DotRuby::GemSpec.instance 188 | -------------------------------------------------------------------------------- /lib/cli/parser.rb: -------------------------------------------------------------------------------- 1 | module CLI 2 | 3 | # The Parse class does all the heavy lifting for CLI::Base. 4 | # 5 | class Parser 6 | 7 | # 8 | # @param cli_class [CLI::Base] command class 9 | # 10 | def initialize(cli_class) 11 | @cli_class = cli_class 12 | end 13 | 14 | attr :cli_class 15 | 16 | # Parse command-line. 17 | # 18 | # @param argv [Array,String] command-line arguments 19 | # 20 | def parse(argv=ARGV) 21 | argv = parse_shellwords(argv) 22 | 23 | cmd, argv = parse_subcommand(argv) 24 | cli = cmd.new 25 | args = parse_arguments(cli, argv) 26 | 27 | return cli, args 28 | end 29 | 30 | # Make sure arguments are an array. If argv is a String, 31 | # then parse using Shellwords module. 32 | # 33 | # @param argv [Array,String] commmand-line arguments 34 | def parse_shellwords(argv) 35 | if String === argv 36 | require 'shellwords' 37 | argv = Shellwords.shellwords(argv) 38 | end 39 | argv.to_a 40 | end 41 | 42 | # 43 | # 44 | # 45 | def parse_subcommand(argv) 46 | cmd = cli_class 47 | arg = argv.first 48 | 49 | while c = cmd.subcommands[arg] 50 | cmd = c 51 | argv.shift 52 | arg = argv.first 53 | end 54 | 55 | return cmd, argv 56 | end 57 | 58 | # 59 | # Parse command line options based on given object. 60 | # 61 | # @param obj [Object] basis for command-line parsing 62 | # @param argv [Array,String] command-line arguments 63 | # @param args [Array] pre-seeded arguments to add to 64 | # 65 | # @return [Array] parsed arguments 66 | # 67 | def parse_arguments(obj, argv, args=[]) 68 | case argv 69 | when String 70 | require 'shellwords' 71 | argv = Shellwords.shellwords(argv) 72 | #else 73 | # argv = argv.dup 74 | end 75 | 76 | #subc = nil 77 | #@args = [] #opts, i = {}, 0 78 | 79 | while argv.size > 0 80 | case arg = argv.shift 81 | when /=/ 82 | parse_equal(obj, arg, argv, args) 83 | when /^--/ 84 | parse_long(obj, arg, argv, args) 85 | when /^-/ 86 | parse_flags(obj, arg, argv, args) 87 | else 88 | #if CLI::Base === obj 89 | # if cmd_class = obj.class.subcommands[arg] 90 | # cmd = cmd_class.new(obj) 91 | # subc = cmd 92 | # parse(cmd, argv, args) 93 | # else 94 | args << arg 95 | # end 96 | #end 97 | end 98 | end 99 | 100 | return args 101 | end 102 | 103 | # 104 | # Parse equal setting comman-line option. 105 | # 106 | def parse_equal(obj, opt, argv, args) 107 | if md = /^[-]*(.*?)=(.*?)$/.match(opt) 108 | x, v = md[1], md[2] 109 | else 110 | raise ArgumentError, "#{x}" 111 | end 112 | if obj.respond_to?("#{x}=") 113 | v = true if v == 'true' # yes or on ? 114 | v = false if v == 'false' # no or off ? 115 | obj.send("#{x}=", v) 116 | else 117 | obj.option_missing(x, v) # argv? 118 | end 119 | end 120 | 121 | # 122 | # Parse double-dash command-line option. 123 | # 124 | def parse_long(obj, opt, argv, args) 125 | x = opt.sub(/^\-+/, '') # remove '--' 126 | if obj.respond_to?("#{x}=") 127 | m = obj.method("#{x}=") 128 | if obj.respond_to?("#{x}?") 129 | m.call(true) 130 | else 131 | invoke(obj, m, argv) 132 | end 133 | elsif obj.respond_to?("#{x}!") 134 | invoke(obj, "#{x}!", argv) 135 | else 136 | obj.option_missing(x, argv) 137 | end 138 | end 139 | 140 | # 141 | # Parse single-dash command-line option. 142 | # 143 | # TODO: This needs some thought concerning character spliting and arguments. 144 | def parse_flags(obj, opt, argv, args) 145 | x = opt[1..-1] 146 | c = 0 147 | x.split(//).each do |k| 148 | if obj.respond_to?("#{k}=") 149 | m = obj.method("#{k}=") 150 | if obj.respond_to?("#{x}?") 151 | m.call(true) 152 | else 153 | invoke(obj, m, argv) #m.call(argv.shift) 154 | end 155 | elsif obj.respond_to?("#{k}!") 156 | invoke(obj, "#{k}!", argv) 157 | else 158 | long = find_longer_option(obj, k) 159 | if long 160 | if long.end_with?('=') && obj.respond_to?(long.chomp('=')+'?') 161 | invoke(obj, long, [true]) 162 | else 163 | invoke(obj, long, argv) 164 | end 165 | else 166 | obj.option_missing(x, argv) 167 | end 168 | end 169 | end 170 | end 171 | 172 | # 173 | # 174 | # TODO: Sort alphabetically? 175 | def find_longer_option(obj, char) 176 | meths = obj.methods.map{ |m| m.to_s } 177 | meths = meths.select do |m| 178 | m.start_with?(char) and (m.end_with?('=') or m.end_with?('!')) 179 | end 180 | meths.first 181 | end 182 | 183 | # 184 | # 185 | def invoke(obj, meth, argv) 186 | m = Method === meth ? meth : obj.method(meth) 187 | a = [] 188 | m.arity.abs.times{ a << argv.shift } 189 | m.call(*a) 190 | end 191 | 192 | # Index of subcommands. 193 | # 194 | # @return [Hash] name mapped to subcommnd class 195 | def subcommands 196 | @subcommands ||= ( 197 | consts = @cli_class.constants - @cli_class.superclass.constants 198 | consts.inject({}) do |h, c| 199 | c = @cli_class.const_get(c) 200 | if Class === c && CLI::Base > c 201 | n = c.name.split('::').last.downcase 202 | h[n] = c 203 | end 204 | h 205 | end 206 | ) 207 | end 208 | 209 | end 210 | 211 | end 212 | -------------------------------------------------------------------------------- /lib/cli/help.rb: -------------------------------------------------------------------------------- 1 | module CLI 2 | 3 | require 'cli/source' 4 | require 'cli/core_ext' 5 | 6 | # Encpsulates command help for deefining and displaying well formated help 7 | # output in plain text or via manpages if found. 8 | class Help 9 | 10 | # Setup new help object. 11 | def initialize(cli_class) 12 | @cli_class = cli_class 13 | 14 | @usage = nil 15 | @footer = nil 16 | 17 | @options = {} 18 | @subcmds = {} 19 | end 20 | 21 | # Set file and line under which the CLI::Base subclass is defined. 22 | def setup(file, line=nil) 23 | @file = file 24 | @line = line 25 | end 26 | 27 | # The CLI::Base subclass to which this help applies. 28 | attr :cli_class 29 | 30 | # Get or set command name. 31 | # 32 | # By default the name is assumed to be the class name, substituting 33 | # dashes for double colons. 34 | def name(name=nil) 35 | @name = name if name 36 | @name ||= cli_class.name.downcase.gsub('::','-') 37 | #File.basename($0) 38 | @name 39 | end 40 | 41 | # Get or set command usage. 42 | def usage(text=nil) 43 | @usage ||= "Usage: " + File.basename($0) + ' [options...] [subcommand]' 44 | @usage = text unless text.nil? 45 | @usage 46 | end 47 | 48 | # Set command usage. 49 | def usage=(text) 50 | @usage = text 51 | end 52 | 53 | # Get or set command description. 54 | def description(text=nil) 55 | @description = text unless text.nil? 56 | end 57 | alias_method :header, :description 58 | 59 | # Get or set command help footer. 60 | def footer(text=nil) 61 | @footer = text unless text.nil? 62 | @footer 63 | end 64 | 65 | # Set comamnd help footer. 66 | def footer=(text) 67 | @footer = text 68 | end 69 | 70 | # Set description of an option. 71 | def option(name, description) 72 | @options[name.to_s] = description 73 | end 74 | 75 | # Set desciption of a subcommand. 76 | def subcommand(name, description) 77 | @subcmds[name.to_s] = description 78 | end 79 | 80 | #alias_method :inspect, :to_s 81 | 82 | # Show help. 83 | # 84 | # @todo man-pages will probably fail on Windows 85 | def show_help(hint=nil) 86 | if file = manpage(hint) 87 | system "man #{file}" 88 | else 89 | puts text 90 | end 91 | end 92 | 93 | # M A N P A G E 94 | 95 | # Get man-page if there is one. 96 | def manpage(hint=nil) 97 | @manpage ||= ( 98 | man = [] 99 | dir = @file ? File.dirname(@file) : nil 100 | glob = "man/#{name}.1" 101 | 102 | if hint 103 | if File.exist?(hint) 104 | return hint 105 | elsif File.directory?(hint) 106 | dir = hint 107 | else 108 | glob = hint if hint 109 | end 110 | end 111 | 112 | if dir 113 | while dir != '/' 114 | man.concat(Dir[File.join(dir, glob)]) 115 | #man.concat(Dir[File.join(dir, "man/man1/#{name}.1")]) 116 | #man.concat(Dir[File.join(dir, "man/#{name}.1.ronn")]) 117 | #man.concat(Dir[File.join(dir, "man/man1/#{name}.1")]) 118 | break unless man.empty? 119 | dir = File.dirname(dir) 120 | end 121 | end 122 | 123 | man.first 124 | ) 125 | end 126 | 127 | # H E L P T E X T 128 | 129 | # 130 | def to_s; text; end 131 | 132 | # 133 | def text(file=nil) 134 | s = [] 135 | s << text_usage 136 | s << text_description 137 | s << text_subcommands 138 | s << text_options 139 | s << text_footer 140 | s.compact.join("\n\n") 141 | end 142 | 143 | # Command usage. 144 | def text_usage 145 | usage 146 | end 147 | 148 | # TODO: Maybe default description should always come from `main` 149 | # instead of the the class comment ? 150 | 151 | # Description of command in printable form. 152 | # But will return +nil+ if there is no description. 153 | # 154 | # @return [String,NilClass] command description 155 | def text_description 156 | if @description 157 | @description 158 | elsif @file 159 | Source.get_above_comment(@file, @line) 160 | elsif main = method_list.find{ |m| m.name == 'main' } 161 | main.comment 162 | else 163 | nil 164 | end 165 | end 166 | 167 | # List of subcommands converted to a printable string. 168 | # But will return +nil+ if there are no subcommands. 169 | # 170 | # @return [String,NilClass] subcommand list text 171 | def text_subcommands 172 | commands = @cli_class.subcommands 173 | s = [] 174 | if !commands.empty? 175 | s << "COMMANDS" 176 | commands.each do |cmd, klass| 177 | desc = klass.help.text_description.to_s.split("\n").first 178 | s << " %-17s %s" % [cmd, desc] 179 | end 180 | end 181 | return nil if s.empty? 182 | return s.join("\n") 183 | end 184 | 185 | # List of options coverted to a printable string. 186 | # But will return +nil+ if there are no options. 187 | # 188 | # @return [String,NilClass] option list text 189 | def text_options 190 | option_list.each do |opt| 191 | if @options.key?(opt.name) 192 | opt.description = @options[opt.name] 193 | end 194 | end 195 | 196 | max = option_list.map{ |opt| opt.usage.size }.max + 2 197 | 198 | s = [] 199 | s << "OPTIONS" 200 | option_list.each do |opt| 201 | mark = (opt.name.size == 1 ? ' -' : '--') 202 | s << " #{mark}%-#{max}s %s" % [opt.usage, opt.description] 203 | end 204 | s.join("\n") 205 | end 206 | 207 | # 208 | def text_footer 209 | footer 210 | end 211 | 212 | # 213 | #def text_common_options 214 | #s << "\nCOMMON OPTIONS:\n\n" 215 | #global_options.each do |(name, meth)| 216 | # if name.size == 1 217 | # s << " -%-15s %s\n" % [name, descriptions[meth]] 218 | # else 219 | # s << " --%-15s %s\n" % [name, descriptions[meth]] 220 | # end 221 | #end 222 | #end 223 | 224 | # 225 | def option_list 226 | @option_list ||= ( 227 | method_list.map do |meth| 228 | case meth.name 229 | when /^(.*?)[\!\=]$/ 230 | Option.new(meth) 231 | end 232 | end.compact.sort 233 | ) 234 | end 235 | 236 | private 237 | 238 | # Produce a list relavent methods. 239 | # 240 | def method_list 241 | list = [] 242 | methods = [] 243 | stop_at = cli_class.ancestors.index(CLI::Base) || -1 244 | ancestors = cli_class.ancestors[0...stop_at] 245 | ancestors.reverse_each do |a| 246 | a.instance_methods(false).each do |m| 247 | list << cli_class.instance_method(m) 248 | end 249 | end 250 | list 251 | end 252 | 253 | # Encapsualtes a command line option. 254 | class Option 255 | def initialize(method) 256 | @method = method 257 | end 258 | 259 | def name 260 | @method.name.to_s.chomp('!').chomp('=') 261 | end 262 | 263 | def comment 264 | @method.comment 265 | end 266 | 267 | def description 268 | @description ||= comment.split("\n").first 269 | end 270 | 271 | # Set description manually. 272 | def description=(desc) 273 | @description = desc 274 | end 275 | 276 | def parameter 277 | param = @method.parameters.first 278 | param.last if param 279 | end 280 | 281 | def usage 282 | if parameter 283 | "#{name}=#{parameter.to_s.upcase}" 284 | else 285 | "#{name}" 286 | end 287 | end 288 | 289 | def <=>(other) 290 | self.name <=> other.name 291 | end 292 | end 293 | end 294 | 295 | end 296 | -------------------------------------------------------------------------------- /work/concepts/r0_underscore/command.rb: -------------------------------------------------------------------------------- 1 | require 'clio/errors' 2 | require 'clio/option' 3 | 4 | module Clio 5 | 6 | # = Command 7 | # 8 | # Command is Clio's low-level command option parser solution. 9 | # Despite being low-level, it is actually quite easy to used 10 | # and integrates will with Ruby. 11 | # 12 | # The Command class does not to try overly determine your needs via a 13 | # declarative options DSL, rather it just makes it easy for you to 14 | # process options into a command class. It does this primarily by 15 | # using a simple method naming trick. Methods with names starting 16 | # with underscores (eg. _n or __name) are treated as options. 17 | # 18 | # Clio::Command encourages the command pattern (hence the name). So one 19 | # class does one thing and one thing only. This helps ensure a robust 20 | # design, albeit at the expense of churning a quick all-in-one solution. 21 | # 22 | # For a quicker solution to command line parsing have a look at 23 | # Clio::Commandable or Clio::Commandline. 24 | # 25 | # Although it is low-level it does provide a single high-level "DSL" 26 | # command for describing usage. This is purely a descriptive measure, 27 | # and has no barring on the functionality. It is provided to ease 28 | # the creation of help and command completion output. 29 | # 30 | # Simply specify: 31 | # 32 | # usage :optname, "options description", :type=>"TYPE", :default=>"DEFAULT" 33 | # 34 | # Here is an example of usage. 35 | # 36 | # MainCommand < Clio::Command 37 | # 38 | # usage :quiet, "keep it quiet?", :type=>:BOOLEAN, :default=>:FALSE 39 | # usage :file, "what file to use", :type=>:FILE, :alias => :f 40 | # 41 | # # boolean flag 42 | # def __quiet 43 | # @quiet = true 44 | # end 45 | # 46 | # # required option 47 | # def __file(fname) 48 | # @file = fname 49 | # end 50 | # 51 | # # one letter shortcut 52 | # alias _f __flag 53 | # 54 | # # run command 55 | # def call(*args) 56 | # subcommand = args.shift 57 | # case subcommand 58 | # when 'show' 59 | # puts File.read(@file) 60 | # when 'rshow' 61 | # puts File.read(@file).reverse 62 | # else 63 | # puts "Unknown subcommand" 64 | # end 65 | # end 66 | # 67 | # end 68 | # 69 | # MainCommand.run 70 | # 71 | # You can chain subcommands together via a case statement like 72 | # that given above. Eg. 73 | # 74 | # case subcommand 75 | # when 'build' 76 | # BuildCommand.run(args) 77 | # ... 78 | # 79 | # TODO: Support passing a string or *args, opts in place of ARGV. 80 | # 81 | class Command 82 | 83 | # Used to invoke this command. 84 | def run(argv=ARGV) 85 | args = self.class.parse(self, argv) 86 | call(*args) 87 | end 88 | 89 | # This is command function. Override this 90 | # to do what the command does. 91 | def call(*args) 92 | end 93 | 94 | # Override option_missing if needed. 95 | # This receives the name of the option and 96 | # the remaining arguments list. It must consume 97 | # any argument it uses from the (begining of) 98 | # the list. 99 | def option_missing(opt, *argv) 100 | raise NoOptionError, opt 101 | end 102 | 103 | class << self 104 | 105 | # 106 | def parse(obj, argv=ARGV) 107 | argv = argv.dup 108 | args, opts, i = [], {}, 0 109 | while argv.size > 0 110 | case opt = argv.shift 111 | when /=/ 112 | parse_equal(obj, opt, argv) 113 | when /^--/ 114 | parse_option(obj, opt, argv) 115 | when /^-/ 116 | parse_flags(obj, opt, argv) 117 | else 118 | args << opt 119 | end 120 | end 121 | return args 122 | end 123 | 124 | # 125 | def parse_equal(obj, opt, argv) 126 | if md = /^[-]*(.*?)=(.*?)$/.match(opt) 127 | x, v = md[1], md[2] 128 | else 129 | raise ArgumentError, "#{x}" 130 | end 131 | if obj.respond_to?("__#{x}") 132 | obj.send("__#{x}",v) 133 | else 134 | obj.option_missing(x, v) # argv? 135 | end 136 | end 137 | 138 | # 139 | def parse_option(obj, opt, argv) 140 | x = opt[2..-1] 141 | if obj.respond_to?("__#{x}") 142 | m = obj.method("__#{x}") 143 | if m.arity >= 0 144 | a = [] 145 | m.arity.times{ a << argv.shift } 146 | m.call(*a) 147 | else 148 | m.call 149 | end 150 | else 151 | obj.option_missing(x, argv) 152 | end 153 | end 154 | 155 | # 156 | def parse_flags(obj, opt, args) 157 | x = opt[1..-1] 158 | c = 0 159 | x.split(//).each do |k| 160 | if m = obj.method("_#{k}") 161 | a = [] 162 | m.arity.times{ a << argv.shift } 163 | m.call(*a) 164 | else 165 | obj.option_missing(x, argv) 166 | end 167 | end 168 | end 169 | 170 | # Shortcut for 171 | # 172 | # Command.new.run() 173 | # 174 | def run(argv=ARGV) 175 | new.run(argv) 176 | end 177 | 178 | def uses 179 | @usage ||= [] 180 | @usage.collect do |u| 181 | u.usage 182 | end.join(' ') 183 | end 184 | 185 | def usage(name, desc, opts) 186 | @usage ||= [] 187 | @usage << Option.new(name, desc, opts) 188 | end 189 | 190 | #def help 191 | # #command_attrs.each do |k, o| 192 | # # puts "%-20s %s" % [o.usage, o.description] 193 | # #end 194 | #end 195 | 196 | end #class << self 197 | 198 | end 199 | 200 | =begin 201 | # TODO: use clio/option 202 | class UseOption 203 | attr_reader :name 204 | attr_accessor :type 205 | attr_accessor :init 206 | attr_accessor :desc 207 | 208 | alias_method :default, :init 209 | alias_method :description, :desc 210 | 211 | def initialize(name, desc, opts) 212 | @name = name 213 | @desc = desc 214 | @type = opts[:type] || 'value' 215 | @init = opts[:default] || opts[:init] 216 | end 217 | def usage 218 | "--#{name}=#{type.to_s.upcase}" 219 | end 220 | def assert_valid(value) 221 | raise "invalid" unless valid?(value) 222 | end 223 | def valid?(value) 224 | validation ? validation.call(value) : true 225 | end 226 | def validation(&block) 227 | @validation = block if block 228 | @validation 229 | end 230 | end 231 | =end 232 | 233 | end 234 | 235 | 236 | =begin :spec: 237 | 238 | require 'quarry/spec' 239 | 240 | class MyCommand < Clio::Command 241 | attr_reader :size, :quiet, :file 242 | 243 | def initialize 244 | @file = 'hey.txt' # default 245 | end 246 | 247 | use :quiet, "supress standard output", :type => :boolean 248 | 249 | def __quiet(bool=true) 250 | @quiet = bool ? true : bool 251 | end 252 | 253 | use :size, "what size will it be?", :type => :integer, :default => '0' 254 | 255 | def __size(integer) 256 | @size = integer.to_i 257 | end 258 | 259 | use :file, "where to store the stuff", :init => 'hey.txt' 260 | 261 | def __file(fname) 262 | @file = fname 263 | end 264 | 265 | def call(*args) 266 | @args = args 267 | end 268 | end 269 | 270 | Quarry.spec "Command" do 271 | before do 272 | @mc = MyCommand.new 273 | end 274 | 275 | demonstrate 'boolean option' do 276 | @mc.run(['--quiet']) 277 | @mc.quiet.assert == true 278 | end 279 | 280 | demonstrate 'integer option' do 281 | @mc.run(['--size=4']) 282 | @mc.size.assert == 4 283 | end 284 | 285 | demonstrate 'default value' do 286 | @mc.run(['']) 287 | @mc.file.assert == 'hey.txt' 288 | end 289 | 290 | demonstrate 'usage output' do 291 | MyCommand.usage.assert == "--quiet=BOOLEAN --size=INTEGER --file=VALUE" 292 | end 293 | end 294 | 295 | =end 296 | 297 | -------------------------------------------------------------------------------- /work/concepts/r5_subclasses/lib/subcommand.rb: -------------------------------------------------------------------------------- 1 | # = Executioner 2 | # 3 | # class MyCLI < Executioner 4 | # 5 | # # cmd --debug 6 | # 7 | # def debug? 8 | # $DEBUG 9 | # end 10 | # 11 | # def debug=(bool) 12 | # $DEBUG = bool 13 | # end 14 | # 15 | # # $ foo remote 16 | # 17 | # class Remote < self 18 | # 19 | # # $ foo remote --verbose 20 | # 21 | # def verbose? 22 | # @verbose 23 | # end 24 | # 25 | # def verbose=(bool) 26 | # @verbose = bool 27 | # end 28 | # 29 | # # $ foo remote --force 30 | # 31 | # def force? 32 | # @force 33 | # end 34 | # 35 | # def remote=(bool) 36 | # @force = bool 37 | # end 38 | # 39 | # # $ foo remote --output 40 | # 41 | # def output=(path) 42 | # @path = path 43 | # end 44 | # 45 | # # $ foo remote -o 46 | # 47 | # alias_method :o=, :output= 48 | # 49 | # # $ foo remote add 50 | # 51 | # class Add < subcommand 52 | # 53 | # def main(name, branch) 54 | # # ... 55 | # end 56 | # 57 | # end 58 | # 59 | # # $ foo remote show 60 | # 61 | # class Show < Subcommand() 62 | # 63 | # def main(name) 64 | # # ... 65 | # end 66 | # 67 | # end 68 | # 69 | # end 70 | # 71 | # end 72 | # 73 | class Executioner 74 | 75 | require 'executioner/help' 76 | 77 | 78 | # 79 | def initialize(master=nil) 80 | @master = master 81 | end 82 | 83 | def main(*args) 84 | #raise MissingCommandError 85 | raise NoCommandError 86 | end 87 | 88 | ## Used to invoke the command. 89 | #def run(argv=ARGV) 90 | # args = parse(argv) 91 | #p args 92 | # main(*args) 93 | #end 94 | 95 | # This is the fallback subcommand. Override this to provide 96 | # a fallback when no command is given on the commandline. 97 | #def command_missing 98 | # begin 99 | # main 100 | # rescue NameError 101 | # raise MissingCommandError 102 | # end 103 | #end 104 | 105 | # Override option_missing if needed. This receives 106 | # the name of the option and the remaining arguments 107 | # list. It must consume any argument it uses from 108 | # the (begining of) the list. 109 | def option_missing(opt, argv) 110 | raise NoOptionError, opt 111 | end 112 | 113 | class << self 114 | 115 | # 116 | def execute(argv=ARGV) 117 | if String === argv 118 | require 'shellwords' 119 | argv = Shellwords.shellwords(argv) 120 | end 121 | 122 | cmd, argv = parse_subcommand(argv) 123 | cli = cmd.new 124 | args = parse(cli, argv) 125 | 126 | cli.main(*args) 127 | 128 | return cli 129 | end 130 | 131 | # 132 | alias_method :run, :execute 133 | 134 | # 135 | def parse_subcommand(argv) 136 | cmd = self 137 | arg = argv.first 138 | 139 | while c = cmd.subcommands[arg] 140 | cmd = c 141 | argv.shift 142 | arg = argv.first 143 | end 144 | 145 | return cmd, argv 146 | end 147 | 148 | # 149 | def attr_switch(name) 150 | attr_writer :name 151 | module_eval %{ 152 | def #{name}? 153 | @#{name} 154 | end 155 | } 156 | end 157 | 158 | # 159 | #def new(argv=ARGV) 160 | # if String === argv 161 | # require 'shellwords' 162 | # argv = Shellwords.shellwords(argv) 163 | # end 164 | # 165 | # subc, args = parse(_new, argv) 166 | # subc.arguments = args 167 | #end 168 | 169 | def subcommands 170 | @subcommands ||= ( 171 | constants.inject({}){ |h, c| 172 | if Executioner === c 173 | n = c.name.split('::').last.downcase 174 | h[n] = c 175 | end 176 | h 177 | end 178 | } 179 | end 180 | 181 | #def inherited(subclass) 182 | # name = subclass.name.split('::').last.downcase 183 | # subcommands[name] = subclass 184 | # #define_method(name.downcase) do 185 | # # subclass.new(self) 186 | # #end 187 | #end 188 | 189 | # subc, args = parse(obj, argv) 190 | # obj.main(*args) unless subc 191 | #subcmd = args.shift 192 | #if subcmd && !obj.respond_to?("#{subcmd}=") 193 | # begin 194 | # obj.send(subcmd, *args) 195 | # rescue NoMethodError 196 | # raise NoCommandError, subcmd 197 | # end 198 | #else 199 | # obj.command_missing 200 | #end 201 | #end 202 | 203 | #def run(obj) 204 | # methname, args = *parse(obj) 205 | # meth = obj.method(methname) 206 | # meth.call(*args) 207 | #end 208 | 209 | 210 | # 211 | def parse(obj, argv, args=[]) 212 | case argv 213 | when String 214 | require 'shellwords' 215 | argv = Shellwords.shellwords(argv) 216 | #else 217 | # argv = argv.dup 218 | end 219 | 220 | #subc = nil 221 | #@args = [] #opts, i = {}, 0 222 | 223 | while argv.size > 0 224 | case arg = argv.shift 225 | when /=/ 226 | parse_equal(obj, arg, argv, args) 227 | when /^--/ 228 | parse_option(obj, arg, argv, args) 229 | when /^-/ 230 | parse_flags(obj, arg, argv, args) 231 | else 232 | #if Executioner === obj 233 | # if cmd_class = obj.class.subcommands[arg] 234 | # cmd = cmd_class.new(obj) 235 | # subc = cmd 236 | # parse(cmd, argv, args) 237 | # else 238 | args << arg 239 | # end 240 | #end 241 | end 242 | end 243 | 244 | #while argv.size > 0 245 | # case opt = argv.shift 246 | # when /=/ 247 | # parse_equal(obj, opt, argv, args) 248 | # when /^--/ 249 | # parse_option(obj, opt, argv, args) 250 | # when /^-/ 251 | # parse_flags(obj, opt, argv, args) 252 | # else 253 | # args << opt 254 | # end 255 | #end 256 | return args 257 | end 258 | 259 | # 260 | def parse_equal(obj, opt, argv, args) 261 | if md = /^[-]*(.*?)=(.*?)$/.match(opt) 262 | x, v = md[1], md[2] 263 | else 264 | raise ArgumentError, "#{x}" 265 | end 266 | if obj.respond_to?("#{x}=") 267 | obj.send("#{x}=", v) 268 | #elsif obj.respond_to?("#{args.join('_')}_#{x}=") 269 | # obj.send("#{args.join('_')}_#{x}=", v) 270 | else 271 | obj.option_missing(x, v) # argv? 272 | end 273 | #if obj.respond_to?("#{x}=") 274 | # # TODO: to_b if 'true' or 'false' ? 275 | # obj.send("#{x}=",v) 276 | #else 277 | # obj.option_missing(x, v) # argv? 278 | #end 279 | end 280 | 281 | # Parse a command-line option. 282 | def parse_option(obj, opt, argv, args) 283 | x = opt.sub(/^\-+/, '') # remove '--' 284 | #if obj.respond_to?("#{args.join('_')}_#{x}=") 285 | # m = obj.method("#{args.join('_')}_#{x}=") 286 | # if obj.respond_to?("#{args.join('_')}_#{x}?") 287 | # m.call(true) 288 | # else 289 | # m.call(argv.shift) 290 | # end 291 | #elsif obj.respond_to?("#{args.join('_')}_#{x}?") 292 | # m = obj.method("#{args.join('_')}_#{x}?") 293 | # a = [] 294 | # m.arity.abs.times{ a << argv.shift } 295 | # m.call(*a) 296 | # m.call 297 | if obj.respond_to?("#{x}=") 298 | m = obj.method("#{x}=") 299 | if obj.respond_to?("#{x}?") 300 | m.call(true) 301 | else 302 | invoke(obj, m, argv) 303 | end 304 | elsif obj.respond_to?("#{x}!") 305 | invoke(obj, "#{x}!", argv) 306 | else 307 | obj.option_missing(x, argv) 308 | end 309 | end 310 | 311 | # TODO: this needs some thought concerning character spliting and arguments. 312 | def parse_flags(obj, opt, argv, args) 313 | x = opt[1..-1] 314 | c = 0 315 | x.split(//).each do |k| 316 | if obj.respond_to?("#{k}=") 317 | m = obj.method("#{k}=") 318 | if obj.respond_to?("#{x}?") 319 | m.call(true) 320 | else 321 | invoke(obj, m, argv) #m.call(argv.shift) 322 | end 323 | elsif obj.respond_to?("#{k}!") 324 | invoke(obj, "#{k}!", argv) 325 | else 326 | long = find_longer_option(obj, k) 327 | if long 328 | if long.end_with?('=') && obj.respond_to?(long.chomp('=')+'?') 329 | invoke(obj, long, [true]) 330 | else 331 | invoke(obj, long, argv) 332 | end 333 | else 334 | obj.option_missing(x, argv) 335 | end 336 | end 337 | end 338 | end 339 | 340 | # 341 | def invoke(obj, meth, argv) 342 | m = Method === meth ? meth : obj.method(meth) 343 | a = [] 344 | m.arity.abs.times{ a << argv.shift } 345 | m.call(*a) 346 | end 347 | 348 | # TODO: Sort alphabetically? 349 | def find_longer_option(obj, char) 350 | meths = obj.methods.map{ |m| m.to_s } 351 | meths = meths.select do |m| 352 | m.start_with?(k) and (m.end_with?('=') or m.end_with?('!')) 353 | end 354 | meths.first 355 | end 356 | 357 | # 358 | def help(description) 359 | @desc = description 360 | end 361 | 362 | # Hash for storing descriptions. 363 | def descriptions 364 | @descriptions ||= {} 365 | end 366 | 367 | # 368 | def method_added(name) 369 | #name = name.to_s.chomp('?').chomp('=') 370 | descriptions[name.to_s] = @desc if @desc 371 | @desc = nil 372 | end 373 | 374 | # 375 | def to_s 376 | Help.new(self).to_s 377 | end 378 | 379 | end 380 | 381 | class NoOptionError < ::NoMethodError # ArgumentError ? 382 | def initialize(name, *arg) 383 | super("unknown option -- #{name}", name, *args) 384 | end 385 | end 386 | 387 | #class NoCommandError < ::NoMethodError 388 | # def initialize(name, *args) 389 | # super("unknown command -- #{name}", name, *args) 390 | # end 391 | # alias_method :to_str, :to_s 392 | #end 393 | 394 | class NoCommandError < ::ArgumentError 395 | def initialize(*args) 396 | super("nothing to do", *args) 397 | end 398 | end 399 | 400 | end 401 | -------------------------------------------------------------------------------- /work/concepts/r2_subcommand/command3.rb: -------------------------------------------------------------------------------- 1 | # == CLI::Command 2 | # 3 | # Here is an example of usage: 4 | # 5 | # # General Options 6 | # 7 | # module GeneralOptions 8 | # attr_accessor :dryrun ; alias_accessor :n, :noharm, :dryrun 9 | # attr_accessor :quiet ; alias_accessor :q, :quiet 10 | # attr_accessor :force 11 | # attr_accessor :trace 12 | # end 13 | # 14 | # # Build Subcommand 15 | # 16 | # class BuildCommand < CLI::Command 17 | # include GeneralOptions 18 | # 19 | # # metadata files 20 | # attr_accessor :file ; alias_accessor :f, :file 21 | # attr_accessor :manifest ; alias_accessor :m, :manifest 22 | # 23 | # def call 24 | # # do stuf here 25 | # end 26 | # end 27 | # 28 | # # Box Master Command 29 | # 30 | # class BoxCommand < CLI::Command 31 | # subcommand :build, BuildCommand 32 | # end 33 | # 34 | # BoxCommand.start 35 | # 36 | # == Authors 37 | # 38 | # * Trans 39 | # 40 | # == Todo 41 | # 42 | # * Move helpers into core. 43 | # * Add global options to master command, or "all are master options" flag (?) 44 | # * Add usage/help/documentation/man features (?) 45 | # 46 | # == Copying 47 | # 48 | # Copyright (c) 2005,2008 Thomas Sawyer 49 | # 50 | # Ruby License 51 | # 52 | # This module is free software. You may use, modify, and/or 53 | # redistribute this software under the same terms as Ruby. 54 | # 55 | # This program is distributed in the hope that it will be 56 | # useful, but WITHOUT ANY WARRANTY; without even the implied 57 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 58 | # PURPOSE. 59 | 60 | warn "command.rb will be deprecated, use clio instead (clio.rubyforge.org)" 61 | 62 | require 'facets/arguments' 63 | require 'facets/consoleutils' 64 | require 'facets/array/not_empty' 65 | 66 | module CLI 67 | 68 | # For CommandOptions, but defined external to it, so 69 | # that it is easy to access from user defined commands. 70 | # (This lookup issue should be fixed in Ruby 1.9+, and then 71 | # the class can be moved back into Command namespace.) 72 | 73 | class NoOptionError < NoMethodError 74 | def initialize(name, *arg) 75 | super("unknown option -- #{name}", name, *args) 76 | end 77 | end 78 | 79 | class NoCommandError < NoMethodError 80 | def initialize(name, *arg) 81 | super("unknown subcommand -- #{name}", name, *args) 82 | end 83 | end 84 | 85 | # Here is an example of usage: 86 | # 87 | # # General Options 88 | # 89 | # module GeneralOptions 90 | # attr_writer :dryrun ; alias_writer :n, :noharm, :dryrun 91 | # attr_writer :quiet ; alias_writer :q, :quiet 92 | # attr_writer :force 93 | # attr_writer :trace 94 | # end 95 | # 96 | # # Build Subcommand 97 | # 98 | # class BuildCommand < Command 99 | # include GeneralOptions 100 | # 101 | # # metadata files 102 | # attr_writer :file ; alias_writer :f, :file 103 | # attr_writer :manifest ; alias_writer :m, :manifest 104 | # 105 | # def call 106 | # # do stuf here 107 | # end 108 | # end 109 | # 110 | # # Box Master Command 111 | # 112 | # class BoxCommand < CLI::Command 113 | # subcommand :build, BuildCommand 114 | # end 115 | # 116 | # BoxCommand.start 117 | 118 | class Command 119 | 120 | # Include this in your dispatch command if you want 121 | # all options to be traeted the same. 122 | 123 | module UniversalOptions 124 | end 125 | 126 | # 127 | 128 | def self.option_arity(arity_hash=nil) 129 | @option_arity ||= {} 130 | if arity_hash 131 | @option_arity.merge!(arity_hash) 132 | end 133 | @option_arity 134 | end 135 | 136 | # 137 | 138 | def self.start(line=nil) 139 | cargs = Arguments.new(line || ARGV, option_arity) 140 | pre = cargs.preoptions 141 | 142 | if instance_method(:call).arity == 0 #is_a?(SingleCommand) 143 | args, opts = *cargs.parameters 144 | new(args, opts).call 145 | else 146 | subc, args, opts = *cargs.subcommand 147 | if self < UniversalOptions 148 | new(pre, opts).call(subc, args, opts) 149 | else 150 | new(pre).call(subc, args, opts) 151 | end 152 | end 153 | end 154 | 155 | # Command Arguments (for single commands). 156 | 157 | attr :arguments 158 | 159 | # Command options. For dispatch commands these are the pre-options. 160 | 161 | attr :options 162 | 163 | # For dispatchers, this is a convenience method for creating subcommands. 164 | 165 | def self.subcommand(name, command_class, options=nil) 166 | options ||= {} 167 | if options[:no_merge] 168 | file, line = __FILE__, __LINE__+1 169 | code = %{ 170 | def #{name}(args, opts) 171 | #{command_class}.new(args, opts).call 172 | end 173 | } 174 | else 175 | file, line = __FILE__, __LINE__+1 176 | code = %{ 177 | def #{name}(args, opts) 178 | opts.merge(options) 179 | #{command_class}.new(args, opts).call 180 | end 181 | } 182 | end 183 | class_eval(code, file, line) 184 | end 185 | 186 | private 187 | 188 | # 189 | 190 | def initialize(*args) 191 | @arguments = [] 192 | @options = {} 193 | 194 | opts, args = *args.partition{ |e| Hash===e } 195 | #TEST("options should all be hashes"){ ! opts.all?{ |e| Hash===e } 196 | initialize_arguments(*args) 197 | initialize_options(*opts) 198 | end 199 | 200 | # 201 | 202 | def initialize_arguments(*arguments) 203 | @arguments.concat(arguments) 204 | end 205 | 206 | # 207 | 208 | def initialize_options(*options) 209 | options = options.inject{ |h,o| h.merge(o) } 210 | begin 211 | opt, val = nil, nil 212 | options.each do |opt, val| 213 | opt = opt.gsub('-','_') 214 | send("#{opt}=", val) 215 | end 216 | rescue NoMethodError 217 | option_missing(opt, val) 218 | end 219 | @options.update(options) 220 | end 221 | 222 | public 223 | 224 | # For a single command (ie. a subcommand) override #call with arity=0. 225 | 226 | def call(cmd=nil, *args) 227 | opts = Hash==args.last ? args.pop : {} 228 | #TEST("options should all be hashes"){ ! opts.all?{ |e| Hash===e } 229 | #cmd = :default if cmd.nil? 230 | if cmd.nil? 231 | default 232 | else 233 | begin 234 | # FIXME: rename call to [] ? 235 | raise NameError if cmd == 'call' 236 | raise NameError unless commands.include?(cmd.to_sym) 237 | subcommand = method(cmd) 238 | parameters = [args, opts] 239 | rescue NameError 240 | subcommand = method(:command_missing) 241 | parameters = [cmd, args, opts] 242 | end 243 | if subcommand.arity < 0 244 | subcommand.call(*parameters[0..subcommand.arity]) 245 | else 246 | subcommand.call(*parameters[0,subcommand.arity]) 247 | end 248 | end 249 | end 250 | 251 | # Display help message. 252 | # The one provided is just a very limited dummy routine. 253 | 254 | # def help 255 | # puts "USAGE #{File.basename($0)} [options]" 256 | # puts "\nOptions:" 257 | # options = self.class.instance_methods(false) 258 | # options = options - Command.instance_methods(true) 259 | # options = options.select{ |m| m.to_s =~ /=$/ } 260 | # options.each do |opt| 261 | # puts " --#{opt.to_s.chomp('=')}" 262 | # end 263 | # end 264 | 265 | private 266 | 267 | # Override default to provide non-subcommand functionality. 268 | 269 | def default; end 270 | 271 | # 272 | def commands 273 | @_commands ||= ( 274 | cmds = self.class.instance_methods(true) - Command.instance_methods(true) 275 | cmds.select{ |c| c !~ /\W/ } 276 | cmds.collect{ |c| c.to_sym } 277 | ) 278 | end 279 | 280 | # 281 | 282 | def command_missing(cmd, args, opt) 283 | raise NoCommandError.new(cmd, args << opt) 284 | end 285 | 286 | # 287 | 288 | def option_missing(opt, arg=nil) 289 | raise NoOptionError.new(opt) 290 | end 291 | 292 | end 293 | 294 | # Temporary backward compatability. 295 | MasterCommand = Command 296 | 297 | end 298 | 299 | 300 | module Console #:nodoc: 301 | # For backward compatibility. 302 | Command = CLI::Command 303 | end 304 | 305 | 306 | # SCRAP CODE FOR REFERENCE TO POSSIBLE ADD FUTURE FEATURES 307 | 308 | =begin 309 | 310 | # We include a module here so you can define your own help 311 | # command and call #super to utilize this one. 312 | 313 | module Help 314 | 315 | def help 316 | opts = help_options 317 | s = "" 318 | s << "#{File.basename($0)}\n\n" 319 | unless opts.empty? 320 | s << "OPTIONS\n" 321 | s << help_options 322 | s << "\n" 323 | end 324 | s << "COMMANDS\n" 325 | s << help_commands 326 | puts s 327 | end 328 | 329 | private 330 | 331 | def help_commands 332 | help = self.class.help 333 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 334 | lines = [] 335 | help.each { |cmd, str| 336 | cmd = cmd.to_s 337 | if cmd !~ /^_/ 338 | lines << " " + cmd + (" " * (bufs - cmd.size)) + str 339 | end 340 | } 341 | lines.join("\n") 342 | end 343 | 344 | def help_options 345 | help = self.class.help 346 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 347 | lines = [] 348 | help.each { |cmd, str| 349 | cmd = cmd.to_s 350 | if cmd =~ /^_/ 351 | lines << " " + cmd.gsub(/_/,'-') + (" " * (bufs - cmd.size)) + str 352 | end 353 | } 354 | lines.join("\n") 355 | end 356 | 357 | module ClassMethods 358 | 359 | def help( str=nil ) 360 | return (@help ||= {}) unless str 361 | @current_help = str 362 | end 363 | 364 | def method_added( meth ) 365 | if @current_help 366 | @help ||= {} 367 | @help[meth] = @current_help 368 | @current_help = nil 369 | end 370 | end 371 | 372 | end 373 | 374 | end 375 | 376 | include Help 377 | extend Help::ClassMethods 378 | 379 | =end 380 | 381 | =begin 382 | 383 | # Provides a very basic usage help string. 384 | # 385 | # TODO Add support for __options. 386 | def usage 387 | str = [] 388 | public_methods(false).sort.each do |meth| 389 | meth = meth.to_s 390 | case meth 391 | when /^_/ 392 | opt = meth.sub(/^_+/, '') 393 | meth = method(meth) 394 | if meth.arity == 0 395 | str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") 396 | elsif meth.arity == 1 397 | str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") 398 | elsif meth.arity > 0 399 | v = []; meth.arity.times{ |i| v << 'value' + (i + 1).to_s } 400 | str << (opt.size > 1 ? "[--#{opt} #{v.join(' ')}]" : "[-#{opt} #{v.join(' ')}]") 401 | else 402 | str << (opt.size > 1 ? "[--#{opt} *values]" : "[-#{opt} *values]") 403 | end 404 | when /=$/ 405 | opt = meth.chomp('=') 406 | str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") 407 | when /!$/ 408 | opt = meth.chomp('!') 409 | str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") 410 | end 411 | end 412 | return str.join(" ") 413 | end 414 | 415 | # 416 | 417 | def self.usage_class(usage) 418 | c = Class.new(self) 419 | argv = Shellwords.shellwords(usage) 420 | argv.each_with_index do |name, i| 421 | if name =~ /^-/ 422 | if argv[i+1] =~ /^[(.*?)]/ 423 | c.class_eval %{ 424 | attr_accessor :#{name} 425 | } 426 | else 427 | c.class_eval %{ 428 | attr_reader :#{name} 429 | def #{name}! ; @#{name} = true ; end 430 | } 431 | end 432 | end 433 | end 434 | return c 435 | end 436 | 437 | end 438 | 439 | =end 440 | -------------------------------------------------------------------------------- /work/concepts/r2_subcommand/command2.rb: -------------------------------------------------------------------------------- 1 | # TITLE: 2 | # 3 | # Command 4 | # 5 | # COPYRIGHT: 6 | # 7 | # Copyright (c) 2005 Thomas Sawyer 8 | # 9 | # LICENSE: 10 | # 11 | # Ruby License 12 | # 13 | # This module is free software. You may use, modify, and/or 14 | # redistribute this software under the same terms as Ruby. 15 | # 16 | # This program is distributed in the hope that it will be 17 | # useful, but WITHOUT ANY WARRANTY; without even the implied 18 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 19 | # PURPOSE. 20 | # 21 | # AUTHORS: 22 | # 23 | # - Trans 24 | # 25 | # TODO: 26 | # 27 | # - Add global options to master command, or "all are master options" flag? 28 | # - Add usage/help/documentation/man features. 29 | 30 | #require 'facets/annotations' # for help ? 31 | #require 'facets/module/attr' 32 | #require 'facets/kernel/constant' 33 | #require 'shellwords' 34 | require 'facets/arguments' 35 | 36 | module Console 37 | 38 | # For CommandOptions, but defined external to it, so 39 | # that it is easy to access from user defined commands. 40 | # (This lookup issue should be fixed in Ruby 1.9+, and then 41 | # the class can be moved back into Command namespace.) 42 | 43 | class NoOptionError < NoMethodError 44 | def initialize(name, *arg) 45 | super("unknown option -- #{name}", name, *args) 46 | end 47 | end 48 | 49 | class NoCommandError < NoMethodError 50 | def initialize(name, *arg) 51 | super("unknown subcommand -- #{name}", name, *args) 52 | end 53 | end 54 | 55 | # Here is an example of usage: 56 | # 57 | # # General Options 58 | # 59 | # module GeneralOptions 60 | # attr_accessor :dryrun ; alias_accessor :n, :noharm, :dryrun 61 | # attr_accessor :quiet ; alias_accessor :q, :quiet 62 | # attr_accessor :force 63 | # attr_accessor :trace 64 | # end 65 | # 66 | # # Build Subcommand 67 | # 68 | # class BuildCommand < Console::Command 69 | # include GeneralOptions 70 | # 71 | # # metadata files 72 | # attr_accessor :file ; alias_accessor :f, :file 73 | # attr_accessor :manifest ; alias_accessor :m, :manifest 74 | # 75 | # def call 76 | # # do stuf here 77 | # end 78 | # end 79 | # 80 | # # Box Master Command 81 | # 82 | # class BoxCommand < Console::MasterCommand 83 | # subcommand :build, BuildCommand 84 | # end 85 | # 86 | # BoxCommand.start 87 | 88 | class MasterCommand 89 | 90 | # 91 | 92 | module UniversalOptions 93 | end 94 | 95 | # 96 | 97 | def self.option_arity(arity_hash=nil) 98 | if arity_hash 99 | (@option_arity ||= {}).merge!(arity_hash) 100 | end 101 | @option_arity 102 | end 103 | 104 | # 105 | 106 | def self.start(line=nil) 107 | cargs = Console::Arguments.new(line || ARGV, option_arity) 108 | pre = cargs.preoptions 109 | cmd, argv = *cargs.subcommand 110 | args, opts = *argv 111 | 112 | if is_a?(UniversalOptions) 113 | new(pre, opts).call(cmd, args, opts) 114 | else 115 | new(pre).call(cmd, args, opts) 116 | end 117 | end 118 | 119 | # 120 | 121 | def self.subcommand(name, command_class, options=nil) 122 | options ||= {} 123 | if options[:no_merge] 124 | file, line = __FILE__, __LINE__+1 125 | code = %{ 126 | def #{name}(args, opts) 127 | #{command_class}.new(args, opts).call 128 | end 129 | } 130 | else 131 | file, line = __FILE__, __LINE__+1 132 | code = %{ 133 | def #{name}(args, opts) 134 | opts.merge(master_options) 135 | #{command_class}.new(args, opts).call 136 | end 137 | } 138 | end 139 | class_eval(code, file, line) 140 | end 141 | 142 | private 143 | 144 | attr :master_options 145 | 146 | # 147 | 148 | def initialize(*options) 149 | @master_options = {} 150 | initialize_options(*options) 151 | end 152 | 153 | # 154 | 155 | def initialize_options(*options) 156 | options = options.inject{ |h,o| h.merge(o) } 157 | begin 158 | opt, val = nil, nil 159 | options.each do |opt, val| 160 | opt = opt.gsub('-','_') 161 | send("#{opt}=", val) 162 | end 163 | rescue NoMethodError 164 | option_missing(opt, val) 165 | end 166 | @master_options.update(options) 167 | end 168 | 169 | public 170 | 171 | # 172 | 173 | def call(cmd, args, opts) 174 | cmd = :default if cmd.nil? 175 | begin 176 | subcommand = method(cmd) 177 | parameters = [args, opts] 178 | rescue NameError 179 | subcommand = method(:subcommand_missing) 180 | parameters = [cmd, args, opts] 181 | end 182 | if subcommand.arity < 0 183 | subcommand.call(*parameters[0..subcommand.arity]) 184 | else 185 | subcommand.call(*parameters[0,subcommand.arity]) 186 | end 187 | end 188 | 189 | # 190 | 191 | def help; end 192 | 193 | def default ; help ; end 194 | 195 | private 196 | 197 | # 198 | 199 | def subcommand_missing(cmd, args, opt) 200 | help 201 | #raise NoCommandError.new(cmd, args << opt) 202 | end 203 | 204 | # 205 | 206 | def option_missing(opt, arg=nil) 207 | raise NoOptionError.new(opt) 208 | end 209 | 210 | end 211 | 212 | # = Command base class 213 | # 214 | # See MasterCommand for example. 215 | 216 | class Command 217 | 218 | def self.option_arity(arity_hash=nil) 219 | if arity_hash 220 | (@option_arity ||= {}).merge!(arity_hash) 221 | end 222 | @option_arity 223 | end 224 | 225 | def self.start(line=nil) 226 | cargs = Console::Argument.new(line || ARGV, option_arity) 227 | pre = cargs.preoptions 228 | args, opts = *cargs.parameters 229 | new(args, opts).call 230 | end 231 | 232 | attr :arguments 233 | attr :options 234 | 235 | # 236 | 237 | def call 238 | puts "Not implemented yet." 239 | end 240 | 241 | private 242 | 243 | # 244 | 245 | def initialize(arguments, options=nil) 246 | initialize_arguments(*arguments) 247 | initialize_options(options) 248 | end 249 | 250 | # 251 | 252 | def initialize_arguments(*arguments) 253 | @arguments = arguments 254 | end 255 | 256 | # 257 | 258 | def initialize_options(options) 259 | options = options || {} 260 | begin 261 | opt, val = nil, nil 262 | options.each do |opt, val| 263 | send("#{opt}=", val) 264 | end 265 | rescue NoMethodError 266 | option_missing(opt, val) 267 | end 268 | @options = options 269 | end 270 | 271 | # 272 | 273 | def option_missing(opt, arg=nil) 274 | raise NoOptionError.new(opt) 275 | end 276 | 277 | end 278 | 279 | end 280 | 281 | 282 | class Array 283 | 284 | # Not empty? 285 | 286 | def not_empty? 287 | !empty? 288 | end 289 | 290 | # Convert an array into command line parameters. 291 | # The array is accepted in the format of Ruby 292 | # method arguments --ie. [arg1, arg2, ..., hash] 293 | 294 | def to_console 295 | flags = (Hash===last ? pop : {}) 296 | flags = flags.to_console 297 | flags + ' ' + join(" ") 298 | end 299 | 300 | end 301 | 302 | 303 | class Hash 304 | 305 | # Convert an array into command line parameters. 306 | # The array is accepted in the format of Ruby 307 | # method arguments --ie. [arg1, arg2, ..., hash] 308 | 309 | def to_console 310 | flags = collect do |f,v| 311 | m = f.to_s.size == 1 ? '-' : '--' 312 | case v 313 | when Array 314 | v.collect{ |e| "#{m}#{f}='#{e}'" }.join(' ') 315 | when true 316 | "#{m}#{f}" 317 | when false, nil 318 | '' 319 | else 320 | "#{m}#{f}='#{v}'" 321 | end 322 | end 323 | flags.join(" ") 324 | end 325 | 326 | # Turn a hash into arguments. 327 | # 328 | # h = { :list => [1,2], :base => "HI" } 329 | # h.argumentize #=> [ [], { :list => [1,2], :base => "HI" } ] 330 | # h.argumentize(:list) #=> [ [1,2], { :base => "HI" } ] 331 | # 332 | def argumentize(args_field=nil) 333 | config = dup 334 | if args_field 335 | args = [config.delete(args_field)].flatten.compact 336 | else 337 | args = [] 338 | end 339 | args << config 340 | return args 341 | end 342 | 343 | end 344 | 345 | 346 | # SCRAP CODE FOR REFERENCE TO POSSIBLE ADD FUTURE FEATURES 347 | 348 | =begin 349 | 350 | # We include a module here so you can define your own help 351 | # command and call #super to utilize this one. 352 | 353 | module Help 354 | 355 | def help 356 | opts = help_options 357 | s = "" 358 | s << "#{File.basename($0)}\n\n" 359 | unless opts.empty? 360 | s << "OPTIONS\n" 361 | s << help_options 362 | s << "\n" 363 | end 364 | s << "COMMANDS\n" 365 | s << help_commands 366 | puts s 367 | end 368 | 369 | private 370 | 371 | def help_commands 372 | help = self.class.help 373 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 374 | lines = [] 375 | help.each { |cmd, str| 376 | cmd = cmd.to_s 377 | if cmd !~ /^_/ 378 | lines << " " + cmd + (" " * (bufs - cmd.size)) + str 379 | end 380 | } 381 | lines.join("\n") 382 | end 383 | 384 | def help_options 385 | help = self.class.help 386 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 387 | lines = [] 388 | help.each { |cmd, str| 389 | cmd = cmd.to_s 390 | if cmd =~ /^_/ 391 | lines << " " + cmd.gsub(/_/,'-') + (" " * (bufs - cmd.size)) + str 392 | end 393 | } 394 | lines.join("\n") 395 | end 396 | 397 | module ClassMethods 398 | 399 | def help( str=nil ) 400 | return (@help ||= {}) unless str 401 | @current_help = str 402 | end 403 | 404 | def method_added( meth ) 405 | if @current_help 406 | @help ||= {} 407 | @help[meth] = @current_help 408 | @current_help = nil 409 | end 410 | end 411 | 412 | end 413 | 414 | end 415 | 416 | include Help 417 | extend Help::ClassMethods 418 | 419 | =end 420 | 421 | =begin 422 | 423 | # Provides a very basic usage help string. 424 | # 425 | # TODO Add support for __options. 426 | def usage 427 | str = [] 428 | public_methods(false).sort.each do |meth| 429 | meth = meth.to_s 430 | case meth 431 | when /^_/ 432 | opt = meth.sub(/^_+/, '') 433 | meth = method(meth) 434 | if meth.arity == 0 435 | str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") 436 | elsif meth.arity == 1 437 | str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") 438 | elsif meth.arity > 0 439 | v = []; meth.arity.times{ |i| v << 'value' + (i + 1).to_s } 440 | str << (opt.size > 1 ? "[--#{opt} #{v.join(' ')}]" : "[-#{opt} #{v.join(' ')}]") 441 | else 442 | str << (opt.size > 1 ? "[--#{opt} *values]" : "[-#{opt} *values]") 443 | end 444 | when /=$/ 445 | opt = meth.chomp('=') 446 | str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") 447 | when /!$/ 448 | opt = meth.chomp('!') 449 | str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") 450 | end 451 | end 452 | return str.join(" ") 453 | end 454 | 455 | # 456 | 457 | def self.usage_class(usage) 458 | c = Class.new(self) 459 | argv = Shellwords.shellwords(usage) 460 | argv.each_with_index do |name, i| 461 | if name =~ /^-/ 462 | if argv[i+1] =~ /^[(.*?)]/ 463 | c.class_eval %{ 464 | attr_accessor :#{name} 465 | } 466 | else 467 | c.class_eval %{ 468 | attr_reader :#{name} 469 | def #{name}! ; @#{name} = true ; end 470 | } 471 | end 472 | end 473 | end 474 | return c 475 | end 476 | 477 | end 478 | 479 | =end 480 | -------------------------------------------------------------------------------- /work/concepts/r2_subcommand/command.rb: -------------------------------------------------------------------------------- 1 | # TITLE: 2 | # 3 | # Command 4 | # 5 | # COPYRIGHT: 6 | # 7 | # Copyright (c) 2005 Thomas Sawyer 8 | # 9 | # LICENSE: 10 | # 11 | # Ruby License 12 | # 13 | # This module is free software. You may use, modify, and/or 14 | # redistribute this software under the same terms as Ruby. 15 | # 16 | # This program is distributed in the hope that it will be 17 | # useful, but WITHOUT ANY WARRANTY; without even the implied 18 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 19 | # PURPOSE. 20 | # 21 | # AUTHORS: 22 | # 23 | # - 7rans 24 | # - Tyler Rick 25 | # 26 | # TODOs: 27 | # 28 | # - Add help/documentation features. 29 | # - Problem with exit -1 when testing. See IMPORTANT!!! remark below. 30 | # 31 | # LOG: 32 | # 33 | # - 2007.10.31 TRANS 34 | # Re-added support for __option notation. 35 | 36 | require 'facets/module/attr' 37 | require 'facets/kernel/constant' 38 | require 'facets/arguments' 39 | require 'shellwords' 40 | #require 'facets/annotations' # for help ? 41 | 42 | module Console 43 | 44 | # For CommandOptions, but defined external to it, so 45 | # that it is easy to access from user defined commands. 46 | # (This lookup issue should be fixed in Ruby 1.9+, and then 47 | # the class can be moved back into Command namespace.) 48 | 49 | class NoOptionError < NoMethodError 50 | def initialize(name, *arg) 51 | super("unknown option -- #{name}", name, *args) 52 | end 53 | end 54 | 55 | class NoCommandError < NoMethodError 56 | def initialize(name, *arg) 57 | super("unknown subcommand -- #{name}", name, *args) 58 | end 59 | end 60 | 61 | # Here is an example of usage: 62 | # 63 | # # General Options 64 | # 65 | # module GeneralOptions 66 | # attr_accessor :dryrun ; alias_accessor :n, :noharm, :dryrun 67 | # attr_accessor :quiet ; alias_accessor :q, :quiet 68 | # attr_accessor :force 69 | # attr_accessor :trace 70 | # end 71 | # 72 | # # Build Subcommand 73 | # 74 | # class BuildCommand < Console::Command 75 | # include GeneralOptions 76 | # 77 | # # metadata files 78 | # attr_accessor :file ; alias_accessor :f, :file 79 | # attr_accessor :manifest ; alias_accessor :m, :manifest 80 | # 81 | # def call 82 | # # do stuf here 83 | # end 84 | # end 85 | # 86 | # # Box Master Command 87 | # 88 | # class BoxCommand < Console::MasterCommand 89 | # subcommand :build, BuildCommand 90 | # subcommand :install, InstallCommand 91 | # subcommand :uninstall, UninstallCommand 92 | # end 93 | 94 | class MasterCommand 95 | 96 | # 97 | 98 | def self.start(line=nil) 99 | cargs = Console::Arguments.new(line || ARGV) 100 | pre = cargs.preoptions 101 | cmd, argv = *cargs.subcommand 102 | args, opts = *argv 103 | new(pre).send(cmd, *(args << opts)) 104 | end 105 | 106 | # 107 | 108 | def self.subcommand(name, command_class, options=nil) 109 | options ||= {} 110 | if options[:no_merge] 111 | file, line = __FILE__, __LINE__+1 112 | code = %{ 113 | def #{name}(*args) 114 | #{command_class}.new(*args).call 115 | end 116 | } 117 | else 118 | file, line = __FILE__, __LINE__+1 119 | code = %{ 120 | def #{name}(*args) 121 | (Hash===args.last ? args.last.merge(master_options) : args << master_options) 122 | #{command_class}.new(*args).call 123 | end 124 | } 125 | end 126 | class_eval(code, file, line) 127 | end 128 | 129 | private 130 | 131 | attr :master_options 132 | 133 | # 134 | 135 | def initialize(master_options=nil) 136 | @master_options = master_options || {} 137 | end 138 | 139 | public 140 | 141 | # 142 | 143 | def send(cmd, *args) 144 | cmd = :default if cmd.nil? 145 | begin 146 | subcommand = method(cmd) 147 | parameters = args 148 | rescue NameError 149 | subcommand = method(:subcommand_missing) 150 | parameters = [cmd, *args] 151 | end 152 | if subcommand.arity < 0 153 | subcommand.call(*parameters[0..subcommand.arity]) 154 | else 155 | subcommand.call(*parameters[0,subcommand.arity]) 156 | end 157 | end 158 | 159 | # 160 | 161 | def help; end 162 | 163 | def default ; help ; end 164 | 165 | # 166 | 167 | def subcommand_missing(cmd, *args) 168 | help 169 | #raise NoCommandError.new(cmd, *args) 170 | end 171 | 172 | end 173 | 174 | 175 | # = Command base class 176 | # 177 | # See MasterCommand for example. 178 | 179 | class Command 180 | 181 | def self.start(line=nil) 182 | cargs = Console::Argument.new(line || ARGV) 183 | pre = cargs.preoptions 184 | args, opts = *cargs.parameters 185 | new(args, opts).call 186 | end 187 | 188 | attr :arguments 189 | attr :options 190 | 191 | # 192 | 193 | def call 194 | puts "Not implemented yet." 195 | end 196 | 197 | private 198 | 199 | # 200 | 201 | def initialize(*arguments) 202 | options = (Hash===arguments.last ? arguments.pop : nil) 203 | initialize_arguments(*arguments) 204 | initialize_options(options) 205 | end 206 | 207 | # 208 | 209 | def initialize_arguments(*arguments) 210 | @arguments = arguments 211 | end 212 | 213 | # 214 | 215 | def initialize_options(options) 216 | options = options || {} 217 | begin 218 | opt, val = nil, nil 219 | options.each do |opt, val| 220 | send("#{opt}=", val) 221 | end 222 | rescue NoMethodError 223 | option_missing(opt, val) 224 | end 225 | @options = options 226 | end 227 | 228 | # 229 | 230 | def option_missing(opt, arg=nil) 231 | raise NoOptionError.new(opt) 232 | end 233 | 234 | end 235 | 236 | end 237 | 238 | 239 | =begin 240 | 241 | 242 | class Command 243 | 244 | # Command Syntax DSL 245 | 246 | class << self 247 | 248 | # Starts the command execution. 249 | def execute(*args) 250 | cmd = new() 251 | #cmd.instance_variable_set("@global_options",global_options) 252 | cmd.execute(*args) 253 | end 254 | alias_method :start, :execute 255 | 256 | # Change the option mode. 257 | def global_option(*names) 258 | names.each{ |name| global_options << name.to_sym } 259 | end 260 | alias_method :global_options, :global_option 261 | 262 | # Define a set of options. This can be a Command::Options subclass, 263 | # or a block which will be used to create an Command::Options subclass. 264 | 265 | def options(name, klass=nil, &block) 266 | raise ArgumentError if klass && block 267 | if block 268 | command_options[name.to_sym] = Class.new(Options, &block) 269 | else 270 | command_options[name.to_sym] = klass 271 | end 272 | end 273 | alias_method :opts, :options 274 | alias_method :opt, :options 275 | 276 | # 277 | 278 | def command_options 279 | @_command_options ||= {} 280 | end 281 | 282 | # 283 | 284 | def global_options 285 | @_global_options ||= [] 286 | end 287 | end 288 | 289 | #def initialize #(global_options=nil) 290 | # #@global_options = global_options || [] 291 | #end 292 | 293 | # 294 | 295 | def execute(line=ARGV) 296 | argv = line 297 | 298 | g_opts = Command::Options.new(self) 299 | g_keys = self.class.global_options 300 | 301 | # Deal with global options. 302 | if g_keys && ! g_keys.empty? 303 | argv = g_opts.parse(argv, :only => g_keys) 304 | end 305 | 306 | # Sole main command or has subcommands? 307 | if respond_to?(:main) 308 | argv = g_opts.parse(argv, :pass => true) 309 | cmd = :main 310 | else 311 | argv = g_opts.parse(argv, :stop => true) 312 | cmd = argv.find{ |s| s !~ /^-/ } 313 | argv.delete_at(argv.index(cmd)) if cmd 314 | cmd = :default unless cmd 315 | cmd = cmd.to_sym 316 | end 317 | 318 | keys = self.class.command_options 319 | 320 | if keys.key?(cmd) 321 | opts = keys[cmd].new 322 | argv = opts.parse(argv) 323 | end 324 | 325 | argv = g_opts.parse_missing(argv) 326 | 327 | call(cmd, argv, opts) 328 | end 329 | 330 | # 331 | 332 | def call(subcmd, argv, opts) 333 | @options = opts # should we use this it fill in instance vars? 334 | 335 | # This is a little tricky. The method has to be defined by a subclass. 336 | if self.respond_to?(subcmd) and not Console::Command.public_instance_methods.include?(subcmd.to_s) 337 | puts "# call: #{subcmd}(*#{argv.inspect})" if $debug 338 | __send__(subcmd, *argv) 339 | else 340 | begin 341 | puts "# call: method_missing(#{subcmd.inspect}, #{argv.inspect})" if $debug 342 | method_missing(subcmd.to_sym, *argv) 343 | rescue NoMethodError => e 344 | #if self.private_methods.include?( "no_command_error" ) 345 | # no_command_error( *args ) 346 | #else 347 | $stderr << "Unrecognized subcommand -- #{subcmd}\n" 348 | exit -1 349 | #end 350 | end 351 | end 352 | end 353 | 354 | # 355 | 356 | def call(argv, opts) 357 | begin 358 | opts.each do |opt, val| 359 | send("#{opt}=", val) 360 | end 361 | rescue NoMethodError => e 362 | option_missing(opt, val) 363 | end 364 | end 365 | 366 | #def global_options 367 | # self.class.global_options 368 | #end 369 | 370 | def option_missing(opt, arg=nil) 371 | raise InvalidOptionError.new(opt) 372 | end 373 | 374 | end 375 | 376 | 377 | # We include a module here so you can define your own help 378 | # command and call #super to utilize this one. 379 | 380 | module Help 381 | 382 | def help 383 | opts = help_options 384 | s = "" 385 | s << "#{File.basename($0)}\n\n" 386 | unless opts.empty? 387 | s << "OPTIONS\n" 388 | s << help_options 389 | s << "\n" 390 | end 391 | s << "COMMANDS\n" 392 | s << help_commands 393 | puts s 394 | end 395 | 396 | private 397 | 398 | def help_commands 399 | help = self.class.help 400 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 401 | lines = [] 402 | help.each { |cmd, str| 403 | cmd = cmd.to_s 404 | if cmd !~ /^_/ 405 | lines << " " + cmd + (" " * (bufs - cmd.size)) + str 406 | end 407 | } 408 | lines.join("\n") 409 | end 410 | 411 | def help_options 412 | help = self.class.help 413 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 414 | lines = [] 415 | help.each { |cmd, str| 416 | cmd = cmd.to_s 417 | if cmd =~ /^_/ 418 | lines << " " + cmd.gsub(/_/,'-') + (" " * (bufs - cmd.size)) + str 419 | end 420 | } 421 | lines.join("\n") 422 | end 423 | 424 | module ClassMethods 425 | 426 | def help( str=nil ) 427 | return (@help ||= {}) unless str 428 | @current_help = str 429 | end 430 | 431 | def method_added( meth ) 432 | if @current_help 433 | @help ||= {} 434 | @help[meth] = @current_help 435 | @current_help = nil 436 | end 437 | end 438 | 439 | end 440 | 441 | end 442 | 443 | include Help 444 | extend Help::ClassMethods 445 | 446 | 447 | 448 | # = Command::Options 449 | # 450 | # CommandOptions provides the basis for Command to Object Mapping (COM). 451 | # It is an commandline options parser that uses method definitions 452 | # as means of interprting command arguments. 453 | # 454 | # == Synopsis 455 | # 456 | # Let's make an executable called 'mycmd'. 457 | # 458 | # #!/usr/bin/env ruby 459 | # 460 | # require 'facets/command_options' 461 | # 462 | # class MyOptions < CommandOptions 463 | # attr_accessor :file 464 | # 465 | # def v! 466 | # @verbose = true 467 | # end 468 | # end 469 | # 470 | # opts = MyOptions.parse("-v --file hello.rb") 471 | # 472 | # opts.verbose #=> true 473 | # opts.file #=> "hello.rb" 474 | # 475 | #-- 476 | # == Global Options 477 | # 478 | # You can define global options which are options that will be 479 | # processed no matter where they occur in the command line. In the above 480 | # examples only the options occuring before the subcommand are processed 481 | # globally. Anything occuring after the subcommand belonds strictly to 482 | # the subcommand. For instance, if we had added the following to the above 483 | # example: 484 | # 485 | # global_option :_v 486 | # 487 | # Then -v could appear anywhere in the command line, even on the end, 488 | # and still work as expected. 489 | # 490 | # % mycmd jump -h 3 -v 491 | #++ 492 | # 493 | # == Missing Options 494 | # 495 | # You can use #option_missing to catch any options that are not explicility 496 | # defined. 497 | # 498 | # The method signature should look like: 499 | # 500 | # option_missing(option_name, args) 501 | # 502 | # Example: 503 | # def option_missing(option_name, args) 504 | # p args if $debug 505 | # case option_name 506 | # when 'p' 507 | # @a = args[0].to_i 508 | # @b = args[1].to_i 509 | # 2 510 | # else 511 | # raise InvalidOptionError(option_name, args) 512 | # end 513 | # end 514 | # 515 | # Its return value should be the effective "arity" of that options -- that is, 516 | # how many arguments it consumed ("-p a b", for example, would consume 2 args: 517 | # "a" and "b"). An arity of 1 is assumed if nil or false is returned. 518 | 519 | class Command::Options 520 | 521 | def self.parse(*line_and_options) 522 | o = new 523 | o.parse(*line_and_options) 524 | o 525 | end 526 | 527 | def initialize(delegate=nil) 528 | @__self__ = delegate || self 529 | end 530 | 531 | # Parse line for options in the context self. 532 | # 533 | # Options: 534 | # 535 | # :pass => true || false 536 | # 537 | # Setting this to true prevents the parse_missing routine from running. 538 | # 539 | # :only => [ global options, ... ] 540 | # 541 | # When processing global options, we only want to parse selected options. 542 | # This also set +pass+ to true. 543 | # 544 | # :stop => true || false 545 | # 546 | # If we are parsing options for the *main* command and we are allowing 547 | # subcommands, then we want to stop as soon as we get to the first non-option, 548 | # because that non-option will be the name of our subcommand and all options that 549 | # follow should be parsed later when we handle the subcommand. 550 | # This also set +pass+ to true. 551 | 552 | def parse(*line_and_options) 553 | __self__ = @__self__ 554 | 555 | if Hash === line_and_options.last 556 | options = line_and_options.pop 557 | line = line_and_options.first 558 | else 559 | options = {} 560 | line = line_and_options.first 561 | end 562 | 563 | case line 564 | when String 565 | argv = Shellwords.shellwords(line) 566 | when Array 567 | argv = line.dup 568 | else 569 | argv = ARGV.dup 570 | end 571 | 572 | only = options[:only] # only parse these options 573 | stop = options[:stop] # stop at first non-option 574 | pass = options[:pass] || only || stop # don't run options_missing 575 | 576 | if $debug 577 | puts(only ? "\nGlobal parsing..." : "\nParsing...") 578 | end 579 | 580 | puts "# line: #{argv.inspect}" if $debug 581 | 582 | # Split single letter option groupings into separate options. 583 | # ie. -xyz => -x -y -z 584 | argv = argv.collect { |arg| 585 | if md = /^-(\w{2,})/.match( arg ) 586 | md[1].split(//).collect { |c| "-#{c}" } 587 | else 588 | arg 589 | end 590 | }.flatten 591 | 592 | index = 0 593 | 594 | until index >= argv.size 595 | arg = argv.at(index) 596 | break if arg == '--' # POSIX compliance 597 | if arg[0,1] == '-' 598 | puts "# option: #{arg}" if $debug 599 | cnt = (arg[0,2] == '--' ? 2 : 1) 600 | #opt = Option.new(arg) 601 | #name = opt.methodize 602 | name = arg.sub(/^-{1,2}/,'') 603 | skip = only && only.include?(name) 604 | unam = ('__'*cnt)+name 605 | if __self__.respond_to?(unam) 606 | puts "# method: #{uname}" if $debug 607 | meth = method(unam) 608 | arity = meth.arity 609 | if arity < 0 610 | meth.call(*argv.slice(index+1..-1)) unless skip 611 | arity[index..-1] = nil # Get rid of the *name* and values 612 | elsif arity == 0 613 | meth.call unless skip 614 | argv.delete_at(index) # Get rid of the *name* of the option 615 | else 616 | meth.call(*argv.slice(index+1, arity)) unless skip 617 | #argv.delete_at(index) # Get rid of the *name* of the option 618 | #arity.times{ argv.delete_at(index) } # Get rid of the *value* of the option 619 | arity[index,arity] = nil 620 | end 621 | elsif __self__.respond_to?(name+'=') 622 | puts "# method: #{name}=" if $debug 623 | __self__.send(name+'=', *argv.slice(index+1, 1)) unless skip 624 | argv.delete_at(index) # Get rid of the *name* of the option 625 | argv.delete_at(index) # Get rid of the *value* of the option 626 | elsif __self__.respond_to?(name+'!') 627 | puts "# method: #{name}!" if $debug 628 | __self__.send(name+'!') unless skip 629 | argv.delete_at(index) # Get rid of the *name* of the option 630 | else 631 | index += 1 632 | end 633 | else 634 | index += 1 635 | break if stop 636 | end 637 | end 638 | # parse missing ? 639 | argv = parse_missing(argv) unless pass 640 | # return the remaining argv 641 | puts "# return: #{argv.inspect}" if $debug 642 | return argv 643 | end 644 | 645 | # 646 | 647 | def parse_missing(argv) 648 | argv.each_with_index do |a,i| 649 | if a =~ /^-/ 650 | #raise InvalidOptionError.new(a) unless @__self__.respond_to?(:option_missing) 651 | kept = @__self__.option_missing(a, *argv[i+1,1]) 652 | argv.delete_at(i) if kept # delete if value kept 653 | argv.delete_at(i) # delete option 654 | end 655 | end 656 | return argv 657 | end 658 | 659 | # 660 | 661 | def option_missing(opt, arg=nil) 662 | raise InvalidOptionError.new(opt) 663 | # #$stderr << "Unknown option '#{arg}'.\n" 664 | # #exit -1 665 | end 666 | 667 | # 668 | 669 | def to_h 670 | opts = @__self__.public_methods(true).select{ |m| m =~ /^[A-Za-z0-9]+[=!]$/ || m =~ /^[_][A-Za-z0-9]+$/ } 671 | #opts.reject!{ |k| k =~ /_$/ } 672 | opts.collect!{ |m| m.chomp('=').chomp('!') } 673 | opts.inject({}) do |h, m| 674 | k = m.sub(/^_+/, '') 675 | v = @__self__.send(m) 676 | h[k] = v if v 677 | h 678 | end 679 | 680 | #@__self__.instance_variables.inject({}) do |h, v| 681 | # next h if v == "@__self__" 682 | # h[v[1..-1]] = @__self__.instance_variable_get(v); h 683 | #end 684 | end 685 | 686 | # Provides a very basic usage help string. 687 | # 688 | # TODO Add support for __options. 689 | def usage 690 | str = [] 691 | public_methods(false).sort.each do |meth| 692 | meth = meth.to_s 693 | case meth 694 | when /^_/ 695 | opt = meth.sub(/^_+/, '') 696 | meth = method(meth) 697 | if meth.arity == 0 698 | str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") 699 | elsif meth.arity == 1 700 | str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") 701 | elsif meth.arity > 0 702 | v = []; meth.arity.times{ |i| v << 'value' + (i + 1).to_s } 703 | str << (opt.size > 1 ? "[--#{opt} #{v.join(' ')}]" : "[-#{opt} #{v.join(' ')}]") 704 | else 705 | str << (opt.size > 1 ? "[--#{opt} *values]" : "[-#{opt} *values]") 706 | end 707 | when /=$/ 708 | opt = meth.chomp('=') 709 | str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") 710 | when /!$/ 711 | opt = meth.chomp('!') 712 | str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") 713 | end 714 | end 715 | return str.join(" ") 716 | end 717 | 718 | # 719 | 720 | def self.usage_class(usage) 721 | c = Class.new(self) 722 | argv = Shellwords.shellwords(usage) 723 | argv.each_with_index do |name, i| 724 | if name =~ /^-/ 725 | if argv[i+1] =~ /^[(.*?)]/ 726 | c.class_eval %{ 727 | attr_accessor :#{name} 728 | } 729 | else 730 | c.class_eval %{ 731 | attr_reader :#{name} 732 | def #{name}! ; @#{name} = true ; end 733 | } 734 | end 735 | end 736 | end 737 | return c 738 | end 739 | 740 | end 741 | 742 | =end 743 | -------------------------------------------------------------------------------- /work/concepts/r1_option_mixins/command.rb: -------------------------------------------------------------------------------- 1 | # = Command 2 | # 3 | # == Copyright (c) 2005 Thomas Sawyer 4 | # 5 | # Ruby License 6 | # 7 | # This module is free software. You may use, modify, and/or 8 | # redistribute this software under the same terms as Ruby. 9 | # 10 | # This program is distributed in the hope that it will be 11 | # useful, but WITHOUT ANY WARRANTY; without even the implied 12 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 13 | # PURPOSE. 14 | # 15 | # == Author(s) 16 | # 17 | # CREDIT Thomas Sawyer 18 | # CREDIT Tyler Rick 19 | # 20 | # == Developer Notes 21 | # 22 | # TODO Add help/documentation features. 23 | # 24 | # TODO Problem wiht exit -1 when testing. See IMPORTANT!!! remark below. 25 | 26 | require 'shellwords' 27 | 28 | # = Console Namespace 29 | 30 | module Console; end 31 | 32 | # = Command 33 | # 34 | # Command provides a clean and easy way to create a command 35 | # line interface for your program. The unique technique 36 | # utlizes a Commandline to Object Mapping (COM) to make 37 | # it quick and easy. 38 | # 39 | # == Synopsis 40 | # 41 | # Let's make an executable called 'mycmd'. 42 | # 43 | # #!/usr/bin/env ruby 44 | # 45 | # require 'facets/console/command' 46 | # 47 | # class MyCmd < Console::Command 48 | # 49 | # def _v 50 | # $VERBOSE = true 51 | # end 52 | # 53 | # def jump 54 | # if $VERBOSE 55 | # puts "JUMP! JUMP! JUMP!" 56 | # else 57 | # puts "Jump" 58 | # end 59 | # end 60 | # 61 | # end 62 | # 63 | # MyCmd.execute 64 | # 65 | # Then on the command line: 66 | # 67 | # % mycmd jump 68 | # Jump 69 | # 70 | # % mycmd -v jump 71 | # JUMP! JUMP! JUMP! 72 | # 73 | # == Subcommands 74 | # 75 | # Commands can take subcommand and suboptions. To do this 76 | # simply add a module to your class with the same name 77 | # as the subcommand, in which the suboption methods are defined. 78 | # 79 | # MyCmd << Console::Command 80 | # 81 | # def initialize 82 | # @height = 1 83 | # end 84 | # 85 | # def _v 86 | # $VERBOSE = true 87 | # end 88 | # 89 | # def jump 90 | # if $VERBOSE 91 | # puts "JUMP!" * @height 92 | # else 93 | # puts "Jump" * @height 94 | # end 95 | # end 96 | # 97 | # module Jump 98 | # def __height(h) 99 | # @height = h.to_i 100 | # end 101 | # end 102 | # 103 | # end 104 | # 105 | # MyCmd.start 106 | # 107 | # Then on the command line: 108 | # 109 | # % mycmd jump -h 2 110 | # Jump Jump 111 | # 112 | # % mycmd -v jump -h 3 113 | # JUMP! JUMP! JUMP! 114 | # 115 | # Another thing to notice about this example is that #start is an alias 116 | # for #execute. 117 | # 118 | # == Missing Subcommands 119 | # 120 | # You can use #method_missing to catch missing subcommand calls. 121 | # 122 | # == Main and Default 123 | # 124 | # If your command does not take subcommands then simply define 125 | # a #main method to dispatch action. All options will be treated globablly 126 | # in this case and any remaining comman-line arguments will be passed 127 | # to #main. 128 | # 129 | # If on the other hand your command does take subcommands but none is given, 130 | # the #default method will be called, if defined. If not defined 131 | # an error will be raised (but only reported if $DEBUG is true). 132 | # 133 | # == Global Options 134 | # 135 | # You can define global options which are options that will be 136 | # processed no matter where they occur in the command line. In the above 137 | # examples only the options occuring before the subcommand are processed 138 | # globally. Anything occuring after the subcommand belonds strictly to 139 | # the subcommand. For instance, if we had added the following to the above 140 | # example: 141 | # 142 | # global_option :_v 143 | # 144 | # Then -v could appear anywhere in the command line, even on the end, 145 | # and still work as expected. 146 | # 147 | # % mycmd jump -h 3 -v 148 | # 149 | # == Missing Options 150 | # 151 | # You can use #option_missing to catch any options that are not explicility 152 | # defined. 153 | # 154 | # The method signature should look like: 155 | # 156 | # option_missing(option_name, args) 157 | # 158 | # Example: 159 | # def option_missing(option_name, args) 160 | # p args if $debug 161 | # case option_name 162 | # when 'p' 163 | # @a = args[0].to_i 164 | # @b = args[1].to_i 165 | # 2 166 | # else 167 | # raise InvalidOptionError(option_name, args) 168 | # end 169 | # end 170 | # 171 | # Its return value should be the effective "arity" of that options -- that is, 172 | # how many arguments it consumed ("-p a b", for example, would consume 2 args: 173 | # "a" and "b"). An arity of 1 is assumed if nil or false is returned. 174 | # 175 | # Be aware that when using subcommand modules, the same option_missing 176 | # method will catch missing options for global options and subcommand 177 | # options too unless an option_missing method is also defined in the 178 | # subcommand module. 179 | # 180 | #-- 181 | # 182 | # == Help Documentation 183 | # 184 | # You can also add help information quite easily. If the following code 185 | # is saved as 'foo' for instance. 186 | # 187 | # MyCmd << Console::Command 188 | # 189 | # help "Dispays the word JUMP!" 190 | # 191 | # def jump 192 | # if $VERBOSE 193 | # puts "JUMP! JUMP! JUMP!" 194 | # else 195 | # puts "Jump" 196 | # end 197 | # end 198 | # 199 | # end 200 | # 201 | # MyCmd.execute 202 | # 203 | # then by running 'foo help' on the command line, standard help information 204 | # will be displayed. 205 | # 206 | # foo 207 | # 208 | # jump Displays the word JUMP! 209 | # 210 | #++ 211 | 212 | class Console::Command 213 | 214 | # Representation of a single commandline option. 215 | 216 | class Option < String 217 | 218 | def initialize(option) 219 | @flag = option 220 | @long = (/^--/ =~ option) 221 | super(option.sub(/^-{1,2}/,'')) 222 | end 223 | 224 | def long? 225 | @long 226 | end 227 | 228 | def short? 229 | !@long 230 | end 231 | 232 | #def demethodize 233 | # sub('__','--').sub('_','-') 234 | #end 235 | 236 | def methodize 237 | @flag.gsub('-','_') 238 | end 239 | 240 | end 241 | 242 | # Command Syntax DSL 243 | # 244 | module Syntax 245 | 246 | # Starts the command execution. 247 | def execute( *args ) 248 | cmd = new() 249 | #cmd.instance_variable_set("@global_options",global_options) 250 | cmd.execute( *args ) 251 | end 252 | alias_method :start, :execute 253 | 254 | # Change the option mode. 255 | def global_option( *names ) 256 | names.each{ |name| global_options << name.to_sym } 257 | end 258 | 259 | # TODO collect ancestors global_options 260 | def global_options 261 | @global_options ||= [] 262 | end 263 | 264 | # 265 | def option(name, &block) 266 | name = name.to_s 267 | if name.size > 1 268 | methname = "__#{name}" 269 | else 270 | methname = "_#{name}" 271 | end 272 | define_method(methname, &block) 273 | end 274 | 275 | # 276 | def subcommand(name, subclass=nil, &block) 277 | if block_given? 278 | base = self 279 | define_method(name) do |*args| 280 | Class.new(base, &block).new(self).execute(args) 281 | end 282 | else 283 | raise "not a command" unless subclass < Command 284 | define_method(name) do |*args| 285 | subclass.new(self).execute(args) 286 | end 287 | end 288 | end 289 | 290 | end 291 | 292 | extend Syntax 293 | 294 | attr :parent 295 | 296 | #def initialize #(global_options=nil) 297 | # #@global_options = global_options || [] 298 | #end 299 | 300 | def initialize(parent=nil) 301 | @parent = parent 302 | # TODO is iv transfer really a good idea? 303 | if parent 304 | parent.instance_variables.each do |iv| 305 | next if iv == "@parent" 306 | instance_variable_set(iv, parent.instance_variable_get(iv)) 307 | end 308 | end 309 | end 310 | 311 | # Execute the command. 312 | 313 | def execute(line=nil) 314 | case line 315 | when String 316 | arguments = Shellwords.shellwords(line) 317 | when Array 318 | arguments = line 319 | else 320 | arguments = ARGV 321 | end 322 | 323 | # duplicate arguments to work on them in-place. 324 | 325 | argv = arguments.dup 326 | 327 | # Split single letter option groupings into separate options. 328 | # ie. -xyz => -x -y -z 329 | 330 | argv = argv.collect { |arg| 331 | if md = /^-(\w{2,})/.match( arg ) 332 | md[1].split(//).collect { |c| "-#{c}" } 333 | else 334 | arg 335 | end 336 | }.flatten 337 | 338 | # process global options 339 | global_options.each do |name| 340 | o = name.to_s.sub('__','--').sub('_','-') 341 | m = method(name) 342 | c = m.arity 343 | while i = argv.index(o) 344 | args = argv.slice!(i,c+1) 345 | args.shift 346 | m.call(*args) 347 | end 348 | end 349 | 350 | # Does this command take subcommands? 351 | subcommand = !respond_to?(:main) 352 | 353 | # process primary options 354 | argv = execute_options( argv, subcommand ) 355 | 356 | # If this command doesn't take subcommands, then 357 | # the remaining arguments are arguments for main(). 358 | return send(:main, *argv) unless subcommand 359 | 360 | # What to do if there is nothing else? 361 | if argv.empty? 362 | if respond_to?(:default) 363 | return __send__(:default) 364 | else 365 | $stderr << "Nothing to do." 366 | return 367 | end 368 | end 369 | 370 | # Remaining arguments are subcommand and suboptions. 371 | 372 | subcmd = argv.shift.gsub('-','_') 373 | #puts "subcmd = #{subcmd}" 374 | 375 | # # Extend subcommand option module 376 | # subconst = subcmd.gsub(/\W/,'_').capitalize 377 | # #puts self.class.name 378 | # if self.class.const_defined?(subconst) 379 | # puts "Extending self (#{self.class}) with subcommand module #{subconst}" if $debug 380 | # submod = self.class.const_get(subconst) 381 | # self.extend submod 382 | # end 383 | 384 | # process subcommand options 385 | #puts "Treating the rest of the args as subcommand options:" 386 | #argv = execute_options( argv ) 387 | 388 | # This is a little tricky. The method has to be defined by a subclass. 389 | if self.respond_to?(subcmd) and not Console::Command.public_instance_methods.include?(subcmd.to_s) 390 | puts "Calling #{subcmd}(#{argv.inspect})" if $debug 391 | __send__(subcmd, *argv) 392 | else 393 | begin 394 | puts "Calling method_missing with #{subcmd}, #{argv.inspect}" if $debug 395 | method_missing(subcmd.to_sym, *argv) 396 | rescue NoMethodError => e 397 | #if self.private_methods.include?( "no_command_error" ) 398 | # no_command_error( *args ) 399 | #else 400 | $stderr << "Unrecognized subcommand -- #{subcmd}\n" 401 | exit -1 402 | #end 403 | end 404 | end 405 | 406 | # rescue => err 407 | # if $DEBUG 408 | # raise err 409 | # else 410 | # msg = err.message.chomp('.') + '.' 411 | # msg[0,1] = msg[0,1].capitalize 412 | # msg << " (#{err.class})" if $VERBOSE 413 | # $stderr << msg 414 | # end 415 | end 416 | 417 | private 418 | 419 | # Return the list of global options. 420 | 421 | def global_options 422 | self.class.global_options 423 | end 424 | 425 | # 426 | 427 | def execute_options( argv, break_on_subcommand=false ) 428 | puts "in execute_options:" if $debug 429 | argv = argv.dup 430 | args_to_return = [] 431 | until argv.empty? 432 | arg = argv.first 433 | if arg[0,1] == '-' 434 | puts "'#{arg}' -- is an option" if $debug 435 | opt = Option.new(arg) 436 | name = opt.methodize 437 | if respond_to?(name) 438 | m = method(name) 439 | puts "Method named #{name} exists and has an arity of #{m.arity}" if $debug 440 | if m.arity == -1 441 | # Implemented the same as for option_missing, except that we don't pass the *name* of the option 442 | arity = m.call(*argv[1..-1]) || 1 443 | puts "#{name} returned an arity of #{arity}" if $debug 444 | unless arity.is_a?(Fixnum) 445 | raise "Expected #{name} to return a valid arity, but it didn't" 446 | end 447 | #puts "argv before: #{argv.inspect}" 448 | argv.shift # Get rid of the *name* of the option 449 | argv.slice!(0, arity) # Then discard as many arguments as that option claimed it used up 450 | #puts "argv after: #{argv.inspect}" 451 | else 452 | # The +1 is so that we also remove the option name from argv 453 | args_for_current_option = argv.slice!(0, m.arity+1) 454 | # Remove the option name from args_for_current_option as well 455 | args_for_current_option.shift 456 | m.call(*args_for_current_option) 457 | end 458 | elsif respond_to?(:option_missing) 459 | puts " option_missing(#{argv.inspect})" if $debug 460 | #arity = option_missing(arg.gsub(/^[-]+/,''), argv[1..-1]) || 1 461 | arity = option_missing(opt, argv[1..-1]) || 1 462 | unless arity.is_a?(Fixnum) 463 | raise "Expected #{name} to return a valid arity, but it didn't" 464 | end 465 | argv.slice!(0, arity) 466 | argv.shift # Get rid of the *name* of the option 467 | else 468 | # IMPORTANT!!! WHEN HAND TESTING UNREMARK THE NEXT LINE. HOW TO FIX? 469 | #raise InvalidOptionError.new(arg) 470 | $stderr << "Unknown option '#{arg}'.\n" 471 | exit -1 472 | end 473 | else 474 | puts "'#{arg}' -- not an option. Adding to args_to_return..." if $debug 475 | if break_on_subcommand 476 | # If we are parsing options for the *main* command and we are allowing 477 | # subcommands, then we want to stop as soon as we get to the first non-option, 478 | # because that non-option will be the name of our subcommand and all options that 479 | # follow should be parsed later when we handle the subcommand. 480 | args_to_return = argv 481 | break 482 | else 483 | args_to_return << argv.shift 484 | end 485 | end 486 | end 487 | puts "Returning #{args_to_return.inspect}" if $debug 488 | return args_to_return 489 | end 490 | 491 | public 492 | 493 | =begin 494 | # We include a module here so you can define your own help 495 | # command and call #super to utilize this one. 496 | 497 | module Help 498 | 499 | def help 500 | opts = help_options 501 | s = "" 502 | s << "#{File.basename($0)}\n\n" 503 | unless opts.empty? 504 | s << "OPTIONS\n" 505 | s << help_options 506 | s << "\n" 507 | end 508 | s << "COMMANDS\n" 509 | s << help_commands 510 | puts s 511 | end 512 | 513 | private 514 | 515 | def help_commands 516 | help = self.class.help 517 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 518 | lines = [] 519 | help.each { |cmd, str| 520 | cmd = cmd.to_s 521 | if cmd !~ /^_/ 522 | lines << " " + cmd + (" " * (bufs - cmd.size)) + str 523 | end 524 | } 525 | lines.join("\n") 526 | end 527 | 528 | def help_options 529 | help = self.class.help 530 | bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 531 | lines = [] 532 | help.each { |cmd, str| 533 | cmd = cmd.to_s 534 | if cmd =~ /^_/ 535 | lines << " " + cmd.gsub(/_/,'-') + (" " * (bufs - cmd.size)) + str 536 | end 537 | } 538 | lines.join("\n") 539 | end 540 | 541 | module ClassMethods 542 | 543 | def help( str=nil ) 544 | return (@help ||= {}) unless str 545 | @current_help = str 546 | end 547 | 548 | def method_added( meth ) 549 | if @current_help 550 | @help ||= {} 551 | @help[meth] = @current_help 552 | @current_help = nil 553 | end 554 | end 555 | 556 | end 557 | 558 | end 559 | 560 | include Help 561 | extend Help::ClassMethods 562 | =end 563 | 564 | end 565 | 566 | # For Command, but defined external to it, so 567 | # that it is easy to access from user defined commands. 568 | # (This lookup issue should be fixed in Ruby 1.9+, and then 569 | # the class can be moved back into Command namespace.) 570 | 571 | class InvalidOptionError < StandardError 572 | def initialize(option_name) 573 | @option_name = option_name 574 | end 575 | def message 576 | "Unknown option '#{@option_name}'." 577 | end 578 | end 579 | 580 | 581 | 582 | # _____ _ 583 | # |_ _|__ ___| |_ 584 | # | |/ _ \/ __| __| 585 | # | | __/\__ \ |_ 586 | # |_|\___||___/\__| 587 | # 588 | 589 | =begin test 590 | 591 | require 'test/unit' 592 | require 'stringio' 593 | 594 | include Console 595 | 596 | class TestCommand < Test::Unit::TestCase 597 | Output = [] 598 | 599 | def setup 600 | Output.clear 601 | $stderr = StringIO.new 602 | end 603 | 604 | class TestCommand < Command 605 | end 606 | 607 | # Test basic command. 608 | 609 | class SimpleCommand < TestCommand 610 | def __here ; @here = true ; end 611 | 612 | def main(*args) 613 | Output.concat([@here] | args) 614 | end 615 | end 616 | 617 | def test_SimpleCommand 618 | cmd = SimpleCommand.new 619 | cmd.execute( '--here file1 file2' ) 620 | assert_equal( [true, 'file1', 'file2'], Output ) 621 | end 622 | 623 | # Test Subcommand. 624 | 625 | class FooSubcommand < TestCommand 626 | def main 627 | Output << "here" 628 | end 629 | end 630 | 631 | class CommandUsingSubcommand < TestCommand 632 | subcommand :foo, FooSubcommand 633 | end 634 | 635 | def test_CommandUsingSubcommand 636 | cmd = CommandUsingSubcommand.new 637 | cmd.execute('foo') 638 | assert_equal(["here"], Output) 639 | end 640 | 641 | # 642 | 643 | class CommandWithMethodMissingSubcommand < TestCommand 644 | def __here ; @here = true ; end 645 | 646 | def method_missing(subcommand, *args) 647 | Output.concat([@here, subcommand] | args) 648 | end 649 | end 650 | 651 | def test_CommandWithMethodMissingSubcommand 652 | cmd = CommandWithMethodMissingSubcommand.new 653 | cmd.execute( '--here go file1' ) 654 | assert_equal( [true, 'go', 'file1'], Output ) 655 | end 656 | 657 | # 658 | 659 | class CommandWithSimpleSubcommand < TestCommand 660 | def __here ; @here = true ; end 661 | 662 | # subcommand 663 | subcommand :go do 664 | def _p(n) 665 | @p = n.to_i 666 | end 667 | def main ; Output.concat([@here, @p]) ; end 668 | end 669 | end 670 | 671 | def test_CommandWithSimpleSubcommand 672 | cmd = CommandWithSimpleSubcommand.new 673 | cmd.execute( '--here go -p 1' ) 674 | assert_equal( [true, 1], Output ) 675 | end 676 | 677 | # 678 | 679 | # Global options can be anywhere, right? Even after subcommands? Let's find out. 680 | class CommandWithGlobalOptionsAfterSubcommand < TestCommand 681 | def _x ; @x = true ; end 682 | global_option :_x 683 | 684 | subcommand :go do 685 | def _p(n) 686 | @p = n.to_i 687 | end 688 | 689 | def main ; Output.concat([@x, @p]) ; end 690 | end 691 | end 692 | 693 | def test_CommandWithGlobalOptionsAfterSubcommand_01 694 | cmd = CommandWithGlobalOptionsAfterSubcommand.new 695 | cmd.execute( 'go -x -p 1' ) 696 | assert_equal( [true, 1], Output ) 697 | end 698 | 699 | def test_CommandWithGlobalOptionsAfterSubcommand_02 700 | cmd = CommandWithGlobalOptionsAfterSubcommand.new 701 | cmd.execute( 'go -p 1 -x' ) 702 | assert_equal( [true, 1], Output ) 703 | end 704 | 705 | # 706 | 707 | class GivingUnrecognizedOptions < TestCommand 708 | def _x ; @x = true ; end 709 | def go ; Output.concat([@x, @p]) ; end 710 | end 711 | 712 | def test_GivingUnrecognizedOptions 713 | cmd = GivingUnrecognizedOptions.new 714 | assert_raise(SystemExit) do 715 | cmd.execute( '--an-option-that-wont-be-recognized -x go' ) 716 | end 717 | assert_equal "Unknown option '--an-option-that-wont-be-recognized'.\n", $stderr.string 718 | assert_equal( [], Output ) 719 | end 720 | 721 | # 722 | 723 | class PassingMultipleSingleCharOptionsAsOneOption < TestCommand 724 | def _x ; @x = true ; end 725 | def _y ; @y = true ; end 726 | def _z(n) ; @z = n ; end 727 | 728 | global_option :_x 729 | 730 | subcommand :go do 731 | def _p(n) 732 | @p = n.to_i 733 | end 734 | def main ; Output.concat([@x, @y, @z, @p]) ; end 735 | end 736 | end 737 | 738 | def test_PassingMultipleSingleCharOptionsAsOneOption 739 | cmd = PassingMultipleSingleCharOptionsAsOneOption.new 740 | cmd.execute( '-xy -z HERE go -p 1' ) 741 | assert_equal( [true, true, 'HERE', 1], Output ) 742 | end 743 | 744 | # 745 | 746 | class CommandWithOptionUsingEquals < TestCommand 747 | subcommand :go do 748 | def __mode(mode) ; @mode = mode ; end 749 | def main ; Output.concat([@mode]) ; end 750 | end 751 | end 752 | 753 | def test_CommandWithOptionUsingEquals 754 | cmd = CommandWithOptionUsingEquals.new 755 | cmd.execute( 'go --mode smart' ) 756 | assert_equal( ['smart'], Output ) 757 | 758 | # I would expect this to work too, but currently it doesn't. 759 | #assert_nothing_raised { CommandWithOptionUsingEquals.execute( 'go --mode=smart' ) } 760 | #assert_equal( ['smart'], Output ) 761 | end 762 | 763 | # 764 | 765 | class CommandWithSubcommandThatTakesArgs < TestCommand 766 | def go(arg1, *args) ; Output.concat([arg1] | args) ; end 767 | end 768 | 769 | def test_CommandWithSubcommandThatTakesArgs 770 | cmd = CommandWithSubcommandThatTakesArgs.new 771 | cmd.execute( 'go file1 file2 file3' ) 772 | assert_equal( ['file1', 'file2', 'file3'], Output ) 773 | end 774 | 775 | # 776 | 777 | class CommandWith2OptionalArgs < TestCommand 778 | def __here ; @here = true ; end 779 | 780 | subcommand :go do 781 | def _p(n) 782 | @p = n.to_i 783 | end 784 | 785 | def main(required1 = nil, optional2 = nil) 786 | Output.concat [@here, @p, required1, optional2] 787 | end 788 | end 789 | end 790 | 791 | def test_CommandWith2OptionalArgs 792 | cmd = CommandWith2OptionalArgs.new 793 | cmd.execute( '--here go -p 1 to' ) 794 | assert_equal( [true, 1, 'to', nil], Output ) 795 | end 796 | 797 | # 798 | 799 | class CommandWithVariableArgs < TestCommand 800 | def __here ; @here = true ; end 801 | 802 | subcommand :go do 803 | def _p(n) 804 | @p = n.to_i 805 | end 806 | 807 | def main(*args) ; Output.concat([@here, @p] | args) ; end 808 | end 809 | end 810 | 811 | def test_CommandWithVariableArgs 812 | cmd = CommandWithVariableArgs.new 813 | cmd.execute( '--here go -p 1 to bed' ) 814 | assert_equal( [true, 1, 'to', 'bed'], Output ) 815 | end 816 | 817 | # 818 | 819 | class CommandWithOptionMissing < TestCommand 820 | def __here ; @here = true ; end 821 | 822 | subcommand :go do 823 | def option_missing(option_name, args) 824 | p args if $debug 825 | case option_name 826 | when 'p' 827 | @p = args[0].to_i 828 | 1 829 | else 830 | raise InvalidOptionError(option_name, args) 831 | end 832 | end 833 | 834 | def main(*args) ; Output.concat([@here, @p] | args) ; end 835 | end 836 | end 837 | 838 | def test_CommandWithOptionMissing 839 | cmd = CommandWithOptionMissing.new 840 | cmd.execute( '--here go -p 1 to bed right now' ) 841 | assert_equal( [true, 1, 'to', 'bed', 'right', 'now'], Output ) 842 | end 843 | 844 | # 845 | 846 | class CommandWithOptionMissingArityOf2 < TestCommand 847 | def __here ; @here = true ; end 848 | 849 | subcommand :go do 850 | def option_missing(option_name, args) 851 | p args if $debug 852 | case option_name 853 | when 'p' 854 | @p1 = args[0].to_i 855 | @p2 = args[1].to_i 856 | 2 857 | when 'q' 858 | @q = args[0].to_i 859 | nil # Test default arity 860 | else 861 | raise InvalidOptionError(option_name, args) 862 | end 863 | end 864 | 865 | def main(*args) ; Output.concat [@here, @p1, @p2, @q] | args ; end 866 | end 867 | end 868 | 869 | def test_CommandWithOptionMissingArityOf2 870 | cmd = CommandWithOptionMissingArityOf2.new 871 | cmd.execute( '--here go -p 1 2 -q 3 to bed right now' ) 872 | assert_equal( [true, 1, 2, 3, 'to', 'bed', 'right', 'now'], Output ) 873 | end 874 | 875 | end 876 | 877 | =end 878 | 879 | 880 | # Author:: Thomas Sawyer, Tyler Rick 881 | # Copyright:: Copyright (c) 2005-2007 882 | # License:: Ruby License 883 | --------------------------------------------------------------------------------