command_kit 0.1.0 → 0.3.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +18 -3
  3. data/.rubocop.yml +141 -0
  4. data/ChangeLog.md +165 -0
  5. data/Gemfile +3 -0
  6. data/README.md +186 -118
  7. data/Rakefile +3 -2
  8. data/command_kit.gemspec +4 -4
  9. data/examples/command.rb +1 -1
  10. data/gemspec.yml +7 -0
  11. data/lib/command_kit/arguments/argument.rb +2 -2
  12. data/lib/command_kit/arguments.rb +36 -7
  13. data/lib/command_kit/colors.rb +702 -53
  14. data/lib/command_kit/command.rb +2 -3
  15. data/lib/command_kit/commands/auto_load.rb +8 -1
  16. data/lib/command_kit/commands/help.rb +3 -2
  17. data/lib/command_kit/commands/subcommand.rb +1 -1
  18. data/lib/command_kit/commands.rb +24 -9
  19. data/lib/command_kit/env/path.rb +1 -1
  20. data/lib/command_kit/file_utils.rb +46 -0
  21. data/lib/command_kit/help/man.rb +17 -33
  22. data/lib/command_kit/inflector.rb +47 -17
  23. data/lib/command_kit/interactive.rb +9 -0
  24. data/lib/command_kit/main.rb +7 -9
  25. data/lib/command_kit/man.rb +44 -0
  26. data/lib/command_kit/open_app.rb +69 -0
  27. data/lib/command_kit/options/option.rb +41 -27
  28. data/lib/command_kit/options/option_value.rb +3 -2
  29. data/lib/command_kit/options/parser.rb +17 -22
  30. data/lib/command_kit/options.rb +102 -14
  31. data/lib/command_kit/os/linux.rb +157 -0
  32. data/lib/command_kit/os.rb +159 -11
  33. data/lib/command_kit/package_manager.rb +200 -0
  34. data/lib/command_kit/pager.rb +46 -4
  35. data/lib/command_kit/printing/indent.rb +4 -4
  36. data/lib/command_kit/printing.rb +14 -3
  37. data/lib/command_kit/program_name.rb +9 -0
  38. data/lib/command_kit/sudo.rb +40 -0
  39. data/lib/command_kit/terminal.rb +5 -0
  40. data/lib/command_kit/version.rb +1 -1
  41. data/spec/arguments/argument_spec.rb +1 -1
  42. data/spec/arguments_spec.rb +84 -1
  43. data/spec/colors_spec.rb +357 -70
  44. data/spec/command_spec.rb +77 -6
  45. data/spec/commands/auto_load_spec.rb +33 -2
  46. data/spec/commands_spec.rb +101 -29
  47. data/spec/env/path_spec.rb +6 -0
  48. data/spec/exception_handler_spec.rb +1 -1
  49. data/spec/file_utils_spec.rb +59 -0
  50. data/spec/fixtures/template.erb +5 -0
  51. data/spec/help/man_spec.rb +54 -57
  52. data/spec/inflector_spec.rb +70 -8
  53. data/spec/man_spec.rb +46 -0
  54. data/spec/open_app_spec.rb +85 -0
  55. data/spec/options/option_spec.rb +38 -2
  56. data/spec/options/option_value_spec.rb +55 -0
  57. data/spec/options/parser_spec.rb +0 -10
  58. data/spec/options_spec.rb +328 -0
  59. data/spec/os/linux_spec.rb +164 -0
  60. data/spec/os_spec.rb +200 -13
  61. data/spec/package_manager_spec.rb +806 -0
  62. data/spec/pager_spec.rb +71 -6
  63. data/spec/printing/indent_spec.rb +7 -5
  64. data/spec/printing_spec.rb +23 -1
  65. data/spec/program_name_spec.rb +8 -0
  66. data/spec/sudo_spec.rb +51 -0
  67. data/spec/terminal_spec.rb +30 -0
  68. data/spec/usage_spec.rb +1 -1
  69. metadata +23 -4
@@ -78,9 +78,6 @@ module CommandKit
78
78
  @option_parser = OptionParser.new do |opts|
79
79
  opts.banner = "Usage: #{usage}"
80
80
 
81
- opts.separator ''
82
- opts.separator 'Options:'
83
-
84
81
  opts.on_tail('-h','--help','Print help information') do
85
82
  help
86
83
  exit(0)
