Skip to content

Commit

Permalink
Further steps towards a functional "command".
Browse files Browse the repository at this point in the history
  • Loading branch information
apeiros committed Jan 18, 2010
1 parent a2cbccc commit 8534327
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 46 deletions.
8 changes: 4 additions & 4 deletions examples/baretest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
# Specify commands and options
Command "run" do
# global arguments
argument :command, '[command]', :Virtual, "The command to run. See `baretest commands`"
argument :options, '[options]', :Virtual, "The flags and options, see in the \"Options\" section."
virtual_argument :command, '[command]', "The command to run. See `baretest commands`"
virtual_argument :options, '[options]', "The flags and options, see in the \"Options\" section."

# global options
o :commands, nil, '--commands', :Boolean, "overview over the commands"
Expand All @@ -20,8 +20,8 @@
command "run", :format => 'cli', :interactive => false, :verbose => false do
usage

argument :command
argument :options
virtual_argument :command
virtual_argument :options
argument '*glob', File, "The testfiles to run.\n" \
"Defaults to 'test/{suite,unit,integration,system}/**/*.rb'\n" \
"Providing a directory is equivalent to dir/**/*.rb"
Expand Down
6 changes: 6 additions & 0 deletions lib/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ def self.define(*args, &block)
@main = Definition.new(*args, &block)
@main
end

def self.with(argv, &block)
parser = Parser.new($command, argv)
parser.instance_eval(&block)
parser
end
end
67 changes: 43 additions & 24 deletions lib/command/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ class Definition
NegationSequence = /\[(?:no-|with-|without-)\]/
LongOption = /\A--[A-Za-z0-9][A-Za-z0-9_-]*(?: #{OptionArgument})?\z/

def self.create_argument(*args)
name = Symbol === args.first && args.shift
usage = args.shift
bare = usage[/\w+/]
type = Symbol === args.first && args.shift
description = args.shift

Argument.new(name, bare, usage, type, description)
end

# valid arguments:
# name # --> copy from parent
# name, short[, long][, type][, description]
Expand Down Expand Up @@ -110,29 +120,32 @@ def self.create_option(name, *args)

attr_reader :arguments
attr_reader :arguments_by_name
attr_reader :default_options
attr_reader :options_by_name
attr_reader :options_by_flag
attr_reader :commands_by_name
attr_reader :default_command
attr_reader :argument_position
attr_reader :env_by_variable
attr_reader :parent # parent= must update the DecoratingHashes

def initialize(parent=nil, default_command=nil, default_options={}, &block)
@default_command = default_command
@default_options = default_options
@default_options = DecoratingHash.new(@parent && @parent.default_options).update(default_options)
@elements = []
@parent = parent
@arguments_by_name = DecoratingHash.new(@parent && @parent.arguments_by_name)
@options_by_name = DecoratingHash.new(@parent && @parent.options_by_name)
@options_by_flag = DecoratingHash.new(@parent && @parent.options_by_flag)
@commands_by_name = DecoratingHash.new(@parent && @parent.commands_by_name)
@env_by_variable = DecoratingHash.new(@parent && @parent.env_by_variable)
@argument_position = {}
@text = []
instance_eval(&block) if block
end

def [](command)
@commands_by_name[command]
command ? @commands_by_name[command] : self
end

def usage_text
Expand All @@ -143,11 +156,11 @@ def usage_text
a.declaration.size <=> b.declaration.size
}
longest_env_name = @elements.grep(Env).max { |a,b|
a.name.size <=> b.name.size
a.variable.size <=> b.variable.size
}
longest_arg_bare = longest_arg_bare && longest_arg_bare.bare.size
longest_option = longest_option && longest_option.declaration.size
longest_env_name = longest_env_name && longest_env_name.name.size
longest_env_name = longest_env_name && longest_env_name.variable.size
arguments = @elements.grep(Argument)

