command_kit 0.1.0.pre1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +15 -0
  3. data/.rubocop.yml +138 -0
  4. data/ChangeLog.md +34 -2
  5. data/Gemfile +3 -0
  6. data/README.md +135 -214
  7. data/Rakefile +3 -2
  8. data/command_kit.gemspec +4 -4
  9. data/examples/colors.rb +30 -0
  10. data/examples/command.rb +65 -0
  11. data/examples/pager.rb +30 -0
  12. data/gemspec.yml +10 -2
  13. data/lib/command_kit/arguments/argument.rb +16 -44
  14. data/lib/command_kit/arguments/argument_value.rb +3 -30
  15. data/lib/command_kit/arguments.rb +66 -20
  16. data/lib/command_kit/colors.rb +253 -45
  17. data/lib/command_kit/command.rb +50 -3
  18. data/lib/command_kit/command_name.rb +9 -0
  19. data/lib/command_kit/commands/auto_load/subcommand.rb +3 -0
  20. data/lib/command_kit/commands/auto_load.rb +16 -0
  21. data/lib/command_kit/commands/auto_require.rb +16 -0
  22. data/lib/command_kit/commands/command.rb +3 -0
  23. data/lib/command_kit/commands/help.rb +2 -0
  24. data/lib/command_kit/commands/parent_command.rb +7 -0
  25. data/lib/command_kit/commands/subcommand.rb +15 -0
  26. data/lib/command_kit/commands.rb +40 -4
  27. data/lib/command_kit/description.rb +15 -2
  28. data/lib/command_kit/env/home.rb +9 -0
  29. data/lib/command_kit/env/path.rb +15 -0
  30. data/lib/command_kit/env.rb +4 -0
  31. data/lib/command_kit/examples.rb +15 -2
  32. data/lib/command_kit/exception_handler.rb +4 -0
  33. data/lib/command_kit/help/man.rb +74 -47
  34. data/lib/command_kit/help.rb +10 -1
  35. data/lib/command_kit/inflector.rb +49 -17
  36. data/lib/command_kit/interactive.rb +239 -0
  37. data/lib/command_kit/main.rb +20 -9
  38. data/lib/command_kit/man.rb +44 -0
  39. data/lib/command_kit/open_app.rb +69 -0
  40. data/lib/command_kit/options/option.rb +36 -9
  41. data/lib/command_kit/options/option_value.rb +42 -3
  42. data/lib/command_kit/options/parser.rb +44 -17
  43. data/lib/command_kit/options/quiet.rb +3 -0
  44. data/lib/command_kit/options/verbose.rb +5 -0
  45. data/lib/command_kit/options/version.rb +6 -0
  46. data/lib/command_kit/options.rb +59 -10
  47. data/lib/command_kit/os/linux.rb +157 -0
  48. data/lib/command_kit/os.rb +165 -11
  49. data/lib/command_kit/package_manager.rb +200 -0
  50. data/lib/command_kit/pager.rb +84 -9
  51. data/lib/command_kit/printing/indent.rb +25 -2
  52. data/lib/command_kit/printing.rb +23 -0
  53. data/lib/command_kit/program_name.rb +7 -0
  54. data/lib/command_kit/stdio.rb +24 -0
  55. data/lib/command_kit/sudo.rb +40 -0
  56. data/lib/command_kit/terminal.rb +159 -0
  57. data/lib/command_kit/usage.rb +14 -0
  58. data/lib/command_kit/version.rb +1 -1
  59. data/lib/command_kit/xdg.rb +21 -1
  60. data/lib/command_kit.rb +1 -0
  61. data/spec/arguments/argument_spec.rb +5 -41
  62. data/spec/arguments/argument_value_spec.rb +1 -61
  63. data/spec/arguments_spec.rb +8 -25
  64. data/spec/colors_spec.rb +277 -13
  65. data/spec/command_name_spec.rb +1 -1
  66. data/spec/command_spec.rb +4 -1
  67. data/spec/commands/auto_load/subcommand_spec.rb +1 -1
  68. data/spec/commands/auto_load_spec.rb +1 -1
  69. data/spec/commands/auto_require_spec.rb +2 -2
  70. data/spec/commands/help_spec.rb +1 -1
  71. data/spec/commands/parent_command_spec.rb +1 -1
  72. data/spec/commands/subcommand_spec.rb +1 -1
  73. data/spec/commands_spec.rb +2 -2
  74. data/spec/description_spec.rb +1 -25
  75. data/spec/env/home_spec.rb +1 -1
  76. data/spec/env/path_spec.rb +1 -1
  77. data/spec/examples_spec.rb +1 -25
  78. data/spec/exception_handler_spec.rb +1 -1
  79. data/spec/help/man_spec.rb +316 -0
  80. data/spec/help_spec.rb +0 -25
  81. data/spec/inflector_spec.rb +71 -9
  82. data/spec/interactive_spec.rb +415 -0
  83. data/spec/main_spec.rb +7 -7
  84. data/spec/man_spec.rb +46 -0
  85. data/spec/open_app_spec.rb +85 -0
  86. data/spec/options/option_spec.rb +48 -9
  87. data/spec/options/option_value_spec.rb +53 -4
  88. data/spec/options_spec.rb +1 -1
  89. data/spec/os/linux_spec.rb +154 -0
  90. data/spec/os_spec.rb +201 -14
  91. data/spec/package_manager_spec.rb +806 -0
  92. data/spec/pager_spec.rb +78 -15
  93. data/spec/printing/indent_spec.rb +1 -1
  94. data/spec/printing_spec.rb +10 -2
  95. data/spec/program_name_spec.rb +1 -1
  96. data/spec/spec_helper.rb +0 -3
  97. data/spec/sudo_spec.rb +51 -0
  98. data/spec/{console_spec.rb → terminal_spec.rb} +65 -35
  99. data/spec/usage_spec.rb +2 -2
  100. data/spec/xdg_spec.rb +1 -1
  101. metadata +32 -13
  102. data/lib/command_kit/arguments/usage.rb +0 -6
  103. data/lib/command_kit/console.rb +0 -141
  104. data/lib/command_kit/options/usage.rb +0 -6
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'rubygems'
4
2
 