@@ -118,23 +115,21 @@ module CommandKit
118
115
  # @api semipublic
119
116
  #
120
117
  def parse_options(argv)
121
- begin
122
- option_parser.parse(argv)
123
- rescue OptionParser::InvalidOption => error
124
- on_invalid_option(error)
125
- rescue OptionParser::AmbiguousOption => error
126
- on_ambiguous_option(error)
127
- rescue OptionParser::InvalidArgument => error
128
- on_invalid_argument(error)
129
- rescue OptionParser::MissingArgument => error
130
- on_missing_argument(error)
131
- rescue OptionParser::NeedlessArgument => error
132
- on_needless_argument(error)
133
- rescue OptionParser::AmbiguousArgument => error
134
- on_ambiguous_argument(error)
135
- rescue OptionParser::ParseError => error
136
- on_parse_error(error)
137
- end
118
+ option_parser.parse(argv)
119
+ rescue OptionParser::InvalidOption => error
120
+ on_invalid_option(error)
121
+ rescue OptionParser::AmbiguousOption => error
122
+ on_ambiguous_option(error)
123
+ rescue OptionParser::InvalidArgument => error
124
+ on_invalid_argument(error)
125
+ rescue OptionParser::MissingArgument => error
126
+ on_missing_argument(error)
127
+ rescue OptionParser::NeedlessArgument => error
128
+ on_needless_argument(error)
129
+ rescue OptionParser::AmbiguousArgument => error
130
+ on_ambiguous_argument(error)
131
+ rescue OptionParser::ParseError => error
132
+ on_parse_error(error)
138
133
  end
139
134
 
140
135
  #
@@ -146,8 +141,8 @@ module CommandKit
146
141
  # @api semipublic
147
142
  #
148
143
  def on_parse_error(error)
149
- print_error("#{command_name}: #{error.message}")
150
- print_error("Try '#{command_name} --help' for more information.")
144
+ print_error(error.message)
145
+ stderr.puts("Try '#{command_name} --help' for more information.")
151
146
  exit(1)
152
147
  end
153
148
 
@@ -1,3 +1,4 @@
1
+ require 'command_kit/arguments'
1
2
  require 'command_kit/options/option'
2
3
  require 'command_kit/options/parser'
3
4
 
@@ -22,7 +23,26 @@ module CommandKit
22
23
  # @bar = arg.split(':')
23
24
  # end
24
25
  #
25
- # ### initialize and using ivars
26
+ # ### Multi-line Descriptions
27
+ #
28
+ # option :opt, value: {type: String},
29
+ # desc: [
30
+ # 'line1',
31
+ # 'line2',
32
+ # '...'
33
+ # ]
34
+ #
35
+ # ### Option Categories
36
+ #
37
+ # option :opt1, value: {type: String},
38
+ # category: 'Foo Category',
39
+ # desc: 'Option 1'
40
+ #
41
+ # option :opt2, value: {type: String},
42
+ # category: 'Foo Category',
43
+ # desc: 'Option 2'
44
+ #
45
+ # ### initialize and using instance variables
26
46
  #
27
47
  # option :number, value: {type: Integer},
28
48
  # desc: 'Numbers' do |num|
@@ -36,6 +56,7 @@ module CommandKit
36
56
  # end
37
57
  #
38
58
  module Options
59
+ include Arguments
39
60
  include Parser
40
61
 
41
62
  #
@@ -109,9 +130,12 @@ module CommandKit
109
130
  # @option value [String, nil] usage
110
131
  # The usage string for the option's value.
111
132
  #
112
- # @option kwargs [String] desc
133
+ # @option kwargs [String, Array<String>] desc
113
134
  # The description for the option.
114
135
  #
136
+ # @option kwargs [String, nil] category
137
+ # The optional category to group the option under.
138
+ #
115
139
  # @yield [(value)]
116
140
  # If a block is given, it will be passed the parsed option value.
117
141
  #
@@ -127,6 +151,19 @@ module CommandKit
127
151
  # @example Define an option:
128
152
  # option :foo, desc: "Foo option"
129
153
  #
154
+ # @example Define an option with a multi-line description:
155
+ # option :foo, desc: [
156
+ # "Line 1",
157
+ # "Line 2"
158
+ # ]
159
+ #
160
+ # @example Defines multiple options within a category:
161
+ # option :foo, desc: "Foo option",
162
+ # category: 'Other Options'
163
+ #
164
+ # option :bar, desc: "Bar option",
165
+ # category: 'Other Options'
166
+ #
130
167
  # @example With a custom short option:
