cl 0.0.4 → 0.1.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +47 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +22 -12
- data/NOTES.md +2 -24
- data/README.md +1 -1
- data/examples/args/cast +82 -0
- data/examples/args/opts +36 -0
- data/examples/args/required +46 -0
- data/examples/args/splat +61 -0
- data/examples/gem +104 -0
- data/examples/heroku +49 -0
- data/examples/{multi.rb → rakeish} +13 -11
- data/lib/cl/arg.rb +12 -30
- data/lib/cl/args.rb +8 -5
- data/lib/cl/cast.rb +29 -0
- data/lib/cl/cmd.rb +65 -17
- data/lib/cl/config/env.rb +52 -0
- data/lib/cl/config/files.rb +34 -0
- data/lib/cl/config.rb +30 -0
- data/lib/cl/ctx.rb +29 -0
- data/lib/cl/help/cmd.rb +142 -0
- data/lib/cl/help/cmds.rb +28 -0
- data/lib/cl/help/table.rb +45 -0
- data/lib/cl/{format → help}/usage.rb +10 -4
- data/lib/cl/help.rb +10 -12
- data/lib/cl/helper.rb +11 -0
- data/lib/cl/opt.rb +141 -0
- data/lib/cl/opts.rb +155 -0
- data/lib/cl/parser.rb +39 -0
- data/lib/cl/runner/default.rb +35 -19
- data/lib/cl/runner/multi.rb +6 -7
- data/lib/cl/ui.rb +137 -0
- data/lib/cl/version.rb +2 -2
- data/lib/cl.rb +64 -19
- metadata +40 -18
- data/examples/args/cast.rb +0 -63
- data/examples/args/opts.rb +0 -32
- data/examples/args/required.rb +0 -35
- data/examples/args/splat.rb +0 -52
- data/examples/gem.rb +0 -81
- data/examples/heroku.rb +0 -41
- data/lib/cl/format/cmd.rb +0 -31
- data/lib/cl/format/list.rb +0 -22
- data/lib/cl/format/table.rb +0 -19
- data/lib/cl/options.rb +0 -13
- data/lib/cl/registry.rb +0 -53
data/lib/cl/cast.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Cl
|
2
|
+
module Cast
|
3
|
+
TRUE = /^(true|yes|on)$/
|
4
|
+
FALSE = /^(false|no|off)$/
|
5
|
+
|
6
|
+
def cast(value)
|
7
|
+
case type
|
8
|
+
when nil
|
9
|
+
value
|
10
|
+
when :array
|
11
|
+
Array(value).compact.flatten.map { |value| value.to_s.split(',') }.flatten
|
12
|
+
when :string, :str
|
13
|
+
value.to_s unless value.to_s.empty?
|
14
|
+
when :flag, :boolean, :bool
|
15
|
+
return true if value.to_s =~ TRUE
|
16
|
+
return false if value.to_s =~ FALSE
|
17
|
+
!!value
|
18
|
+
when :integer, :int
|
19
|
+
Integer(value)
|
20
|
+
when :float
|
21
|
+
Float(value)
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Unknown type: #{type}" if value
|
24
|
+
end
|
25
|
+
rescue ::ArgumentError => e
|
26
|
+
raise ArgumentError.new(:wrong_type, value.inspect, type)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/cl/cmd.rb
CHANGED
@@ -1,13 +1,35 @@
|
|
1
|
+
require 'registry'
|
1
2
|
require 'cl/args'
|
2
|
-
require 'cl/
|
3
|
+
require 'cl/helper'
|
4
|
+
require 'cl/opts'
|
5
|
+
# require 'cl/registry'
|
3
6
|
|
4
|
-
|
5
|
-
class Cmd
|
7
|
+
class Cl
|
8
|
+
class Cmd
|
6
9
|
include Registry
|
7
10
|
|
8
11
|
class << self
|
9
|
-
|
10
|
-
|
12
|
+
include Merge
|
13
|
+
|
14
|
+
inherited = ->(const) do
|
15
|
+
const.register [registry_key, underscore(const.name.split('::').last)].compact.join(':') if const.name
|
16
|
+
const.define_singleton_method(:inherited, &inherited)
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method(:inherited, &inherited)
|
20
|
+
|
21
|
+
def cmds
|
22
|
+
registry.values
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(ctx, args)
|
26
|
+
opts = Parser.new(self.opts, args).opts unless self == Help
|
27
|
+
opts = merge(ctx.config[registry_key], opts) if ctx.config[registry_key]
|
28
|
+
[args, opts]
|
29
|
+
end
|
30
|
+
|
31
|
+
def abstract
|
32
|
+
unregister
|
11
33
|
end
|
12
34
|
|
13
35
|
def args(*args)
|
@@ -16,22 +38,35 @@ module Cl
|
|
16
38
|
args.each { |arg| arg(arg, opts) }
|
17
39
|
end
|
18
40
|
|
19
|
-
def arg(
|
20
|
-
args.define(self,
|
21
|
-
end
|
22
|
-
|
23
|
-
def purpose(purpose = nil)
|
24
|
-
purpose ? @purpose = purpose : @purpose
|
41
|
+
def arg(*args)
|
42
|
+
self.args.define(self, *args)
|
25
43
|
end
|
26
44
|
|
27
45
|
def opt(*args, &block)
|
28
|
-
opts
|
46
|
+
self.opts.define(self, *args, &block)
|
29
47
|
end
|
30
48
|
|
31
49
|
def opts
|
32
|
-
@opts ||=
|
50
|
+
@opts ||= self == Cmd ? Opts.new : superclass.opts.dup
|
51
|
+
end
|
52
|
+
|
53
|
+
def description(description = nil)
|
54
|
+
description ? @description = description : @description
|
33
55
|
end
|
34
56
|
|
57
|
+
def required?
|
58
|
+
!!@required
|
59
|
+
end
|
60
|
+
|
61
|
+
def required(*required)
|
62
|
+
required.any? ? self.required << required : @required ||= []
|
63
|
+
end
|
64
|
+
|
65
|
+
def summary(summary = nil)
|
66
|
+
summary ? @summary = summary : @summary
|
67
|
+
end
|
68
|
+
alias purpose summary
|
69
|
+
|
35
70
|
def underscore(string)
|
36
71
|
string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
37
72
|
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
@@ -39,10 +74,23 @@ module Cl
|
|
39
74
|
end
|
40
75
|
end
|
41
76
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
77
|
+
opt '--help', 'Get help on this command'
|
78
|
+
|
79
|
+
attr_reader :ctx, :args
|
80
|
+
|
81
|
+
def initialize(ctx, args)
|
82
|
+
args, opts = self.class.parse(ctx, args)
|
83
|
+
@ctx = ctx
|
84
|
+
@opts = self.class.opts.apply(self, self.opts.merge(opts || {}))
|
85
|
+
@args = @opts[:help] ? args : self.class.args.apply(self, args)
|
86
|
+
end
|
87
|
+
|
88
|
+
def opts
|
89
|
+
@opts ||= {}
|
90
|
+
end
|
91
|
+
|
92
|
+
def deprecated_opts
|
93
|
+
opts.keys & self.class.opts.deprecated
|
46
94
|
end
|
47
95
|
end
|
48
96
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'cl/helper'
|
2
|
+
|
3
|
+
class Cl
|
4
|
+
class Config
|
5
|
+
class Env < Struct.new(:name)
|
6
|
+
include Merge
|
7
|
+
|
8
|
+
TRUE = /^(true|yes|on)$/
|
9
|
+
FALSE = /^(false|no|off)$/
|
10
|
+
|
11
|
+
def load
|
12
|
+
vars = opts.map { |cmd, opts| vars(cmd, opts) }
|
13
|
+
merge(*vars.flatten.compact)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def vars(cmd, opts)
|
19
|
+
opts.map { |opt| var(cmd, opt, key(cmd, opt)) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def opts
|
23
|
+
Cmd.registry.map { |key, cmd| [key, cmd.opts.map(&:name) - [:help]] }
|
24
|
+
end
|
25
|
+
|
26
|
+
def var(cmd, opt, key)
|
27
|
+
{ cmd => { opt => cast(ENV[key]) } } if ENV[key]
|
28
|
+
end
|
29
|
+
|
30
|
+
def key(*keys)
|
31
|
+
[name.upcase, *keys].join('_').upcase.sub('-', '_')
|
32
|
+
end
|
33
|
+
|
34
|
+
def only(hash, *keys)
|
35
|
+
hash.select { |key, _| keys.include?(key) }.to_h
|
36
|
+
end
|
37
|
+
|
38
|
+
def cast(value)
|
39
|
+
case value
|
40
|
+
when TRUE
|
41
|
+
true
|
42
|
+
when FALSE
|
43
|
+
false
|
44
|
+
when ''
|
45
|
+
false
|
46
|
+
else
|
47
|
+
value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'cl/helper'
|
3
|
+
|
4
|
+
class Cl
|
5
|
+
class Config
|
6
|
+
class Files < Struct.new(:name)
|
7
|
+
include Merge
|
8
|
+
|
9
|
+
PATHS = %w(
|
10
|
+
~/.%s.yml
|
11
|
+
./.%s.yml
|
12
|
+
)
|
13
|
+
|
14
|
+
def load
|
15
|
+
configs.any? ? symbolize(merge(*configs)) : {}
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def configs
|
21
|
+
@configs ||= paths.map { |path| YAML.load_file(path) || {} }
|
22
|
+
end
|
23
|
+
|
24
|
+
def paths
|
25
|
+
paths = PATHS.map { |path| File.expand_path(path % name) }
|
26
|
+
paths.select { |path| File.exist?(path) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def symbolize(hash)
|
30
|
+
hash.map { |key, value| [key.to_sym, value] }.to_h
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/cl/config.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'cl/config/env'
|
2
|
+
require 'cl/config/files'
|
3
|
+
require 'cl/helper'
|
4
|
+
|
5
|
+
class Cl
|
6
|
+
class Config
|
7
|
+
include Merge
|
8
|
+
|
9
|
+
attr_reader :name, :opts
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
@opts = load
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
opts
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def load
|
23
|
+
merge(*sources.map(&:load))
|
24
|
+
end
|
25
|
+
|
26
|
+
def sources
|
27
|
+
[Files.new(name), Env.new(name)]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/cl/ctx.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'cl/config'
|
3
|
+
require 'cl/ui'
|
4
|
+
|
5
|
+
class Cl
|
6
|
+
class Ctx
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :ui, :puts, :stdout, :announce, :info, :notice, :warn,
|
10
|
+
:error, :success, :cmd
|
11
|
+
|
12
|
+
attr_accessor :config, :ui
|
13
|
+
|
14
|
+
def initialize(name, opts = {})
|
15
|
+
@config = Config.new(name).to_h
|
16
|
+
@ui = Ui.new(self, opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def abort(str)
|
20
|
+
fail(str) if test?
|
21
|
+
ui.error(str)
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def test?
|
26
|
+
ENV['ENV'] == 'test'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/cl/help/cmd.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'cl/help/table'
|
2
|
+
require 'cl/help/usage'
|
3
|
+
|
4
|
+
class Cl
|
5
|
+
class Help
|
6
|
+
class Cmd
|
7
|
+
attr_reader :cmd
|
8
|
+
|
9
|
+
def initialize(cmd)
|
10
|
+
@cmd = cmd
|
11
|
+
end
|
12
|
+
|
13
|
+
def format
|
14
|
+
[usage, arguments, options, common, summary, description].compact.join("\n\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def usage
|
18
|
+
"Usage: #{Usage.new(cmd).format}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def summary
|
22
|
+
['Summary:', indent(cmd.summary)] if cmd.summary
|
23
|
+
end
|
24
|
+
|
25
|
+
def description
|
26
|
+
['Description:', indent(cmd.description)] if cmd.description
|
27
|
+
end
|
28
|
+
|
29
|
+
def arguments
|
30
|
+
['Arguments:', table(:args)] if args.any?
|
31
|
+
end
|
32
|
+
|
33
|
+
def options
|
34
|
+
['Options:', requireds, table(:opts)].compact if opts.any?
|
35
|
+
end
|
36
|
+
|
37
|
+
def common
|
38
|
+
['Common Options:', table(:cmmn)] if common?
|
39
|
+
end
|
40
|
+
|
41
|
+
def table(name)
|
42
|
+
table = send(name)
|
43
|
+
indent(table.to_s(width - table.width + 5))
|
44
|
+
end
|
45
|
+
|
46
|
+
def args
|
47
|
+
@args ||= begin
|
48
|
+
Table.new(cmd.args.map { |arg| [arg.name, format_obj(arg)] })
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def opts
|
53
|
+
@opts ||= begin
|
54
|
+
opts = cmd.opts.to_a
|
55
|
+
opts = opts - cmd.superclass.opts.to_a if common?
|
56
|
+
strs = Table.new(rjust(opts.map { |opt| [*opt.strs] }))
|
57
|
+
opts = opts.map { |opt| format_obj(opt) }
|
58
|
+
Table.new(strs.rows.zip(opts))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def cmmn
|
63
|
+
@cmmn ||= begin
|
64
|
+
opts = cmd.superclass.opts
|
65
|
+
strs = Table.new(rjust(opts.map { |opt| [*opt.strs] }))
|
66
|
+
opts = opts.map { |opt| format_obj(opt) }
|
67
|
+
Table.new(strs.rows.zip(opts))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def requireds
|
72
|
+
return unless cmd.required?
|
73
|
+
opts = cmd.required
|
74
|
+
strs = opts.map { |alts| alts.map { |alt| Array(alt).join(' and ') }.join(', or ' ) }
|
75
|
+
strs = strs.map { |str| "Either #{str} are required." }.join("\n")
|
76
|
+
indent(strs)
|
77
|
+
end
|
78
|
+
|
79
|
+
def common?
|
80
|
+
cmd.superclass < Cl::Cmd
|
81
|
+
end
|
82
|
+
|
83
|
+
def width
|
84
|
+
[args.width, opts.width, cmmn.width].max
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_obj(obj)
|
88
|
+
opts = []
|
89
|
+
opts << "type: #{format_type(obj)}"
|
90
|
+
opts << 'required: true' if obj.required?
|
91
|
+
opts += format_opt(obj) if obj.is_a?(Opt)
|
92
|
+
opts = opts.join(', ')
|
93
|
+
opts = "(#{opts})" if obj.description && !opts.empty?
|
94
|
+
opts = [obj.description, opts]
|
95
|
+
opts.compact.join(' ')
|
96
|
+
end
|
97
|
+
|
98
|
+
def format_opt(opt)
|
99
|
+
opts = []
|
100
|
+
opts << "alias: #{format_aliases(opt)}" if opt.aliases?
|
101
|
+
opts << "requires: #{opt.requires.join(', ')}" if opt.requires?
|
102
|
+
opts << "default: #{format_default(opt)}" if opt.default?
|
103
|
+
opts << "known values: #{opt.enum.join(', ')}" if opt.enum?
|
104
|
+
opts << "format: #{opt.format}" if opt.format?
|
105
|
+
opts << "max: #{opt.max}" if opt.max?
|
106
|
+
opts << format_deprecated(opt) if opt.deprecated?
|
107
|
+
opts.compact
|
108
|
+
end
|
109
|
+
|
110
|
+
def format_aliases(opt)
|
111
|
+
opt.aliases.map do |name|
|
112
|
+
strs = [name]
|
113
|
+
strs << '(deprecated)' if Array(opt.deprecated).include?(name)
|
114
|
+
strs.join(' ')
|
115
|
+
end.join(', ')
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def format_type(obj)
|
120
|
+
return obj.type unless obj.is_a?(Opt) && obj.type == :array
|
121
|
+
"array (can be given multiple times)"
|
122
|
+
end
|
123
|
+
|
124
|
+
def format_default(opt)
|
125
|
+
opt.default.is_a?(Symbol) ? opt.default.to_s.sub('_', ' ') : opt.default
|
126
|
+
end
|
127
|
+
|
128
|
+
def format_deprecated(opt)
|
129
|
+
return 'deprecated' if opt.deprecated == [opt.name]
|
130
|
+
end
|
131
|
+
|
132
|
+
def rjust(objs)
|
133
|
+
width = objs.max_by(&:size).size
|
134
|
+
objs.map { |objs| [*Array.new(width - objs.size) { '' }, *objs] }
|
135
|
+
end
|
136
|
+
|
137
|
+
def indent(str)
|
138
|
+
str.lines.map { |line| " #{line}" }.join
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/cl/help/cmds.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'cl/help/table'
|
2
|
+
require 'cl/help/usage'
|
3
|
+
|
4
|
+
class Cl
|
5
|
+
class Help
|
6
|
+
class Cmds
|
7
|
+
HEAD = %(Type "#{$0.split('/').last} help COMMAND [SUBCOMMAND]" for more details:\n)
|
8
|
+
|
9
|
+
attr_reader :cmds
|
10
|
+
|
11
|
+
def initialize(cmds)
|
12
|
+
@cmds = cmds
|
13
|
+
end
|
14
|
+
|
15
|
+
def format
|
16
|
+
[HEAD, Table.new(list).format].join("\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
def list
|
20
|
+
cmds.any? ? cmds.map { |cmd| format_cmd(cmd) } : [['[no commands]']]
|
21
|
+
end
|
22
|
+
|
23
|
+
def format_cmd(cmd)
|
24
|
+
["#{Usage.new(cmd).format}", cmd.summary]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Cl
|
2
|
+
class Help
|
3
|
+
class Table
|
4
|
+
attr_reader :data, :padding
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def any?
|
11
|
+
data.any?
|
12
|
+
end
|
13
|
+
|
14
|
+
def format(padding = 8)
|
15
|
+
@padding = padding
|
16
|
+
rows.join("\n")
|
17
|
+
end
|
18
|
+
alias to_s format
|
19
|
+
|
20
|
+
def rows
|
21
|
+
data.map { |row| cells(row).join(' ').rstrip }
|
22
|
+
end
|
23
|
+
|
24
|
+
def cells(row)
|
25
|
+
row.map.with_index { |cell, ix| cell.to_s.ljust(widths[ix]) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def width
|
29
|
+
widths = cols[0..-2].map { |col| col.max_by(&:size).size }.inject(&:+).to_i
|
30
|
+
widths + cols.size - 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def widths
|
34
|
+
cols.map.with_index do |col, ix|
|
35
|
+
width = col.compact.max_by(&:size)&.size
|
36
|
+
ix < cols.size - 2 ? width.to_i : width.to_i + padding.to_i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def cols
|
41
|
+
@cols ||= data.transpose
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,9 +1,15 @@
|
|
1
|
-
|
2
|
-
class
|
3
|
-
class Usage
|
1
|
+
class Cl
|
2
|
+
class Help
|
3
|
+
class Usage
|
4
|
+
attr_reader :cmd
|
5
|
+
|
6
|
+
def initialize(cmd)
|
7
|
+
@cmd = cmd
|
8
|
+
end
|
9
|
+
|
4
10
|
def format
|
5
11
|
usage = [$0.split('/').last, name]
|
6
|
-
usage += cmd.args.map(&:to_s)
|
12
|
+
usage += cmd.args.map(&:to_s) # { |arg| "[#{arg}]" }
|
7
13
|
usage << '[options]' if opts?
|
8
14
|
usage.join(' ')
|
9
15
|
end
|
data/lib/cl/help.rb
CHANGED
@@ -1,33 +1,31 @@
|
|
1
|
-
|
2
|
-
require 'cl/format/list'
|
3
|
-
|
4
|
-
module Cl
|
1
|
+
class Cl
|
5
2
|
class Help < Cl::Cmd
|
6
3
|
register :help
|
7
4
|
|
8
5
|
def run
|
9
|
-
puts help
|
6
|
+
ctx.puts help
|
10
7
|
end
|
11
8
|
|
12
9
|
def help
|
13
|
-
|
10
|
+
args.any? ? Cmd.new(cmd).format : Cmds.new(cmds).format
|
14
11
|
end
|
15
12
|
|
16
13
|
private
|
17
14
|
|
18
15
|
def cmds
|
19
|
-
cmds = Cl.cmds.reject { |cmd| cmd.registry_key == :help }
|
16
|
+
cmds = Cl::Cmd.cmds.reject { |cmd| cmd.registry_key == :help }
|
20
17
|
key = args.join(':') if args
|
21
18
|
cmds = cmds.select { |cmd| cmd.registry_key.to_s.start_with?(key) } if key
|
22
19
|
cmds
|
23
20
|
end
|
24
21
|
|
25
22
|
def cmd
|
26
|
-
args.
|
27
|
-
|
28
|
-
|
29
|
-
[args, cmds.compact]
|
30
|
-
end.last.last
|
23
|
+
key = args.join(':')
|
24
|
+
return Cl::Cmd[key] if Cl::Cmd.registered?(key)
|
25
|
+
ctx.abort("Unknown command: #{key}")
|
31
26
|
end
|
32
27
|
end
|
33
28
|
end
|
29
|
+
|
30
|
+
require 'cl/help/cmd'
|
31
|
+
require 'cl/help/cmds'
|