@elements.map { |e|
Expand All @@ -164,16 +177,16 @@ def usage_text
when Env
sprintf "* %*s%s\n",
-longest_env_name-2,
e.name,
@options_by_name[e.map].description
e.variable,
@options_by_name[e.name].description
when String
e+"\n"
when Argument
indent = "\n "+(" "*longest_arg_bare)
sprintf " %*s%s\n",
-(longest_arg_bare+3),
"#{e.bare}:",
e.description.gsub(/\n/, indent)
e.description.to_s.gsub(/\n/, indent)
when Definition
else
"unimplemented(#{e.class})"
Expand All @@ -193,33 +206,37 @@ def argument(*args)
if args.size == 1 then
argument = @arguments_by_name[args.first]
raise ArgumentError, "No argument with name #{args.first.inspect} in any parent found." unless argument
@elements << argument

argument
else
name = Symbol === args.first && args.shift
usage = args.shift
position = @arguments_by_name.size
bare = usage[/\w+/]
type = args.shift
description = args.shift
argument = Argument.new(name, bare, usage, type, description)
@elements << argument
@arguments_by_name[name] = argument
argument = self.class.create_argument(*args)
@arguments_by_name[argument.name] = argument
end
@elements << argument

argument
argument
end

def virtual_argument(*args)
if args.size == 1 then
argument = @arguments_by_name[args.first]
raise ArgumentError, "No argument with name #{args.first.inspect} in any parent found." unless argument
else
argument = self.class.create_argument(*args)
@arguments_by_name[argument.name] = argument
end

@elements << argument
argument
end

def option(*args, &block)
def option(*args)
if args.size == 1 then
inherited_option = @options_by_name[args.first]
raise ArgumentError, "No inherited option #{args.first.inspect}" unless inherited_option
@elements << inherited_option

inherited_option
else
option = self.class.create_option(*args, &block)
option = self.class.create_option(*args)

@options_by_name[option.name] = option
@options_by_flag[option.short] = option
Expand Down Expand Up @@ -247,8 +264,10 @@ def placeholder(identifier)
@elements << identifier
end

def env_option(map, name)
@elements << Env.new(map, name)
def env_option(name, variable)
env = Env.new(name, variable)
@env_by_variable[variable] = env
@elements << env
end

def command(*args, &block)
Expand Down
2 changes: 1 addition & 1 deletion lib/command/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@


module Command
Env = Struct.new(:map, :name)
Env = Struct.new(:name, :variable)
end
62 changes: 45 additions & 17 deletions lib/command/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

module Command
class Parser
Token = Struct.new(:type, :value)

attr_reader :definition
attr_reader :command
attr_reader :options
attr_reader :argv
Expand All @@ -24,6 +23,8 @@ def initialize(definition, argv)
else
@arguments = @argv.dup
end
@affix = []
@options = {}
end

def argument(name)
Expand All @@ -40,28 +41,44 @@ def arguments
@arguments+@affix
end

def parse(*flags)
ignore_invalid_options = flags.delete(:ignore_invalid_options)
@affix = []
@parse_argv = @arguments
@arguments = []
@options = {}

if @definition.commands_by_name.include?(@parse_argv.first)
@command = @parse_argv.shift
def command!
if @definition.commands_by_name.include?(@arguments.first)
@command = @arguments.shift
else
@command = @definition.default_command
end
options = (@command ? @definition[@command] : @definition).options_by_flag # options available to this command

while arg = @parse_argv.shift
@command
end

def options!(*flags)
ignore_invalid_options = flags.delete(:ignore_invalid_options)
options = @definition[@command].options_by_flag # options available to this command
env = @definition[@command].env_by_variable # options available to this command
defaults = @definition[@command].default_options # options available to this command
parse_argv = @arguments
@arguments = []

defaults.each do |key, default|
@options[key] = default unless @options.has_key?(key)
end

env.each do |key, definition|
if ENV.has_key?(key) && !@options.has_key?(key) then
mapped = options[definition.name]
value = mapped.process!(ENV[key])
@options[key] = value
end
end

while arg = parse_argv.shift
if option = options[arg] then
case option.necessity
when :required
value = option.process!(@parse_argv.shift)
value = option.process!(parse_argv.shift)
when :optional
if @parse_argv.first && @parse_argv.first !~ /\A-/ then
value = option.process!(@parse_argv.shift)
if parse_argv.first && parse_argv.first !~ /\A-/ then
value = option.process!(parse_argv.shift)
else
value = true
end
Expand All @@ -77,7 +94,18 @@ def parse(*flags)
end
end

@result = Result.new(@command, @options, arguments+@affix)
@options
end

def result
Result.new(@command, @options, @arguments+@affix)
end

def parse(*flags)
command!
options!(*flags)

result
end
end
end

0 comments on commit 8534327

Please sign in to comment.