131
168
  # option :foo, short: '-f',
132
169
  # desc: "Foo option"
@@ -197,7 +234,7 @@ module CommandKit
197
234
  # {Parser#option_parser option parser}.
198
235
  #
199
236
  # @param [Hash{Symbol => Object}] options
200
- # Optional pre-populated options hash.
237
+ # Optional prepopulated options hash.
201
238
  #
202
239
  # @note
203
240
  # The {#option_parser} will populate {#options} and also call any
@@ -210,17 +247,68 @@ module CommandKit
210
247
 
211
248
  super(**kwargs)
212
249
 
213
- self.class.options.each_value do |option|
214
- option_parser.on(*option.usage,option.type,option.desc) do |arg,*captures|
215
- @options[option.name] = if arg.nil?
216
- option.default_value
217
- else
218
- arg
219
- end
220
-
221
- if option.block
222
- instance_exec(*arg,*captures,&option.block)
223
- end
250
+ define_option_categories
251
+ end
252
+
253
+ #
254
+ # Overrides the default {Usage#help help} method and calls {#help_options}
255
+ # and {#help_arguments}.
256
+ #
257
+ # @api public
258
+ #
259
+ def help
260
+ help_options
261
+ help_arguments
262
+ end
263
+
264
+ private
265
+
266
+ #
267
+ # Defines all of the options, grouped by category.
268
+ #
269
+ def define_option_categories
270
+ categories = self.class.options.values.group_by(&:category)
271
+
272
+ categories.each do |category,options|
273
+ if category
274
+ define_options_category(category,options)
275
+ end
276
+ end
277
+
278
+ define_options_category('Options',categories.fetch(nil,[]))
279
+ end
280
+
281
+ #
282
+ # Defines a new category of options with a header.
283
+ #
284
+ # @param [String] category
285
+ # The category name.
286
+ #
287
+ # @param [Array<Option>, nil] options
288
+ # The options to define.
289
+ #
290
+ def define_options_category(category,options)
291
+ option_parser.separator ''
292
+ option_parser.separator "#{category}:"
293
+
294
+ options.each(&method(:define_option))
295
+ end
296
+
297
+ #
298
+ # Defines an individual option.
299
+ #
300
+ # @param [Option] option
301
+ #
302
+ def define_option(option)
303
+ default_value = option.default_value
304
+
305
+ @options[option.name] = default_value unless default_value.nil?
306
+
307
+ option_parser.on(*option.usage,option.type,option.desc) do |arg,*captures|
308
+ @options[option.name] = arg
309
+
310
+ if option.block
311
+ instance_exec(*arg,*captures,&option.block)
224
312
  end
225
313
  end
226
314
  end
