Module: Drydock

Extended by:
Drydock
Included in:
Drydock
Defined in:
lib/drydock.rb,
lib/drydock.rb,
lib/drydock.rb,
lib/drydock/screen.rb

Overview

Drydock is a DSL for command-line apps. See bin/example for usage examples.

Defined Under Namespace

Modules: Screen Classes: ArgError, Command, FancyArray, InvalidArgument, MissingArgument, NoCommandsDefined, OptError, UnknownCommand

Constant Summary collapse

VERSION =
0.6
@@project =
nil
@@debug =
false
@@has_run =
false
@@run =
true
@@global_opts_parser =
OptionParser.new
@@global_option_names =
[]
@@command_opts_parser =
[]
@@command_option_names =
[]
@@command_actions =
[]
@@default_command_with_args =
false
@@commands =
{}
@@command_index =
0
@@command_index_map =
{}
@@command_argv_names =

an array of names for values of argv

[]
@@capture =

contains one of :stdout, :stderr

nil
@@captured =
nil
@@trawler =
nil

Instance Method Summary collapse

Instance Method Details

#about(txt) ⇒ Object

Provide a description for a command



703
704
705
706
707
# File 'lib/drydock.rb', line 703

def about(txt)
  @@command_descriptions += [txt]
  return if get_current_option_parser.is_a?(Symbol)
  get_current_option_parser.on "ABOUT: #{txt}"
end

#action(*args, &b) ⇒ Object

Define a command-specific action.

This is functionally very similar to option, but with an exciting and buoyant twist: Drydock keeps track of actions for each command (in addition to treating it like an option). When an action is specified on the command line Drydock looks for command_action or action_command methods in the command class.

action :E, :eat, "Eat something"
command :oysters => Fresh::Oysters

# Drydock will look for Fresh::Oysters#eat_oysters and Fresh::Oysters#oysters_eat.


590
591
592
593
# File 'lib/drydock.rb', line 590

def action(*args, &b)
  ret = option(*args, &b) # returns an array of all the current option names
  current_command_action << ret.last # the most recent is last
end

#after(&b) ⇒ Object

Define a block to be called after the command. This is useful for stopping, closing, etc… the stuff in the before block.



504
505
506
# File 'lib/drydock.rb', line 504

def after(&b)
  @@after_block = b
end

#alias_command(aliaz, cmd) ⇒ Object

Used to create an alias to a defined command. Here’s an example:

command :task do; ...; end
alias_command :pointer, :task

Either name can be used on the command-line:

$ yourscript task [options]
$ yourscript pointer [options]

Inside of the command definition, you have access to the command name that was used via obj.alias.



664
665
666
667
# File 'lib/drydock.rb', line 664

def alias_command(aliaz, cmd)
  return unless commands.has_key? cmd
  commands[canonize(aliaz)] = commands[cmd]
end

#argv(*args) ⇒ Object

Provide names for CLI arguments, in the order they appear.

$ yourscript sample malpeque zinqy
argv :name, :flavour
command :sample do |obj|
  obj.argv.name        # => malpeque
  obj.argv.flavour     # => zinqy
end


418
419
420
421
# File 'lib/drydock.rb', line 418

def argv(*args)
  @@command_argv_names[@@command_index] ||= []
  @@command_argv_names[@@command_index] += args.flatten
end

#before(&b) ⇒ Object

Define a block to be called before the command. This is useful for opening database connections, etc…



498
499
500
# File 'lib/drydock.rb', line 498

def before(&b)
  @@before_block = b
end

#canonize(cmd) ⇒ Object

Canonizes a string (cmd) to the symbol for command names ‘-’ is replaced with ‘_’



781
782
783
784
785
# File 'lib/drydock.rb', line 781

def canonize(cmd)
  return unless cmd
  return cmd if cmd.kind_of?(Symbol)
  cmd.to_s.tr('-', '_').to_sym
end

#capture(io) ⇒ Object



761
762
763
# File 'lib/drydock.rb', line 761

def capture(io)
  @@capture = io
end

#capture?Boolean

Returns:

  • (Boolean)


769
770
771
# File 'lib/drydock.rb', line 769

def capture?
  !@@capture.nil?
end

