leap_cli 1.2.5

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.
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