linen 0.3.1 → 0.3.2
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/lib/indifferent_hash.rb +9 -9
- data/lib/linen.rb +8 -8
- data/lib/linen/argument.rb +21 -21
- data/lib/linen/cli.rb +214 -214
- data/lib/linen/command.rb +118 -118
- data/lib/linen/exceptions.rb +13 -13
- data/lib/linen/plugin.rb +63 -63
- data/lib/linen/plugin_registry.rb +38 -38
- data/lib/linen/workspace.rb +15 -15
- data/lib/string_extensions.rb +3 -3
- data/test/test_cli.rb +72 -72
- data/test/test_indifferent_hash.rb +26 -26
- data/test/test_plugins.rb +35 -35
- metadata +1 -1
data/lib/linen/command.rb
CHANGED
@@ -8,171 +8,171 @@
|
|
8
8
|
##############################################################
|
9
9
|
|
10
10
|
class Linen::Plugin::Command
|
11
|
-
|
11
|
+
attr_reader :name
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
def initialize( plugin, name, &block )
|
14
|
+
@plugin = plugin
|
15
|
+
@name = name
|
16
|
+
@arguments = []
|
17
|
+
@help_text = "No help for #{plugin.short_name} #{name}"
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
self.instance_eval &block
|
20
|
+
end
|
21
21
|
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def execute( workspace = Linen::Workspace.new )
|
24
|
+
return workspace.instance_eval( &@action_proc )
|
25
|
+
end
|
26
26
|
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
def help
|
29
|
+
output = []
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
output << @help_text.wrap
|
32
|
+
output << nil # blank line
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
# this map turns our list of args into a list like this:
|
35
|
+
# <arg1> <arg2> <arg3> <arg4>...
|
36
|
+
arg_list = arguments.map {|a| "<#{a.to_s}>"}.join( ' ' )
|
37
37
|
|
38
|
-
|
38
|
+
output << "Usage: #{@plugin.short_name} #{name} #{arg_list}"
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
return output.join( "\n" )
|
41
|
+
end
|
42
42
|
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
#############################
|
45
|
+
# PLUGIN DEFINITION METHODS #
|
46
|
+
#############################
|
47
47
|
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def one_of( *args )
|
50
|
+
raise Linen::Plugin::ArgumentError,
|
51
|
+
"You may not specify both required and one_of arguments" if @argument_type == :required
|
52
52
|
|
53
|
-
|
53
|
+
@argument_type = :one_of
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
args.each do |arg|
|
56
|
+
raise Linen::Plugin::ArgumentError,
|
57
|
+
"Argument '#{arg}' has not been defined" unless @plugin.arguments.include? arg
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
@arguments << arg
|
60
|
+
end
|
61
|
+
end
|
62
62
|
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
def required_arguments( *args )
|
65
|
+
raise Linen::Plugin::ArgumentError,
|
66
|
+
"You may not specify both required and one_of arguments" if @argument_type == :one_of
|
67
67
|
|
68
|
-
|
68
|
+
@argument_type = :required
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
args.each do |arg|
|
71
|
+
raise Linen::Plugin::ArgumentError,
|
72
|
+
"Argument '#{arg}' has not been defined" unless @plugin.arguments.include? arg
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
@arguments << arg
|
75
|
+
end
|
76
|
+
end
|
77
|
+
alias required_argument required_arguments
|
78
78
|
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
80
|
+
def help_message( message )
|
81
|
+
@help_text = message
|
82
|
+
end
|
83
83
|
|
84
84
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
def require_confirmation
|
91
|
-
@require_confirmation = true
|
92
|
-
end
|
85
|
+
def action( &block )
|
86
|
+
@action_proc = block
|
87
|
+
end
|
93
88
|
|
94
89
|
|
95
|
-
|
96
|
-
|
97
|
-
|
90
|
+
def require_confirmation
|
91
|
+
@require_confirmation = true
|
92
|
+
end
|
98
93
|
|
99
|
-
def validate_arguments( args )
|
100
|
-
if @argument_type == :one_of
|
101
|
-
return validate_one_of_arguments( args.first )
|
102
|
-
else # @argument_type == :required
|
103
|
-
return validate_required_arguments( args )
|
104
|
-
end
|
105
94
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
def requires_confirmation?
|
113
|
-
@require_confirmation
|
114
|
-
end
|
95
|
+
##################
|
96
|
+
# HELPER METHODS #
|
97
|
+
##################
|
115
98
|
|
116
|
-
|
117
|
-
|
118
|
-
|
99
|
+
def validate_arguments( args )
|
100
|
+
if @argument_type == :one_of
|
101
|
+
return validate_one_of_arguments( args.first )
|
102
|
+
else # @argument_type == :required
|
103
|
+
return validate_required_arguments( args )
|
104
|
+
end
|
119
105
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
### door, we're going to leave it be for now.
|
106
|
+
# this is a Can't Happen(tm) so I'm comfortable with the crappy
|
107
|
+
# exception string. :P
|
108
|
+
raise "Something has happened!"
|
109
|
+
end
|
125
110
|
|
126
|
-
def validate_one_of_arguments( arg )
|
127
|
-
results = IndifferentHash.new
|
128
111
|
|
129
|
-
|
130
|
-
|
131
|
-
|
112
|
+
def requires_confirmation?
|
113
|
+
@require_confirmation
|
114
|
+
end
|
132
115
|
|
133
|
-
|
134
|
-
|
116
|
+
###########
|
117
|
+
private #
|
118
|
+
###########
|
135
119
|
|
136
|
-
|
137
|
-
|
138
|
-
|
120
|
+
### FIXME:bbleything
|
121
|
+
###
|
122
|
+
### The reprompting stuff below is going to come back to bite us when we try to
|
123
|
+
### add batch mode later... that said, in the interest of getting things out the
|
124
|
+
### door, we're going to leave it be for now.
|
139
125
|
|
140
|
-
|
141
|
-
|
126
|
+
def validate_one_of_arguments( arg )
|
127
|
+
results = IndifferentHash.new
|
142
128
|
|
143
|
-
|
129
|
+
begin
|
130
|
+
@arguments.each do |arg_name|
|
131
|
+
argument = @plugin.arguments[ arg_name ]
|
144
132
|
|
145
|
-
|
146
|
-
|
133
|
+
results[ arg_name ] = argument.validate( arg ) rescue nil
|
134
|
+
end
|
147
135
|
|
148
|
-
|
149
|
-
|
136
|
+
raise Linen::Plugin::ArgumentError,
|
137
|
+
"The value you entered ('#{arg}') is invalid for all arguments." if
|
138
|
+
results.values.compact.empty?
|
150
139
|
|
140
|
+
rescue Linen::Plugin::ArgumentError => e
|
141
|
+
print "#{e} "
|
151
142
|
|
152
|
-
|
153
|
-
results = IndifferentHash.new
|
143
|
+
arg = Linen::CLI.reprompt.split.first
|
154
144
|
|
155
|
-
|
156
|
-
|
157
|
-
arg_value = args.shift
|
145
|
+
retry
|
146
|
+
end
|
158
147
|
|
159
|
-
|
160
|
-
|
148
|
+
return results
|
149
|
+
end
|
161
150
|
|
162
|
-
argument.validate arg_value
|
163
151
|
|
164
|
-
|
165
|
-
|
152
|
+
def validate_required_arguments( args )
|
153
|
+
results = IndifferentHash.new
|
166
154
|
|
167
|
-
|
168
|
-
|
155
|
+
@arguments.each do |arg_name|
|
156
|
+
argument = @plugin.arguments[ arg_name ]
|
157
|
+
arg_value = args.shift
|
169
158
|
|
170
|
-
|
171
|
-
|
172
|
-
results[ arg_name ] = argument.convert( arg_value )
|
173
|
-
end
|
174
|
-
end
|
159
|
+
begin
|
160
|
+
arg_value = Linen::CLI.reprompt( argument.prompt ) if arg_value.nil?
|
175
161
|
|
176
|
-
|
177
|
-
|
162
|
+
argument.validate arg_value
|
163
|
+
|
164
|
+
rescue Linen::Plugin::ArgumentError => e
|
165
|
+
print "#{e} "
|
166
|
+
|
167
|
+
# reset arg_value to nil so we get prompted on retry
|
168
|
+
arg_value = nil
|
169
|
+
|
170
|
+
retry
|
171
|
+
else
|
172
|
+
results[ arg_name ] = argument.convert( arg_value )
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
return results
|
177
|
+
end
|
178
178
|
end
|
data/lib/linen/exceptions.rb
CHANGED
@@ -14,26 +14,26 @@ class Linen::CLI::PluginNotFoundError < NameError ; end
|
|
14
14
|
class Linen::CLI::CommandNotFoundError < NameError ; end
|
15
15
|
|
16
16
|
class Linen::CLI::AbstractAmbiguityError < NameError
|
17
|
-
|
17
|
+
attr_accessor :candidates, :input
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def initialize( candidates = [], input = '' )
|
20
|
+
@candidates = candidates
|
21
|
+
@input = input
|
22
|
+
end
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
def to_s
|
25
|
+
type = self.class.to_s.match( /Ambiguous(.*?)Error/ )[1].downcase
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
return "The #{type} you entered ('#{@input}') was ambiguous; please select from the following: #{@candidates.join ', '}"
|
28
|
+
end
|
29
29
|
end
|
30
30
|
|
31
31
|
class Linen::CLI::AmbiguousPluginError < Linen::CLI::AbstractAmbiguityError ; end
|
32
32
|
|
33
33
|
class Linen::CLI::AmbiguousCommandError < Linen::CLI::AbstractAmbiguityError
|
34
|
-
|
35
|
-
|
34
|
+
def to_s
|
35
|
+
@candidates.map! {|c| [plugin, c].join ' '}
|
36
36
|
|
37
|
-
|
38
|
-
|
37
|
+
super
|
38
|
+
end
|
39
39
|
end
|
data/lib/linen/plugin.rb
CHANGED
@@ -8,94 +8,94 @@
|
|
8
8
|
##############################################################
|
9
9
|
|
10
10
|
class Linen::Plugin
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
class << self
|
12
|
+
attr_reader :commands
|
13
|
+
end
|
14
14
|
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
def self::inherited( plugin )
|
17
|
+
Linen::PluginRegistry.instance.register plugin
|
18
|
+
end
|
19
19
|
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
def self::short_name( short_name = nil )
|
22
|
+
@short_name = short_name if short_name
|
23
|
+
return @short_name || self.to_s.downcase.gsub( /plugin$/, '' )
|
24
|
+
end
|
25
25
|
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
def self::help
|
28
|
+
output = []
|
29
29
|
|
30
|
-
|
30
|
+
desc = description || "No help for #{short_name}"
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
output << desc
|
33
|
+
output << nil #blank line
|
34
|
+
output << "Available commands:"
|
35
|
+
output << nil #blank line
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
commands.each do |name, command|
|
38
|
+
output << "- #{name}"
|
39
|
+
end
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
output << nil #blank line
|
42
|
+
output << "For detailed help on a command, enter \"help #{short_name} <command>\"".wrap
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
return output.join( "\n" )
|
45
|
+
end
|
46
46
|
|
47
47
|
|
48
|
-
|
49
|
-
|
48
|
+
def self::argument( name, opts = {} )
|
49
|
+
@defined_arguments ||= IndifferentHash.new
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
if opts[:error_message].nil? and opts['error_message'].nil?
|
52
|
+
opts[ :error_message ] = "The value for #{name} is invalid."
|
53
|
+
end
|
54
54
|
|
55
|
-
|
56
|
-
|
55
|
+
@defined_arguments[ name ] = Argument.new( self, name, opts )
|
56
|
+
end
|
57
57
|
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
### If args is empty, assume it was called like Plugin.arguments,
|
60
|
+
### so return the hash.
|
61
|
+
def self::arguments( *args )
|
62
|
+
@defined_arguments ||= IndifferentHash.new
|
63
63
|
|
64
|
-
|
64
|
+
return @defined_arguments if args.empty?
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
args.each do |arg|
|
67
|
+
argument arg
|
68
|
+
end
|
69
|
+
end
|
70
70
|
|
71
71
|
|
72
|
-
|
73
|
-
|
72
|
+
def self::command( name, &block )
|
73
|
+
@commands ||= IndifferentHash.new
|
74
74
|
|
75
|
-
|
76
|
-
|
75
|
+
@commands[ name ] = Command.new( self, name, &block )
|
76
|
+
end
|
77
77
|
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
79
|
+
### multi-purpose method!
|
80
|
+
###
|
81
|
+
### if passed a block, set this plugin's cleanup proc to the block.
|
82
|
+
### if called without a block, execute the proc.
|
83
|
+
###
|
84
|
+
### this is meant to allow plugins to clean up after themselves.
|
85
|
+
def self::cleanup( &block )
|
86
|
+
if block_given?
|
87
|
+
@cleanup_proc = block
|
88
|
+
else
|
89
|
+
# if we didn't define a proc, don't try to call it
|
90
|
+
@cleanup_proc.call if @cleanup_proc
|
91
|
+
end
|
92
|
+
end
|
93
93
|
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
### define the plugin's description, or fetch it if nothing passed
|
96
|
+
def self::description( input = nil )
|
97
|
+
return @description unless input
|
98
98
|
|
99
|
-
|
100
|
-
|
99
|
+
@description = input
|
100
|
+
end
|
101
101
|
end
|