@@ -0,0 +1,157 @@
1
+ require 'command_kit/os'
2
+
3
+ module CommandKit
4
+ module OS
5
+ #
6
+ # Provides methods for determining the specific type of Linux.
7
+ #
8
+ # ## Example
9
+ #
10
+ # require 'command_kit/command'
11
+ # require 'command_kit/os/linux'
12
+ #
13
+ # class Command < CommandKit::Command
14
+ #
15
+ # include CommandKit::OS::Linux
16
+ #
17
+ # def run
18
+ # if debian_linux?
19
+ # # ...
20
+ # elsif redhat_linux?
21
+ # # ...
22
+ # elsif suse_linux?
23
+ # # ...
24
+ # elsif arch_linux?
25
+ # # ...
26
+ # end
27
+ # end
28
+ # end
29
+ #
30
+ # @since 0.2.0
31
+ #
32
+ module Linux
33
+ #
34
+ # @api private
35
+ #
36
+ module ModuleMethods
37
+ #
38
+ # Extends {ClassMethods} or {ModuleMethods}, depending on whether
39
+ # {OS} is being included into a class or a module..
40
+ #
41
+ # @param [Class, Module] context
42
+ # The class or module which is including {OS}.
43
+ #
44
+ def included(context)
45
+ super(context)
46
+
47
+ if context.class == Module
48
+ context.extend ModuleMethods
49
+ else
50
+ context.extend ClassMethods
51
+ end
52
+ end
53
+ end
54
+
55
+ extend ModuleMethods
56
+
57
+ module ClassMethods
58
+ #
59
+ # Determines the specific Linux distro.
60
+ #
61
+ # @return [:fedora, :redhat, :debian, :suse, :arch, nil]
62
+ # Returns the type of Linux distro or `nil` if the Linux distro could
63
+ # not be determined.
64
+ #
65
+ # @api semipublic
66
+ #
67
+ def linux_distro
68
+ if File.file?('/etc/fedora-release') then :fedora
69
+ elsif File.file?('/etc/redhat-release') then :redhat
70
+ elsif File.file?('/etc/debian_version') then :debian
71
+ elsif File.file?('/etc/SuSE-release') then :suse
72
+ elsif File.file?('/etc/arch-release') then :arch
73
+ end
74
+ end
75
+ end
76
+
77
+ # The Linux distro.
78
+ #
79
+ # @return [:fedora, :redhat, :debian, :suse, :arch, nil]
80
+ #
81
+ # @api public
82
+ attr_reader :linux_distro
83
+
84
+ #
85
+ # Initializes the command.
86
+ #
87
+ # @param [:fedora, :redhat, :debian, :suse, :arch, nil] linux_distro
88
+ # Overrides the default detected Linux distro.
89
+ #
90
+ # @param [Hash{Symbol => Object}] kwargs
91
+ # Additional keyword arguments.
92
+ #
93
+ # @api public
94
+ #
95
+ def initialize(linux_distro: self.class.linux_distro, **kwargs)
96
+ super(**kwargs)
97
+
98
+ @linux_distro = linux_distro
99
+ end
100
+
101
+ #
102
+ # Determines if the current OS is RedHat Linux based distro.
103
+ #
104
+ # @return [Boolean]
105
+ #
106
+ # @api public
107
+ #
108
+ def redhat_linux?
109
+ @linux_distro == :redhat
110
+ end
111
+
112
+ #
113
+ # Determines if the current OS is Fedora Linux based distro.
114
+ #
115
+ # @return [Boolean]
116
+ #
117
+ # @api public
118
+ #
119
+ def fedora_linux?
120
+ @linux_distro == :fedora
121
+ end
122
+
123
+ #
124
+ # Determines if the current OS is Debian Linux based distro.
125
+ #
126
+ # @return [Boolean]
127
+ #
128
+ # @api public
129
+ #
130
+ def debian_linux?
131
+ @linux_distro == :debian
132
+ end
133
+
134
+ #
135
+ # Determines if the current OS is SUSE Linux based distro.
136
+ #
137
+ # @return [Boolean]
138
+ #
139
+ # @api public
140
+ #
141
+ def suse_linux?
142
+ @linux_distro == :suse
143
+ end
144
+
145
+ #
146
+ # Determines if the current OS is Arch Linux based distro.
147
+ #
148
+ # @return [Boolean]
149
+ #
150
+ # @api public
151
+ #
152
+ def arch_linux?
153
+ @linux_distro == :arch
154
+ end
155
+ end
156
+ end
157
+ end
@@ -4,19 +4,102 @@ module CommandKit
4
4
  #
5
5
  # ## Examples
6
6
  #
7
- # include CommandKit::OS
7
+ # require 'command_kit/command'
8
+ # require 'command_kit/os'
8
9
  #
9
- # def main(*argv)
10
- # if linux?
11
- # # ...
12
- # elsif macos?
13
- # # ...
14
- # elsif windows?
15
- # # ...
10
+ # class Command < CommandKit::Command
11
+ #
12
+ # include CommandKit::OS
13
+ #
14
+ # def main(*argv)
15
+ # if linux?
16
+ # # ...
17
+ # elsif macos?
18
+ # # ...
19
+ # elsif freebsd?
20
+ # # ...
21
+ # elsif windows?
22
+ # # ...
23
+ # end
16
24
  # end
25
+ #
17
26
  # end
18
27
  #
19
28
  module OS
