clio 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. data/CHANGES +66 -0
  2. data/MANIFEST +48 -169
  3. data/README +13 -17
  4. data/RELEASE +8 -0
  5. data/VERSION +1 -0
  6. data/lib/clio/buffer.rb +93 -0
  7. data/lib/clio/commandable.rb +71 -69
  8. data/lib/clio/commandline.rb +429 -230
  9. data/lib/clio/consoleutils.rb +0 -16
  10. data/lib/clio/facets/kernel.rb +13 -0
  11. data/lib/clio/facets/string.rb +25 -0
  12. data/lib/clio/layout.rb +14 -0
  13. data/lib/clio/layout/flow.rb +1 -0
  14. data/lib/clio/layout/line.rb +23 -0
  15. data/lib/clio/layout/list.rb +33 -0
  16. data/lib/clio/layout/split.rb +122 -0
  17. data/lib/clio/layout/stack.rb +17 -0
  18. data/lib/clio/layout/table.rb +46 -0
  19. data/lib/clio/progressbar.rb +189 -210
  20. data/lib/clio/string.rb +116 -109
  21. data/lib/clio/usage.rb +184 -0
  22. data/lib/clio/usage/argument.rb +84 -0
  23. data/lib/clio/usage/command.rb +440 -0
  24. data/lib/clio/usage/interface.rb +122 -0
  25. data/lib/clio/usage/main.rb +165 -0
  26. data/lib/clio/usage/option.rb +251 -0
  27. data/lib/clio/usage/parser.rb +191 -0
  28. data/lib/clio/usage/signature.rb +55 -0
  29. data/meta/abstract +3 -0
  30. data/meta/authors +1 -0
  31. data/meta/created +1 -0
  32. data/meta/homepage +1 -0
  33. data/meta/license +1 -0
  34. data/meta/repository +1 -0
  35. data/meta/summary +1 -0
  36. data/spec/commandline/autousage.rd +56 -0
  37. data/spec/commandline/bracket.rd +64 -0
  38. data/spec/commandline/completion.rd +38 -0
  39. data/spec/commandline/define.rd +44 -0
  40. data/spec/commandline/method.rd +60 -0
  41. data/spec/commandline/parse.rd +72 -0
  42. data/spec/commandline/scenario.rd +81 -0
  43. data/spec/commandline/subclass.rd +54 -0
  44. data/spec/string/unit.rd +58 -0
  45. data/spec/usage/define.rd +44 -0
  46. data/spec/usage/parse.rd +47 -0
  47. metadata +65 -196
  48. data/HISTORY +0 -11
  49. data/METADATA +0 -18
  50. data/NEWS +0 -10
  51. data/admin/config/reap.yaml +0 -30
  52. data/admin/depot/commandline.rb +0 -219
  53. data/admin/depot/multicommand.rb +0 -403
  54. data/admin/depot/test_multicommand.rb +0 -40
  55. data/admin/log/notes.xml +0 -28
  56. data/admin/log/stats.html +0 -25
  57. data/admin/log/syntax.log +0 -0
  58. data/admin/log/testunit.log +0 -16
  59. data/admin/pack/clio-0.0.1.gem +0 -0
  60. data/admin/share/reap/example.rb +0 -7
  61. data/admin/temps/lib/clio/about.rb.erb +0 -4
  62. data/lib/clio/command.rb +0 -296
  63. data/lib/clio/option.rb +0 -36
  64. data/lib/clio/runmode.rb +0 -126
  65. data/test/test_command.rb +0 -42
  66. data/test/test_commandline.rb +0 -83
  67. data/vendor/Console/Console.cpp +0 -1203
  68. data/vendor/Console/Console.rdoc +0 -690
  69. data/vendor/Console/Console_ANSI.rdoc +0 -302
  70. data/vendor/Console/HISTORY.txt +0 -7
  71. data/vendor/Console/INSTALL.txt +0 -18
  72. data/vendor/Console/Makefile +0 -162
  73. data/vendor/Console/README.txt +0 -26
  74. data/vendor/Console/doc/classes/Win32.html +0 -115
  75. data/vendor/Console/doc/classes/Win32/Console.html +0 -650
  76. data/vendor/Console/doc/classes/Win32/Console.src/M000001.html +0 -31
  77. data/vendor/Console/doc/classes/Win32/Console.src/M000002.html +0 -23
  78. data/vendor/Console/doc/classes/Win32/Console.src/M000003.html +0 -23
  79. data/vendor/Console/doc/classes/Win32/Console.src/M000004.html +0 -27
  80. data/vendor/Console/doc/classes/Win32/Console.src/M000005.html +0 -23
  81. data/vendor/Console/doc/classes/Win32/Console.src/M000006.html +0 -28
  82. data/vendor/Console/doc/classes/Win32/Console.src/M000007.html +0 -23
  83. data/vendor/Console/doc/classes/Win32/Console.src/M000008.html +0 -24
  84. data/vendor/Console/doc/classes/Win32/Console.src/M000009.html +0 -44
  85. data/vendor/Console/doc/classes/Win32/Console.src/M000010.html +0 -23
  86. data/vendor/Console/doc/classes/Win32/Console.src/M000011.html +0 -33
  87. data/vendor/Console/doc/classes/Win32/Console.src/M000012.html +0 -26
  88. data/vendor/Console/doc/classes/Win32/Console.src/M000013.html +0 -27
  89. data/vendor/Console/doc/classes/Win32/Console.src/M000014.html +0 -28
  90. data/vendor/Console/doc/classes/Win32/Console.src/M000015.html +0 -23
  91. data/vendor/Console/doc/classes/Win32/Console.src/M000016.html +0 -23
  92. data/vendor/Console/doc/classes/Win32/Console.src/M000017.html +0 -23
  93. data/vendor/Console/doc/classes/Win32/Console.src/M000018.html +0 -29
  94. data/vendor/Console/doc/classes/Win32/Console.src/M000019.html +0 -23
  95. data/vendor/Console/doc/classes/Win32/Console.src/M000020.html +0 -23
  96. data/vendor/Console/doc/classes/Win32/Console.src/M000021.html +0 -28
  97. data/vendor/Console/doc/classes/Win32/Console.src/M000022.html +0 -23
  98. data/vendor/Console/doc/classes/Win32/Console.src/M000023.html +0 -28
  99. data/vendor/Console/doc/classes/Win32/Console.src/M000024.html +0 -35
  100. data/vendor/Console/doc/classes/Win32/Console.src/M000025.html +0 -28
  101. data/vendor/Console/doc/classes/Win32/Console.src/M000026.html +0 -28
  102. data/vendor/Console/doc/classes/Win32/Console.src/M000027.html +0 -28
  103. data/vendor/Console/doc/classes/Win32/Console.src/M000028.html +0 -31
  104. data/vendor/Console/doc/classes/Win32/Console.src/M000029.html +0 -23
  105. data/vendor/Console/doc/classes/Win32/Console.src/M000030.html +0 -23
  106. data/vendor/Console/doc/classes/Win32/Console.src/M000031.html +0 -23
  107. data/vendor/Console/doc/classes/Win32/Console.src/M000032.html +0 -27
  108. data/vendor/Console/doc/classes/Win32/Console.src/M000033.html +0 -27
  109. data/vendor/Console/doc/classes/Win32/Console.src/M000034.html +0 -25
  110. data/vendor/Console/doc/classes/Win32/Console/ANSI.html +0 -103
  111. data/vendor/Console/doc/classes/Win32/Console/ANSI/IO.html +0 -220
  112. data/vendor/Console/doc/classes/Win32/Console/ANSI/IO.src/M000035.html +0 -32
  113. data/vendor/Console/doc/classes/Win32/Console/ANSI/IO.src/M000036.html +0 -205
  114. data/vendor/Console/doc/classes/Win32/Console/ANSI/IO.src/M000037.html +0 -40
  115. data/vendor/Console/doc/classes/Win32/Console/ANSI/IO.src/M000038.html +0 -25
  116. data/vendor/Console/doc/classes/Win32/Console/API.html +0 -758
  117. data/vendor/Console/doc/classes/Win32/Console/API.src/M000039.html +0 -27
  118. data/vendor/Console/doc/classes/Win32/Console/API.src/M000040.html +0 -27
  119. data/vendor/Console/doc/classes/Win32/Console/API.src/M000041.html +0 -27
  120. data/vendor/Console/doc/classes/Win32/Console/API.src/M000042.html +0 -32
  121. data/vendor/Console/doc/classes/Win32/Console/API.src/M000043.html +0 -32
  122. data/vendor/Console/doc/classes/Win32/Console/API.src/M000044.html +0 -28
  123. data/vendor/Console/doc/classes/Win32/Console/API.src/M000045.html +0 -26
  124. data/vendor/Console/doc/classes/Win32/Console/API.src/M000046.html +0 -26
  125. data/vendor/Console/doc/classes/Win32/Console/API.src/M000047.html +0 -27
  126. data/vendor/Console/doc/classes/Win32/Console/API.src/M000048.html +0 -30
  127. data/vendor/Console/doc/classes/Win32/Console/API.src/M000049.html +0 -29
  128. data/vendor/Console/doc/classes/Win32/Console/API.src/M000050.html +0 -27
  129. data/vendor/Console/doc/classes/Win32/Console/API.src/M000051.html +0 -28
  130. data/vendor/Console/doc/classes/Win32/Console/API.src/M000052.html +0 -30
  131. data/vendor/Console/doc/classes/Win32/Console/API.src/M000053.html +0 -27
  132. data/vendor/Console/doc/classes/Win32/Console/API.src/M000054.html +0 -29
  133. data/vendor/Console/doc/classes/Win32/Console/API.src/M000055.html +0 -29
  134. data/vendor/Console/doc/classes/Win32/Console/API.src/M000056.html +0 -28
  135. data/vendor/Console/doc/classes/Win32/Console/API.src/M000057.html +0 -27
  136. data/vendor/Console/doc/classes/Win32/Console/API.src/M000058.html +0 -47
  137. data/vendor/Console/doc/classes/Win32/Console/API.src/M000059.html +0 -32
  138. data/vendor/Console/doc/classes/Win32/Console/API.src/M000060.html +0 -47
  139. data/vendor/Console/doc/classes/Win32/Console/API.src/M000061.html +0 -34
  140. data/vendor/Console/doc/classes/Win32/Console/API.src/M000062.html +0 -32
  141. data/vendor/Console/doc/classes/Win32/Console/API.src/M000063.html +0 -32
  142. data/vendor/Console/doc/classes/Win32/Console/API.src/M000064.html +0 -35
  143. data/vendor/Console/doc/classes/Win32/Console/API.src/M000065.html +0 -26
  144. data/vendor/Console/doc/classes/Win32/Console/API.src/M000066.html +0 -27
  145. data/vendor/Console/doc/classes/Win32/Console/API.src/M000067.html +0 -29
  146. data/vendor/Console/doc/classes/Win32/Console/API.src/M000068.html +0 -27
  147. data/vendor/Console/doc/classes/Win32/Console/API.src/M000069.html +0 -27
  148. data/vendor/Console/doc/classes/Win32/Console/API.src/M000070.html +0 -28
  149. data/vendor/Console/doc/classes/Win32/Console/API.src/M000071.html +0 -27
  150. data/vendor/Console/doc/classes/Win32/Console/API.src/M000072.html +0 -26
  151. data/vendor/Console/doc/classes/Win32/Console/API.src/M000073.html +0 -27
  152. data/vendor/Console/doc/classes/Win32/Console/API.src/M000074.html +0 -31
  153. data/vendor/Console/doc/classes/Win32/Console/API.src/M000075.html +0 -27
  154. data/vendor/Console/doc/classes/Win32/Console/API.src/M000076.html +0 -32
  155. data/vendor/Console/doc/classes/Win32/Console/API.src/M000077.html +0 -27
  156. data/vendor/Console/doc/classes/Win32/Console/API.src/M000078.html +0 -32
  157. data/vendor/Console/doc/classes/Win32/Console/API.src/M000079.html +0 -32
  158. data/vendor/Console/doc/classes/Win32/Console/API.src/M000080.html +0 -32
  159. data/vendor/Console/doc/classes/Win32/Console/Constants.html +0 -360
  160. data/vendor/Console/doc/created.rid +0 -1
  161. data/vendor/Console/doc/files/Console_ANSI_rdoc.html +0 -407
  162. data/vendor/Console/doc/files/Console_cpp.html +0 -104
  163. data/vendor/Console/doc/files/Console_rdoc.html +0 -964
  164. data/vendor/Console/doc/files/lib/Win32/Console/ANSI_rb.html +0 -123
  165. data/vendor/Console/doc/files/lib/Win32/Console_rb.html +0 -297
  166. data/vendor/Console/doc/fr_class_index.html +0 -32
  167. data/vendor/Console/doc/fr_file_index.html +0 -31
  168. data/vendor/Console/doc/fr_method_index.html +0 -106
  169. data/vendor/Console/doc/index.html +0 -24
  170. data/vendor/Console/doc/rdoc-style.css +0 -172
  171. data/vendor/Console/extconf.rb +0 -18
  172. data/vendor/Console/lib/Term/ansicolor.rb +0 -76
  173. data/vendor/Console/lib/Win32/Console.rb +0 -970
  174. data/vendor/Console/lib/Win32/Console/ANSI.rb +0 -305
  175. data/vendor/Console/test/test_cursor.rb +0 -9
  176. data/vendor/Console/test/test_mouse.rb +0 -6
  177. data/vendor/Console/test/test_readinput.rb +0 -62
  178. data/vendor/Console/test/test_readoutput.rb +0 -52
  179. data/vendor/Console/test/test_sendevent.rb +0 -17
  180. data/vendor/Console/test/test_title.rb +0 -14
  181. data/vendor/Console/test/test_write.rb +0 -36