#capture_io(stream, &block) ⇒ Object

Capture STDOUT or STDERR to prevent it from being printed.

capture(:stdout) do
  ...
end


800
801
802
803
804
805
806
807
808
809
810
# File 'lib/drydock.rb', line 800

def capture_io(stream, &block)
  raise "We can only capture STDOUT or STDERR" unless stream == :stdout || stream == :stderr
  begin
    eval "$#{stream} = StringIO.new"
    block.call
    eval("$#{stream}").rewind                  # Otherwise we'll get nil 
    result = eval("$#{stream}").read
  ensure
    eval "$#{stream} = #{stream.to_s.upcase}"  # Put it back!
  end
end

#capturedObject



765
766
767
# File 'lib/drydock.rb', line 765

def captured
  @@captured
end

#command(*cmds, &b) ⇒ Object

Define a command.

command :task do
  ...
end

A custom command class can be specified using Hash syntax. The class must inherit from Drydock::Command (class CustomeClass < Drydock::Command)

command :task => CustomCommand do
  ...
end


608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
# File 'lib/drydock.rb', line 608

def command(*cmds, &b)
  cmd = cmds.shift # Should we accept aliases here?
  
  if cmd.is_a? Hash
    klass = cmd.values.first
    names = cmd.keys.first
    if names.is_a? Array
      cmd, cmds = names.shift, [names].flatten.compact
    else
      cmd = names
    end
    raise "#{klass} is not a subclass of Drydock::Command" unless klass.ancestors.member?(Drydock::Command)
    c = klass.new(cmd, &b)          # A custom class was specified
  else
    c = Drydock::Command.new(cmd, &b)
  end
  
  @@command_descriptions[@@command_index] ||= ""
  @@command_actions[@@command_index] ||= []
  @@command_argv_names[@@command_index] ||= []
  
  c.desc = @@command_descriptions[@@command_index]
  c.actions = @@command_actions[@@command_index]
  c.argv.fields = @@command_argv_names[@@command_index]
  
  # Default Usage Banner. 
  # Without this, there's no help displayed for the command. 
  option_parser = get_option_parser(@@command_index)
  if option_parser.is_a?(OptionParser) && option_parser.banner !~ /^USAGE/
    usage "#{c.executable} #{c.cmd}"
  end
  
  @@commands[c.cmd] = c
  @@command_index_map[c.cmd] = @@command_index
  @@command_index += 1 # This will point to the next command
  
  # Created aliases to the command using any additional command names 
  # i.e. command :something, :sumpin => Something
  cmds.each { |aliaz| command_alias(cmd, aliaz); } unless cmds.empty?
  
  c  # Return the Command object
end

#command?(cmd) ⇒ Boolean

Returns true if a command with the name cmd has been defined.

Returns:

  • (Boolean)


774
775
776
777
# File 'lib/drydock.rb', line 774

def command?(cmd)
  name = canonize(cmd)
  @@commands.has_key? name
end

#command_alias(cmd, aliaz) ⇒ Object

Identical to alias_command with reversed arguments. For whatever reason I forget the order so Drydock supports both. Tip: the argument order matches the method name.



672
673
674
675
# File 'lib/drydock.rb', line 672

def command_alias(cmd, aliaz)
  return unless commands.has_key? cmd
  commands[canonize(aliaz)] = commands[cmd]
end

#command_namesObject

An array of the currently defined commands names



683
684
685
# File 'lib/drydock.rb', line 683

def command_names
  @@commands.keys.collect { |cmd| decanonize(cmd); }
end

#commandsObject

A hash of the currently defined Drydock::Command objects



678
679
680
# File 'lib/drydock.rb', line 678

def commands
  @@commands
end

#debug(toggle = false) ⇒ Object

Enable or disable debug output.

debug :on
debug :off

Calling without :on or :off will toggle the value.



395
396
397
398
399
400
401
402
# File 'lib/drydock.rb', line 395

def debug(toggle=false)
  if toggle.is_a? Symbol
    @@debug = true if toggle == :on
    @@debug = false if toggle == :off
  else
    @@debug = (!@@debug)
  end
end

#debug?Boolean

Returns true if debug output is enabled.

Returns:

  • (Boolean)


