leap_cli 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/bin/leap +81 -0
  2. data/lib/core_ext/boolean.rb +14 -0
  3. data/lib/core_ext/hash.rb +35 -0
  4. data/lib/core_ext/json.rb +42 -0
  5. data/lib/core_ext/nil.rb +5 -0
  6. data/lib/core_ext/string.rb +14 -0
  7. data/lib/leap/platform.rb +52 -0
  8. data/lib/leap_cli/commands/ca.rb +430 -0
  9. data/lib/leap_cli/commands/clean.rb +16 -0
  10. data/lib/leap_cli/commands/compile.rb +134 -0
  11. data/lib/leap_cli/commands/deploy.rb +172 -0
  12. data/lib/leap_cli/commands/facts.rb +93 -0
  13. data/lib/leap_cli/commands/inspect.rb +140 -0
  14. data/lib/leap_cli/commands/list.rb +122 -0
  15. data/lib/leap_cli/commands/new.rb +126 -0
  16. data/lib/leap_cli/commands/node.rb +272 -0
  17. data/lib/leap_cli/commands/pre.rb +99 -0
  18. data/lib/leap_cli/commands/shell.rb +67 -0
  19. data/lib/leap_cli/commands/test.rb +55 -0
  20. data/lib/leap_cli/commands/user.rb +140 -0
  21. data/lib/leap_cli/commands/util.rb +50 -0
  22. data/lib/leap_cli/commands/vagrant.rb +201 -0
  23. data/lib/leap_cli/config/macros.rb +369 -0
  24. data/lib/leap_cli/config/manager.rb +369 -0
  25. data/lib/leap_cli/config/node.rb +37 -0
  26. data/lib/leap_cli/config/object.rb +336 -0
  27. data/lib/leap_cli/config/object_list.rb +174 -0
  28. data/lib/leap_cli/config/secrets.rb +43 -0
  29. data/lib/leap_cli/config/tag.rb +18 -0
  30. data/lib/leap_cli/constants.rb +7 -0
  31. data/lib/leap_cli/leapfile.rb +97 -0
  32. data/lib/leap_cli/load_paths.rb +15 -0
  33. data/lib/leap_cli/log.rb +166 -0
  34. data/lib/leap_cli/logger.rb +216 -0
  35. data/lib/leap_cli/markdown_document_listener.rb +134 -0
  36. data/lib/leap_cli/path.rb +84 -0
  37. data/lib/leap_cli/remote/leap_plugin.rb +204 -0
  38. data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
  39. data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
  40. data/lib/leap_cli/remote/tasks.rb +36 -0
  41. data/lib/leap_cli/requirements.rb +19 -0
  42. data/lib/leap_cli/ssh_key.rb +130 -0
  43. data/lib/leap_cli/util/remote_command.rb +110 -0
  44. data/lib/leap_cli/util/secret.rb +54 -0
  45. data/lib/leap_cli/util/x509.rb +32 -0
  46. data/lib/leap_cli/util.rb +431 -0
  47. data/lib/leap_cli/version.rb +9 -0
  48. data/lib/leap_cli.rb +46 -0
  49. data/lib/lib_ext/capistrano_connections.rb +16 -0
  50. data/lib/lib_ext/gli.rb +52 -0
  51. data/lib/lib_ext/markdown_document_listener.rb +122 -0
  52. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
  54. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
  55. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
  57. data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
  58. data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
  59. data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
  60. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
  61. data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
  62. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
  63. data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
  64. data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
  65. data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
  66. data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
  67. data/vendor/rsync_command/lib/rsync_command.rb +96 -0
  68. data/vendor/rsync_command/test/rsync_test.rb +74 -0
  69. data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
  70. data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
  71. data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
  72. metadata +345 -0