@@ -2,7 +2,7 @@ require 'clio/errors'
2
2
 
3
3
  module Clio
4
4
 
5
- # = Commandable mixin
5
+ # = Commandable Mixin
6
6
  #
7
7
  # The Commandable mixin is a very quick and and easy
8
8
  # way to make almost any class usable via a command
@@ -40,6 +40,8 @@ module Clio
40
40
  # Commandable also defines #command_missing and #option_missing,
41
41
  # which you can override to provide suitable results.
42
42
  #
43
+ # TODO: Maybe command_missing is redundant, and method_missing would suffice?
44
+ #
43
45
  module Commandable
44
46
 
45
47
  # Used to invoke the command.
@@ -50,7 +52,7 @@ module Clio
50
52
  # This is the fallback subcommand. Override this to provide
51
53
  # a fallback when no command is given on the commandline.
52
54
  def command_missing
53
- raise NoMethodError
55
+ raise NoCommandError
54
56
  end
55
57
 
56
58
  # Override option_missing if needed.
@@ -62,88 +64,88 @@ module Clio
62
64
  raise NoOptionError, opt
63
65
  end
64
66
 
65
- class << self
67
+ class << self
66
68
 
67
- def run(obj, argv=ARGV)
68
- args = parse(obj, argv)
69
- subcmd = args.shift
70
- if subcmd && !obj.respond_to?("#{subcmd}=")
71
- obj.send(subcmd, *args)
72
- else
73
- obj.command_missing
69
+ def run(obj, argv=ARGV)
70
+ args = parse(obj, argv)
71
+ subcmd = args.shift
72
+ if subcmd && !obj.respond_to?("#{subcmd}=")
73
+ obj.send(subcmd, *args)
74
+ else
75
+ obj.command_missing
76
+ end
74
77
  end
