Skip to content

Commit

Permalink
Beginnings of refactor for better design
Browse files Browse the repository at this point in the history
  • Loading branch information
skorks committed Jan 9, 2013
1 parent 47ca406 commit 160a75c
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 132 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -15,3 +15,4 @@ spec/reports
test/tmp
test/version_tmp
tmp
tags
2 changes: 1 addition & 1 deletion .rvmrc
Expand Up @@ -2,7 +2,7 @@

failed=1
gemset=escort
rubies="ruby-1.9.3-p362 ruby-1.9.2-p290 ruby-1.9.3-p194"
rubies="ruby-1.9.3-p362 ruby-1.9.3-p194 ruby-1.9.2-p290 "

for ruby in $rubies; do
rvm use $ruby@$gemset --create 1>/dev/null 2>&1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -294,7 +294,7 @@ No input from STDIN necessary. This works just as well when you have commands in
### TODO
- if no arguments are provided should take argument/arguments from STDIN (ctrl-d to stop inputting arguments) unless configured via app.no_arguments_valid to be a valid without any arguments passed, just options/flags DONE
- do readme on no arguments and mandatory arguments DONE
- exception hierarchy for gem and better exit codes (better exception handling for the whole gem)
- exception hierarchy for gem and better exit codes (better exception handling for the whole gem, UserError, LogicErrors(InternalError, ClientError), TransientError, no raise library api, tagging exceptions)
- support for configuration files for your command line apps
- the ability to set the config file name
- ability to switch on and off default creation of config file
Expand Down
4 changes: 2 additions & 2 deletions examples/basic/app.rb
Expand Up @@ -8,7 +8,7 @@
opt :multi_option, "Option that can be specified multiple times", :short => '-m', :long => '--multi', :type => :string, :multi => true
end

app.action do |global_options, arguments|
puts "Action for my_command\nglobal options: #{global_options} \narguments: #{arguments}"
app.action do |options, arguments|
puts "Action for my_command\nglobal options: #{options} \narguments: #{arguments}"
end
end
10 changes: 7 additions & 3 deletions lib/escort.rb
@@ -1,12 +1,16 @@
require 'escort/version'

require 'escort/trollop'
require 'escort/error/error'

require 'escort/setup/option_string_tokenizer'
require 'escort/setup/global_setup_accessor'
require 'escort/setup/common'
require 'escort/setup/command'
require 'escort/setup/global'

require 'escort/validations'
require 'escort/arguments'

require 'escort/global_dsl'
require 'escort/dsl'

require 'escort/command'
require 'escort/app'
169 changes: 106 additions & 63 deletions lib/escort/app.rb
@@ -1,90 +1,133 @@
module Escort
class App
include Dsl
include GlobalDsl

class << self
def create(options_string = nil, &block)
self.new(options_string).tap do |app|
block.call(app) #run the block to get the various sub blocks
begin
app.use_default_options_string_if_needed
app.parse_options # parse the global options
if !app.command_names.nil? && app.command_names.size > 0
app.current_command.parse_options # parse the current command options
app.execute_before_block(app.current_command.name, app.current_options, app.current_command.current_options, app.arguments)
app.current_command.perform_action(app.current_options, app.arguments, app.valid_with_no_arguments)
else
app.perform_action(app.current_options, app.arguments)
end
exit(0) #everything executed successfully so returning 0 as exit status
rescue => e
app.execute_error_block(e)
#TODO once we have a standard exception hierarchy we can have different exit codes for the various different exceptions here
exit(1) #execution finished unsuccessfully
end
end
app = self.new(Escort::Setup::Global.new(options_string, &block))
app.has_sub_commands? ? app.execute_active_command : app.execute_global_action
exit(0)
end
end

attr_reader :current_options
attr_reader :global_setup, :options, :global_setup_accessor

def initialize(options_string = nil)
@options_string = options_string || ARGV.dup
def initialize(global_setup)
@global_setup = global_setup
@global_setup_accessor = GlobalSetupAccessor.new(global_setup)
end

def use_default_options_string_if_needed
@default_options_string ||= ['-h']
if @options_string.size == 0
@options_string = @default_options_string
end
def has_sub_commands?
!global_setup_accessor.command_names.nil? && global_setup_accessor.command_names.size > 0
rescue => e
handle_error(e)
end

def execute_global_action
parse_options
perform_action
rescue => e
handle_error(e)
end

def arguments
@options_string
def execute_active_command
parse_options # we still need to parse the global options
current_command_setup = Escort::Setup::Command.new(global_setup)
command = Command.new(current_command_setup, self)
command.parse_options
command.perform_action
rescue => e
handle_error(e)
end

def valid_with_no_arguments
@no_arguments_valid
private

def handle_error(e)
e.extend(Escort::Error)
raise e
exit(1) #execution finished unsuccessfully
end

def parse_options
parser = Trollop::Parser.new(&@options_block)
parser.stop_on(@command_names)
@parser = Trollop::Parser.new(&global_setup_accessor.options_block)
@parser.stop_on(global_setup_accessor.command_names)

@current_options = Trollop::with_standard_exception_handling(parser) do
parser.parse @options_string
@options = Trollop::with_standard_exception_handling(@parser) do
@parser.parse(global_setup_accessor.options_string)
end
Escort::Validations.validate(@current_options, parser, &@validations_block) if @validations_block
#Escort::Validations.validate(@current_options, parser, &@validations_block) if @validations_block
end

