kknife 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2013 Deployable Ltd
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ ## kknife 0.1.1
2
+
3
+ ### Shortcuts for the chef knife command
4
+
5
+ `k n e somenode = knife node edit somenode`
6
+ `k c u somebook = knife cookbook upload somebook`
7
+ `k e u ff somefile = knife cookbook from_file somefile`
8
+
9
+ `k -l` lists all your commands
10
+
11
+ `k -d` might tell you what's going wrong.
12
+
13
+ Lookups for shortcuts and ambiguous commands are currently statically defined in `lib/kknife/lookup.rb`. These will be user configurable/overridable. Some form of json/yml blob in ~/.chef/ should do.
14
+
15
+ Shortcuts
16
+ ```
17
+ 'ff' => [ 'from', 'file' ],
18
+ 'db' => [ 'data', 'bag' ],
19
+ 'cs' => [ 'cookbook', 'site' ],
20
+ 'bd' => [ 'bulk', 'delete' ],
21
+ 'ne' => [ 'node', 'edit' ],
22
+ 'ns' => [ 'node', 'show' ],
23
+ 'rl' => [ 'run', 'list' ],
24
+ ```
25
+ Ambiguities
26
+ ```
27
+ 'd' => ['download'],
28
+ 'e' => ['environment'],
29
+ 'r' => ['role'],
30
+ 'c' => ['cookbook'],
31
+ ```
data/bin/k ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ### ./k - shortcuts for chef knife command line
4
+
5
+ # Author: Matt Hoyle (<matt@deployable.co>)
6
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
7
+ # License: Apache License, Version 2.0
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+
21
+ # nothing to see here...
22
+ require 'kknife'
23
+ Kknife.new.run
@@ -0,0 +1,208 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'kknife/dbg'
18
+
19
+ ### Command
20
+ # This is used to store a tree of commands
21
+ # Allow you to navigate up and down the tree.
22
+
23
+ class Command
24
+
25
+ include Dbg
26
+
27
+ class NoMoreSubCommands < StandardError; end
28
+ class NoMoreSuppliedCommands < StandardError; end
29
+ class AmbiguousCommand < StandardError; end
30
+ class NotFoundCommand < StandardError; end
31
+
32
+ attr_accessor :cmd, :cmd_short, :sub_commands, :source, :level, :parent, :root, :controller
33
+
34
+ def initialize( command, argopts = {} )
35
+ @cmd = command
36
+
37
+ opts = {
38
+ :cmd_short => nil,
39
+ :source => nil,
40
+ :level => 0,
41
+ :sub_commands => {},
42
+ :parent => nil,
43
+ :root => self,
44
+ :controller => nil
45
+ }.merge argopts
46
+
47
+ @cmd_short = opts[:cmd_short]
48
+ @source = opts[:source]
49
+ @parent = opts[:parent]
50
+ @level = opts[:level]
51
+ @sub_commands = opts[:sub_commands]
52
+ @root = opts[:root]
53
+ @controller = opts[:controller]
54
+
55
+ # this could just return the root's controller.
56
+ @controller = @root.controller if not @root.nil? and @controller.nil?
57
+ raise "require controller" if @controller.nil?
58
+
59
+ @level = @parent.level + 1 unless @parent.nil?
60
+
61
+ dbg 'test'
62
+ debug_logger.debug( 'Command init' )
63
+ end
64
+
65
+ # return the command
66
+ def to_s
67
+ @cmd
68
+ end
69
+
70
+
71
+ # Add a sub Command to this Command
72
+ def add_subcmd( command )
73
+ unless has_sub_command? command
74
+ dbg 'add_subcmd', command, cmd
75
+ @sub_commands[command] = Command.new( command, :parent => self, :root => @root )
76
+ end
77
+ @sub_commands[command]
78
+ end
79
+
80
+
81
+ # Print current command recurse through sub commands
82
+ def pp
83
+ printf "%s%s\n", ' ' * level, cmd
84
+ @sub_commands.each_key do |key|
85
+ @sub_commands[key].pp
86
+ end
87
+ end
88
+
89
+ def pp_single
90
+ printf "%s\n", command_lookup.reverse.join(' ') if has_no_sub_commands?
91
+ @sub_commands.each_key do |key|
92
+ @sub_commands[key].pp_single
93
+ end
94
+ end
95
+
96
+ def command_lookup
97
+ return [ @cmd ] + @parent.command_lookup unless @parent.nil?
98
+ [ @cmd ]
99
+ end
100
+
101
+ # Test for existence of a sub Command
102
+ def has_no_sub_commands?
103
+ @sub_commands.empty?
104
+ end
105
+
106
+ # Test for existence of a sub Command
107
+ def has_sub_command?( command )
108
+ @sub_commands.has_key? command
109
+ end
110
+
111
+ # Find a sub Comamnd via substr
112
+ def sub_command_find( command )
113
+ @sub_commands.keys.grep( Regexp.new( '^' + Regexp.escape(command) + '.*' ) )
114
+ end
115
+
116
+ # Return a sub Comamnd by name
117
+ def sub_command( command )
118
+ @sub_commands[command] if has_sub_command? command
119
+ end
120
+
121
+
122
+ # Add a list of commands, recursing down the tree
123
+ def add_command( commands )
124
+ first = commands.shift
125
+
126
+ dbg "first", first
127
+ if has_sub_command? first
128
+ dbg "first exists", first
129
+ end
130
+
131
+ sub_cmd = add_subcmd first
132
+ sub_cmd.add_command( commands ) unless commands.empty?
133
+ end
134
+
135
+
136
+ # Recursively lookup a list of commands
137
+ def process_lookup( commands )
138
+
139
+ raise NoMoreSuppliedCommands, "end of args" if commands.empty?
140
+
141
+ commands_local = commands.dup
142
+ first_command = commands_local.shift
143
+ sub_command_list = sub_command_find first_command
144
+ rest_commands = commands_local
145
+
146
+ dbg 'first', first_command
147
+ dbg 'list', sub_command_list
148
+ dbg 'rest', rest_commands
149
+
150
+ if has_sub_command? first_command or sub_command_list.length == 1
151
+
152
+ cmd = sub_command first_command
153
+ cmd ||= sub_command sub_command_list.first
154
+
155
+ dbg 'cmd', cmd.cmd
156
+ @controller.found_cmd cmd.cmd
157
+ raise NoMoreSubCommands, "end of lookup commands" if cmd.sub_commands.empty?
158
+
159
+ return cmd.process_lookup rest_commands
160
+
161
+
162
+ elsif sub_command_list.length > 1
163
+ # If there was more than one match, check the ambiguity config
164
+ # or error
165
+
166
+ if Lookup.ambiguity( first_command )
167
+ process_lookup Lookup.ambiguity( first_command ) + rest_commands
168
+
169
+ else
170
+ raise AmbiguousCommand, "ambiguous [#{first_command}] [#{sub_command_list.join(',')}]"
171
+ end
172
+
173
+
174
+ elsif first_command.index(/_/)
175
+ # If there's an underscore, split it out
176
+
177
+ cmdsplit = first_command.split( /_/ )
178
+
179
+ # notify the controller of the command changes
180
+ @controller.cmd_split cmdsplit
181
+
182
+ # rebuild the local commands
183
+ first_command = cmdsplit.shift
184
+ rest_commands = cmdsplit + rest_commands
185
+
186
+ # now start again with the new values
187
+ process_lookup [ first_command ] + rest_commands
188
+
189
+
190
+ else
191
+ # Otherwise the command wasn't found,
192
+ # Look up shortcuts before giving up
193
+
194
+ shortcut = Lookup.shortcut( first_command )
195
+ if shortcut
196
+ @controller.found_shortcut shortcut
197
+ process_lookup shortcut + rest_commands
198
+
199
+ else
200
+ raise NotFoundCommand, "not found [#{first_command}.*]"
201
+ end
202
+
203
+ end
204
+
205
+ end
206
+
207
+ end
208
+
data/lib/kknife/dbg.rb ADDED
@@ -0,0 +1,62 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'logger'
18
+
19
+ ### Dbg
20
+ # a quick debug logger
21
+ # classes can run `dbg`
22
+
23
+ # ```
24
+ # class Yours
25
+ # include Dbg
26
+ # def method
27
+ # dbg 'some', values, logged
28
+ # end
29
+ # end
30
+ # ```
31
+
32
+ module Dbg
33
+
34
+ DefaultIO = STDERR
35
+ DefaultLevel = Logger::INFO
36
+
37
+ def debug_logger
38
+ Dbg.debug_logger
39
+ end
40
+
41
+ def self.replace( io = DefaultIO, llevel = DefaultLevel )
42
+ llevel = @debug_logger.level if @debug_logger
43
+ @debug_logger = create( io, llevel )
44
+ end
45
+
46
+ def self.create( io = DefaultIO, llevel = DefaultLevel )
47
+ l = Logger.new io
48
+ l.level = llevel
49
+ l
50
+ end
51
+
52
+ def self.debug_logger
53
+ @debug_logger ||= create
54
+ end
55
+
56
+ def dbg( str, *vars )
57
+ Dbg.debug_logger.debug sprintf( "%s [%s]", str, vars.join("] [") ) # if Debug
58
+ end
59
+
60
+ end
61
+
62
+
data/lib/kknife/k2.rb ADDED
@@ -0,0 +1,99 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'chef/knife'
18
+
19
+ Debug = true
20
+ $cmd = {}
21
+
22
+ def dbg( str, *vars )
23
+ printf "%s [%s]\n", str, vars.join("] [") if Debug
24
+ end
25
+
26
+ def add( h, cmds )
27
+ dbg 'cmds', cmds.to_s
28
+ first = cmds.shift
29
+ if cmds.empty?
30
+ h[first] = nil
31
+ else
32
+ h[first] = {} unless h.has_key? first and !h[first].nil?
33
+ add h[first], cmds
34
+ end
35
+ end
36
+
37
+ Chef::Knife.load_commands
38
+ Chef::Knife.subcommands_by_category.each do |category,command_arr|
39
+ dbg 'category', category
40
+ #$cmd[category] = {} unless $cmd.has_key? category
41
+ command_arr.each do |command|
42
+ dbg 'command', command
43
+ commands = command.split( /_/ )
44
+ add $cmd, commands
45
+ end
46
+ end
47
+
48
+ pp $cmd
49
+
50
+ commands = ARGV.dup
51
+ cmd_extra = ARGV.dup
52
+ h = $cmd
53
+ cmd_knife = []
54
+
55
+ commands.each do |command|
56
+ dbg 'command', command
57
+
58
+ if h.nil?
59
+ # Last knife command possible
60
+ dbg 'final'
61
+ break
62
+
63
+ elsif h.has_key? command
64
+ # Direct lookup
65
+ dbg 'found cmd', command
66
+ h = h[command]
67
+ cmd_knife.push command
68
+ cmd_extra.shift
69
+
70
+ elsif command.index(/_/)
71
+ # Split into spaces
72
+ cmdsplit = command.split( /_/ )
73
+ cmdsplit.each do |cmd|
74
+ if h.has_key? cmd
75
+ h = h[cmd]
76
+ cmd_knife.push cmd
77
+ else
78
+ raise "command not found [#{cmd}] [#{command}]"
79
+ end
80
+ end
81
+ cmd_extra.shift
82
+
83
+ else
84
+ # Search for ^command.*
85
+ re = Regexp.new( '^' + Regexp.escape(command) + '.*$')
86
+ search = h.keys.grep re
87
+ case search.length
88
+ when 0
89
+ raise "command not found [#{re}]"
90
+ when 1
91
+ dbg 'srch cmd', command, search[0]
92
+ cmd_knife.push command
93
+ cmd_extra.shift
94
+ else
95
+ dbg 'srch cmds', command, search.to_s
96
+ end
97
+ end
98
+ end
99
+ dbg cmd_knife, '|', cmd_extra