5
3
  begin
@@ -21,3 +19,6 @@ task :default => :spec
21
19
  require 'yard'
22
20
  YARD::Rake::YardocTask.new
23
21
  task :doc => :yard
22
+
23
+ require 'rubocop/rake_task'
24
+ RuboCop::RakeTask.new
data/command_kit.gemspec CHANGED
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'yaml'
4
2
 
5
3
  Gem::Specification.new do |gem|
@@ -20,11 +18,13 @@ Gem::Specification.new do |gem|
20
18
  gem.authors = Array(gemspec['authors'])
21
19
  gem.email = gemspec['email']
22
20
  gem.homepage = gemspec['homepage']
21
+ gem.metadata = gemspec['metadata'] if gemspec['metadata']
23
22
 
24
23
  glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
24
 
26
- gem.files = `git ls-files`.split($/)
27
- gem.files = glob[gemspec['files']] if gemspec['files']
25
+ gem.files = if gemspec['files'] then glob[gemspec['files']]
26
+ else `git ls-files`.split($/)
27
+ end
28
28
 
29
29
  gem.executables = gemspec.fetch('executables') do
30
30
  glob['bin/*'].map { |path| File.basename(path) }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib',__FILE__))
4
+ require 'command_kit/command'
5
+ require 'command_kit/colors'
6
+
7
+ class ColorsCmd < CommandKit::Command
8
+
9
+ include CommandKit::Colors
10
+
11
+ description "Prints all of the standard ANSI colors"
12
+
13
+ def run
14
+ colors do |c|
15
+ puts c.black("Black") + "\t" + c.bold(c.black("Bold"))
16
+ puts c.red("Red") + "\t" + c.bold(c.red("Bold"))
17
+ puts c.green("Green") + "\t" + c.bold(c.green("Bold"))
18
+ puts c.yellow("Yellow") + "\t" + c.bold(c.yellow("Bold"))
19
+ puts c.blue("Blue") + "\t" + c.bold(c.blue("Bold"))
20
+ puts c.magenta("Magenta") + "\t" + c.bold(c.magenta("Bold"))
21
+ puts c.cyan("Cyan") + "\t" + c.bold(c.cyan("Bold"))
22
+ puts c.cyan("White") + "\t" + c.bold(c.white("Bold"))
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ if __FILE__ == $0
29
+ ColorsCmd.start
30
+ end
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib',__FILE__))
4
+ require 'command_kit/command'
5
+
6
+ class Command < CommandKit::Command
7
+
8
+ usage '[OPTIONS] [-o OUTPUT] FILE'
9
+
10
+ option :count, short: '-c',
11
+ value: {
12
+ type: Integer,
13
+ default: 1
14
+ },
15
+ desc: "Number of times"
16
+
17
+ option :output, value: {
18
+ type: String,
19
+ usage: 'FILE'
20
+ },
21
+ short: '-o',
22
+ desc: "Optional output file"
23
+
24
+ option :verbose, short: '-v', desc: "Increase verbose level" do
25
+ @verbose += 1
26
+ end
27
+
28
+ argument :file, required: true,
29
+ usage: 'FILE',
30
+ desc: "Input file"
31
+
32
+ examples [
33
+ '-o path/to/output.txt path/to/input.txt',
34
+ '-v -c 2 -o path/to/output.txt path/to/input.txt'
35
+ ]
36
+
37
+ description "Example command"
38
+
39
+ def initialize
40
+ super
41
+
42
+ @verbose = 0
43
+ end
44
+
45
+ def run(file)
46
+ unless options.empty?
47
+ puts "Options:"
48
+ options.each do |name,value|
49
+ puts " #{name.inspect} => #{value.inspect}"
50
+ end
51
+ puts
52
+ end
53
+
54
+ puts "Arguments:"
55
+ puts " file = #{file.inspect}"
56
+ puts
57
+
58
+ puts "Custom Variables:"
59
+ puts " version = #{@verbose.inspect}"
60
+ end
61
+ end
62
+
63
+ if __FILE__ == $0
64
+ Command.start
65
+ end
data/examples/pager.rb ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib',__FILE__))
4
+ require 'command_kit/command'
5
+ require 'command_kit/pager'
6
+
7
+ class PagerCmd < CommandKit::Command
8
+
9
+ include CommandKit::Pager
10
+
11
+ description "Demos using the pager"
12
+
13
+ def run
14
+ puts "Starting pager ..."
15
+
16
+ pager do |io|
17
+ 10.times do |i|
18
+ io.puts i
19
+ sleep 0.5
20
+ end
21
+ end
22
+
23
+ puts "Exiting pager ..."
24
+ end
25
+
26
+ end
27
+
28
+ if __FILE__ == $0
29
+ PagerCmd.start
30
+ end
data/gemspec.yml CHANGED
@@ -2,11 +2,19 @@ name: command_kit
2
2
  summary: A toolkit for building Ruby CLI commands
