command_kit 0.1.0.pre1 → 0.2.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.
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
@@ -12,6 +12,9 @@ module CommandKit
12
12
  # end
13
13
  #
14
14
  module Main
15
+ #
16
+ # @api private
17
+ #
15
18
  module ModuleMethods
16
19
  #
17
20
  # Extends {ClassMethods} or {ModuleMethods}, depending on whether {Main}
@@ -43,16 +46,16 @@ module CommandKit
43
46
  # @param [Array<String>] argv
44
47
  # The Array of command-line arguments.
45
48
  #
49
+ # @api public
50
+ #
46
51
  def start(argv=ARGV, **kwargs)
47
- begin
48
- exit main(argv, **kwargs)
49
- rescue Interrupt
50
- # https://tldp.org/LDP/abs/html/exitcodes.html
51
- exit 130
52
- rescue Errno::EPIPE
53
- # STDOUT pipe broken
54
- exit 0
55
- end
52
+ exit main(argv, **kwargs)
53
+ rescue Interrupt
54
+ # https://tldp.org/LDP/abs/html/exitcodes.html
55
+ exit 130
56
+ rescue Errno::EPIPE
57
+ # STDOUT pipe broken
58
+ exit 0
56
59
  end
57
60
 
58
61
  #
@@ -68,6 +71,8 @@ module CommandKit
68
71
  # @return [Integer]
69
72
  # The exit status of the command.
70
73
  #
74
+ # @api public
75
+ #
71
76
  def main(argv=[], **kwargs)
72
77
  new(**kwargs).main(argv)
73
78
  end
@@ -82,6 +87,10 @@ module CommandKit
82
87
  # @return [Integer]
83
88
  # The exit status code.
84
89
  #
90
+ # @note `argv` is splatted into {#run}.
91
+ #
92
+ # @api public
93
+ #
85
94
  def main(argv=[])
86
95
  run(*argv)
87
96
  return 0
@@ -97,6 +106,8 @@ module CommandKit
97
106
  #
98
107
  # @abstract
99
108
  #
109
+ # @api public
110
+ #
100
111
  def run(*args)
101
112
  end
102
113
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandKit
4
+ #
5
+ # Allows displaying man pages.
6
+ #
7
+ # ## Examples
8
+ #
9
+ # man "passwd"
10
+ # man "passwd", section: 5
11
+ #
12
+ # @since 0.2.0
13
+ #
14
+ module Man
15
+ #
16
+ # Displays the given man page.
17
+ #
18
+ # @param [String] page
19
+ # The man page file name.
20
+ #
21
+ # @param [Integer, String, nil] section
22
+ # The optional section number to specify.
23
+ #
24
+ # @return [Boolean, nil]
25
+ # Specifies whether the `man` command was successful or not.
26
+ # Returns `nil` when the `man` command is not installed.
27
+ #
28
+ # @example
29
+ # man "passwd"
30
+ #
31
+ # @example Display a man-page from a specific section:
32
+ # man "passwd", section: 5
33
+ #
34
+ # @api public
35
+ #
36
+ def man(page, section: nil)
37
+ if section
38
+ system('man',section.to_s,page.to_s)
39
+ else
40
+ system('man',page.to_s)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,69 @@
1
+ require 'command_kit/os'
2
+ require 'command_kit/env/path'
3
+
4
+ module CommandKit
5
+ #
6
+ # Allows opening a file or a URI with the system's preferred application for
7
+ # that file type or URI scheme.
8
+ #
9
+ # ## Examples
10
+ #
11
+ # open_app_for "movie.avi"
12
+ # open_app_for "https://github.com/postmodern/command_kit.rb"
13
+ #
14
+ # @since 0.2.0
15
+ #
16
+ module OpenApp
17
+ include OS
18
+ include Env::Path
19
+
20
+ #
21
+ # Initializes the command and determines which open command to use.
22
+ #
23
+ # @param [Hash{Symbol => Object}] kwargs
24
+ # Additional keyword arguments.
25
+ #
26
+ # @api public
27
+ #
28
+ def initialize(**kwargs)
29
+ super(**kwargs)
30
+
31
+ @open_command = if macos?
32
+ 'open'
33
+ elsif linux? || bsd?
34
+ if command_installed?('xdg-open')
35
+ 'xdg-open'
36
+ end
37
+ elsif windows?
38
+ if command_installed?('invoke-item')
39
+ 'invoke-item'
40
+ else
41
+ 'start'
42
+ end
43
+ end
44
+ end
45
+
46
+ #
47
+ # Opens a file or URI using the system's preferred application for that
48
+ # file type or URI scheme.
49
+ #
50
+ # @param [String, URI] file_or_uri
51
+ # The file path or URI to open.
52
+ #
53
+ # @return [Boolean, nil]
54
+ # Specifies whether the file or URI was successfully opened or not.
55
+ # If the open command could not be determined, `nil` is returned.
56
+ #
57
+ # @example Open a file:
58
+ # open_app_for "movie.avi"
59
+ #
60
+ # @example Open a URI:
61
+ # open_app_for "https://github.com/postmodern/command_kit.rb"
62
+ #
63
+ def open_app_for(file_or_uri)
64
+ if @open_command
65
+ system(@open_command,file_or_uri.to_s)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -11,26 +11,32 @@ module CommandKit
11
11
  #