405
406
407
# File 'lib/drydock.rb', line 405

def debug?
  @@debug
end

#decanonize(cmd) ⇒ Object

Returns a string version of cmd, decanonized. Lowercase, ‘_’ is replaced with ‘-’



789
790
791
792
# File 'lib/drydock.rb', line 789

def decanonize(cmd)
  return unless cmd
  cmd.to_s.tr('_', '-')
end

#default(cmd = nil, with_args = false, &b) ⇒ Object

Define a default command. You can specify a command name that has been or will be defined in your script:

default :task

Or you can supply a block which will be used as the default command:

default do |obj|            # This command will be named "default"
  # ...
end

default :hullinspector do   # This one will be named "hullinspector"
  # ...
end

If with_args is specified, the default command will receive all unknown values as arguments. This is necessary to define explicitly because drydock parses arguments expecting a command name. If the default command accepts arguments and with_args is not specified, drydock will raise an unknown command exception for the first argument.



465
466
467
468
469
470
471
472
# File 'lib/drydock.rb', line 465

def default(cmd=nil, with_args=false, &b)
  raise "Calling default requires a command name or a block" unless cmd || b
  # Creates the command and returns the name or just stores given name
  @@default_command = (b) ? command(cmd || :default, &b).cmd : canonize(cmd)
  # IDEA: refactor out the argument parser to support different types of CLI
  @@default_command_with_args = with_args ? true : false
  @@default_command
end

#default?(cmd) ⇒ Boolean

Is cmd the default command?

Returns:

  • (Boolean)


475
476
477
478
# File 'lib/drydock.rb', line 475

def default?(cmd)
  return false if @@default_command.nil?
  (@@default_command == canonize(cmd))
end

#default_with_args?Boolean

Returns:

  • (Boolean)


481
# File 'lib/drydock.rb', line 481

def default_with_args?; @@default_command_with_args; end

#desc(txt) ⇒ Object

Deprecated. Use about.



709
710
711
712
# File 'lib/drydock.rb', line 709

def desc(txt)
  STDERR.puts "'desc' is deprecated. Please use 'about' instead."
  about(txt) 
end

#global_option(*args, &b) ⇒ Object Also known as: global

Define a global option. See option for more info.



536
537
538
539
# File 'lib/drydock.rb', line 536

def global_option(*args, &b)
  args.unshift(@@global_opts_parser)
  @@global_option_names << option_parser(args, &b)
end

#global_usage(msg) ⇒ Object

Define the default global usage banner. This is displayed with “script -h”.



510
511
512
# File 'lib/drydock.rb', line 510

def global_usage(msg)
  @@global_opts_parser.banner = "USAGE: #{msg}"
end

#has_run?Boolean

Return true if a command has been executed.

Returns:

  • (Boolean)


727
728
729
# File 'lib/drydock.rb', line 727

def has_run?
  @@has_run
end

#ignore(what = :nothing) ⇒ Object

Tell the Drydock parser to ignore something. Drydock will currently only listen to you if you tell it to “ignore :options”, otherwise it will ignore you!

what the thing to ignore. When it equals :options Drydock will not parse the command-specific arguments. It will pass the arguments directly to the Command object. This is useful when you want to parse the arguments in some a way that’s too crazy, dangerous for Drydock to handle automatically.



531
532
533
# File 'lib/drydock.rb', line 531

def ignore(what=:nothing)
  @@command_opts_parser[@@command_index] = :ignore if what == :options || what == :all
end

#option(*args, &b) ⇒ Object

Define a command-specific option.

args is passed directly to OptionParser.on so it can contain anything that’s valid to that method. If a class is included, it will tell OptionParser to expect a value otherwise it assumes a boolean value. Some examples:

option :h, :help, "Displays this message"
option '-l x,y,z', '--lang=x,y,z', Array, "Requested languages"

You can also supply a block to fiddle with the values. The final 
value becomes the option's value:

option :m, :max, Integer, "Maximum threshold" do |v|
  v = 100 if v > 100
  v
end

All calls to option must come before the command they’re associated to. Example:

option :t, :tasty,          "A boolean switch"
option     :reason, String, "Requires a parameter"
command :task do |obj|; 
  obj.options.tasty       # => true
  obj.options.reason      # => I made the sandwich!