75
- end
76
78
 
77
- #def run(obj)
78
- # methname, args = *parse(obj)
79
- # meth = obj.method(methname)
80
- # meth.call(*args)
81
- #end
79
+ #def run(obj)
80
+ # methname, args = *parse(obj)
81
+ # meth = obj.method(methname)
82
+ # meth.call(*args)
83
+ #end
84
+
85
+ #
86
+ def parse(obj, argv)
87
+ case argv
88
+ when String
89
+ require 'shellwords'
90
+ argv = Shellwords.shellwords(argv)
91
+ else
92
+ argv = argv.dup
93
+ end
82
94
 
83
- #
84
- def parse(obj, argv)
85
- case argv
86
- when String
87
- require 'shellwords'
88
- argv = Shellwords.shellwords(argv)
89
- else
90
95
  argv = argv.dup
96
+ args, opts, i = [], {}, 0
97
+ while argv.size > 0
98
+ case opt = argv.shift
99
+ when /=/
100
+ parse_equal(obj, opt, argv)
101
+ when /^--/
102
+ parse_option(obj, opt, argv)
103
+ when /^-/
104
+ parse_flags(obj, opt, argv)
105
+ else
106
+ args << opt
107
+ end
108
+ end
109
+ return args
91
110
  end
92
111
 