12
12
  # Represents a defined option.
13
13
  #
14
+ # @api private
15
+ #
14
16
  class Option
15
17
 
18
+ # The option's name.
19
+ #
16
20
  # @return [Symbol]
17
21
  attr_reader :name
18
22
 
23
+ # The option's optional short-flag.
24
+ #
19
25
  # @return [String, nil]
20
26
  attr_reader :short
21
27
 
28
+ # The option's long-flag.
29
+ #
22
30
  # @return [String]
23
31
  attr_reader :long
24
32
 
25
- # @return [Boolean]
26
- attr_reader :equals
27
-
33
+ # The option value's type.
34
+ #
28
35
  # @return [OptionValue, nil]
29
36
  attr_reader :value
30
37
 
31
- # @return [String]
32
- attr_reader :desc
33
-
38
+ # The optional block that will receive the parsed option value.
39
+ #
34
40
  # @return [Proc, nil]
35
41
  attr_reader :block
36
42
 
@@ -38,26 +44,39 @@ module CommandKit
38
44
  # Initializes the option.
39
45
  #
40
46
  # @param [Symbol] name
47
+ # The name of the option.
41
48
  #
42
49
  # @param [String, nil] short
50
+ # Optional short-flag for the option.
43
51
  #
44
52
  # @param [String, nil] long
53
+ # Optional explicit long-flag for the option.
45
54
  #
46
55
  # @param [Boolean] equals
56
+ # Specifies whether the option is of the form (`--opt=value`).
47
57
  #
48
- # @param [Hash{Symbol => Object}, nil] value
58
+ # @param [Hash{Symbol => Object}, true, false, nil] value
49
59
  # Keyword arguments for {OptionValue#initialize}, or `nil` if the option
50
60
  # has no additional value.
51
61
  #
52
62
  # @option value [Class, Hash, Array, Regexp] type
63
+ # The type of the option's value.
53
64
  #
54
65
  # @option value [String, nil] usage
66
+ # The usage string for the option's value.
55
67
  #
56
68
  # @param [String] desc
69
+ # The description for the option.
57
70
  #
58
71
  # @yield [(value)]
72
+ # If a block is given, it will be called when the option is parsed.
59
73
  #
60
74
  # @yieldparam [Object, nil] value
75
+ # The given block will be passed the parsed option's value.
76
+ #
77
+ # @raise [TypeError]
78
+ # The `value` keyword argument was not a `Hash`, `true`, `false`, or
79
+ # `nil`.
61
80
  #