@@ -0,0 +1,431 @@
1
+ require 'digest/md5'
2
+ require 'fileutils'
3
+ require 'pathname'
4
+ require 'erb'
5
+ require 'pty'
6
+
7
+ module LeapCli
8
+ module Util
9
+ extend self
10
+
11
+ ##
12
+ ## QUITTING
13
+ ##
14
+
15
+ def exit_status(code=nil)
16
+ @exit_status = code if code
17
+ @exit_status
18
+ end
19
+
20
+ #
21
+ # quit and print help
22
+ #
23
+ def help!(message=nil)
24
+ ENV['GLI_DEBUG'] = "false"
25
+ help_now!(message)
26
+ end
27
+
28
+ #
29
+ # exit with error code and with a message that we are bailing out.
30
+ #
31
+ def bail!(*message)
32
+ if block_given?
33
+ LeapCli.log_level = 3
34
+ yield
35
+ elsif message
36
+ log 0, *message
37
+ end
38
+ log 0, :bail, ""
39
+ raise SystemExit.new(@exit_status || 1)
40
+ end
41
+
42
+ #
43
+ # quit with message, but no additional error or warning about bailing.
44
+ #
45
+ def quit!(message='')
46
+ puts(message)
47
+ raise SystemExit.new(@exit_status || 0)
48
+ end
49
+
50
+ #
51
+ # bails out with message if assertion is false.
52
+ #
53
+ def assert!(boolean, message=nil, &block)
54
+ if !boolean
55
+ bail!(message, &block)
56
+ end
57
+ end
58
+
59
+ #
60
+ # assert that the command is available
61
+ #
62
+ def assert_bin!(cmd_name, msg=nil)
63
+ assert! `which #{cmd_name}`.strip.any? do
64
+ log :missing, "command '%s'" % cmd_name do
65
+ if msg
66
+ log msg
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ #
73
+ # assert that the command is run without an error.
74
+ # if successful, return output.
75
+ #
76
+ def assert_run!(cmd, message=nil)
77
+ cmd = cmd + " 2>&1"
78
+ output = `#{cmd}`.strip
79
+ unless $?.success?
80
+ exit_status($?.exitstatus)
81
+ bail! do
82
+ log :run, cmd
83
+ log :failed, "(exit #{$?.exitstatus}) #{output}", :indent => 1
84
+ log message, :indent => 1 if message
85
+ end
86
+ else
87
+ log 2, :ran, cmd
88
+ end
89
+ return output
90
+ end
91
+
92
+ def assert_files_missing!(*files)
93
+ options = files.last.is_a?(Hash) ? files.pop : {}
94
+ base = options[:base] || Path.provider
95
+ file_list = files.collect { |file_path|
96
+ file_path = Path.named_path(file_path, base)
97
+ File.exists?(file_path) ? Path.relative_path(file_path, base) : nil
98
+ }.compact
99
+ if file_list.length > 1
100
+ bail! do
101
+ log :error, "Sorry, we can't continue because these files already exist: #{file_list.join(', ')}."
102
+ log options[:msg] if options[:msg]
103
+ end
104
+ elsif file_list.length == 1
105
+ bail! do
106
+ log :error, "Sorry, we can't continue because this file already exists: #{file_list.first}."
107
+ log options[:msg] if options[:msg]
108
+ end
109
+ end
110
+ end
111
+
112
+ def assert_config!(conf_path)
113
+ value = nil
114
+ begin
115
+ value = manager.instance_eval(conf_path)
116
+ #rescue NoMethodError
117
+ #rescue NameError
118
+ ensure
119
+ assert! !value.nil? && value != "REQUIRED" do
120
+ log :missing, "required configuration value for #{conf_path}"
121
+ end
122
+ end
123
+ end
124
+
125
+ def assert_files_exist!(*files)
126
+ options = files.last.is_a?(Hash) ? files.pop : {}
127
+ file_list = files.collect { |file_path|
128
+ file_path = Path.named_path(file_path)
129
+ !File.exists?(file_path) ? Path.relative_path(file_path) : nil
130
+ }.compact
131
+ if file_list.length > 1
132
+ bail! do
133
+ log :missing, "these files: #{file_list.join(', ')}"
134
+ log options[:msg] if options[:msg]
135
+ end
136
+ elsif file_list.length == 1
137
+ bail! do
138
+ log :missing, "file #{file_list.first}"
139
+ log options[:msg] if options[:msg]
140
+ end
141
+ end
142
+ end
143
+
144
+ def file_exists?(*files)
145
+ files.each do |file_path|
146
+ file_path = Path.named_path(file_path)
147
+ if !File.exists?(file_path)
148
+ return false
149
+ end
150
+ end
151
+ return true
152
+ end
153
+
154
+ ##
155
+ ## FILES AND DIRECTORIES
156
+ ##
157
+
158
+ #
159
+ # creates a directory if it doesn't already exist
160
+ #
161
+ def ensure_dir(dir)
162
+ dir = Path.named_path(dir)
163
+ unless File.directory?(dir)
164
+ assert_files_missing!(dir, :msg => "Cannot create directory #{dir}")
165
+ FileUtils.mkdir_p(dir, :mode => 0700)
166
+ unless dir =~ /\/$/
167
+ dir = dir + '/'
168
+ end
169
+ log :created, dir
170
+ end
171
+ end
172
+
173
+ ##
174
+ ## FILE READING, WRITING, DELETING, and MOVING
175
+ ##
176
+
177
+ #
178
+ # All file read and write methods support using named paths in the place of an actual file path.
179
+ #
180
+ # To call using a named path, use a symbol in the place of filepath, like so:
181
+ #
182
+ # read_file(:known_hosts)
183
+ #
184
+ # In some cases, the named path will take an argument. In this case, set the filepath to be an array:
185
+ #
186
+ # write_file!([:user_ssh, 'bob'], ssh_key_str)
187
+ #
188
+ # To resolve a named path, use the shortcut helper 'path()'
189
+ #
190
+ # path([:user_ssh, 'bob']) ==> files/users/bob/bob_ssh_pub.key
191
+ #
192
+
193
+ def read_file!(filepath)
194
+ filepath = Path.named_path(filepath)
195
+ assert_files_exist!(filepath)
196
+ File.read(filepath)
197
+ end
198
+
199
+ def read_file(filepath)
200
+ filepath = Path.named_path(filepath)
201
+ if file_exists?(filepath)
202
+ File.read(filepath)
203
+ end
204
+ end
205
+
206
+ #
207
+ # replace contents of a file, with an exclusive lock.
208
+ #
209
+ # 1. locks file
210
+ # 2. reads contents
211
+ # 3. yields contents
212
+ # 4. replaces file with return value of the block
213
+ #
214
+ def replace_file!(filepath, &block)
215
+ filepath = Path.named_path(filepath)
216
+ if !File.exists?(filepath)
217
+ content = yield(nil)
218
+ unless content.nil?
219
+ write_file!(filepath, content)
220
+ end
221
+ else
222
+ File.open(filepath, File::RDWR|File::CREAT, 0600) do |f|
223
+ f.flock(File::LOCK_EX)
224
+ old_content = f.read
225
+ new_content = yield(old_content)
226
+ if old_content == new_content
227
+ log :nochange, filepath, 2
228
+ else
229
+ f.rewind
230
+ f.write(new_content)
231
+ f.flush
232
+ f.truncate(f.pos)
233
+ log :updated, filepath
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ def remove_file!(filepath)
240
+ filepath = Path.named_path(filepath)
241
+ if File.exists?(filepath)
242
+ if File.directory?(filepath)
243
+ remove_directory!(filepath)
244
+ else
245
+ begin
246
+ File.unlink(filepath)
247
+ log :removed, filepath
248
+ rescue Exception => exc
249
+ bail! do
250
+ log :failed, "to remove file #{filepath}"
251
+ log "error message: " + exc.to_s
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ def remove_directory!(filepath)
259
+ filepath = Path.named_path(filepath)
260
+ if filepath !~ /^#{Regexp.escape(Path.provider)}/ || filepath =~ /\.\./
261
+ bail! "sanity check on rm -r did not pass for #{filepath}"
262
+ end
263
+ if File.directory?(filepath)
264
+ begin
265
+ FileUtils.rm_r(filepath)
266
+ log :removed, filepath
267
+ rescue Exception => exc
268
+ bail! do
269
+ log :failed, "to remove directory #{filepath}"
270
+ log "error message: " + exc.to_s
271
+ end
272
+ end
273
+ else
274
+ log :failed, "to remove '#{filepath}', it is not a directory"
275
+ end
276
+ end
277
+
278
+ def write_file!(filepath, contents)
279
+ filepath = Path.named_path(filepath)
280
+ ensure_dir File.dirname(filepath)
281
+ existed = File.exists?(filepath)
282
+ if existed
283
+ if file_content_equals?(filepath, contents)
284
+ log :nochange, filepath, 2
285
+ return
286
+ end
287
+ end
288
+
289
+ File.open(filepath, 'w', 0600) do |f|
290
+ f.write contents
291
+ end
292
+
293
+ if existed
294
+ log :updated, filepath
295
+ else
296
+ log :created, filepath
297
+ end
298
+ end
299
+
300
+ def rename_file!(oldpath, newpath)
301
+ oldpath = Path.named_path(oldpath)
302
+ newpath = Path.named_path(newpath)
303
+ if File.exists? newpath
304
+ log :skipping, "#{Path.relative_path(newpath)}, file already exists"
305
+ return
306
+ end
307
+ if !File.exists? oldpath
308
+ log :skipping, "#{Path.relative_path(oldpath)}, file is missing"
309
+ return
310
+ end
311
+ FileUtils.mv oldpath, newpath
312
+ log :moved, "#{Path.relative_path(oldpath)} to #{Path.relative_path(newpath)}"
313
+ end
314
+
315
+ def cmd_exists?(cmd)
316
+ `which #{cmd}`.strip.chars.any?
317
+ end
318
+
319
+ #
320
+ # creates a relative symlink from absolute paths, removing prior symlink if necessary
321
+ #
322
+ # symlink 'new' is created, pointing to 'old'
323
+ #
324
+ def relative_symlink(old, new)
325
+ relative_path = Pathname.new(old).relative_path_from(Pathname.new(new))
326
+ if File.symlink?(new)
327
+ if File.readlink(new) != relative_path.to_s
328
+ File.unlink(new)
329
+ log :updated, 'symlink %s' % Path.relative_path(new)
330
+ end
331
+ else
332
+ log :created, 'symlink %s' % Path.relative_path(new)
333
+ end
334
+ FileUtils.ln_s(relative_path, new)
335
+ end
336
+
337
+ #
338
+ # compares md5 fingerprints to see if the contents of a file match the string we have in memory
339
+ #
340
+ def file_content_equals?(filepath, contents)
341
+ filepath = Path.named_path(filepath)
342
+ output = `md5sum '#{filepath}'`.strip
343
+ if $?.to_i == 0
344
+ return output.split(" ").first == Digest::MD5.hexdigest(contents).to_s
345
+ else
346
+ return false
347
+ end
348
+ end
349
+
350
+ ##
351
+ ## PROCESSES
352
+ ##
353
+
354
+ #
355
+ # run a long running block of code in a separate process and display marching ants as time goes by.
356
+ # if the user hits ctrl-c, the program exits.
357
+ #
358
+ def long_running(&block)
359
+ pid = fork
360
+ if pid == nil
361
+ yield
362
+ exit!
363
+ end
364
+ Signal.trap("SIGINT") do
365
+ Process.kill("KILL", pid)
366
+ Process.wait(pid)
367
+ bail!
368
+ end
369
+ while true
370
+ sleep 0.2
371
+ STDOUT.print '.'
372
+ STDOUT.flush
373
+ break if Process.wait(pid, Process::WNOHANG)
374
+ end
375
+ STDOUT.puts
376
+ end
377
+
378
+ #
379
+ # runs a command in a pseudo terminal
380
+ #
381
+ def pty_run(cmd)
382
+ PTY.spawn(cmd) do |output, input, pid|
383
+ begin
384
+ while line = output.gets do
385
+ puts line
386
+ end
387
+ rescue Errno::EIO
388
+ end
389
+ end
390
+ rescue PTY::ChildExited
391
+ end
392
+
393
+ ##
394
+ ## ERB
395
+ ##
396
+
397
+ def erb_eval(string, binding=nil)
398
+ ERB.new(string, nil, '%<>-').result(binding)
399
+ end
400
+
401
+ ##
402
+ ## GIT
403
+ ##
404
+
405
+ def is_git_directory?(dir)
406
+ Dir.chdir(dir) do
407
+ `which git && git rev-parse 2>/dev/null`
408
+ return $? == 0
409
+ end
410
+ end
411
+
412
+ def current_git_branch(dir)
413
+ Dir.chdir(dir) do
414
+ branch = `git symbolic-ref HEAD 2>/dev/null`.strip
415
+ if branch.chars.any?
416
+ branch.sub /^refs\/heads\//, ''
417
+ else
418
+ nil
419
+ end
420
+ end
421
+ end
422
+
423
+ def current_git_commit(dir)
424
+ Dir.chdir(dir) do
425
+ `git rev-parse HEAD 2>/dev/null`.strip
426
+ end
427
+ end
428
+
429
+ end
430
+ end
431
+
@@ -0,0 +1,9 @@
1
+ module LeapCli
2
+ unless defined?(LeapCli::VERSION)
3
+ VERSION = '1.2.5'
4
+ COMPATIBLE_PLATFORM_VERSION = '0.2.4'..'1.99'
5
+ SUMMARY = 'Command line interface to the LEAP platform'
6
+ DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
7
+ LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib']
8
+ end
9
+ end
data/lib/leap_cli.rb ADDED
@@ -0,0 +1,46 @@
1
+ module LeapCli; end
2
+
3
+ $ruby_version = RUBY_VERSION.split('.').collect{ |i| i.to_i }.extend(Comparable)
4
+
5
+ require 'leap/platform.rb'
6
+
7
+ require 'leap_cli/version.rb'
8
+ require 'leap_cli/constants.rb'
9
+ require 'leap_cli/requirements.rb'
10
+
11
+ require 'leap_cli/leapfile.rb'
12
+ require 'core_ext/hash'
13
+ require 'core_ext/boolean'
14
+ require 'core_ext/nil'
15
+ require 'core_ext/string'
16
+ require 'core_ext/json'
17
+
18
+ require 'leap_cli/log'
19
+ require 'leap_cli/path'
20
+ require 'leap_cli/util'
21
+ require 'leap_cli/util/secret'
22
+ require 'leap_cli/util/remote_command'
23
+ require 'leap_cli/util/x509'
24
+ require 'leap_cli/logger'
25
+
26
+ require 'leap_cli/ssh_key'
27
+ require 'leap_cli/config/object'
28
+ require 'leap_cli/config/node'
29
+ require 'leap_cli/config/tag'
30
+ require 'leap_cli/config/secrets'
31
+ require 'leap_cli/config/object_list'
32
+ require 'leap_cli/config/manager'
33
+
34
+ require 'leap_cli/markdown_document_listener'
35
+
36
+ module LeapCli::Commands; end
37
+
38
+ #
39
+ # allow everyone easy access to log() command.
40
+ #
41
+ module LeapCli
42
+ Util.send(:extend, LeapCli::Log)
43
+ Commands.send(:extend, LeapCli::Log)
44
+ Config::Manager.send(:include, LeapCli::Log)
45
+ extend LeapCli::Log
46
+ end
@@ -0,0 +1,16 @@
1
+ module Capistrano
2
+ class Configuration
3
+ module Connections
4
+ def failed!(server)
5
+ @failure_callback.call(server) if @failure_callback
6
+ Thread.current[:failed_sessions] << server
7
+ end
8
+
9
+ def call_on_failure(&block)
10
+ @failure_callback = block
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+
@@ -0,0 +1,52 @@
1
+ #
2
+ # print subcommands indented in the main global help screen
3
+ #
4
+
5
+ module GLI
6
+ module Commands
7
+ module HelpModules
8
+ class GlobalHelpFormat
9
+ SUB_CMD_INDENT = " "
10
+ def format
11
+ program_desc = @app.program_desc
12
+ program_long_desc = @app.program_long_desc
13
+ if program_long_desc
14
+ wrapper = @wrapper_class.new(Terminal.instance.size[0],4)
15
+ program_long_desc = "\n #{wrapper.wrap(program_long_desc)}\n\n" if program_long_desc
16
+ else
17
+ program_long_desc = "\n"
18
+ end
19
+
20
+ # build a list of commands, sort them so the commands with subcommands are at the bottom
21
+ commands = @sorter.call(@app.commands_declaration_order.reject(&:nodoc)).sort do |a,b|
22
+ if a.commands.any? && b.commands.any?; a.name.to_s <=> b.name.to_s
23
+ elsif a.commands.any?; 1
24
+ elsif b.commands.any?; -1
25
+ else; a.name.to_s <=> b.name.to_s
26
+ end
27
+ end
28
+
29
+ # build a list of command info ([name, description]), including subcommands if appropriate
30
+ command_info_list = []
31
+ commands.each do |command|
32
+ name = [command.name, Array(command.aliases)].flatten.join(', ')
33
+ command_info_list << [name, command.description]
34
+ if command.commands.any?
35
+ @sorter.call(command.commands_declaration_order).each do |cmd|
36
+ command_info_list << [SUB_CMD_INDENT + command.name.to_s + " " + cmd.names, cmd.description + (command.get_default_command == cmd.name ? " (default)" : "")]
37
+ end
38
+ end
39
+ end
40
+
41
+ # display
42
+ command_formatter = ListFormatter.new(command_info_list, @wrapper_class)
43
+ stringio = StringIO.new
44
+ command_formatter.output(stringio)
45
+ commands = stringio.string
46
+ global_option_descriptions = OptionsFormatter.new(global_flags_and_switches, @sorter, @wrapper_class).format
47
+ GLOBAL_HELP.result(binding)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,122 @@
1
+ require 'stringio'
2
+ require 'gli/commands/help_modules/arg_name_formatter'
3
+
4
+ #
5
+ # adaption of RdocDocumentListener to use Markdown
6
+ # see http://rtomayko.github.com/ronn/ronn-format.7
7
+ #
8
+
9
+ module GLI
10
+ module Commands
11
+ class MarkdownDocumentListener
12
+
13
+ def initialize(global_options,options,arguments)
14
+ @io = STDOUT #File.new(File.basename($0) + ".rdoc",'w')
15
+ @nest = ''
16
+ @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new
17
+ end
18
+
19
+ def beginning
20
+ end
21
+
22
+ # Called when processing has completed
23
+ def ending
24
+ #@io.close
25
+ end
26
+
27
+ # Gives you the program description
28
+ def program_desc(desc)
29
+ @io.puts "== #{File.basename($0)} - #{desc}"
30
+ @io.puts
31
+ end
32
+
33
+ def program_long_desc(desc)
34
+ @io.puts desc
35
+ @io.puts
36
+ end
37
+
38
+ # Gives you the program version
39
+ def version(version)
40
+ @io.puts "v#{version}"
41
+ @io.puts
42
+ end
43
+
44
+ def options
45
+ if @nest.size == 0
46
+ @io.puts "=== Global Options"
47
+ else
48
+ @io.puts "#{@nest}=== Options"
49
+ end
50
+ end
51
+
52
+ # Gives you a flag in the current context
53
+ def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type)
54
+ invocations = ([name] + Array(aliases)).map { |_| add_dashes(_) }.join('|')
55
+ usage = "#{invocations} #{arg_name || 'arg'}"
56
+ @io.puts "#{@nest}=== #{usage}"
57
+ @io.puts
58
+ @io.puts String(desc).strip
59
+ @io.puts
60
+ @io.puts "[Default Value] #{default_value || 'None'}"
61
+ @io.puts "[Must Match] #{must_match.to_s}" unless must_match.nil?
62
+ @io.puts String(long_desc).strip
63
+ @io.puts
64
+ end
65
+
66
+ # Gives you a switch in the current context
67
+ def switch(name,aliases,desc,long_desc,negetable)
68
+ if negetable
69
+ name = "[no-]#{name}" if name.to_s.length > 1
70
+ aliases = aliases.map { |_| _.to_s.length > 1 ? "[no-]#{_}" : _ }
71
+ end
72
+ invocations = ([name] + aliases).map { |_| add_dashes(_) }.join('|')
73
+ @io.puts "#{@nest}=== #{invocations}"
74
+ @io.puts String(desc).strip
75
+ @io.puts
76
+ @io.puts String(long_desc).strip
77
+ @io.puts
78
+ end
79
+
80
+ def end_options
81
+ end
82
+
83
+ def commands
84
+ @io.puts "#{@nest}=== Commands"
85
+ @nest = "#{@nest}="
86
+ end
87
+
88
+ # Gives you a command in the current context and creates a new context of this command
89
+ def command(name,aliases,desc,long_desc,arg_name,arg_options)
90
+ @io.puts "#{@nest}=== Command: <tt>#{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options)}</tt>"
91
+ @io.puts String(desc).strip
92
+ @io.puts
93
+ @io.puts String(long_desc).strip
94
+ @nest = "#{@nest}="
95
+ end
96
+
97
+ # Ends a command, and "pops" you back up one context
98
+ def end_command(name)
99
+ @nest.gsub!(/=$/,'')
100
+ end
101
+
102
+ # Gives you the name of the current command in the current context
103
+ def default_command(name)
104
+ @io.puts "[Default Command] #{name}" unless name.nil?
105
+ end
106
+
107
+ def end_commands
108
+ @nest.gsub!(/=$/,'')
109
+ end
110
+
111
+ private
112
+
113
+ def add_dashes(name)
114
+ name = "-#{name}"
115
+ name = "-#{name}" if name.length > 2
116
+ name
117
+ end
118
+
119
+
120
+ end
121
+ end
122
+ end