93
- argv = argv.dup
94
- args, opts, i = [], {}, 0
95
- while argv.size > 0
96
- case opt = argv.shift
97
- when /=/
98
- parse_equal(obj, opt, argv)
99
- when /^--/
100
- parse_option(obj, opt, argv)
101
- when /^-/
102
- parse_flags(obj, opt, argv)
112
+ #
113
+ def parse_equal(obj, opt, argv)
114
+ if md = /^[-]*(.*?)=(.*?)$/.match(opt)
115
+ x, v = md[1], md[2]
103
116
  else
104
- args << opt
117
+ raise ArgumentError, "#{x}"
118
+ end
119
+ if obj.respond_to?("#{x}=")
120
+ # TODO: to_b if 'true' or 'false' ?
121
+ obj.send("#{x}=",v)
122
+ else
123
+ obj.option_missing(x, v) # argv?
105
124
  end
106
125
  end
107
- return args
108
- end
109
-
110
- #
111
- def parse_equal(obj, opt, argv)
112
- if md = /^[-]*(.*?)=(.*?)$/.match(opt)
113
- x, v = md[1], md[2]
114
- else
115
- raise ArgumentError, "#{x}"
116
- end
117
- if obj.respond_to?("#{x}=")
118
- # TODO: to_b if 'true' or 'false' ?
119
- obj.send("#{x}=",v)
120
- else
121
- obj.option_missing(x, v) # argv?
122
- end
123
- end
124
-
125
- #
126
- def parse_option(obj, opt, argv)
127
- x = opt[2..-1]
128
- if obj.respond_to?("#{x}=")
129
- obj.send("#{x}=",true)
130
- else
131
- obj.option_missing(x, argv)
132
- end
133
- end
134
126
 