3
3
  description:
4
4
  A Ruby toolkit for building clean, correct, and robust CLI commands as
5
- Ruby classes.
5
+ plain-old Ruby classes.
6
+
6
7
  license: MIT
7
8
  authors: Postmodern
8
9
  email: postmodern.mod3@gmail.com
9
- homepage: https://github.com/postmodern/command_kit#readme
10
+ homepage: https://github.com/postmodern/command_kit.rb#readme
11
+
12
+ metadata:
13
+ documentation_uri: https://rubydoc.info/gems/command_kit
14
+ source_code_uri: https://github.com/postmodern/command_kit.rb
15
+ bug_tracker_uri: https://github.com/postmodern/command_kit.rb/issues
16
+ changelog_uri: https://github.com/postmodern/command_kit.rb/blob/main/ChangeLog.md
17
+
10
18
 
11
19
  required_ruby_version: ">= 2.7.0"
12
20
 
@@ -5,84 +5,56 @@ module CommandKit
5
5
  #
6
6
  # Represents a defined argument.
7
7
  #
8
+ # @api private
9
+ #
8
10
  class Argument < ArgumentValue
9
11
 
12
+ # The argument's name.
13
+ #
10
14
  # @return [Symbol]
11
15
  attr_reader :name
12
16
 
13
- # @return [Boolean]
14
- attr_reader :repeats
15
-
16
- # @return [String, nil]
17
+ # The argument's description.
18
+ #
19
+ # @return [String]
17
20
  attr_reader :desc
18
21
 
19
- # @return [Regexp, nil]
20
- attr_reader :pattern
21
-
22
- # @return [Proc, nil]
23
- attr_reader :parser
24
-
25
- # @return [Proc, nil]
26
- attr_reader :block
27
-
28
22
  #
29
23
  # Initializes the argument.
30
24
  #
31
25
  # @param [Symbol] name
32
- #
33
- # @param [Class, Hash, Array, Regexp] type
26
+ # The name of the argument.
34
27
  #
35
28
  # @param [String, nil] usage
36
- #
37
- # @param [Object, Proc, nil] default
29
+ # The usage string for the argument. Defaults to the argument's name.
38
30
  #
39
31
  # @param [Boolean] required
32
+ # Specifies whether the argument is required or optional.
40
33
  #
