escort 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +1 -0
- data/.irbrc +1 -0
- data/.rspec +3 -0
- data/.rvmrc +22 -0
- data/README.md +31 -56
- data/TODO.md +152 -0
- data/escort.gemspec +6 -2
- data/examples/1_1_basic.rb +15 -0
- data/examples/1_2_basic_requires_arguments.rb +15 -0
- data/examples/2_2_command.rb +18 -0
- data/examples/2_2_command_requires_arguments.rb +20 -0
- data/examples/2_3_nested_commands.rb +26 -0
- data/examples/3_validations.rb +31 -0
- data/examples/4_1_config_file.rb +42 -0
- data/examples/argument_handling/basic.rb +12 -0
- data/examples/argument_handling/basic_command.rb +18 -0
- data/examples/argument_handling/no_arguments.rb +14 -0
- data/examples/argument_handling/no_arguments_command.rb +20 -0
- data/examples/basic/app.rb +16 -0
- data/examples/command_aliases/app.rb +31 -0
- data/examples/config_file/.apprc2 +16 -0
- data/examples/config_file/app.rb +78 -0
- data/examples/config_file/sub_commands.rb +35 -0
- data/examples/default_command/app.rb +20 -0
- data/examples/sub_commands/app.rb +18 -0
- data/examples/validation_basic/app.rb +31 -0
- data/lib/escort.rb +51 -4
- data/lib/escort/action_command/base.rb +79 -0
- data/lib/escort/action_command/escort_utility_command.rb +53 -0
- data/lib/escort/app.rb +89 -36
- data/lib/escort/arguments.rb +20 -0
- data/lib/escort/auto_options.rb +71 -0
- data/lib/escort/error/error.rb +50 -0
- data/lib/escort/formatter/borderless_table.rb +102 -0
- data/lib/escort/formatter/common.rb +58 -0
- data/lib/escort/formatter/default_help_formatter.rb +106 -0
- data/lib/escort/formatter/options.rb +13 -0
- data/lib/escort/formatter/string_splitter.rb +30 -0
- data/lib/escort/formatter/terminal.rb +22 -0
- data/lib/escort/formatter/terminal_formatter.rb +52 -0
- data/lib/escort/global_pre_parser.rb +43 -0
- data/lib/escort/logger.rb +75 -0
- data/lib/escort/option_parser.rb +145 -0
- data/lib/escort/setup/configuration/generator.rb +75 -0
- data/lib/escort/setup/configuration/instance.rb +33 -0
- data/lib/escort/setup/configuration/loader.rb +37 -0
- data/lib/escort/setup/configuration/locator/base.rb +19 -0
- data/lib/escort/setup/configuration/locator/descending_to_home.rb +23 -0
- data/lib/escort/setup/configuration/merge_tool.rb +38 -0
- data/lib/escort/setup/configuration/reader.rb +36 -0
- data/lib/escort/setup/configuration/writer.rb +44 -0
- data/lib/escort/setup/dsl/action.rb +17 -0
- data/lib/escort/setup/dsl/command.rb +74 -0
- data/lib/escort/setup/dsl/config_file.rb +13 -0
- data/lib/escort/setup/dsl/global.rb +84 -0
- data/lib/escort/setup/dsl/options.rb +25 -0
- data/lib/escort/setup/dsl/validations.rb +25 -0
- data/lib/escort/setup_accessor.rb +194 -0
- data/lib/escort/trollop.rb +15 -4
- data/lib/escort/utils.rb +21 -0
- data/lib/escort/validator.rb +42 -0
- data/lib/escort/version.rb +1 -1
- data/spec/helpers/execute_action_matcher.rb +21 -0
- data/spec/helpers/exit_with_code_matcher.rb +21 -0
- data/spec/helpers/give_option_to_action_with_value_matcher.rb +22 -0
- data/spec/integration/basic_options_spec.rb +53 -0
- data/spec/integration/basic_spec.rb +24 -0
- data/spec/lib/escort/formatter/string_splitter_spec.rb +38 -0
- data/spec/lib/escort/setup_accessor_spec.rb +42 -0
- data/spec/lib/escort/utils_spec.rb +30 -0
- data/spec/spec_helper.rb +22 -0
- metadata +128 -16
- data/lib/escort/command.rb +0 -23
- data/lib/escort/dsl.rb +0 -20
- data/lib/escort/global_dsl.rb +0 -11
@@ -0,0 +1,75 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
class Generator
|
5
|
+
attr_reader :setup
|
6
|
+
|
7
|
+
def initialize(setup)
|
8
|
+
@setup = setup
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_data
|
12
|
+
config_hash = init_config_hash
|
13
|
+
options([], config_hash[:global][:options]) #global options
|
14
|
+
options_for_commands(setup.canonical_command_names_for([]), [], config_hash[:global][:commands])
|
15
|
+
config_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def options_for_commands(commands, context, options = {})
|
21
|
+
commands.each do |command_name|
|
22
|
+
command_name = command_name.to_sym
|
23
|
+
#next if command_name == :escort
|
24
|
+
options[command_name] = {}
|
25
|
+
options[command_name][:options] = {}
|
26
|
+
options[command_name][:commands] = {}
|
27
|
+
current_context = context.dup
|
28
|
+
current_context << command_name
|
29
|
+
options(current_context, options[command_name][:options]) #command_options
|
30
|
+
options_for_commands(setup.canonical_command_names_for(current_context), current_context, options[command_name][:commands])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def init_config_hash
|
35
|
+
{
|
36
|
+
:global => {
|
37
|
+
:options => {},
|
38
|
+
:commands => {}
|
39
|
+
},
|
40
|
+
:user => {}
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def options(context = [], options = {})
|
45
|
+
command_names = setup.command_names_for(context)
|
46
|
+
parser = init_parser(command_names)
|
47
|
+
parser = add_setup_options_to(parser, context)
|
48
|
+
options.merge!(default_option_values(parser))
|
49
|
+
end
|
50
|
+
|
51
|
+
def init_parser(stop_words)
|
52
|
+
Trollop::Parser.new.tap do |parser|
|
53
|
+
parser.stop_on(stop_words) # make sure we halt parsing if we see a command
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_setup_options_to(parser, context = [])
|
58
|
+
setup.options_for(context).each do |name, opts|
|
59
|
+
parser.opt name, opts[:desc] || "", opts.dup #have to make sure to dup here, otherwise opts might get stuff added to it and it
|
60
|
+
#may cause problems later, e.g. adds default value and when parsed again trollop barfs
|
61
|
+
end
|
62
|
+
parser
|
63
|
+
end
|
64
|
+
|
65
|
+
def default_option_values(parser)
|
66
|
+
hash = {}
|
67
|
+
parser.specs.each_pair do |key, data|
|
68
|
+
hash[key] = data[:default] || nil
|
69
|
+
end
|
70
|
+
hash
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
class Instance
|
5
|
+
class << self
|
6
|
+
def blank
|
7
|
+
self.new(nil, {})
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :data, :path
|
12
|
+
|
13
|
+
def initialize(path, hash)
|
14
|
+
@data = hash
|
15
|
+
@path = path
|
16
|
+
end
|
17
|
+
|
18
|
+
def blank?
|
19
|
+
data.empty?
|
20
|
+
end
|
21
|
+
alias empty? blank?
|
22
|
+
|
23
|
+
def global
|
24
|
+
data[:global] || {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def user
|
28
|
+
data[:user] || {}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
class Loader
|
5
|
+
attr_reader :setup, :auto_options
|
6
|
+
|
7
|
+
def initialize(setup, auto_options)
|
8
|
+
@setup = setup
|
9
|
+
@auto_options = auto_options
|
10
|
+
end
|
11
|
+
|
12
|
+
def configuration
|
13
|
+
if setup.has_config_file?
|
14
|
+
Writer.new(config_path, Generator.new(setup).default_data).write if setup.config_file_autocreatable?
|
15
|
+
Reader.new(config_path).read
|
16
|
+
else
|
17
|
+
Instance.blank
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_config_path
|
22
|
+
@default_config_path ||= File.join(File.expand_path(ENV["HOME"]), config_filename)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def config_filename
|
28
|
+
@config_filename ||= setup.config_file
|
29
|
+
end
|
30
|
+
|
31
|
+
def config_path
|
32
|
+
@config_path ||= (auto_options.non_default_config_path || Locator::DescendingToHome.new(config_filename).locate || default_config_path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
module Locator
|
5
|
+
class Base
|
6
|
+
attr_reader :filename
|
7
|
+
|
8
|
+
def initialize(filename)
|
9
|
+
@filename = filename
|
10
|
+
end
|
11
|
+
|
12
|
+
def locate
|
13
|
+
raise "Must be defined in child class"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Escort
|
4
|
+
module Setup
|
5
|
+
module Configuration
|
6
|
+
module Locator
|
7
|
+
class DescendingToHome < Base
|
8
|
+
def locate
|
9
|
+
return nil unless filename
|
10
|
+
possible_configs = []
|
11
|
+
Pathname.new(Dir.pwd).descend do |path|
|
12
|
+
filepath = File.join(path, filename)
|
13
|
+
if File.exists?(filepath)
|
14
|
+
possible_configs << filepath
|
15
|
+
end
|
16
|
+
end
|
17
|
+
possible_configs.empty? ? nil : possible_configs.last
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Configuration
|
4
|
+
class MergeTool
|
5
|
+
def initialize(new_config_hash, old_config_hash)
|
6
|
+
@new_config_hash = new_config_hash
|
7
|
+
@old_config_hash = old_config_hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def config_hash
|
11
|
+
merge_config(@new_config_hash, @old_config_hash)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def merge_config(new_hash, old_hash)
|
17
|
+
new_hash.keys.each do |key|
|
18
|
+
new_config_value = new_hash[key]
|
19
|
+
old_config_value = old_hash[key]
|
20
|
+
|
21
|
+
if new_config_value.kind_of?(Hash) && old_config_value.kind_of?(Hash)
|
22
|
+
new_hash[key] = merge_config(new_config_value, old_config_value)
|
23
|
+
elsif old_config_value.nil?
|
24
|
+
new_hash[key] = new_config_value
|
25
|
+
else
|
26
|
+
new_hash[key] = old_config_value
|
27
|
+
end
|
28
|
+
|
29
|
+
if key == :user
|
30
|
+
new_hash[key] = old_config_value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
new_hash
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Escort
|
4
|
+
module Setup
|
5
|
+
module Configuration
|
6
|
+
class Reader
|
7
|
+
attr_reader :path
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def read
|
14
|
+
data = {}
|
15
|
+
data = load_config_at_path if path
|
16
|
+
Instance.new(path, data)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def load_config_at_path
|
22
|
+
data = {}
|
23
|
+
begin
|
24
|
+
json = File.read(path)
|
25
|
+
hash = JSON.parse(json)
|
26
|
+
data = Escort::Utils.symbolize_keys(hash)
|
27
|
+
rescue => e
|
28
|
+
error_logger.warn { "Found config at #{path}, but failed to load it, perhaps your JSON syntax is invalid. Attempting to continue without..." }
|
29
|
+
error_logger.error(e)
|
30
|
+
end
|
31
|
+
data
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Escort
|
4
|
+
module Setup
|
5
|
+
module Configuration
|
6
|
+
class Writer
|
7
|
+
attr_reader :path, :data
|
8
|
+
|
9
|
+
def initialize(path, data)
|
10
|
+
@path = path
|
11
|
+
@data = data
|
12
|
+
end
|
13
|
+
|
14
|
+
def write
|
15
|
+
if path && !File.exists?(path)
|
16
|
+
save_to_file
|
17
|
+
Instance.new(path, data)
|
18
|
+
else
|
19
|
+
Instance.blank
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def update
|
24
|
+
current_data = {}
|
25
|
+
if File.exists? path
|
26
|
+
current_data = Reader.new(path).read.data
|
27
|
+
end
|
28
|
+
@data = Escort::Setup::Configuration::MergeTool.new(data, current_data).config_hash
|
29
|
+
save_to_file
|
30
|
+
Instance.new(path, data)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def save_to_file
|
36
|
+
current_path = File.expand_path(path)
|
37
|
+
File.open(current_path,"w") do |f|
|
38
|
+
f.puts JSON.pretty_generate(data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Dsl
|
4
|
+
class Action
|
5
|
+
class << self
|
6
|
+
def action(command_name, instance, &block)
|
7
|
+
instance.instance_variable_set(:"@block", block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(command_name = :global)
|
12
|
+
@command_name = command_name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Dsl
|
4
|
+
class Command
|
5
|
+
def initialize(name, options = {}, &block)
|
6
|
+
reset(name)
|
7
|
+
@name = name
|
8
|
+
@description = options[:description] || options[:desc] || ""
|
9
|
+
@aliases = [options[:aliases] || []].flatten
|
10
|
+
@requires_arguments ||= options[:requires_arguments]
|
11
|
+
block.call(self) if block_given?
|
12
|
+
rescue => e
|
13
|
+
raise Escort::ClientError.new("Problem with syntax of command '#{@name}' configuration", e)
|
14
|
+
end
|
15
|
+
|
16
|
+
def options(&block)
|
17
|
+
Options.options(@name, @options, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def action(&block)
|
21
|
+
Action.action(@name, @action, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def validations(&block)
|
25
|
+
Validations.validations(@name, @validations, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def command(name, options = {}, &block)
|
29
|
+
options[:requires_arguments] = @requires_arguments
|
30
|
+
command = Command.new(name.to_sym, options, &block)
|
31
|
+
aliases = [options[:aliases] || []].flatten + [name]
|
32
|
+
aliases.each do |name|
|
33
|
+
@commands[name.to_sym] = command
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def requires_arguments(boolean = true)
|
38
|
+
#TODO raise a client error if the value is anything besides true or false
|
39
|
+
@requires_arguments = boolean
|
40
|
+
@commands.each do |command|
|
41
|
+
command.requires_arguments(boolean)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def conflicting_options(*command_names)
|
46
|
+
@conflicts << command_names
|
47
|
+
end
|
48
|
+
|
49
|
+
def summary(summary)
|
50
|
+
@summary = summary
|
51
|
+
end
|
52
|
+
|
53
|
+
def description(description)
|
54
|
+
@description = description
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def reset(name)
|
60
|
+
@requires_arguments = false
|
61
|
+
@commands = {}
|
62
|
+
@options = Options.new(name)
|
63
|
+
@action = Action.new(name)
|
64
|
+
@validations = Validations.new(name)
|
65
|
+
@name = nil
|
66
|
+
@description = nil
|
67
|
+
@aliases = []
|
68
|
+
@conflicts = []
|
69
|
+
@summary = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Escort
|
2
|
+
module Setup
|
3
|
+
module Dsl
|
4
|
+
class Global
|
5
|
+
def initialize(&block)
|
6
|
+
reset
|
7
|
+
block.call(self)
|
8
|
+
rescue => e
|
9
|
+
raise Escort::ClientError.new("Problem with syntax of global configuration", e)
|
10
|
+
end
|
11
|
+
|
12
|
+
def options(&block)
|
13
|
+
Options.options(:global, @options, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def action(&block)
|
17
|
+
Action.action(:global, @action, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def validations(&block)
|
21
|
+
Validations.validations(:global, @validations, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def command(name, options = {}, &block)
|
25
|
+
options[:requires_arguments] = @requires_arguments
|
26
|
+
command = Command.new(name.to_sym, options, &block)
|
27
|
+
aliases = [options[:aliases] || []].flatten + [name]
|
28
|
+
aliases.each do |name|
|
29
|
+
@commands[name.to_sym] = command
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def requires_arguments(boolean = true)
|
34
|
+
#TODO raise a client error if the value is anything besides true or false
|
35
|
+
@requires_arguments = boolean
|
36
|
+
@commands.each do |command|
|
37
|
+
command.requires_arguments(boolean)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def config_file(name, options = {})
|
42
|
+
@config_file = ConfigFile.new(name, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def version(version)
|
46
|
+
@version = version
|
47
|
+
end
|
48
|
+
|
49
|
+
def summary(summary)
|
50
|
+
@summary = summary
|
51
|
+
end
|
52
|
+
|
53
|
+
def description(description)
|
54
|
+
@description = description
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def conflicting_options(*command_names)
|
59
|
+
@conflicts << command_names
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def reset
|
65
|
+
@version = nil
|
66
|
+
@summary = nil
|
67
|
+
@description = nil
|
68
|
+
@commands = {}
|
69
|
+
@requires_arguments = false
|
70
|
+
@options = Options.new
|
71
|
+
@action = Action.new
|
72
|
+
@validations = Validations.new
|
73
|
+
@config_file = nil
|
74
|
+
@conflicts = []
|
75
|
+
end
|
76
|
+
|
77
|
+
def set_instance_variable_on(instance, instance_variable, value)
|
78
|
+
instance_variable_symbol = :"@#{instance_variable.to_s}"
|
79
|
+
instance.instance_variable_set(instance_variable_symbol, value)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|