commandable 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rbenv-gemsets +2 -0
- data/{README.markdown → README.md} +64 -64
- data/Rakefile +2 -6
- data/bin/commandable +1 -0
- data/commandable.gemspec +1 -3
- data/lib/commandable.rb +15 -2
- data/lib/commandable/app_controller.rb +21 -39
- data/lib/commandable/argument_parser.rb +45 -0
- data/lib/commandable/coloring.rb +100 -0
- data/lib/commandable/command_parser.rb +139 -0
- data/lib/commandable/controller.rb +24 -0
- data/lib/commandable/exceptions.rb +1 -1
- data/lib/commandable/execution_controller.rb +122 -0
- data/lib/commandable/help_text.rb +73 -0
- data/lib/commandable/version.rb +4 -3
- data/spec/commandable/app_controller_spec.rb +2 -30
- data/spec/commandable/coloring_spec.rb +96 -0
- data/spec/commandable/command_line_execution_spec.rb +7 -1
- data/spec/commandable/execution_controller_spec.rb +17 -0
- data/spec/commandable/help_generator_spec.rb +5 -66
- data/spec/source_code_examples/parameter_class.rb +4 -0
- data/spec/source_code_for_errors/private_methods_bad.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +20 -38
- data/features/ansicolor.feature +0 -11
- data/features/copy_examples.feature +0 -13
- data/features/download_widget.feature +0 -18
- data/features/setup/env.rb +0 -5
- data/features/step-definitions/step-definitions.rb +0 -15
- data/lib/commandable/commandable.rb +0 -447
- data/lib/monkey_patch/file_utils.rb +0 -11
@@ -50,6 +50,12 @@ describe Commandable do
|
|
50
50
|
lambda{execute_queue(["fly", "navy"])}.should raise_error(Commandable::UnknownCommandError)
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
context "and even when silent is on" do
|
55
|
+
it "prints usage/help instructions" do
|
56
|
+
capture_output{Commandable.execute(["help"], :silent)}.to_s.should match(/Usage:/)
|
57
|
+
end
|
58
|
+
end
|
53
59
|
|
54
60
|
context "and running commands automatically via execute" do
|
55
61
|
|
@@ -179,4 +185,4 @@ describe Commandable do
|
|
179
185
|
|
180
186
|
end
|
181
187
|
|
182
|
-
end
|
188
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commandable do
|
4
|
+
|
5
|
+
before(:each) {Commandable.reset_all}
|
6
|
+
|
7
|
+
context "when using Commandable directly" do
|
8
|
+
|
9
|
+
before(:each) { load 'test_class.rb' }
|
10
|
+
|
11
|
+
it "should use ARGV by default" do
|
12
|
+
expect {capture_output{Commandable.execute()}}.should_not raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -69,7 +69,10 @@ describe Commandable do
|
|
69
69
|
|
70
70
|
it "adds (default) to the end of the default command description when printing" do
|
71
71
|
load 'default_method.rb'
|
72
|
-
Commandable.
|
72
|
+
Commandable.color_output = true
|
73
|
+
Commandable.help.join.should match(/\(default\)/)
|
74
|
+
Commandable.color_output = false
|
75
|
+
Commandable.help.join.should match(/\(default\)/)
|
73
76
|
end
|
74
77
|
|
75
78
|
context "and there are no parameters" do
|
@@ -85,7 +88,6 @@ describe Commandable do
|
|
85
88
|
execute_output_s([]).should_not match(/\[parameters\]/)
|
86
89
|
end
|
87
90
|
|
88
|
-
|
89
91
|
end
|
90
92
|
|
91
93
|
context "and there is a new line in a description" do
|
@@ -104,67 +106,4 @@ describe Commandable do
|
|
104
106
|
|
105
107
|
end
|
106
108
|
|
107
|
-
|
108
|
-
|
109
|
-
before(:each) do
|
110
|
-
load 'private_methods_bad.rb'
|
111
|
-
Commandable.app_exe = "mycoolapp"
|
112
|
-
Commandable.app_info =
|
113
|
-
""" My Cool App - It does stuff and things!
|
114
|
-
Copyright (c) 2011 Acme Inc."""
|
115
|
-
end
|
116
|
-
|
117
|
-
let(:c) {Term::ANSIColorHI}
|
118
|
-
|
119
|
-
it "changes the output if color is enabled" do
|
120
|
-
Commandable.color_output = false
|
121
|
-
lambda {Commandable.color_output = true}.should change{Commandable.help}
|
122
|
-
end
|
123
|
-
|
124
|
-
it "resets text to plain if colors are turned off" do
|
125
|
-
Commandable.color_output = true
|
126
|
-
lambda {Commandable.color_output = false}.should change{Commandable.help}
|
127
|
-
end
|
128
|
-
|
129
|
-
context "when a specific setting's color is changed" do
|
130
|
-
|
131
|
-
before(:each) { Commandable.color_output = true }
|
132
|
-
|
133
|
-
# This seems ripe for meta-zation
|
134
|
-
context "when app_info is changed" do
|
135
|
-
specify {lambda {Commandable.color_app_info = c.black}.should change{Commandable.help}}
|
136
|
-
end
|
137
|
-
|
138
|
-
context "when app_exe is changed" do
|
139
|
-
specify {lambda {Commandable.color_app_exe = c.black}.should change{Commandable.help}}
|
140
|
-
end
|
141
|
-
|
142
|
-
context "when color_command is changed" do
|
143
|
-
specify {lambda {Commandable.color_command = c.black}.should change{Commandable.help}}
|
144
|
-
end
|
145
|
-
|
146
|
-
context "when color_description is changed" do
|
147
|
-
specify {lambda {Commandable.color_description = c.black}.should change{Commandable.help}}
|
148
|
-
end
|
149
|
-
|
150
|
-
context "when color_parameter is changed" do
|
151
|
-
specify {lambda {Commandable.color_parameter = c.black}.should change{Commandable.help}}
|
152
|
-
end
|
153
|
-
|
154
|
-
context "when color_usage is changed" do
|
155
|
-
specify {lambda {Commandable.color_usage = c.black}.should change{Commandable.help}}
|
156
|
-
end
|
157
|
-
|
158
|
-
context "when there is an error" do
|
159
|
-
|
160
|
-
specify { lambda {Commandable.color_error_word = c.magenta}.should change{capture_output{Commandable.execute(["fly", "navy"])}}}
|
161
|
-
specify { lambda {Commandable.color_error_name = c.intense_red}.should change{capture_output{Commandable.execute(["fly", "navy"])}}}
|
162
|
-
specify { lambda {Commandable.color_error_description = c.black + c.bold}.should change{capture_output{Commandable.execute(["fly", "navy"])}}}
|
163
|
-
|
164
|
-
end
|
165
|
-
|
166
|
-
end
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
end
|
109
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require "commandable"
|
2
2
|
|
3
|
-
# This is just a not so clever way of getting at the instance methods of Commandable
|
3
|
+
# This is just a not so clever way of getting at the instance methods of Commandable.
|
4
4
|
# Accessing the private methods of a class/module is a bad idea but I really need to
|
5
5
|
# test them. Plus making a helper module just to test them is also against best practices
|
6
6
|
# so...
|
data/spec/spec_helper.rb
CHANGED
@@ -12,7 +12,7 @@ require 'commandable'
|
|
12
12
|
|
13
13
|
# A note on the specs:
|
14
14
|
# Since Commandable uses singletons the tests sometimes get confused about their state.
|
15
|
-
# I'm working on a way to properly clear
|
15
|
+
# I'm working on a way to properly clear everything out of memory before each test but tests
|
16
16
|
# doesn't pass in autotest try running the test again or run rspec yourself and they'll pass.
|
17
17
|
|
18
18
|
# Debug print
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: commandable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-02-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name: term-ansicolor
|
16
|
-
requirement: &
|
15
|
+
name: term-ansicolor
|
16
|
+
requirement: &70273995390760 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.0.7
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70273995390760
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70273995389900 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,29 +32,7 @@ dependencies:
|
|
32
32
|
version: '2.5'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
|
-
name: cucumber
|
38
|
-
requirement: &2160864460 !ruby/object:Gem::Requirement
|
39
|
-
none: false
|
40
|
-
requirements:
|
41
|
-
- - ~>
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: '0.10'
|
44
|
-
type: :development
|
45
|
-
prerelease: false
|
46
|
-
version_requirements: *2160864460
|
47
|
-
- !ruby/object:Gem::Dependency
|
48
|
-
name: aruba
|
49
|
-
requirement: &2160863860 !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
|
-
requirements:
|
52
|
-
- - ~>
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0.3'
|
55
|
-
type: :development
|
56
|
-
prerelease: false
|
57
|
-
version_requirements: *2160863860
|
35
|
+
version_requirements: *70273995389900
|
58
36
|
description: ! 'The easiest way to add command line control to your Ruby app. You
|
59
37
|
can add a single line above an existing method and that method will be available
|
60
38
|
from the command line.
|
@@ -73,29 +51,31 @@ files:
|
|
73
51
|
- .gemrc
|
74
52
|
- .gemtest
|
75
53
|
- .gitignore
|
54
|
+
- .rbenv-gemsets
|
76
55
|
- BUGS.txt
|
77
56
|
- Gemfile
|
78
57
|
- LICENCE
|
79
|
-
- README.
|
58
|
+
- README.md
|
80
59
|
- Rakefile
|
81
60
|
- autotest/discover.rb
|
82
61
|
- bin/commandable
|
83
62
|
- commandable.gemspec
|
84
|
-
- features/ansicolor.feature
|
85
|
-
- features/copy_examples.feature
|
86
|
-
- features/download_widget.feature
|
87
|
-
- features/setup/env.rb
|
88
|
-
- features/step-definitions/step-definitions.rb
|
89
63
|
- lib/commandable.rb
|
90
64
|
- lib/commandable/app_controller.rb
|
91
|
-
- lib/commandable/
|
65
|
+
- lib/commandable/argument_parser.rb
|
66
|
+
- lib/commandable/coloring.rb
|
67
|
+
- lib/commandable/command_parser.rb
|
68
|
+
- lib/commandable/controller.rb
|
92
69
|
- lib/commandable/exceptions.rb
|
70
|
+
- lib/commandable/execution_controller.rb
|
71
|
+
- lib/commandable/help_text.rb
|
93
72
|
- lib/commandable/version.rb
|
94
|
-
- lib/monkey_patch/file_utils.rb
|
95
73
|
- spec/commandable/app_controller_spec.rb
|
96
74
|
- spec/commandable/attr_accessor_spec.rb
|
75
|
+
- spec/commandable/coloring_spec.rb
|
97
76
|
- spec/commandable/command_line_execution_spec.rb
|
98
77
|
- spec/commandable/commandable_spec.rb
|
78
|
+
- spec/commandable/execution_controller_spec.rb
|
99
79
|
- spec/commandable/help_generator_spec.rb
|
100
80
|
- spec/commandable/helpers_spec.rb
|
101
81
|
- spec/commandable/instance_methods_spec.rb
|
@@ -151,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
131
|
version: '0'
|
152
132
|
requirements: []
|
153
133
|
rubyforge_project:
|
154
|
-
rubygems_version: 1.8.
|
134
|
+
rubygems_version: 1.8.15
|
155
135
|
signing_key:
|
156
136
|
specification_version: 3
|
157
137
|
summary: The easiest way to add command line control to your Ruby apps.
|
@@ -159,8 +139,10 @@ test_files:
|
|
159
139
|
- autotest/discover.rb
|
160
140
|
- spec/commandable/app_controller_spec.rb
|
161
141
|
- spec/commandable/attr_accessor_spec.rb
|
142
|
+
- spec/commandable/coloring_spec.rb
|
162
143
|
- spec/commandable/command_line_execution_spec.rb
|
163
144
|
- spec/commandable/commandable_spec.rb
|
145
|
+
- spec/commandable/execution_controller_spec.rb
|
164
146
|
- spec/commandable/help_generator_spec.rb
|
165
147
|
- spec/commandable/helpers_spec.rb
|
166
148
|
- spec/commandable/instance_methods_spec.rb
|
data/features/ansicolor.feature
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
Feature: Ansicolor
|
2
|
-
In order to not have horrible looking colors
|
3
|
-
As a programmer
|
4
|
-
I want to make sure I'm using ANSIColorHI
|
5
|
-
And it doesn't conflict with the original ANSIColor
|
6
|
-
|
7
|
-
|
8
|
-
Scenario: Run a feature
|
9
|
-
When I run cucumber
|
10
|
-
Then it doesn't give an error
|
11
|
-
|
@@ -1,13 +0,0 @@
|
|
1
|
-
Feature: Copy Examples
|
2
|
-
In order to learn how to use Commandable
|
3
|
-
As a developer
|
4
|
-
I want to look at some example files
|
5
|
-
|
6
|
-
Scenario: Copy example files from specs into the default directory
|
7
|
-
When I run `commandable examples`
|
8
|
-
Then a directory named "examples" should exist
|
9
|
-
|
10
|
-
Scenario: Copy example files from specs into a specified directory
|
11
|
-
When I run `commandable examples shazam`
|
12
|
-
Then a directory named "shazam" should exist
|
13
|
-
|
@@ -1,18 +0,0 @@
|
|
1
|
-
Feature: Download widget
|
2
|
-
In order to learn how to use Commandable by looking at a fully functional application
|
3
|
-
As a developer
|
4
|
-
I want to download the latest source code for Widget from Github
|
5
|
-
|
6
|
-
Background:
|
7
|
-
Given Git is installed
|
8
|
-
|
9
|
-
@download_widget
|
10
|
-
Scenario: Download widget from github into the default directory
|
11
|
-
When I run `commandable widget`
|
12
|
-
Then a directory named "widget" should exist
|
13
|
-
|
14
|
-
@download_widget
|
15
|
-
Scenario: Download widget from github into a specified directory
|
16
|
-
When I run `commandable widget potato`
|
17
|
-
Then a directory named "potato" should exist
|
18
|
-
|
data/features/setup/env.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
Before do
|
2
|
-
@aruba_timeout_seconds = 30
|
3
|
-
end
|
4
|
-
|
5
|
-
When /^I run cucumber$/ do
|
6
|
-
# These are just to see color output is working
|
7
|
-
end
|
8
|
-
|
9
|
-
Then /^it doesn't give an error$/ do
|
10
|
-
# These are just to see color output is working
|
11
|
-
end
|
12
|
-
|
13
|
-
Given /^Git is installed$/ do
|
14
|
-
raise Exception, "\n#{"ATTENION! " * 5}\nYou need git installed to run the Cucumber tests!\n\n" if `which git`.empty?
|
15
|
-
end
|
@@ -1,447 +0,0 @@
|
|
1
|
-
require 'term/ansicolorhi'
|
2
|
-
require 'set'
|
3
|
-
|
4
|
-
# This library allows you to incredibly easily make
|
5
|
-
# your methods directly available from the command line.
|
6
|
-
#
|
7
|
-
# Author:: Mike Bethany (mailto:mikbe.tk@gmail.com)
|
8
|
-
# Copyright:: Copyright (c) 2011 Mike Bethany
|
9
|
-
# License:: Distributed under the MIT licence (See LICENCE file)
|
10
|
-
|
11
|
-
# Extending your class with this module allows you to use the #command
|
12
|
-
# method above your method. This makes them executable from the command line.
|
13
|
-
module Commandable
|
14
|
-
|
15
|
-
# Default command that always gets added to end of the command list
|
16
|
-
HELP_COMMAND = {:help => {:description => "you're looking at it now", :argument_list => "", :class=>"Commandable", :class_method=>true}}
|
17
|
-
|
18
|
-
class << self
|
19
|
-
|
20
|
-
# Describes your application, printed at the top of help/usage messages
|
21
|
-
attr_accessor :app_info
|
22
|
-
|
23
|
-
# Used when building the usage line, e.g. Usage: app_exe [command] [parameters]
|
24
|
-
attr_accessor :app_exe
|
25
|
-
|
26
|
-
# If optional parameters show default values, true by default
|
27
|
-
attr_accessor :verbose_parameters
|
28
|
-
|
29
|
-
# Boolean: If help/usage messages will print in color
|
30
|
-
attr_accessor :color_output
|
31
|
-
# What color the app_info text will be in the help message
|
32
|
-
attr_accessor :color_app_info
|
33
|
-
# What color the app_exe will be in the usage line in the help message
|
34
|
-
attr_accessor :color_app_exe
|
35
|
-
# What color the word "command" and the commands themselves will be in the help message
|
36
|
-
attr_accessor :color_command
|
37
|
-
# What color the description column header and text will be in the help message
|
38
|
-
attr_accessor :color_description
|
39
|
-
# What color the word "parameter" and the parameters themselves will be in the help message
|
40
|
-
attr_accessor :color_parameter
|
41
|
-
# What color the word "Usage:" will be in the help message
|
42
|
-
attr_accessor :color_usage
|
43
|
-
|
44
|
-
# What color the word "Error:" text will be in error messages
|
45
|
-
attr_accessor :color_error_word
|
46
|
-
# What color the friendly name of the error will be in error messages
|
47
|
-
attr_accessor :color_error_name
|
48
|
-
# What color the error description will be in error messages
|
49
|
-
attr_accessor :color_error_description
|
50
|
-
|
51
|
-
# An array of methods that can be executed from the command line
|
52
|
-
def commands
|
53
|
-
@@commands.dup
|
54
|
-
end
|
55
|
-
|
56
|
-
# A hash of instances created when calling instance methods
|
57
|
-
# It's keyed using the class name: {"ClassName"=>#<ClassName:0x00000100b1f188>}
|
58
|
-
def class_cache
|
59
|
-
@@class_cache
|
60
|
-
end
|
61
|
-
|
62
|
-
# Access the command array using the method name (symbol or string)
|
63
|
-
def [](index)
|
64
|
-
raise AccessorError unless index.is_a? String or index.is_a? Symbol
|
65
|
-
@@commands[index.to_sym]
|
66
|
-
end
|
67
|
-
|
68
|
-
# Resets the class to default values clearing any commands
|
69
|
-
# and setting the color back to their default values.
|
70
|
-
def reset_all
|
71
|
-
clear_commands
|
72
|
-
reset_colors
|
73
|
-
@app_info = nil
|
74
|
-
@app_exe = nil
|
75
|
-
@verbose_parameters = true
|
76
|
-
@@default_method = nil
|
77
|
-
@@class_cache = {}
|
78
|
-
end
|
79
|
-
|
80
|
-
# Clears all methods from the list of available commands
|
81
|
-
# This is mostly useful for testing.
|
82
|
-
def clear_commands
|
83
|
-
@@commands = HELP_COMMAND.dup
|
84
|
-
end
|
85
|
-
|
86
|
-
# Convenience method to iterate over the array of commands using the Commandable module
|
87
|
-
def each(&block)
|
88
|
-
@@commands.each do |key, value|
|
89
|
-
yield key => value
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# Generates an array of the available commands with a
|
94
|
-
# list of their parameters and the method's description.
|
95
|
-
# This includes the applicaiton info and app name if given.
|
96
|
-
# It's meant to be printed to the command line.
|
97
|
-
def help(additional_info=nil)
|
98
|
-
|
99
|
-
set_colors
|
100
|
-
|
101
|
-
cmd_length = "Command".length
|
102
|
-
parm_length = "Parameters".length
|
103
|
-
max_command = [(@@commands.keys.max_by{|key| key.to_s.length }).to_s.length, cmd_length].max
|
104
|
-
max_parameter = @@commands[@@commands.keys.max_by{|key| @@commands[key][:argument_list].length }][:argument_list].length
|
105
|
-
max_parameter = [parm_length, max_parameter].max if max_parameter > 0
|
106
|
-
|
107
|
-
usage_text = " #{@c_usage}Usage:#{@c_reset} "
|
108
|
-
|
109
|
-
if Commandable.app_exe
|
110
|
-
cmd_text = "<#{@c_command + @c_bold}command#{@c_reset}>"
|
111
|
-
parm_text = " [#{@c_parameter + @c_bold}parameters#{@c_reset}]" if max_parameter > 0
|
112
|
-
usage_text += "#{@c_app_exe + app_exe + @c_reset} #{cmd_text}#{parm_text} [#{cmd_text}#{parm_text}...]"
|
113
|
-
end
|
114
|
-
|
115
|
-
array = [usage_text, ""]
|
116
|
-
|
117
|
-
array.unshift additional_info if additional_info
|
118
|
-
array.unshift ("\e[2A" + @c_app_info + Commandable.app_info + @c_reset) if Commandable.app_info
|
119
|
-
array.unshift "\e[H\e[2J"
|
120
|
-
|
121
|
-
header_text = " #{" "*(max_command-cmd_length)}#{@c_command + @c_bold}Command#{@c_reset} "
|
122
|
-
header_text += "#{@c_parameter + @c_bold}Parameters #{@c_reset}#{" "*(max_parameter-parm_length)}" if max_parameter > 0
|
123
|
-
header_text += "#{@c_description + @c_bold}Description#{@c_reset}"
|
124
|
-
|
125
|
-
array << header_text
|
126
|
-
|
127
|
-
array += @@commands.keys.collect do |key|
|
128
|
-
default = (@@default_method and key == @@default_method.keys[0]) ? @color_bold : ""
|
129
|
-
|
130
|
-
help_line = " #{" "*(max_command-key.length)}#{@c_command + default + key.to_s + @c_reset}"+
|
131
|
-
" #{default + @c_parameter + @@commands[key][:argument_list] + @c_reset}"
|
132
|
-
help_line += "#{" "*(max_parameter-@@commands[key][:argument_list].length)} " if max_parameter > 0
|
133
|
-
|
134
|
-
# indent new lines
|
135
|
-
description = @@commands[key][:description].gsub("\n", "\n" + (" "*(max_command + max_parameter + (max_parameter > 0 ? 1 : 0) + 4)))
|
136
|
-
|
137
|
-
help_line += ": #{default + @c_description}#{"<#{@@commands[key][:xor]}> " if @@commands[key][:xor]}" +
|
138
|
-
"#{description}" +
|
139
|
-
"#{" (default)" unless default == ""}#{@c_reset}"
|
140
|
-
end
|
141
|
-
array << nil
|
142
|
-
end
|
143
|
-
|
144
|
-
# A wrapper for the execution_queue that runs the queue and traps errors.
|
145
|
-
# If an error occurs inside this method it will print out a complete list
|
146
|
-
# of availavle methods with usage instructions and exit gracefully.
|
147
|
-
#
|
148
|
-
# If you do not want the output from your methods to be printed out automatically
|
149
|
-
# run the execute command with silent set to anything other than false or nil.
|
150
|
-
def execute(argv, silent=false)
|
151
|
-
begin
|
152
|
-
command_queue = execution_queue(argv)
|
153
|
-
command_queue.each do |com|
|
154
|
-
return_value = com[:proc].call
|
155
|
-
puts return_value unless silent
|
156
|
-
end
|
157
|
-
rescue SystemExit => kernel_exit
|
158
|
-
Kernel.exit kernel_exit.status
|
159
|
-
rescue Exception => exception
|
160
|
-
if exception.respond_to?(:friendly_name)
|
161
|
-
set_colors
|
162
|
-
puts help("\n #{@c_error_word}Error:#{@c_reset} #{@c_error_name}#{exception.friendly_name}#{@c_reset}\n #{@c_error_description}#{exception.message}#{@c_reset}\n\n")
|
163
|
-
else
|
164
|
-
puts exception.inspect
|
165
|
-
puts exception.backtrace.collect{|line| " #{line}"}
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# Returns an array of executable procs based on the given array of commands and parameters
|
171
|
-
# Normally this would come from the command line parameters in the ARGV array.
|
172
|
-
def execution_queue(argv)
|
173
|
-
arguments = argv.dup
|
174
|
-
method_hash = {}
|
175
|
-
last_method = nil
|
176
|
-
|
177
|
-
if arguments.empty?
|
178
|
-
arguments << @@default_method.keys[0] if @@default_method and !@@default_method.values[0][:parameters].flatten.include?(:req)
|
179
|
-
end
|
180
|
-
arguments << "help" if arguments.empty?
|
181
|
-
|
182
|
-
# Parse the command line into methods and their parameters
|
183
|
-
|
184
|
-
arguments.each do |arg|
|
185
|
-
if Commandable[arg]
|
186
|
-
last_method = arg.to_sym
|
187
|
-
method_hash.merge!(last_method=>[])
|
188
|
-
else
|
189
|
-
unless last_method
|
190
|
-
default = find_by_subkey(:default)
|
191
|
-
|
192
|
-
# Raise an error if there is no default method and the first item isn't a method
|
193
|
-
raise UnknownCommandError, arguments.first if default.empty?
|
194
|
-
|
195
|
-
# Raise an error if there is a default method but it doesn't take any parameters
|
196
|
-
raise UnknownCommandError, (arguments.first) if default.values[0][:argument_list] == ""
|
197
|
-
|
198
|
-
last_method = default.keys.first
|
199
|
-
method_hash.merge!(last_method=>[])
|
200
|
-
end
|
201
|
-
method_hash[last_method] << arg
|
202
|
-
end
|
203
|
-
# Test for missing required switches
|
204
|
-
@@commands.select do |key, value|
|
205
|
-
if value[:required] and method_hash[key].nil?
|
206
|
-
# If the required switch is also a default have the error be a missing parameter instead of a missing command
|
207
|
-
if value[:default]
|
208
|
-
method_hash.merge!(key=>[])
|
209
|
-
else
|
210
|
-
raise MissingRequiredCommandError, key
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
#puts "method_hash: #{method_hash}" if method_hash.to_s.include?("required_default")
|
216
|
-
|
217
|
-
# Build an array of procs to be called for each method and its given parameters
|
218
|
-
proc_array = []
|
219
|
-
method_hash.each do |meth, params|
|
220
|
-
command = @@commands[meth]
|
221
|
-
|
222
|
-
if command[:parameters] && !command[:parameters].empty?
|
223
|
-
|
224
|
-
#Change the method name for attr_writers
|
225
|
-
meth = "#{meth}=" if command[:parameters][0][0] == :writer
|
226
|
-
|
227
|
-
# Get a list of required parameters and make sure all of them were provided
|
228
|
-
required = command[:parameters].select{|param| [:req, :writer].include?(param[0])}
|
229
|
-
required.shift(params.count)
|
230
|
-
raise MissingRequiredParameterError, {:method=>meth, :parameters=>required.collect!{|meth| meth[1]}.to_s[1...-1].gsub(":",""), :default=>command[:default]} unless required.empty?
|
231
|
-
end
|
232
|
-
|
233
|
-
# Test for duplicate XORs
|
234
|
-
proc_array.select{|x| x[:xor] and x[:xor]==command[:xor] }.each {|bad| raise ExclusiveMethodClashError, "#{meth}, #{bad[:method]}"}
|
235
|
-
|
236
|
-
klass = Object
|
237
|
-
command[:class].split(/::/).each { |name| klass = klass.const_get(name) }
|
238
|
-
## Look for class in class cache
|
239
|
-
unless command[:class_method]
|
240
|
-
klass = (@@class_cache[klass.name] ||= klass.new)
|
241
|
-
end
|
242
|
-
proc_array << {:method=>meth, :xor=>command[:xor], :parameters=>params, :priority=>command[:priority], :proc=>lambda{klass.send(meth, *params)}}
|
243
|
-
end
|
244
|
-
proc_array.sort{|a,b| a[:priority] <=> b[:priority]}.reverse
|
245
|
-
|
246
|
-
end
|
247
|
-
|
248
|
-
# Set colors to their default values
|
249
|
-
def reset_colors
|
250
|
-
@color_output ||= true
|
251
|
-
|
252
|
-
# Build the default colors
|
253
|
-
Term::ANSIColorHI.coloring = color_output
|
254
|
-
c = Term::ANSIColorHI
|
255
|
-
@color_app_info = c.intense_white + c.bold
|
256
|
-
@color_app_exe = c.intense_green + c.bold
|
257
|
-
@color_command = c.intense_yellow
|
258
|
-
@color_description = c.intense_white
|
259
|
-
@color_parameter = c.intense_cyan
|
260
|
-
@color_usage = c.intense_black + c.bold
|
261
|
-
|
262
|
-
@color_error_word = c.intense_black + c.bold
|
263
|
-
@color_error_name = c.intense_red + c.bold
|
264
|
-
@color_error_description = c.intense_white + c.bold
|
265
|
-
|
266
|
-
@color_bold = c.bold
|
267
|
-
@color_reset = c.reset
|
268
|
-
@screen_clear = "\e[H\e[2J"
|
269
|
-
end
|
270
|
-
|
271
|
-
private
|
272
|
-
|
273
|
-
# Look through commands for a specific subkey
|
274
|
-
def find_by_subkey(key, value=true)
|
275
|
-
@@commands.select {|meth, meth_value| meth_value[key]==value}
|
276
|
-
end
|
277
|
-
|
278
|
-
# Changes the colors used when print the help/usage instructions to those set by the user.
|
279
|
-
def set_colors
|
280
|
-
if color_output
|
281
|
-
@c_app_info = @color_app_info
|
282
|
-
@c_app_exe = @color_app_exe
|
283
|
-
@c_command = @color_command
|
284
|
-
@c_description = @color_description
|
285
|
-
@c_parameter = @color_parameter
|
286
|
-
@c_usage = @color_usage
|
287
|
-
|
288
|
-
@c_error_word = @color_error_word
|
289
|
-
@c_error_name = @color_error_name
|
290
|
-
@c_error_description = @color_error_description
|
291
|
-
|
292
|
-
@c_bold = @color_bold
|
293
|
-
@c_reset = @color_reset
|
294
|
-
else
|
295
|
-
@c_app_info, @c_app_exe, @c_command, @c_description,
|
296
|
-
@c_parameter, @c_usage, @c_bold, @c_reset, @c_error_word,
|
297
|
-
@c_error_name, @c_error_description = [""]*12
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
end
|
302
|
-
|
303
|
-
# inititialize the Commandable's settings when it's loaded
|
304
|
-
reset_all
|
305
|
-
|
306
|
-
private
|
307
|
-
|
308
|
-
# This is where the magic happens!
|
309
|
-
# It lets you add a method to the list of command line methods
|
310
|
-
def command(*cmd_parameters)
|
311
|
-
|
312
|
-
@@attribute = nil
|
313
|
-
@@method_file = nil
|
314
|
-
@@method_line = nil
|
315
|
-
@@command_options = {}
|
316
|
-
|
317
|
-
# Include Commandable in singleton classes so class level methods work
|
318
|
-
include Commandable unless self.include? Commandable
|
319
|
-
|
320
|
-
# parse command parameters
|
321
|
-
while (param = cmd_parameters.shift)
|
322
|
-
case param
|
323
|
-
when Symbol
|
324
|
-
if param == :xor
|
325
|
-
@@command_options.merge!(param=>:xor)
|
326
|
-
else
|
327
|
-
@@command_options.merge!(param=>true)
|
328
|
-
end
|
329
|
-
when Hash
|
330
|
-
@@command_options.merge!(param)
|
331
|
-
when String
|
332
|
-
@@command_options.merge!(:description=>param)
|
333
|
-
end
|
334
|
-
end
|
335
|
-
@@command_options[:priority] ||= 0
|
336
|
-
|
337
|
-
# only one default allowed
|
338
|
-
raise ConfigurationError, "Only one default method is allowed." if @@default_method and @@command_options[:default]
|
339
|
-
|
340
|
-
set_trace_func proc { |event, file, line, id, binding, classname|
|
341
|
-
|
342
|
-
@@attribute = id if [:attr_accessor, :attr_writer].include?(id)
|
343
|
-
|
344
|
-
# Traps the line where the method is defined so we can look up
|
345
|
-
# the method source code later if there are optional parameters
|
346
|
-
if event == "line" and !@@method_file
|
347
|
-
@@method_file = file
|
348
|
-
@@method_line = line
|
349
|
-
end
|
350
|
-
|
351
|
-
# Raise an error if there is no method following a command definition
|
352
|
-
if event == "end"
|
353
|
-
set_trace_func(nil)
|
354
|
-
raise SyntaxError, "A command was specified but no method follows"
|
355
|
-
end
|
356
|
-
}
|
357
|
-
end
|
358
|
-
|
359
|
-
# Add a method to the list of available command line methods
|
360
|
-
def add_command(meth)
|
361
|
-
@@commands.delete(:help)
|
362
|
-
|
363
|
-
if @@attribute
|
364
|
-
argument_list = "value"
|
365
|
-
meth = meth.to_s.delete("=").to_sym if @@attribute == :attr_writer
|
366
|
-
else
|
367
|
-
argument_list = parse_arguments(@@command_options[:parameters])
|
368
|
-
end
|
369
|
-
@@command_options.merge!(:argument_list=>argument_list,:class => self.name)
|
370
|
-
|
371
|
-
@@commands.merge!(meth => @@command_options)
|
372
|
-
@@default_method = {meth => @@command_options} if @@command_options[:default]
|
373
|
-
|
374
|
-
@@commands.sort.each {|com| @@commands.merge!(com[0]=>@@commands.delete(com[0]))}
|
375
|
-
|
376
|
-
@@commands.merge!(HELP_COMMAND.dup) # makes sure the help command is always last
|
377
|
-
@@command_options = nil
|
378
|
-
@@attribute = nil
|
379
|
-
end
|
380
|
-
|
381
|
-
# Trap method creation after a command call
|
382
|
-
def method_added(meth)
|
383
|
-
set_trace_func(nil)
|
384
|
-
return super(meth) if meth == :initialize || @@command_options == nil
|
385
|
-
|
386
|
-
if @@attribute
|
387
|
-
#synthesize parameter
|
388
|
-
@@command_options.merge!(:parameters=>[[:writer, :value]],:class_method=>false)
|
389
|
-
else
|
390
|
-
# create parameter
|
391
|
-
@@command_options.merge!(:parameters=>self.instance_method(meth).parameters,:class_method=>false)
|
392
|
-
end
|
393
|
-
|
394
|
-
add_command(meth)
|
395
|
-
end
|
396
|
-
|
397
|
-
# Trap class methods too
|
398
|
-
def singleton_method_added(meth)
|
399
|
-
set_trace_func(nil)
|
400
|
-
return super(meth) if meth == :initialize || @@command_options == nil
|
401
|
-
@@command_options.merge!(:parameters=>method(meth).parameters, :class_method=>true)
|
402
|
-
add_command(meth)
|
403
|
-
end
|
404
|
-
|
405
|
-
# Parse a method's parameters building the argument list for printing help/usage
|
406
|
-
def parse_arguments(parameters)
|
407
|
-
parameter_string = ""
|
408
|
-
method_definition = nil
|
409
|
-
parameters.each do |parameter|
|
410
|
-
arg_type = parameter[0]
|
411
|
-
arg = parameter[1]
|
412
|
-
case arg_type
|
413
|
-
when :req
|
414
|
-
parameter_string += " #{arg}"
|
415
|
-
when :opt
|
416
|
-
if Commandable.verbose_parameters
|
417
|
-
# figure out what the default value is
|
418
|
-
method_definition ||= readline(@@method_file, @@method_line)
|
419
|
-
default = parse_optional(method_definition, arg)
|
420
|
-
parameter_string += " [#{arg}=#{default}]"
|
421
|
-
else
|
422
|
-
parameter_string += " [#{arg}]"
|
423
|
-
end
|
424
|
-
when :rest
|
425
|
-
parameter_string += " *#{arg}"
|
426
|
-
when :block
|
427
|
-
parameter_string += " &#{arg}"
|
428
|
-
end
|
429
|
-
end
|
430
|
-
parameter_string.strip
|
431
|
-
end
|
432
|
-
|
433
|
-
# Reads a line from a source code file.
|
434
|
-
def readline(file, line_number)
|
435
|
-
current_line = 0
|
436
|
-
File.open(file).each do |line_text|
|
437
|
-
current_line += 1
|
438
|
-
return line_text.strip if current_line == line_number
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
# Parses a method defition for the optional values of given argument.
|
443
|
-
def parse_optional(method_def, argument)
|
444
|
-
method_def.scan(/#{argument}\s*=\s*("[^"\r\n]*"|'[^'\r\n]*'|[0-9]*)/)[0][0]
|
445
|
-
end
|
446
|
-
|
447
|
-
end
|