41
34
  # @param [Boolean] repeats
35
+ # Specifies whether the argument can be repeated multiple times.
42
36
  #
43
37
  # @param [String] desc
44
- #
45
- # @note `usage` will be assigned a default value based on `type` and
46
- # `name`.
38
+ # The description for the argument.
47
39
  #
48
40
  # @yield [(value)]
41
+ # If a block is given, it will be used to parse the argument's value.
42
+ # Note: not currently used.
49
43
  #
50
44
  # @yieldparam [Object, nil] value
51
45
  #
52
- def initialize(name, type: String,
53
- usage: name.to_s.upcase,
54
- default: nil,
46
+ def initialize(name, usage: name.to_s.upcase,
55
47
  required: true,
56
48
  repeats: false,
57
- desc: ,
58
- &block)
49
+ desc: )
59
50
  super(
60
- type: type,
61
51
  usage: usage,
62
- default: default,
63
52
  required: required
64
53
  )
65
54
 
66
55
  @name = name
67
56
  @repeats = repeats
68
57
  @desc = desc
69
-
70
- @pattern, @parser = self.class.parser(@type)
71
-
72
- @block = block
73
- end
74
-
75
- #
76
- # Looks up the option parser for the given `OptionParser` type.
77
- #
78
- # @param [Class] type
79
- # The `OptionParser` type class.
80
- #
81
- # @return [(Regexp, Proc), nil]
82
- # The matching pattern and converter proc.
83
- #
84
- def self.parser(type)
85
- OptionParser::DefaultList.search(:atype,type)
86
58
  end
87
59
 
88
60
  #
@@ -3,18 +3,10 @@ module CommandKit
3
3
  #
4
4
  # Represents an individual argument value.
5
5
  #
6
+ # @api private
7
+ #
6
8
  class ArgumentValue
7
9
 
8
- # The desired type of the argument value.
9
- #
10
- # @return [Class, Hash, Array, Regexp, nil]
11
- attr_reader :type
12
-
13
- # The default parsed value for the argument value.
14
- #
15
- # @return [Object, Proc, nil]
16
- attr_reader :default
17
-
18
10
  # Specifies whether the argument value is required or optional.
19
11
  #
20
12
  # @return [Boolean]
@@ -28,22 +20,14 @@ module CommandKit
28
20
  #
29
21
  # Initializes the argument value.
30
22
  #
31
- # @param [Class, Hash, Array, Regexp] type
32
- # The type of the argument value.
33
- #
34
23
  # @param [Boolean] required
35
24
  # Specifies whether the argument value is required or optional.
36
25
  #
37
26
  # @param [String] usage
38
27
  # The usage string to represent the argument value.
39
28
  #
40
- # @param [Object, Proc, nil] default
41
- # The default parsed value for the argument value.
42
- #
43
- def initialize(type: nil, required: true, default: nil, usage: )
44
- @type = type
29
+ def initialize(required: true, usage: )
45
30
  @required = required
46
- @default = default
47
31
  @usage = usage
48
32
  end
49
33
 
@@ -65,17 +49,6 @@ module CommandKit
65
49
  !@required
66
50
  end
67
51
 
68
- #
69
- # Returns a new default value.
70
- #
71
- # @return [Object]
72
- #
73
- def default_value
74
- if @default.respond_to?(:call) then @default.call
75
- else @default.dup
76
- end
77
- end
78
-
79
52
  end
80
53
  end
81
54
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_kit/main'
1
4
  require 'command_kit/help'
2
5
  require 'command_kit/arguments/argument'
3
6
 
@@ -9,15 +12,45 @@ module CommandKit
9
12
  #
10
13
  # include CommandKit::Arguments
11
14
  #
12
- # argument :output, type: String,
13
- # desc: 'The output file'
15
+ # argument :output, desc: 'The output file'
16
+ #
17
+ # argument :input, desc: 'The input file(s)'
14
18
  #
15
- # argument :input, type: Array,
16
- # desc: 'The input file(s)'
19
+ # def run(output,input)
20
+ # end
21
+ #
22
+ # ### Optional Arguments
23
+ #
24
+ # argument :dir, required: false,
25
+ # desc: 'Can be omitted'
26
+ #
27
+ # def run(dir=nil)
28
+ # end
29
+ #
30
+ # ### Repeating Arguments
31
+ #
32
+ # argument :files, repeats: true,
33
+ # desc: 'Can be repeated one or more times'
34
+ #
35
+ # def run(*files)
36
+ # end
37
+ #
38
+ # ### Optional Repeating Arguments
39
+ #
40
+ # argument :files, required: true,
41
+ # repeats: true,
42
+ # desc: 'Can be repeated one or more times'
43
+ #
44
+ # def run(*files)
45
+ # end
17
46
  #