def current_command
return @current_command if @current_command
command_name = @options_string.shift.to_s
command_block = @command_blocks[command_name]
command_description = @command_descriptions[command_name] || nil
raise "No command was passed in" unless command_block
@current_command = Command.new(command_name, command_description, @options_string)
command_block.call(@current_command)
@current_command
def perform_action
#TODO should this even raise, or should it just print an error to stderr and jump to an exit block
raise Escort::ClientError, "Must define a global action block if there are no sub-commands" unless global_setup_accessor.action_block
global_setup_accessor.action_block.call(options, Escort::Arguments.read(global_setup_accessor.arguments))
#if command_names.nil? || command_names.size == 0
##TODO what should be raised here (ClientError), should anything
#raise "Must define a global action block if there are no sub-commands" unless @action_block
#raise "Can't define before blocks if there are no sub-commands" if @before_block
#@action_block.call(current_options, Escort::Arguments.read(arguments, @no_arguments_valid))
#else
##TODO what should be raised here (ClientError), should anything
#raise "Can't define global actions for an app with sub-commands"
#end
end

def execute_before_block(command_name, global_options, command_options, arguments)
@before_block.call(command_name, global_options, command_options, arguments) if @before_block
end

def perform_action(current_options, arguments)
if command_names.nil? || command_names.size == 0
raise "Must define a global action block if there are no sub-commands" unless @action_block
raise "Can't define before blocks if there are no sub-commands" if @before_block
@action_block.call(current_options, Escort::Arguments.read(arguments, @no_arguments_valid))
else
raise "Can't define global actions for an app with sub-commands"
end
end

def execute_error_block(error)
@error_block ? @error_block.call(error) : (raise error)
end






#def arguments
#@options_string
#end

#def valid_with_no_arguments
#@no_arguments_valid
#end

#def parse_options
#parser = Trollop::Parser.new(&@options_block)
#parser.stop_on(@command_names)

#@current_options = Trollop::with_standard_exception_handling(parser) do
#parser.parse @options_string
#end
#Escort::Validations.validate(@current_options, parser, &@validations_block) if @validations_block
#end

#def current_command
#return @current_command if @current_command
#command_name = @options_string.shift.to_s
#command_block = @command_blocks[command_name]
#command_description = @command_descriptions[command_name] || nil
##TODO what should be raised here (UserError), should anything
#raise "No command was passed in" unless command_block
#@current_command = Command.new(command_name, command_description, @options_string)
#command_block.call(@current_command)
#@current_command
#end

#def execute_before_block(command_name, global_options, command_options, arguments)
#@before_block.call(command_name, global_options, command_options, arguments) if @before_block
#end

#def perform_action(current_options, arguments)
#if command_names.nil? || command_names.size == 0
##TODO what should be raised here (ClientError), should anything
#raise "Must define a global action block if there are no sub-commands" unless @action_block
#raise "Can't define before blocks if there are no sub-commands" if @before_block
#@action_block.call(current_options, Escort::Arguments.read(arguments, @no_arguments_valid))
#else
##TODO what should be raised here (ClientError), should anything
#raise "Can't define global actions for an app with sub-commands"
#end
#end

#def execute_error_block(error)
##TODO make sure we tag the error here if we're going to re-raise it
#@error_block ? @error_block.call(error) : (raise error)
#end
end
end
3 changes: 2 additions & 1 deletion lib/escort/arguments.rb
Expand Up @@ -3,12 +3,13 @@
module Escort
class Arguments
class << self
def read(arguments, no_arguments_valid)
def read(arguments, no_arguments_valid=false)
if arguments.empty? && !no_arguments_valid
while command = Readline.readline("> ", true)
arguments << command
end
arguments = arguments.compact.keep_if{|value| value.length > 0}
#TODO what should be raised here (UserError), should anything
raise "You must provider some arguments to this script" if arguments.empty?
end
arguments
Expand Down
3 changes: 1 addition & 2 deletions lib/escort/command.rb
@@ -1,7 +1,5 @@
module Escort
class Command
include Dsl

attr_reader :current_options, :name

def initialize(name, description, options_string)
Expand All @@ -19,6 +17,7 @@ def parse_options
end

def perform_action(parent_command_options, remaining_arguments, no_arguments_valid)
#TODO what should be raised here (ClientError), should anything
raise "Must define an action block for sub commands" unless @action_block
@action_block.call(parent_command_options, @current_options, Escort::Arguments.read(remaining_arguments, no_arguments_valid))
end
Expand Down
35 changes: 0 additions & 35 deletions lib/escort/dsl.rb

This file was deleted.

34 changes: 34 additions & 0 deletions lib/escort/error/error.rb
@@ -0,0 +1,34 @@
module Escort
CLIENT_ERROR_EXIT_CODE = 2
#module to tag all exceptions coming out of Escort with
module Error
end

#all our exceptions will supported nesting other exceptions
#also all our exception will be a kind_of? Escort::Error
class BaseError < StandardError
include Error
attr_reader :original

def initialize(msg, original=$!)
super(msg)
@original = original
end
end

#user did something invalid
class UserError < BaseError
end

#for errors with escort itself
class InternalError < BaseError
end

#for errors with how escort is being used
class ClientError < BaseError
end

#a dependency is temporarily unavailable
class TransientError < BaseError
end
end
24 changes: 0 additions & 24 deletions lib/escort/global_dsl.rb

This file was deleted.

0 comments on commit 160a75c

Please sign in to comment.