135
- #
136
- def parse_flags(obj, opt, args)
137
- x = opt[1..-1]
138
- c = 0
139
- x.split(//).each do |k|
140
- if obj.respond_to?("#{k}=")
141
- obj.send("#{k}=",true)
127
+ #
128
+ def parse_option(obj, opt, argv)
129
+ x = opt[2..-1]
130
+ if obj.respond_to?("#{x}=")
131
+ obj.send("#{x}=",true)
142
132
  else
143
133
  obj.option_missing(x, argv)
144
134
  end
145
135
  end
146
- end
136
+
137
+ #
138
+ def parse_flags(obj, opt, args)
139
+ x = opt[1..-1]
140
+ c = 0
141
+ x.split(//).each do |k|
142
+ if obj.respond_to?("#{k}=")
143
+ obj.send("#{k}=",true)
144
+ else
145
+ obj.option_missing(x, argv)
146
+ end
147
+ end
148
+ end
147
149
 
148
150
  end #class << self
149
151
 
@@ -1,275 +1,474 @@
1
- require 'shellwords'
2
- require 'facets/kernel/object_class'
3
- require 'facets/array/indexable'
1
+ require 'clio/facets/kernel' # for deep_copy
2
+ require 'clio/usage'
3
+ #require 'shellwords'
4
4
 
5
5
  module Clio
6
- ### = Commandline
7
- ###
8
- ### What a strange thing is the Clio Commandline.
9
- ### An entity unknown until put upon.
10
- ###
11
- ### cmd = Clio::Commandline.new("--force copy --file try.rb")
12
- ### cmd.option_alias(:f?, :force?)
13
- ### cmd.option_alias(:o, :file)
14
- ###
15
- ### cmd.file #=> 'try.rb'
16
- ### cmd.force? #=> true
17
- ### cmd.o #=> 'try.rb'
18
- ### cmd.f? #=> true
19
- ###
20
- ### TODO: Allow option setter methods (?)
21
- ### TODO: Allow a hash as argument to initialize (?)
6
+
7
+ # = Commandline
8
+ #
9
+ # Clio's Commandline class is a very versitile command line parser.
10
+ # A Command can be used either declaritively, defining usage
11
+ # and help information upfront; or lazily, whereby information
12
+ # about usage is built-up as the commandline actually gets use in
13
+ # one's program; or you can use a mixture of the two.
14
+ #
15
+ # = Underlying Notation
16
+ #
17
+ # As you might expect the fluent notation can be broken down into
18
+ # block notation.
19
+ #
20
+ # cli = Clio::Command.new
21
+ # cli.usage do
22
+ # option(:verbose, :v) do
23
+ # help('verbose output')
24
+ # end
25
+ # option(:quiet, :q) do
26
+ # help('run silently')
27
+ # xor(:V)
28
+ # end
29
+ # command(:document) do
30
+ # help('generate documentation')
31
+ # option(:output, :o) do
32
+ # type('FILE')
33
+ # help('output directory')
34
+ # end
35
+ # argument('files') do
36
+ # multiple
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # Clearly block notation is DRY and easier to read, but fluent
42
+ # notation is important to have because it allows the Commandline
43
+ # object to be passed around as an argument and modified easily.
44
+ #
45
+ # == Method Notation
46
+ #
47
+ # This notation is very elegant, but slightly more limited in scope.
48
+ # For instance, subcommands that use non-letter characters, such as ':',
49
+ # can not be described with this notation.
50
+ #
51
+ # cli.usage.document('*files', '--output=FILE -o')
52
+ # cli.usage('--verbose -V','--quiet -q')
53
+ #
54
+ # cli.usage.help(
55
+ # 'document' , 'generate documentation',
56
+ # 'validate' , 'run tests or specifications',
57
+ # '--verbose' , 'verbose output',
58
+ # '--quiet' , 'run siltently'
59
+ # )
60
+ #
61
+ # cli.usage.document.help(
62
+ # '--output', 'output directory'
63
+ # 'file*', 'files to document'
64
+ # )
65
+ #
66
+ # This notation is slightly more limited in scope... so...
67
+ #
68
+ # cli.usage.command(:document, '--output=FILE -o', 'files*')
69
+ #
70
+ # == Bracket Shorthand Notation
71
+ #
72
+ # The core notation can be somewhat verbose. As a further convenience
73
+ # commandline usage can be defined with a brief <i>bracket shorthand</i>.
74
+ # This is especailly useful when the usage is simple and statically defined.
75
+ #
76
+ # cli.usage['document']['--output=FILE -o']['FILE*']
77
+ #
78
+ # Using a little creativity to improve readabilty we can convert the
79
+ # whole example from above using this notation.
80
+ #
81
+ # cli.usage['--verbose -V', 'verbose output' ] \
82
+ # ['--quiet -q', 'run silently' ] \
83
+ # ['document', 'generate documention' ] \
84
+ # [ '--output=FILE -o', 'output directory' ] \
85
+ # [ 'FILE*', 'files to document' ]
86
+ #
87
+ # Alternately the help information can be left out and defined in
88
+ # a seprate set of usage calls.
89
+ #
90
+ # cli.usage['--verbose -V']['--quiet -q'] \
91
+ # ['document']['--output=FILE -o']['FILE*']
92
+ #
93
+ # cli.usage.help(
94
+ # 'document' , 'generate documentation',
95
+ # 'validate' , 'run tests or specifications',
96
+ # '--verbose' , 'verbose output',
97
+ # '--quiet' , 'run siltently'
98
+ # )
99
+ #
100
+ # cli.usage['document'].help(
101
+ # '--output', 'output directory'
102
+ # 'FILE', 'files to docment'
103
+ # )
104
+ #
105
+ # A little more verbose, but a bit more intutive.
106
+ #
107
+ # == Combining Notations
108
+ #
109
+ # Since the various notations all translate to same underlying
110
+ # structures, they can be mixed and matched as suites ones taste.
111
+ # For example we could mix Method Notation and Bracket Notation.
112
+ #
113
+ # cli.usage.document['--output=FILE -o']['file*']
114
+ # cli.usage['--verbose -V']['--quiet -q']
115
+ #
116
+ # The important thing to keep in mind when doing this is what is
117
+ # returned by each type of usage call.
118
+ #
119
+ # == Commandline Parsing
120
+ #
121
+ # With usage in place, call the +parse+ method to process the
122
+ # actual commandline.
123
+ #
124
+ # cli.parse
125
+ #
126
+ # If no command arguments are passed to +parse+, ARGV is used.
127
+ #
128
+ #--
129
+ # == Passive Parsing
130
+ #
131
+ # The Command class allows you to declare as little or as
132
+ # much of the commandline interface upfront as is suitable to
133
+ # your application. When using the commandline object, if not
134
+ # already defined, options will be lazily created. For example:
135
+ #
136
+ # cli = Clio::Commandline.new('--force')
137
+ # cli.force? #=> true
138
+ #
139
+ # Commandline sees that you expect a '--force' flag to be an
140
+ # acceptable option. So it will call cli.usage.option('force')
141
+ # behind the scenes before trying to determine the actual value
142
+ # per the content of the command line. You can add aliases as
143
+ # parameters to this call as well.
144
+ #
145
+ # cli = Clio::Commandline.new('-f')
146
+ # cli.force?(:f) #=> true
147
+ #
148
+ # Once set, you do not need to specify the alias again:
149
+ #
150
+ # cli.force? #=> true
151
+ #
152
+ # With the exception of help information, this means you can
153
+ # generally just use a commandline as needed without having
154
+ # to declare anything upfront.
155
+ #++
156
+ #
157
+ # == Usage Cache
158
+ #
159
+ # Lastly, Commandline provides a simple means to cache usage
160
+ # information to a configuration file, which then can be used
161
+ # again the next time the same command is used. This allows
162
+ # Commandline to provide high-performane tab completion.
163
+ #
164
+ #--
165
+ # == Coming Soon
166
+ #
167
+ # In the future Commandline will be able to generate Manpage
168
+ # templates.
169
+ #
170
+ # TODO: Allow option setter methods (?)
171
+ # TODO: Allow a hash as argument to initialize (?)
172
+ #++
173
+
22
174
  class Commandline
23
- instance_methods.each{ |m| private m if m !~ /^(__|instance_|object_|send$|inspect$)/ }
24
-
25
- ### Splits a raw command line into two smaller
26
- ### ones. The first including only options
27
- ### upto the first non-option argument. This
28
- ### makes quick work of separating a subcommand
29
- ### from the options for a main command.
30
- # TODO: Rename this method.
31
- def self.gerrymander(argv=ARGV)
32
- if String===argv
33
- argv = Shellwords.shellwords(argv)
34
- end
35
- sub = argv.find{ |x| x !~ /^[-]/ }
36
- idx = argv.index(sub)
37
- opts = argv[0...idx]
38
- scmd = argv[idx..-1]
39
- return opts, scmd
175
+
176
+ #
177
+ instance_methods.each do |m|
178
+ private m if m !~ /^(__|instance_|object_|send$|class$|inspect$|respond_to\?$)/
40
179
  end
41
180
 
42
- ### Define an option attibute.
43
- ### While commandline can be used without
44
- ### pre-declartion of support options
45
- ### doding so allows for creating option
46
- ### aliases. Eg. --quiet and -q.
47
- def self.attr(name, *aliases)
48
- (@predefined_options ||= []) << [name, *aliases]
49
-
50
- name = name.to_s
51
- if name =~ /\?$/
52
- key = name.chomp('?')
53
- #attr_writer name
54
- module_eval "def #{key}?; @#{key} ; end"
55
- aliases.each do |alt|
56
- alt = alt.to_s.chomp('?')
57
- alias_method("#{alt}?", "#{key}?")
58
- #alias_method("#{alt}=", "#{name}=")
59
- end
60
- else
61
- attr_reader name
62
- #module_eval "def #{name}; self[:#{name}] ; end"
63
- aliases.each do |alt|
64
- #alt = alt.to_s.chomp('?') # TODO: raise error ?
65
- alias_method("#{alt}" , "#{name}")
66
- #alias_method("#{alt}=", "#{name}=")
67
- end
181
+ class << self
182
+
183
+ #def inherited(subclass)
184
+ #p usage.to_s
185
+ #p subclass.usage.to_s
186
+ # subclass.usage = self.usage.clone #deep_copy
187
+ #p subclass.usage.to_s
188
+ # end
189
+
190
+ # Command usage.
191
+ def usage
192
+ @usage ||= (
193
+ if ancestors[1] < Commandline
194
+ ancestors[1].usage.dup
195
+ else
196
+ Usage.new
197
+ end
198
+ )
199
+ end
200
+
201
+ def usage=(u)
202
+ raise ArgumentError unless u <= Usage
203
+ @usage = u
204
+ end
205
+
206
+ # if ancestors[1] < Command
207
+ # @usage = ancestors[0].usage.deep_copy
208
+ # else
209
+ # @usage = Usage.new
210
+ # end
211
+
212
+ #
213
+ def subcommand(name, help=nil, &block)
214
+ usage.subcommand(name, help, &block)
215
+ end
216
+ alias_method :command, :subcommand
217
+ alias_method :cmd, :subcommand
218
+
219
+ #
220
+ def option(name, *aliases, &block)
221
+ usage.option(name, *aliases, &block)
222
+ end
223
+ alias_method :switch, :option
224
+
225
+ #
226
+ def opt(label, help, &block)
227
+ usage.opt(label, help, &block)
228
+ end
229
+ alias_method :swt, :opt
230
+
231
+ #
232
+ def argument(*n_type, &block)
233
+ usage.argument(*n_type, &block)
68
234
  end
235
+
236
+ #
237
+ def help(string=nil)
238
+ usage.help(string)
239
+ end
240
+
241
+ #
242
+ #def arg(label, help, &block)
243
+ # usage.arg(label, help, &block)
244
+ #end
245
+
69
246
  end
70
247
 
71
- ### Returns a list of all pre-defined options.
72
- ### It does this by seaching class ancestry
73
- ### for instance_methods until it reaches the
74
- ### Commandline base class.
75
- ### TODO: Rename #runmodes method.
76
- ### TODO: Robust enough? Use an Inheritor instead?
77
- def self.predefined_options
78
- @predefined_options ||= []
79
- ancestor = ancestors[1]
80
- if ancestor > ::Clio::Commandline
81
- @predefined_options
248
+ # New Command.
249
+ def initialize(argv=nil, opts={}, &block)
250
+ argv_set(argv || ARGV)
251
+ #if opts[:usage]
252
+ # @usage = opts[:usage]
253
+ #else
254
+ # #@usage = load_cache
255
+ #end
256
+ if self.class == Commandline
257
+ @usage = Usage.new
82
258
  else
83
- @predefined_options | ancestor.predefined_options
259
+ @usage = self.class.usage #|| Usage.new #.deep_copy
84
260
  end
261
+ @usage.instance_eval(&block) if block
85
262
  end
86
263
 
87
- public
88
-
89
- ### This method provides the centralized means
90
- ### of accessing the options and arguments on
91
- ### the commandline.
92
- def [](index)
93
- case index
94
- when Integer
95
- @arguments[index] ||= (
96
- args = @argv.select{ |e| e !~ /^-/ }
97
- val = args[index]
98
- @argv.delete(args[index])
99
- val
100
- )
101
- else
102
- return send(index) if respond_to?(index)
103
- key = index.to_s.chomp('?')
104
- val = option_parse(index)
105
- instance_variable_set("@#{key}", val)
106
- (class << self; self; end).class_eval %{
107
- def #{index}; @#{key}; end
108
- }
109
- return val
264
+ #
265
+ def argv_set(argv)
266
+ # reset parser
267
+ @parser = nil
268
+ # convert to array if string
269
+ if String===argv
270
+ argv = Shellwords.shellwords(argv)
110
271
  end
272
+ # remove anything subsequent to '--'
273
+ if index = argv.index('--')
274
+ argv = argv[0...index]
275
+ end
276
+ @argv = argv
111
277
  end
112
278
 
113
- def shift!
114
- args = @argv.select{ |e| e !~ /^-/ }
115
- val = args.first
116
- @argv.delete(val)
117
- val
279
+ #
280
+ def cli
281
+ #parse unless @cli
282
+ @cli
118
283
  end
119
284
 
120
- ### Define an option alias. This adds en entry to
121
- ### the aliases hash, pointing new to a list of
122
- ### all aliases and the first entry on th list
123
- ### being the master key.
124
- def option_alias(new, old)
125
- self[old]
126
- key = old.to_s.chomp('?')
127
- val = option_parse(new)
128
- instance_variable_set("@#{key}", val) if val
129
- (class << self; self; end).class_eval do
130
- alias_method new, old
131
- end
285
+ #
286
+ #def usage(name=nil, &block)
287
+ # @usage ||= Usage.new(name)
288
+ # @usage.instance_eval(&block) if block
289
+ # @usage
290
+ #end
291
+
292
+ def usage
293
+ @usage
132
294
  end
133
295
 
134
- ### Access to the underlying commandline "ARGV".
135
- ### This will show what is yet to be processed.
136
- def instance_delegate ; @argv ; end
137
-
138
- ### Returns a hash of all options parsed.
139
- def instance_options
140
- h = {}
141
- ivs = instance_variables - ['@arguments','@argv']
142
- ivs.each do |iv|
143
- val = instance_variable_get(iv)
144
- h[iv.sub('@','').to_sym] = val if val
145
- end
146
- h
296
+ #
297
+ def to_s
298
+ usage.to_s
147
299
  end
148
300
 
149
- ### Returns a list of all arguments parsed.
150
- def instance_arguments
151
- @arguments
301
+ #
302
+ def to_s_help
303
+ usage.to_s_help
152
304
  end
153
305
 
154
- private
155
-
156
- ### New Commandline. Takse a single argument
157
- ### which can be a "shell" string, or an array
158
- ### of shell arguments, like ARGV. If none
159
- ### is given it defaults to ARGV.
160
- def initialize(argv=ARGV)
161
- case argv
162
- when String
163
- @argv = Shellwords.shellwords(argv)
164
- #when Hash
165
- # argv.each{ |k,v| send("#{k}=", v) }
166
- else
167
- @argv = argv.dup
168
- end
169
- @arguments = []
170
-
171
- # parse predefined options attributes.
172
- object_class.predefined_options.each do |modes|
173
- key = modes.first.to_s.chomp('?')
174
- modes.reverse.each do |i|
175
- val = option_parse(i)
176
- instance_variable_set("@#{key}", val) if val
177
- end
178
- end
306
+ #
307
+ def parse(argv=nil)
308
+ argv_set(argv) if argv
309
+ @cli = parser.parse
179
310
  end
180
311
 
181
- ### Routes to #[].
182
- def method_missing(name, *args)
183
- super unless args.empty?
184
- case name.to_s
185
- when /\=$/
186
- super
187
- else
188
- self[name]
189
- end
312
+ #
313
+ def parser
314
+ @parser ||= Usage::Parser.new(usage, @argv)
315
+ end
316
+
317
+ #
318
+ def [](i)
319
+ @cli[i]
190
320
  end
191
321
 
192
- def option_parse(index)
193
- index = index.to_s
194
- name = index.chomp('?')
195
- key = name.to_sym
322
+ #
323
+ def command ; cli.command ; end
324
+
325
+ #
326
+ def commands ; cli.commands ; end
327
+
328
+ #
329
+ def arguments ; cli.arguments ; end
330
+
331
+ #
332
+ def switches ; cli.options ; end
333
+
334
+ #
335
+ alias_method :options, :switches
336
+
337
+ # Parameters
338
+ #
339
+ def parameters ; cli.parameters ; end
196
340
 
197
- kind = name.size == 1 ? 'letter' : 'word'
198
- flag = index =~ /\?$/ ? 'flag' : 'value'
341
+ #
342
+ def to_a
343
+ cli.to_a
344
+ end
199
345
 
200
- send("option_#{kind}_#{flag}", key)
346
+ # Commandline fully valid?
347
+ #
348
+ def valid?
349
+ @cli.valid?
201
350
  end
202
351
 
203
- ### Parse a flag option.
204
- def option_word_flag(name)
205
- o = "--#{name}"
206
- i = @argv.index_of{ |e| e =~ /^#{o}[=]?/ }
207
- return false unless i
208
- raise ArgumentError if @argv[i] =~ /=/
209
- @argv.delete_at(i)
210
- return true
352
+ # TODO: adding '-' is best idea?
353
+ #
354
+ def completion(argv=nil)
355
+ argv_set(argv) if argv
356
+ @argv << "\t"
357
+ parse
358
+ @argv.pop
359
+ parser.errors[0][1].completion.collect{ |s| s.to_s }
360
+ #@argv.pop if @argv.last == '?'
361
+ #load_cache
362
+ #parse
211
363
  end
212
364
 
213
- ### Parse a value option.
214
- def option_word_value(name)
215
- o = "--#{name}"
216
- i = @argv.index_of{ |e| e =~ /^#{o}[=]?/ }
217
- return false unless i
365
+ #
366
+ #def load_cache
367
+ # if usage = Usage.load_cache
368
+ # @usage = usage
369
+ # end
370
+ #end
218
371
 
219
- if @argv[i] =~ /=/
220
- key, val = *@argv[i].split('=')
221
- argv[i] = nil
222
- else
223
- case @argv[i+1]
224
- when nil, /^-/
225
- raise ArgumentError
372
+ # Method missing provide passive usage and parsing.
373
+ #
374
+ # TODO: This reparses the commandline after every query.
375
+ # Need only parse if usage has change.
376
+ def method_missing(s, *a)
377
+ begin
378
+ s = s.to_s
379
+ case s
380
+ when /[=]$/
381
+ n = s.chomp('=')
382
+ usage.option(n).type(*a)
383
+ parse
384
+ res = @cli.options[n.to_sym]
385
+ when /[!]$/
386
+ n = s.chomp('!')
387
+ cmd = usage.commands[n.to_sym] || usage.command(n, *a)
388
+ res = parse
389
+ when /[?]$/
390
+ n = s.chomp('?')
391
+ u = usage.option(n, *a)
392
+ parse
393
+ res = @cli.options[u.key]
226
394
  else
227
- key = @argv[i]
228
- val = @argv[i+1]
229
- @argv.delete_at(i) # do it twice
230
- @argv.delete_at(i)
395
+ usage.option(s, *a)
396
+ parse
397
+ res = @cli.options[s.to_sym]
231
398
  end
399
+ rescue Usage::ParseError => e
400
+ res = nil
232
401
  end
233
- return val
402
+ return res
234
403
  end
235
404
 
236
- ### Parse a single letter flag option.
237
- def option_letter_flag(letter)
238
- o = letter
239
- i = @argv.index_of{ |e| e =~ /[-][^-]\w*(#{o})\w*$/ }
240
- if i
241
- @argv[i] = @argv[i].gsub(o.to_s,'')
242
- true
243
- end
244
- false
245
- end
405
+ end # class Commandline
246
406
 
247
- ### Parse a single letter value option.
248
- def option_letter_value(letter)
249
- o = letter
250
- i = @argv.index_of{ |e| e =~ /[-]\w*#{o}(\=|$)/ }
251
- return nil unless i
252
- if @argv[i] =~ /=/
253
- rest, val = argv[i].split('=')
254
- @argv[i] = rest
255
- else
256
- case @argv[i+1]
257
- when nil, /^-/
258
- raise ArgumentError
259
- else
260
- val = @argv[i+1]
261
- new = @argv[i].gsub(o.to_s,'')
262
- if new == '-'
263
- @argv.delete_at(i)
264
- else
265
- @argv[i] = new
266
- end
267
- @argv.delete_at(i+1)
268
- end
407
+ end # module Clio
408
+
409
+
410
+
411
+ =begin demo 1
412
+
413
+ cli = Clio::Commandline.new
414
+
415
+ cli.usage do
416
+ command(:document) do
417
+ help('generate documentation')
418
+ option(:output, :o) do
419
+ type('FILE')
420
+ help('output directory')
269
421
  end
270
- return val
271
422
  end
423
+ option(:verbose, :V) do
424
+ help('verbose output')
425
+ end
426
+ option(:quiet, :q) do
427
+ help('run silently')
428
+ xor(:verbose)
429
+ end
430
+ end
431
+
432
+ #p cli
433
+ puts
434
+ puts cli.to_s_help
435
+
436
+ =end
437
+
438
+ =begin demo 2
272
439
 
440
+ cli = Clio::Commandline.new('--verbose')
441
+
442
+ cli.usage do
443
+ cmd(:document, 'generate documentation') do
444
+ opt('--output=FILE -o', 'output directory')
445
+ end
446
+ opt('--verbose -V', 'verbose output')
447
+ opt('--quiet -q', 'run silently')
273
448
  end
274
- end
449
+
450
+ =end
451
+
452
+ =begin demo 3
453
+
454
+ # cli.usage %{
455
+ # document generate documentation
456
+ # -o --output=FILE output directory
457
+ # -V --verbose verbose output
458
+ # -q --quiet run silently
459
+ # }
460
+
461
+ #p cline.verbose?(:V)
462
+ #p cline.force?(:f)
463
+ #p cline.document.output='FILE'
464
+
465
+ p cli
466
+ puts
467
+ puts cli.to_s_help
468
+
469
+ =end
470
+
471
+ #cli[['--verbose', '-V'],['--quiet', '-q']] \
472
+ # ['--force'] \
473
+ # ['document']['--output=FILE', '-o']
275
474