18
47
  module Arguments
48
+ include Main
19
49
  include Help
20
50
 
51
+ #
52
+ # @api private
53
+ #
21
54
  module ModuleMethods
22
55
  #
23
56
  # Extends {ClassMethods} or {ModuleMethods}, depending on whether
@@ -49,6 +82,8 @@ module CommandKit
49
82
  # @return [Hash{Symbol => Argument}]
50
83
  # The defined argument for the class and it's superclass.
51
84
  #
85
+ # @api semipublic
86
+ #
52
87
  def arguments
53
88
  @arguments ||= if superclass.kind_of?(ClassMethods)
54
89
  superclass.arguments.dup
@@ -61,13 +96,22 @@ module CommandKit
61
96
  # Defines an argument for the class.
62
97
  #
63
98
  # @param [Symbol] name
64
- # The argument name.
99
+ # The name of the argument.
100
+ #
101
+ # @param [Hash{Symbol => Object}] kwargs
102
+ # Keyword arguments.
65
103
  #
66
- # @yield [(arg)]
67
- # If a block is given, it will be passed the parsed argument.
104
+ # @option kwargs [String, nil] usage
105
+ # The usage string for the argument. Defaults to the argument's name.
68
106
  #
69
- # @yieldparam [Object, nil] arg
70
- # The parsed argument.
107
+ # @option kwargs [Boolean] required
108
+ # Specifies whether the argument is required or optional.
109
+ #
110
+ # @option kwargs [Boolean] repeats
111
+ # Specifies whether the argument can be repeated multiple times.
112
+ #
113
+ # @option kwargs [String] desc
114
+ # The description for the argument.
71
115
  #
72
116
  # @return [Argument]
73
117
  # The newly defined argument.
@@ -79,14 +123,8 @@ module CommandKit
79
123
  # option :bar, usage: 'BAR',
80
124
  # desc: "Bar argument"
81
125
  #
82
- # @example With a custom block:
83
- # argument :bar, desc: "Bar argument" do |bar|
84
- # # ...
85
- # end
86
- #
87
126
  # @example With a custom type:
88
- # argument :bar, type: Integer,
89
- # desc: "Bar argument"
127
+ # argument :bar, desc: "Bar argument"
90
128
  #
91
129
  # @example With a default value:
92
130
  # argument :bar, default: "bar.txt",
@@ -100,8 +138,10 @@ module CommandKit
100
138
  # argument :bar, repeats: true,
101
139
  # desc: "Bar argument"
102
140
  #
103
- def argument(name,**kwargs,&block)
104
- arguments[name] = Argument.new(name,**kwargs,&block)
141
+ # @api public
142
+ #
143
+ def argument(name,**kwargs)
144
+ arguments[name] = Argument.new(name,**kwargs)
105
145
  end
106
146
  end
107
147
 
@@ -116,6 +156,8 @@ module CommandKit
116
156
  # The exit status code. If too few or too many arguments are given, then
117
157
  # an error message is printed and `1` is returned.
118
158
  #
159
+ # @api public
160
+ #
119
161
  def main(argv=[])
120
162
  required_args = self.class.arguments.each_value.count(&:required?)
121
163
  optional_args = self.class.arguments.each_value.count(&:optional?)
@@ -137,12 +179,14 @@ module CommandKit
137
179
  #
138
180
  # Prints any defined arguments, along with the usual `--help` information.
139
181
  #
182
+ # @api semipublic
183
+ #
140
184
  def help_arguments
141
185
  unless (arguments = self.class.arguments).empty?
142
186
  puts
143
187
  puts 'Arguments:'
144
188
 
145
- self.class.arguments.each_value do |arg|
189
+ arguments.each_value do |arg|
146
190
  puts " #{arg.usage.ljust(33)}#{arg.desc}"
147
191
  end
148
192
  end
@@ -152,8 +196,10 @@ module CommandKit
152
196
  # Calls the superclass'es `#help` method, if it's defined, then calls
153
197
  # {#help_arguments}.
154
198
  #
199
+ # @api public
200
+ #
155
201
  def help
156
- super if defined?(super)
202
+ super
157
203
 
158
204
  help_arguments
159
205
  end