end

When calling your script with a specific command-line option, the value is available via obj.longname inside the command block.



573
574
575
576
# File 'lib/drydock.rb', line 573

def option(*args, &b)
  args.unshift(get_current_option_parser)
  current_command_option_names << option_parser(args, &b)
end

#project(txt = nil) ⇒ Object

The project name. This is currently only used when printing list of commands (see: Drydock::Command#show_commands). It may be used elsewhere in the future.



426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/drydock.rb', line 426

def project(txt=nil)
  
  return @@project unless txt
  
  #begin
  #  require txt.downcase
  #rescue LoadError => ex
  #  Drydock.run = false  # Prevent execution at_exit
  #  abort "Problem during require: #{ex.message}"
  #end
  @@project = txt
end

#project?Boolean

Has the project been set?

Returns:

  • (Boolean)


440
441
442
# File 'lib/drydock.rb', line 440

def project?
  (defined?(@@project) && !@@project.nil?)
end

#run!(argv = [], stdin = STDIN) ⇒ Object

Execute the given command. By default, Drydock automatically executes itself and provides handlers for known errors. You can override this functionality by calling Drydock.run! yourself. Drydock will only call run! once.



735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
# File 'lib/drydock.rb', line 735

def run!(argv=[], stdin=STDIN)
  return if has_run?
  @@has_run = true
  raise NoCommandsDefined.new if commands.empty?
  
  global_options, cmd_name, command_options, argv = process_arguments(argv)
  stdin = (defined? @@stdin_block) ? @@stdin_block.call(stdin, []) : stdin
  
  command_obj = get_command(cmd_name)
  command_obj.prepare(cmd_name, argv, stdin, global_options, command_options)
  
  # Execute before block
  @@before_block.call(command_obj) if defined? @@before_block
  
  # Execute the requested command. We'll capture STDERR or STDOUT if desired. 
  @@captured = capture? ? capture_io(@@capture) { command_obj.call } : command_obj.call
      
  # Execute after block
  @@after_block.call(command_obj) if defined? @@after_block
  
rescue OptionParser::InvalidOption => ex
  raise Drydock::InvalidArgument.new(ex.args)
rescue OptionParser::MissingArgument => ex
  raise Drydock::MissingArgument.new(ex.args)
end

#run=(v) ⇒ Object

Disable automatic execution (enabled by default)

Drydock.run = false


722
723
724
# File 'lib/drydock.rb', line 722

def run=(v)
  @@run = (v.is_a?(TrueClass)) ? true : false 
end

#run?Boolean

Returns true if automatic execution is enabled.

Returns:

  • (Boolean)


715
716
717
# File 'lib/drydock.rb', line 715

def run?
  @@run && has_run? == false
end

#stdin(&b) ⇒ Object

Define a block for processing STDIN before the command is called. The command block receives the return value of this block as obj.stdin:

command :task do |obj|; 
  obj.stdin   # => ...
end

If a stdin block isn’t defined, stdin above will be the STDIN IO handle.



492
493
494
# File 'lib/drydock.rb', line 492

def stdin(&b)
  @@stdin_block = b
end

#trawler(cmd) ⇒ Object

The trawler catches any and all unknown commands that pass through Drydock. It’s like the captain of aliases. cmd is the name of the command to direct unknowns to.

trawler :command_name


693
694
695
# File 'lib/drydock.rb', line 693

def trawler(cmd)
  @@trawler = cmd
end

#trawler?Boolean

Has the trawler been set?

Returns:

  • (Boolean)


698
699
700
# File 'lib/drydock.rb', line 698

def trawler?
  !@@trawler.nil? && !@@trawler.to_s.empty?
end

#usage(msg) ⇒ Object

Define a command-specific usage banner. This is displayed with “script command -h”



516
517
518
519
520
521
# File 'lib/drydock.rb', line 516

def usage(msg)
  # The default value given by OptionParser starts with "Usage". That's how
  # we know we can clear it. 
  get_current_option_parser.banner = "" if get_current_option_parser.banner =~ /^Usage:/
  get_current_option_parser.banner << "USAGE: #{msg}" << $/
end