62
81
  def initialize(name, short: nil,
63
82
  long: self.class.default_long_opt(name),
@@ -69,7 +88,13 @@ module CommandKit
69
88
  @short = short
70
89
  @long = long
71
90
  @equals = equals
72
- @value = OptionValue.new(**value) if value
91
+ @value = case value
92
+ when Hash then OptionValue.new(**value)
93
+ when true then OptionValue.new()
94
+ when false, nil then nil
95
+ else
96
+ raise(TypeError,"value: keyword must be Hash, true, false, or nil")
97
+ end
73
98
  @desc = desc
74
99
  @block = block
75
100
  end
@@ -79,8 +104,10 @@ module CommandKit
79
104
  # (ex: `:long_opt`).
80
105
  #
81
106
  # @param [Symbol] name
107
+ # The option name.
82
108
  #
83
109
  # @return [String]
110
+ # The long-flag for the option.
84
111
  #
85
112
  def self.default_long_opt(name)
86
113
  "--#{Inflector.dasherize(name)}"
@@ -96,7 +123,7 @@ module CommandKit
96
123
  end
97
124
 
98
125
  #
99
- # The separator characer between the option and option value.
126
+ # The separator character between the option and option value.
100
127
  #
101
128
  # @return ['=', ' ', nil]
102
129
  #
@@ -14,8 +14,11 @@ module CommandKit
14
14
  #
15
15
  # Represents an additional argument associated with an option flag.
16
16
  #
17
+ # @api private
18
+ #
17
19
  class OptionValue < Arguments::ArgumentValue
18
20
 
21
+ # Maps OptionParser types to USAGE strings.
19
22
  USAGES = {
20
23
  # NOTE: NilClass and Object are intentionally omitted
21
24
  Date => 'DATE',
@@ -36,27 +39,52 @@ module CommandKit
36
39
  Regexp => '/REGEXP/'
37
40
  }
38
41
 
42
+ # The desired type of the argument value.
43
+ #
44
+ # @return [Class, Hash, Array, Regexp, nil]
45
+ attr_reader :type
46
+
47
+ # The default parsed value for the argument value.
48
+ #
49
+ # @return [Object, Proc, nil]
50
+ attr_reader :default
51
+
39
52
  #
40
53
  # Initializes the option value.
41
54
  #
42
55
  # @param [Class, Hash, Array, Regexp] type
56
+ # The type of the option value.
57
+ #
58
+ # @param [Object, Proc, nil] default
59
+ # The default parsed value for the option value.
43
60
  #
44
61
  # @param [String, nil] usage
62
+ # The optional usage string for the option value.
45
63
  #
46
64
  # @param [Hash{Symbol => Object}] kwargs
65
+ # Additional keyword arguments.
66
+ #
67
+ # @option kwargs [Boolean] required
68
+ # Specifies whether the option value is required or optional.
47
69
  #
48
- def initialize(type: String,
49
- usage: self.class.default_usage(type),
70
+ def initialize(type: String,
71
+ default: nil,
72
+ usage: self.class.default_usage(type),
50
73
  **kwargs)
51
- super(type: type, usage: usage, **kwargs)
74
+ super(usage: usage, **kwargs)
75
+
76
+ @type = type
77
+ @default = default
52
78
  end
53
79
 
54
80
  #
55
81
  # Returns the default option value usage for the given type.
56
82
  #
57
83
  # @param [Class, Hash, Array, Regexp] type
84
+ # The option value type.
58
85
  #
59
86
  # @return [String, nil]
87
+ # A default usage string based on the option value type.
60
88
  #
61
89
  # @raise [TypeError]
62
90
  # The given type was not a Class, Hash, Array, or Regexp.
@@ -85,6 +113,17 @@ module CommandKit
85
113
  string
86
114
  end
87
115
 
116
+ #
117
+ # Returns a new default value.
118
+ #
119
+ # @return [Object]
120
+ #
121
+ def default_value
122
+ if @default.respond_to?(:call) then @default.call
123
+ else @default.dup
124
+ end
125
+ end
126
+
88
127
  end
89
128
  end
90
129
  end
@@ -33,6 +33,9 @@ module CommandKit
33
33
  include Main
34
34
  include Printing
35
35
 
36
+ #
37
+ # @api private
38
+ #
36
39
  module ModuleMethods
37
40
  #
38
41
  # Sets {CommandKit::Usage::ClassMethods#usage .usage} or extends
@@ -58,6 +61,8 @@ module CommandKit
58
61
  # The option parser.
59
62
  #
60
63
  # @return [OptionParser]
64
+ #
65
+ # @api semipublic
61
66
  attr_reader :option_parser
62
67
 
63
68
  #
@@ -65,6 +70,8 @@ module CommandKit
65
70
  #
66
71
  # @return [OptionParser]
67
72
  #
73
+ # @api public
74
+ #
68
75
  def initialize(**kwargs)
69
76
  super(**kwargs)
70
77
 
@@ -91,6 +98,8 @@ module CommandKit
91
98
  # @return [Integer]
92
99
  # The exit status code.
93
100
  #
101
+ # @api public
102
+ #
94
103
  def main(argv=[])
95
104
  super(parse_options(argv))
96
105
  rescue SystemExit => system_exit
@@ -106,24 +115,24 @@ module CommandKit
106
115
  # @return [Array<String>]
107
116
  # The remaining non-option arguments.
108
117
  #
118
+ # @api semipublic
119
+ #
109
120
  def parse_options(argv)
110
- begin
111
- option_parser.parse(argv)
112
- rescue OptionParser::InvalidOption => error
113
- on_invalid_option(error)
114
- rescue OptionParser::AmbiguousOption => error
115
- on_ambiguous_option(error)
116
- rescue OptionParser::InvalidArgument => error
117
- on_invalid_argument(error)
118
- rescue OptionParser::MissingArgument => error
119
- on_missing_argument(error)
120
- rescue OptionParser::NeedlessArgument => error
121
- on_needless_argument(error)
122
- rescue OptionParser::AmbiguousArgument => error
123
- on_ambiguous_argument(error)
124
- rescue OptionParser::ParseError => error
125
- on_parse_error(error)
126
- end
121
+ option_parser.parse(argv)
122
+ rescue OptionParser::InvalidOption => error
123
+ on_invalid_option(error)
124
+ rescue OptionParser::AmbiguousOption => error
125
+ on_ambiguous_option(error)
126
+ rescue OptionParser::InvalidArgument => error
127
+ on_invalid_argument(error)
128
+ rescue OptionParser::MissingArgument => error
129
+ on_missing_argument(error)
130
+ rescue OptionParser::NeedlessArgument => error
131
+ on_needless_argument(error)
132
+ rescue OptionParser::AmbiguousArgument => error
133
+ on_ambiguous_argument(error)
134
+ rescue OptionParser::ParseError => error
135
+ on_parse_error(error)
127
136
  end
128
137
 
129
138
  #
@@ -132,6 +141,8 @@ module CommandKit
132
141
  # @param [OptionParser::ParseError] error
133
142
  # The error from `OptionParser`.
134
143
  #
144
+ # @api semipublic
145
+ #
135
146
  def on_parse_error(error)
136
147
  print_error("#{command_name}: #{error.message}")
137
148
  print_error("Try '#{command_name} --help' for more information.")
@@ -145,6 +156,8 @@ module CommandKit
145
156
  #
146
157
  # @see on_parse_error
147
158
  #
159
+ # @api semipublic
160
+ #
148
161
  def on_invalid_option(error)
149
162
  on_parse_error(error)
150
163
  end
@@ -157,6 +170,8 @@ module CommandKit
157
170
  #
158
171
  # @see on_parse_error
159
172
  #
173
+ # @api semipublic
174
+ #
160
175
  def on_ambiguous_option(error)
161
176
  on_parse_error(error)
162
177
  end
@@ -169,6 +184,8 @@ module CommandKit
169
184
  #
170
185
  # @see on_parse_error
171
186
  #
187
+ # @api semipublic
188
+ #
172
189
  def on_invalid_argument(error)
173
190
  on_parse_error(error)
174
191
  end
@@ -181,6 +198,8 @@ module CommandKit
181
198
  #
182
199
  # @see on_parse_error
183
200
  #
201
+ # @api semipublic
202
+ #
184
203
  def on_missing_argument(error)
185
204
  on_parse_error(error)
186
205
  end
@@ -193,6 +212,8 @@ module CommandKit
193
212
  #
194
213
  # @see on_parse_error
195
214
  #
215
+ # @api semipublic
216
+ #
196
217
  def on_needless_argument(error)
197
218
  on_parse_error(error)
198
219
  end
@@ -205,6 +226,8 @@ module CommandKit
205
226
  #
206
227
  # @see on_parse_error
207
228
  #
229
+ # @api semipublic
230
+ #
208
231
  def on_ambiguous_argument(error)
209
232
  on_parse_error(error)
210
233
  end
@@ -212,6 +235,8 @@ module CommandKit
212
235
  #
213
236
  # Prints the `--help` output.
214
237
  #
238
+ # @api semipublic
239
+ #
215
240
  def help_options
216
241
  puts option_parser
217
242
  end
@@ -219,6 +244,8 @@ module CommandKit
219
244
  #
220
245
  # @see #help_options
221
246
  #
247
+ # @api public
248
+ #
222
249
  def help
223
250
  help_options
224
251
  end