cmdparse 2.0.6 → 3.0.0

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.
@@ -3,189 +3,285 @@ title: Tutorial
3
3
  in_menu: true
4
4
  sort_info: 6
5
5
  ---
6
- ## Quickstart
6
+ ## Tutorial
7
7
 
8
- Here are some short code fragments which show how to use cmdparse. A complete example app can be
9
- found later in the [tutorial section](#tutorial).
8
+ The complete code for this example can be found in the file `example/net.rb` of the `cmdparse`
9
+ package (or online at [cmdparse's Github
10
+ repository][netrb]!
10
11
 
11
- Defining commands using classes:
12
+ **Tl;dr:** In this tutorial we will create a small `net` program which can add, delete and list IP
13
+ addresses as well as show 'network statistics'. By doing this we show how easy it is to use the
14
+ `cmdparse` library for creating a command based program. The last part shows how our created program
15
+ can be invoked and the built-in help facility of `cmdparse`.
12
16
 
13
- ~~~ ruby
14
- class TestCmd < CmdParse::Command
15
- def initialize
16
- super('test', true)
17
- self.add_command(TestSubCmd.new)
18
- end
19
- end
17
+ Note that the shown code fragments do not comprise the whole program. So, depending on what you
18
+ like, just look at the code fragments and the explanations or open the example file alongside this
19
+ tutorial. Later use the `example/net.rb` file for running the program yourself and testing different
20
+ command line arguments.
20
21
 
21
- class TestSubCmd < CmdParse::Command
22
- def initialize
23
- super('sub',false)
24
- end
25
22
 
26
- def execute (args)
27
- puts "Hallo #{args}"
28
- end
29
- end
23
+ ### Require statements
24
+
25
+ First we create a new new file and add the necessary `require` statements:
30
26
 
31
- cmd = CmdParse::CommandParser.new( true )
32
- cmd.add_command(TestCmd.new)
27
+ ~~~ ruby
28
+ {extract: {lines: !ruby/range 4..4}}
33
29
  ~~~
34
30
 
35
- Defining command using the basic `CmdParse::Command` class:
31
+ When requiring `cmdparse`, the `optparse` library from Ruby's standard library is also automatically
32
+ required which is used for doing the option parsing part.
33
+
34
+
35
+ ### The Basic Command Parser Object
36
+
37
+ Next we will define our command parser object through the [CmdParse::CommandParser] class. Objects
38
+ of this class are used for defining the top level commands of a program as well as the global
39
+ options and other information like the name of the program or its version.
36
40
 
37
41
  ~~~ ruby
38
- cmd = CmdParse::CommandParser.new( true )
39
- testcmd = CmdParse::Command.new( 'test', true )
40
- testcmd.short_desc = "Short desc"
41
- cmd.add_command( testcmd )
42
-
43
- sub = CmdParse::Command.new( 'sub', false )
44
- sub.short_desc = "Add an IP address"
45
- sub.set_execution_block do |args|
46
- puts "Hallo #{args}"
47
- end
48
- testcmd.add_command( sub )
42
+ {extract: {lines: !ruby/range 26..29}}
49
43
  ~~~
50
44
 
45
+ We use the `handle_exceptions` argument of the constructor so that exceptions are automatically
46
+ handled gracefully, i.e. by showing an appropriate error message. If we used `true` as value, the
47
+ help screen would be shown in addition to the error message.
51
48
 
52
- ## Tutorial
49
+ The next lines set the name of the program, its version and a banner that is shown on all help
50
+ messages. All this information is set on the [main options][CmdParse::CommandParser#main_options].
51
+ Setting this information on the global options or any other `OptionParser` instance has no effect!
53
52
 
54
- The complete code for this example can be found in the file `net.rb` of the `cmdparse` package!
55
53
 
56
- This tutorial produces a small `net` application which can add, delete and list IP adresses and show
57
- 'network statistics'. The shown code fragments do not include the whole program. So, instead of
58
- writing all the code yourself, just look at the code fragments first and then use the include
59
- `net.rb` file for running the program.
54
+ ### Specifying Options
60
55
 
61
- ### Require statements
56
+ An integral part of any CLI program are options that can be set when invoking the program. A command
57
+ based CLI program has several kinds of options:
62
58
 
63
- Create a new new file and write the necessary `require` statements.
59
+ * The [main options][CmdParse::CommandParser#main_options] which can only be used directly after the
60
+ program name itself. An example for such an option would be a `--version` switch that only makes
61
+ sense at the top level.
64
62
 
65
- ~~~ ruby
66
- {extract: {lines: !ruby/range 5..5}}
67
- ~~~
63
+ * The [command specific options][CmdParse::Command#options] which can only be used after the command
64
+ name and before any sub-command name.
68
65
 
69
- ### The `CommandParser` class
66
+ * The [global options][CmdParse::CommandParser#global_options] which can be used directly after the
67
+ program name as well as after any command. Therefore global options are normally used for things
68
+ that affect all commands, like a global verbosity setting.
70
69
 
71
- Next we will define our basic `CommandParser` by defining the name of the program, its version and
72
- the global options.
70
+ All these options are specified using the Ruby standard library `optparse` and its `OptionParser`
71
+ class. The `OptionParser` implementation is battle tested, easy to use and allows great flexibility.
73
72
 
74
- The first boolean argument to the constructor of the `CommandParser` class defines whether
75
- exceptions should be handled gracefully, i.e. by showing an appropriate error message and the help
76
- screen. The second boolean argument defines whether the top level commands should use partial
77
- command matching instead of full command matching. If partial command matching is used, then the
78
- shortest unambiguous part of a command name can be used instead of always specifing the full command
79
- name.
73
+ We go back to our example now and define a global option:
80
74
 
81
75
  ~~~ ruby
82
- {extract: {lines: !ruby/range 30..36}}
76
+ {extract: {lines: !ruby/range 30..34}}
83
77
  ~~~
84
78
 
85
- The options are defined using an option parser wrapper. Currently, the only option parser library
86
- supported is `optparse` from the Ruby Standard Library. If you want to use another option parser
87
- library, you need to write a wrapper for it so that `cmdparse` can use it.
79
+ The [data attribute][CmdParse::CommandParser#data] on the command parse object (or any command
80
+ object) can be used to store arbitrary information. Here we use it to store the verbosity level so
81
+ that it can easily be used later by any command.
82
+
83
+ We could have used a global variable for this but storing such information with the command parser
84
+ object usually makes for a better design. Note that the data attribute is only really useful when
85
+ the [CmdParse::CommandParser] is not sub-classed.
86
+
87
+
88
+ ### Parsing the Command Line Arguments
88
89
 
89
- Now we only have to tell the program to use our newly defined class to process the command line
90
- arguments.
90
+ We have set up everything that is needed for a basic command based program. The last step is to tell
91
+ the program to use our newly defined command parser object to process the command line arguments:
91
92
 
92
93
  ~~~ ruby
93
- {extract: {lines: !ruby/range 86..86}}
94
+ {extract: {lines: !ruby/range 84..84}}
94
95
  ~~~
95
96
 
96
- The `parse` method of our `CommandParser` instance parses the given array of options (or `ARGV` if
97
- no argument is specified). All the command line options are parsed and the given command executed.
97
+ The [parse method][CmdParse::CommandParser#parse] parses the given array of arguments (or `ARGV` if
98
+ no array is specified). All the command line arguments are parsed and the given command executed.
98
99
 
99
- The program can be executed now but won't be useful as we did not specify any commands.
100
+ The program could now be executed but it won't be useful as we did not specify any commands yet.
100
101
 
101
102
 
102
103
  ### Defining commands
103
104
 
104
- So, as we have defined our `CommandParser` object, we need to add some commands to it. First, we
105
- will add two predefined commands, namely the `help` and the `version` command.
105
+ After performing the basic setup, we need to add some commands so that our program actually does
106
+ something useful.
107
+
108
+ First, we will add two built-in commands, namely the `help` and the `version` command:
106
109
 
107
110
  ~~~ ruby
108
- {extract: {lines: !ruby/range 37..38}}
111
+ {extract: {lines: !ruby/range 35..36}}
109
112
  ~~~
110
113
 
111
- That was easy! Now you can execute the program and specify the commands `help` or `version`.
112
- However, we want the program to do something "useful". Therefore we define a new command.
114
+ That was easy! Now you can execute the program and specify the commands `help` or `version`. You
115
+ will also find that the `help` command is automatically invoked when you don't specify any command.
116
+ This is because we used the `default: true` argument when adding the `help` command which sets the
117
+ added command as the default command that should be used if no explicit command name is given.
118
+
119
+ The next step is to create the needed commands for our program. There are several different ways of
120
+ doing this.
113
121
 
114
122
  ~~~ ruby
115
- {extract: {lines: !ruby/range 41..44}}
123
+ {extract: {lines: !ruby/range 40..42}}
116
124
  ~~~
117
125
 
118
- This command is defined by using the default `Command` class. First an instance is created assigning
119
- a name to the command and defining whether this command takes subcommands and uses partial command
120
- matching. Next we add a short description so that the `help` command can produce something useful.
121
- And at last, we add this command to our `CommandParser` instance.
126
+ One way is to create an instance of [CmdParse::Command], update it with all needed properties and
127
+ then add it to the command parser object or another command.
122
128
 
123
- We specified that our `ipaddr` command takes subcommands. So we have to define them, too:
129
+ Since the `ipaddr` command takes other commands and doesn't do anything by itself, no action is
130
+ defined for it. Also notice that we have redefined the default command to be the `ipaddr` command.
131
+ The last added command with the argument `default: true` will be the default command.
124
132
 
125
133
  ~~~ ruby
126
- {extract: {lines: !ruby/range 46..78}}
134
+ {extract: {lines: !ruby/range 45..52}}
127
135
  ~~~
128
136
 
129
- We add three subcommands to the `ipaddr` command: `add`, `del` and `list`.
130
-
131
- * The `add` command is similar to the `ipaddr` command. However, as the `add` command does not take
132
- other commands, we have to define an execution block.
137
+ Another way would be to take advantage of the fact that the [add_command
138
+ method][CmdParse::Command#add_command] creates a [CmdParse::Command] object when a name is passed as
139
+ argument instead of a command object itself and that the added command is always yielded if a block
140
+ is given.
133
141
 
134
- * The `del` command is similar to the `add` command. As we want to be able to delete all IP
135
- addresses by issuing only one command, we add an option for this.
142
+ Using this block we can now easily customize the command. Since the `ipaddr add` command does not
143
+ take any commands, we need to define an [action block][CmdParse::Command#action] that gets called if
144
+ the command should be executed. By using `*ips` we advertise to `cmdparse` that this command takes
145
+ an arbitrary number of IP addresses as arguments. Note that this is also automatically reflected in
146
+ the usage line for the command!
136
147
 
137
- * By providing `true` as second argument when we add the `list` command to the `ipaddr` command, we
138
- specifiy that this command should be the default command which gets invoked when no command name
139
- is specified on the command line. Only one command can be specified as default command!
140
-
141
- Till now we only used the basic `Command` class to specify commands. However, new commands can also
142
- be created by subclassing the `Command` class, as shown with this last command:
148
+ We add the `ipaddr del` and `ipaddr list` commands in a similar manner and set the `list` command to
149
+ be the default sub-command for the `ipaddr` command.
143
150
 
144
151
  ~~~ ruby
145
- {extract: {lines: !ruby/range 9..28}}
146
- {extract: {lines: !ruby/range 39..39}}
152
+ {extract: {lines: !ruby/range 6..24}}
153
+
154
+ {extract: {lines: !ruby/range 37..37}}
147
155
  ~~~
148
156
 
157
+ The last way for creating a command is to sub-class the [CmdParse::Command] class and do all
158
+ customization there. If this is done, it is recommended to override the `#execute` method instead of
159
+ setting an action block.
160
+
161
+ We can also see that the execute method takes one or two arguments and that these arguments are also
162
+ properly documented.
163
+
164
+
165
+ ### Running the Program
166
+
167
+ <%= (context[:args] = nil; context.render_block('execution')) %>
168
+
169
+ Now that we have completed our program we can finally run it!
149
170
 
150
- ### Running the program
171
+ Below are some sample invocations with their respective output and some explanations.
151
172
 
152
- That's all! You can run the program now and have a look at the output which differs depending on
153
- which arguments you choose.
173
+ <%= (context[:args] = ""; context.render_block('execution')) %>
154
174
 
155
- So, a typical invocation of this program looks like this:
175
+ When called with no arguments, the default command is executed. The default top level command is
176
+ `ipaddr` and its default command is `list` which shows the added IP addresses. So far, no IP
177
+ addresses are stored - we should change that.
156
178
 
157
- $ ruby net.rb --verbose ipaddr add 192.168.0.1 193.150.0.1
179
+ <%= (context[:args] = "ip add 192.168.0.1"; context.render_block('execution')) %>
158
180
 
159
- * `--verbose` is a global option
160
- * `ipaddr` is the first command name (which has no options)
161
- * `add` is the second command name (which also has no options)
162
- * `192.168.0.1 193.150.0.1` are additional arguments
181
+ Now we have added one IP address. You might have noticed that we used `ip` instead of `ipaddr`.
182
+ Since partial command matching is automatically done, the shortest unambiguous name for a command
183
+ can be used. As there is no other command starting with `ip` (or even with the letter `i`), it is
184
+ sufficient to write the above to select the `ipaddr` command.
163
185
 
164
- You should notice that if you type
186
+ Now lets add some more IPs but with some informational output.
165
187
 
166
- $ ruby net.rb
188
+ <%= (context[:args] = "i a 192.168.0.2 192.168.0.4 192.168.0.3 -v"; context.render_block('execution')) %>
167
189
 
168
- you get an error because you did not specify any command.
190
+ This time we added three IP addresses and by using the global option `-v` we got some informational
191
+ output, too.
169
192
 
170
- However, when you type
193
+ Let's display which IP addresses are currently stored.
171
194
 
172
- $ ruby net.rb ipaddr
195
+ <%= (context[:args] = "ipaddr list"; context.render_block('execution')) %>
173
196
 
174
- you do not get an error!
197
+ So we have four IPs stored. However, we really only need three so we delete one.
175
198
 
176
- Why? As the `ipaddr` command takes subcommands there should be an additional command name (e.g.
177
- `list`) on the command line. However, as the `list` command is the default command for `ipaddr` you
178
- do not need to type it.
199
+ <%= (context[:args] = "ip -v del 192.16.8.0.4 "; context.render_block('execution')) %>
179
200
 
180
- *By the way:* You get the same output if you type
201
+ That's much better! But we all are getting sick of this and don't want any IP addresses stored
202
+ anymore.
181
203
 
182
- $ ruby net.rb ip
204
+ <%= (context[:args] = "ip -a del"; context.render_block('execution')) %>
183
205
 
184
- Why? As partial command matching is used for the top level commands, the shortest unambiguous name
185
- for a command can be used. As there is no other command starting with `ip` (or even with the letter
186
- `i`), it is sufficient to write the above to select the `ipaddr` command.
206
+ Alas, I mistyped that last command. The option `-a` is a command specific option of `ipaddr del` and
207
+ therefore not recognized by `ipaddr`.
187
208
 
188
- *Notice:* The options of a command which does not take subcommands do not need to be at the front;
189
- they can be anywhere, like this
209
+ <%= (context[:args] = ["ip del -av", '']; context.render_block('execution')) %>
190
210
 
191
- $ ruby test.rb --verbose mycommand file1 file2 --recursive file3
211
+ After deleting all IP addresses none are shown anymore - perfect!
212
+
213
+ Now we want to see the "network statistics" part of our program. What was the command name again?
214
+ Let's get some help!
215
+
216
+ <%= (context[:args] = ["help"]; context.render_block('execution')) %>
217
+
218
+ Ah, yes, the name was `stat`, now we remember!
219
+
220
+ And there are some interesting things in the help output worth pointing out:
221
+
222
+ * The usage line shows us that we can define top level option (in addition to the global options)
223
+ and also nicely lists the available commands.
224
+
225
+ * The asterisks in the section for the commands show us the default commands.
226
+
227
+ * We have a top level option `-v` for showing the version as well as a global option `-v` for
228
+ setting the verbosity level. As mentioned in the help output, the top level option (or a command
229
+ specific option) always takes precedence over global options.
230
+
231
+ To make the last point clear, we run the command with the `-v` option.
232
+
233
+ <%= (context[:args] = ["-v"]; context.render_block('execution')) %>
234
+
235
+ This shows us the version information as expected instead of invoking the default command.
236
+
237
+ Back to the network statistics. Now we now the command name but we have forgotten how to use the
238
+ command itself. The `help` command comes to our rescue again!
239
+
240
+ <%= (context[:args] = ["help stat"]; context.render_block('execution')) %>
241
+
242
+ There are again some things to point out:
243
+
244
+ * We get a short summary and a more detailed description of the command. The short description is
245
+ the one shown in the general help overview. The detailed description is normally longer than this
246
+ and fully explains the command.
247
+
248
+ * The arguments for the command are also described. When looking at the usage line, you can see that
249
+ the `M` argument is optional, but the `N` argument isn't (indicated by the brackets). This means
250
+ that we need at least one argument. If we provide only one argument, it is used for `N`. And if we
251
+ provide two arguments, they are used for `M` and `N` (in this order).
252
+
253
+ How does `cmdparse` know that `M` is optional? It has inferred this (as well as the names
254
+ themselves) by looking at the signature of the execute method of the command!
255
+
256
+ Now we know everything to invoke the command.
257
+
258
+ <%= (context[:args] = ["stat 5", "stat 3 5"]; context.render_block('execution')) %>
259
+
260
+
261
+ ### Final Words
262
+
263
+ Our `net` program is certainly only useful for this tutorial, however, it nicely showcases many of
264
+ the features of `cmdparse` and how easy `cmdparse` is to use.
265
+
266
+ If you haven't done so by now, [install](installation.html) the `cmdparse` library,
267
+ [download][netrb] our sample `net` program and experiment a bit with it. The [API
268
+ documentation](/api/) is quite extensive and will answer all remaining questions. And if it doesn't,
269
+ you can contact [me](mailto:t_leitner@gmx.at).
270
+
271
+
272
+
273
+ [netrb]: https://github.com/gettalong/cmdparse/blob/master/example/net.rb
274
+
275
+
276
+ --- name:execution pipeline:ruby
277
+ if context[:args].nil?
278
+ require 'fileutils'
279
+ FileUtils.rm(context.website.directory + '/dumpnet', :force => true)
280
+ context.content = ''
281
+ else
282
+ args = [context[:args]].flatten
283
+ result = args.map do |arg|
284
+ ["$ <strong>ruby example/net.rb #{arg}</strong>", h(`ruby -I#{context.website.directory}/lib #{context.website.directory}/example/net.rb #{arg}`).chomp]
285
+ end.flatten.push("$").delete_if {|l| l.empty? }.join("\n")
286
+ context.content = "<pre>#{result}\n</pre>"
287
+ end
@@ -1,5 +1,2 @@
1
- api.html:
2
- dest_path: rdoc/index.html
3
- title: API Reference
4
- sort_info: 7
5
- in_menu: true
1
+ api/index.html:
2
+ dest_path: CmdParse/CommandParser.html
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+ # if something is changed here -> change line numbers in doc/tutorial.page
3
+
4
+ require 'cmdparse'
5
+
6
+ class NetStatCommand < CmdParse::Command
7
+
8
+ def initialize
9
+ super('stat', takes_commands: false)
10
+ short_desc("Show network statistics")
11
+ long_desc("This command shows very useful 'network' statistics - eye catching!!!")
12
+ argument_desc(M: 'start row number', N: 'end row number')
13
+ end
14
+
15
+ def execute(m = 1, n)
16
+ puts "Showing network statistics" if command_parser.data[:verbose]
17
+ puts
18
+ m.to_i.upto(n.to_i) do |row|
19
+ puts " "*(20 - row).abs + "#"*(row*2 - 1).abs
20
+ end
21
+ puts
22
+ end
23
+
24
+ end
25
+
26
+ parser = CmdParse::CommandParser.new(handle_exceptions: :no_help)
27
+ parser.main_options.program_name = "net"
28
+ parser.main_options.version = "0.1.1"
29
+ parser.main_options.banner = "This is net, a s[ai]mple network analytics program"
30
+ parser.global_options do |opt|
31
+ opt.on("-v", "--verbose", "Be verbose when outputting info") do
32
+ parser.data[:verbose] = true
33
+ end
34
+ end
35
+ parser.add_command(CmdParse::HelpCommand.new, default: true)
36
+ parser.add_command(CmdParse::VersionCommand.new)
37
+ parser.add_command(NetStatCommand.new)
38
+
39
+ # ipaddr
40
+ ipaddr = CmdParse::Command.new('ipaddr')
41
+ ipaddr.short_desc = "Manage IP addresses"
42
+ parser.add_command(ipaddr, default: true)
43
+
44
+ # ipaddr add
45
+ ipaddr.add_command('add') do |cmd|
46
+ cmd.takes_commands(false)
47
+ cmd.short_desc("Add an IP address")
48
+ cmd.action do |*ips|
49
+ puts "Adding ip addresses: #{ips.join(', ')}" if parser.data[:verbose]
50
+ parser.data[:ipaddrs] += ips
51
+ end
52
+ end
53
+
54
+ # ipaddr del
55
+ del = CmdParse::Command.new('del', takes_commands: false)
56
+ del.short_desc = "Delete an IP address"
57
+ del.options.on('-a', '--all', 'Delete all IPs') { del.data[:delete_all] = true }
58
+ del.action do |*ips|
59
+ if del.data[:delete_all]
60
+ puts "All IP adresses deleted!" if parser.data[:verbose]
61
+ parser.data[:ipaddrs] = []
62
+ else
63
+ puts "Deleting ip addresses: #{ips.join(', ')}" if parser.data[:verbose]
64
+ ips.each {|ip| parser.data[:ipaddrs].delete(ip) }
65
+ end
66
+ end
67
+ ipaddr.add_command(del)
68
+
69
+ # ipaddr list
70
+ list = CmdParse::Command.new('list', takes_commands: false)
71
+ list.short_desc = "Lists all IP addresses"
72
+ list.action do
73
+ puts "Listing ip addresses:" if parser.data[:verbose]
74
+ puts parser.data[:ipaddrs].join("\n") unless parser.data[:ipaddrs].empty?
75
+ end
76
+ ipaddr.add_command(list, default: true)
77
+
78
+
79
+ parser.data[:ipaddrs] = if File.exists?('dumpnet')
80
+ Marshal.load(File.read('dumpnet', mode: 'rb'))
81
+ else
82
+ []
83
+ end
84
+ parser.parse
85
+ File.write('dumpnet', Marshal.dump(parser.data[:ipaddrs]), mode: 'wb+')