29
+ #
30
+ # @api private
31
+ #
32
+ module ModuleMethods
33
+ #
34
+ # Extends {ClassMethods} or {ModuleMethods}, depending on whether
35
+ # {OS} is being included into a class or a module..
36
+ #
37
+ # @param [Class, Module] context
38
+ # The class or module which is including {OS}.
39
+ #
40
+ def included(context)
41
+ super(context)
42
+
43
+ if context.class == Module
44
+ context.extend ModuleMethods
45
+ else
46
+ context.extend ClassMethods
47
+ end
48
+ end
49
+ end
50
+
51
+ extend ModuleMethods
52
+
53
+ module ClassMethods
54
+ #
55
+ # Determines the current OS.
56
+ #
57
+ # @return [:linux, :macos, :freebsd, :openbsd, :netbsd, :windows, nil]
58
+ # The OS type or `nil` if the OS could not be determined.
59
+ #
60
+ # @api semipublic
61
+ #
62
+ # @since 0.2.0
63
+ #
64
+ def os
65
+ if RUBY_PLATFORM.include?('linux') then :linux
66
+ elsif RUBY_PLATFORM.include?('darwin') then :macos
67
+ elsif RUBY_PLATFORM.include?('freebsd') then :freebsd
68
+ elsif RUBY_PLATFORM.include?('openbsd') then :openbsd
69
+ elsif RUBY_PLATFORM.include?('netbsd') then :netbsd
70
+ elsif Gem.win_platform? then :windows
71
+ end
72
+ end
73
+ end
74
+
75
+ # The current OS.
76
+ #
77
+ # @return [:linux, :macos, :freebsd, :openbsd, :netbsd, :windows, nil]
78
+ #
79
+ # @api public
80
+ #
81
+ # @since 0.2.0
82
+ attr_reader :os
83
+
84
+ #
85
+ # Initializes the command.
86
+ #
87
+ # @param [:linux, :macos, :freebsd, :openbsd, :netbsd, :windows, nil] os
88
+ # Overrides the default OS.
89
+ #
90
+ # @param [Hash{Symbol => Object}] kwargs
91
+ # Additional keyword arguments.
92
+ #
93
+ # @api public
94
+ #
95
+ # @since 0.2.0
96
+ #
97
+ def initialize(os: self.class.os, **kwargs)
98
+ super(**kwargs)
99
+
100
+ @os = os
101
+ end
102
+
20
103
  #
21
104
  # Determines if the current OS is Linux.
22
105
  #
@@ -25,7 +108,7 @@ module CommandKit
25
108
  # @api public
26
109
  #
27
110
  def linux?
28
- RUBY_PLATFORM.include?('linux')
111
+ @os == :linux
29
112
  end
30
113
 
31
114
  #
@@ -36,7 +119,72 @@ module CommandKit
36
119
  # @api public
37
120
  #
38
121
  def macos?
39
- RUBY_PLATFORM.include?('darwin')
122
+ @os == :macos
123
+ end
124
+
125
+ #
126
+ # Determines if the current OS is FreeBSD.
127
+ #
128
+ # @return [Boolean]
129
+ #
130
+ # @api public
131
+ #
132
+ # @since 0.2.0
133
+ #
134
+ def freebsd?
135
+ @os == :freebsd
136
+ end
137
+
138
+ #
139
+ # Determines if the current OS is OpenBSD.
140
+ #
141
+ # @return [Boolean]
142
+ #
143
+ # @api public
144
+ #
145
+ # @since 0.2.0
146
+ #
147
+ def openbsd?
148
+ @os == :openbsd
149
+ end
150
+
151
+ #
152
+ # Determines if the current OS is NetBSD.
153
+ #
154
+ # @return [Boolean]
155
+ #
156
+ # @api public
157
+ #
158
+ # @since 0.2.0
159
+ #
160
+ def netbsd?
161
+ @os == :netbsd
162
+ end
163
+
164
+ #
165
+ # Determines if the current OS is BSD based.
166
+ #
167
+ # @return [Boolean]
168
+ #
169
+ # @since 0.2.0
170
+ #
171
+ # @api public
172
+ #
173
+ def bsd?
174
+ freebsd? || openbsd? || netbsd?
175
+ end
176
+
177
+ #
178
+ # Determines if the current OS is UNIX based.
179
+ #
180
+ # @return [Boolean]
181
+ #
182
+ # @since 0.2.0
183
+ #
184
+ # @api public
185
+ #
186
+ def unix?
187
+ linux? || macos? || bsd?
40
188
  end
41
189
 
42
190
  #
@@ -47,7 +195,7 @@ module CommandKit
47
195
  # @api public
48
196
  #
49
197
  def windows?
50
- Gem.win_platform?
198
+ @os == :windows
51
199
  end
52
200
  end
53
201
  end