command_kit 0.1.0.pre2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +15 -0
  3. data/.rubocop.yml +141 -0
  4. data/ChangeLog.md +98 -2
  5. data/Gemfile +3 -0
  6. data/README.md +189 -117
  7. data/Rakefile +3 -2
  8. data/command_kit.gemspec +4 -4
  9. data/examples/command.rb +1 -1
  10. data/gemspec.yml +10 -2
  11. data/lib/command_kit/arguments/argument.rb +2 -0
  12. data/lib/command_kit/arguments/argument_value.rb +2 -0
  13. data/lib/command_kit/arguments.rb +23 -4
  14. data/lib/command_kit/colors.rb +253 -45
  15. data/lib/command_kit/command.rb +6 -1
  16. data/lib/command_kit/command_name.rb +9 -0
  17. data/lib/command_kit/commands/auto_load.rb +24 -1
  18. data/lib/command_kit/commands/auto_require.rb +16 -0
  19. data/lib/command_kit/commands/command.rb +3 -0
  20. data/lib/command_kit/commands/help.rb +5 -2
  21. data/lib/command_kit/commands/parent_command.rb +7 -0
  22. data/lib/command_kit/commands/subcommand.rb +13 -1
  23. data/lib/command_kit/commands.rb +54 -9
  24. data/lib/command_kit/description.rb +12 -1
  25. data/lib/command_kit/env/home.rb +9 -0
  26. data/lib/command_kit/env/path.rb +16 -1
  27. data/lib/command_kit/env.rb +4 -0
  28. data/lib/command_kit/examples.rb +12 -1
  29. data/lib/command_kit/exception_handler.rb +4 -0
  30. data/lib/command_kit/help/man.rb +26 -30
  31. data/lib/command_kit/help.rb +7 -1
  32. data/lib/command_kit/inflector.rb +49 -17
  33. data/lib/command_kit/interactive.rb +248 -0
  34. data/lib/command_kit/main.rb +18 -9
  35. data/lib/command_kit/man.rb +44 -0
  36. data/lib/command_kit/open_app.rb +69 -0
  37. data/lib/command_kit/options/option.rb +3 -6
  38. data/lib/command_kit/options/option_value.rb +5 -2
  39. data/lib/command_kit/options/parser.rb +46 -19
  40. data/lib/command_kit/options/quiet.rb +3 -0
  41. data/lib/command_kit/options/verbose.rb +5 -0
  42. data/lib/command_kit/options/version.rb +6 -0
  43. data/lib/command_kit/options.rb +32 -7
  44. data/lib/command_kit/os/linux.rb +157 -0
  45. data/lib/command_kit/os.rb +165 -11
  46. data/lib/command_kit/package_manager.rb +200 -0
  47. data/lib/command_kit/pager.rb +80 -11
  48. data/lib/command_kit/printing/indent.rb +27 -4
  49. data/lib/command_kit/printing.rb +35 -1
  50. data/lib/command_kit/program_name.rb +7 -0
  51. data/lib/command_kit/stdio.rb +24 -0
  52. data/lib/command_kit/sudo.rb +40 -0
  53. data/lib/command_kit/terminal.rb +159 -0
  54. data/lib/command_kit/usage.rb +14 -0
  55. data/lib/command_kit/version.rb +1 -1
  56. data/lib/command_kit/xdg.rb +13 -0
  57. data/lib/command_kit.rb +1 -0
  58. data/spec/arguments/argument_spec.rb +2 -2
  59. data/spec/arguments_spec.rb +53 -27
  60. data/spec/colors_spec.rb +277 -13
  61. data/spec/command_name_spec.rb +1 -1
  62. data/spec/command_spec.rb +79 -5
  63. data/spec/commands/auto_load/subcommand_spec.rb +1 -1
  64. data/spec/commands/auto_load_spec.rb +34 -3
  65. data/spec/commands/auto_require_spec.rb +2 -2
  66. data/spec/commands/help_spec.rb +1 -1
  67. data/spec/commands/parent_command_spec.rb +1 -1
  68. data/spec/commands/subcommand_spec.rb +1 -1
  69. data/spec/commands_spec.rb +103 -29
  70. data/spec/description_spec.rb +1 -25
  71. data/spec/env/home_spec.rb +1 -1
  72. data/spec/env/path_spec.rb +7 -1
  73. data/spec/examples_spec.rb +1 -25
  74. data/spec/exception_handler_spec.rb +1 -1
  75. data/spec/help/man_spec.rb +45 -58
  76. data/spec/help_spec.rb +0 -25
  77. data/spec/inflector_spec.rb +71 -9
  78. data/spec/interactive_spec.rb +415 -0
  79. data/spec/main_spec.rb +7 -7
  80. data/spec/man_spec.rb +46 -0
  81. data/spec/open_app_spec.rb +85 -0
  82. data/spec/options/option_spec.rb +5 -5
  83. data/spec/options/option_value_spec.rb +56 -1
  84. data/spec/options_spec.rb +283 -1
  85. data/spec/os/linux_spec.rb +164 -0
  86. data/spec/os_spec.rb +201 -14
  87. data/spec/package_manager_spec.rb +806 -0
  88. data/spec/pager_spec.rb +76 -11
  89. data/spec/printing/indent_spec.rb +8 -6
  90. data/spec/printing_spec.rb +33 -3
  91. data/spec/program_name_spec.rb +1 -1
  92. data/spec/spec_helper.rb +0 -3
  93. data/spec/sudo_spec.rb +51 -0
  94. data/spec/{console_spec.rb → terminal_spec.rb} +65 -35
  95. data/spec/usage_spec.rb +2 -2
  96. data/spec/xdg_spec.rb +1 -1
  97. metadata +26 -8
  98. data/lib/command_kit/console.rb +0 -141
@@ -3,6 +3,7 @@
3
3
  require 'command_kit/command_name'
4
4
  require 'command_kit/help'
5
5
  require 'command_kit/stdio'
6
+ require 'command_kit/man'
6
7
 
7
8
  module CommandKit
8
9
  module Help
@@ -23,7 +24,11 @@ module CommandKit
23
24
  include CommandName
24
25
  include Help
25
26
  include Stdio
27
+ include CommandKit::Man
26
28
 
29
+ #
30
+ # @api private
31
+ #
27
32
  module ModuleMethods
28
33
  #
29
34
  # Extends {ClassMethods} or {ModuleMethods}, depending on whether
@@ -61,9 +66,11 @@ module CommandKit
61
66
  # @example
62
67
  # man_dir "#{__dir__}/../../man"
63
68
  #
69
+ # @api public
70
+ #
64
71
  def man_dir(new_man_dir=nil)
65
72
  if new_man_dir
66
- @man_dir = new_man_dir
73
+ @man_dir = File.expand_path(new_man_dir)
67
74
  else
68
75
  @man_dir || if superclass.kind_of?(ClassMethods)
69
76
  superclass.man_dir
@@ -80,6 +87,8 @@ module CommandKit
80
87
  # @return [String]
81
88
  # The class'es or superclass'es man-page file name.
82
89
  #
90
+ # @api public
91
+ #
83
92
  def man_page(new_man_page=nil)
84
93
  if new_man_page
85
94
  @man_page = new_man_page
@@ -89,27 +98,6 @@ module CommandKit
89
98
  end
90
99
  end
91
100
 
92
- #
93
- # Displays the given man page.
94
- #
95
- # @param [String] page
96
- # The man page file name.
97
- #
98
- # @param [Integer, String, nil] section
99
- # The optional section number to specify.
100
- #
101
- # @return [Boolean, nil]
102
- # Specifies whether the `man` command was successful or not.
103
- # Returns `nil` when the `man` command is not installed.
104
- #
105
- def man(page, section: nil)
106
- if section
107
- system('man',section.to_s,page.to_s)
108
- else
109
- system('man',page.to_s)
110
- end
111
- end
112
-
113
101
  #
114
102
  # Provides help information by showing one of the man pages within
115
103
  # {ClassMethods#man_dir .man_dir}.
@@ -124,11 +112,9 @@ module CommandKit
124
112
  # @raise [NotImplementedError]
125
113
  # {ClassMethods#man_dir .man_dir} does not have a value.
126
114
  #
115
+ # @api semipublic
116
+ #
127
117
  def help_man(man_page=self.class.man_page)
128
- unless self.class.man_dir
129
- raise(NotImplementedError,"#{self.class}.man_dir not set")
130
- end
131
-
132
118
  man_path = File.join(self.class.man_dir,man_page)
133
119
 
134
120
  man(man_path)
@@ -142,16 +128,26 @@ module CommandKit
142
128
  # {ClassMethods#man_dir .man_dir} does not have a value.
143
129
  #
144
130
  # @note
145
- # if `TERM` is `dumb` or `$stdout` is not a TTY, fallsback to printing
146
- # the usual `--help` output.
131
+ # if `TERM` is `dumb` or `$stdout` is not a TTY, will fall back to
132
+ # printing the usual `--help` output.
133
+ #
134
+ # @api public
147
135
  #
148
136
  def help
149
137
  if stdout.tty?
150
- if help_man.nil?
151
- # the `man` command is not installed
138
+ if self.class.man_dir
139
+ status = help_man
140
+
141
+ if status.nil?
142
+ # the `man` command is not installed
143
+ super
144
+ end
145
+ else
146
+ # man_dir was not set
152
147
  super
153
148
  end
154
149
  else
150
+ # stdout is not a TTY
155
151
  super
156
152
  end
157
153
  end
@@ -15,6 +15,9 @@ module CommandKit
15
15
  # MyCmd.help
16
16
  #
17
17
  module Help
18
+ #
19
+ # @api private
20
+ #
18
21
  module ModuleMethods
19
22
  #
20
23
  # Extends {ClassMethods} or {ModuleMethods}, depending on whether {Help}
@@ -48,6 +51,8 @@ module CommandKit
48
51
  #
49
52
  # @see Help#help
50
53
  #
54
+ # @api public
55
+ #
51
56
  def help(**kwargs)
52
57
  new(**kwargs).help
53
58
  end
@@ -58,8 +63,9 @@ module CommandKit
58
63
  #
59
64
  # @abstract
60
65
  #
66
+ # @api public
67
+ #
61
68
  def help
62
- super if defined?(super)
63
69
  end
64
70
  end
65
71
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'strscan'
4
+
3
5
  module CommandKit
4
6
  #
5
7
  # A very simple inflector.
@@ -8,6 +10,8 @@ module CommandKit
8
10
  # If you need something more powerful, checkout
9
11
  # [dry-inflector](https://dry-rb.org/gems/dry-inflector/0.1/)
10
12
  #
13
+ # @api semipublic
14
+ #
11
15
  module Inflector
12
16
  #
13
17
  # Removes the namespace from a constant name.
@@ -31,16 +35,37 @@ module CommandKit
31
35
  # @return [String]
32
36
  # The resulting under_scored name.
33
37
  #
38
+ # @raise [ArgumentError]
39
+ # The given string contained non-alpha-numeric characters.
40
+ #
34
41
  def self.underscore(name)
35
- # sourced from: https://github.com/dry-rb/dry-inflector/blob/c918f967ff82611da374eb0847a77b7e012d3fa8/lib/dry/inflector.rb#L286-L287
36
- name = name.to_s.dup
42
+ scanner = StringScanner.new(name.to_s)
43
+ new_string = String.new
37
44
 
38
- name.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
39
- name.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
40
- name.tr!('-','_')
41
- name.downcase!
45
+ until scanner.eos?
46
+ if (separator = scanner.scan(/[_-]+/))
47
+ new_string << ('_' * separator.length)
48
+ else
49
+ if (capitalized = scanner.scan(/[A-Z][a-z\d]+/))
50
+ new_string << capitalized
51
+ elsif (uppercase = scanner.scan(/[A-Z][A-Z\d]*(?=[A-Z_-]|$)/))
52
+ new_string << uppercase
53
+ elsif (lowercase = scanner.scan(/[a-z][a-z\d]*/))
54
+ new_string << lowercase
55
+ else
56
+ raise(ArgumentError,"cannot convert string to underscored: #{scanner.string.inspect}")
57
+ end
42
58
 
43
- name
59
+ if (separator = scanner.scan(/[_-]+/))
60
+ new_string << ('_' * separator.length)
61
+ elsif !scanner.eos?
62
+ new_string << '_'
63
+ end
64
+ end
65
+ end
66
+
67
+ new_string.downcase!
68
+ new_string
44
69
  end
45
70
 
46
71
  #
@@ -65,20 +90,27 @@ module CommandKit
65
90
  # @return [String]
66
91
  # The CamelCased name.
67
92
  #
93
+ # @raise [ArgumentError]
94
+ # The given under_scored string contained non-alpha-numeric characters.
95
+ #
68
96
  def self.camelize(name)
69
- name = name.to_s.dup
70
-
71
- # sourced from: https://github.com/dry-rb/dry-inflector/blob/c918f967ff82611da374eb0847a77b7e012d3fa8/lib/dry/inflector.rb#L329-L334
72
- name.sub!(/^[a-z\d]*/,&:capitalize)
73
- name.gsub!(%r{(?:[_-]|(/))([a-z\d]*)}i) do |match|
74
- slash = Regexp.last_match(1)
75
- word = Regexp.last_match(2)
97
+ scanner = StringScanner.new(name.to_s)
98
+ new_string = String.new
76
99
 
77
- "#{slash}#{word.capitalize}"
100
+ until scanner.eos?
101
+ if (word = scanner.scan(/[A-Za-z\d]+/))
102
+ word.capitalize!
103
+ new_string << word
104
+ elsif scanner.scan(/[_-]+/)
105
+ # skip
106
+ elsif scanner.scan(/\//)
107
+ new_string << '::'
108
+ else
109
+ raise(ArgumentError,"cannot convert string to CamelCase: #{scanner.string.inspect}")
110
+ end
78
111
  end
79
112
 
80
- name.gsub!('/','::')
81
- name
113
+ new_string
82
114
  end
83
115
  end
84
116
  end
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_kit/stdio'
4
+
5
+ module CommandKit
6
+ #
7
+ # Provides methods for asking the user for input.
8
+ #
9
+ # ## Examples
10
+ #
11
+ # first_name = ask("First name")
12
+ # last_name = ask("Last name")
13
+ #
14
+ # ### Asking for secret input
15
+ #
16
+ # password = ask_secret("Password")
17
+ #
18
+ # ### Asking Y/N?
19
+ #
20
+ # if ask_yes_or_no("Proceed anyways?")
21
+ # # ...
22
+ # else
23
+ # stderr.puts "Aborting!"
24
+ # end
25
+ #
26
+ # ### Asking multi-choice questions
27
+ #
28
+ # ask_multiple_choice("Select a flavor", %w[Apple Orange Lemon Lime])
29
+ # # 1) Apple
30
+ # # 2) Orange
31
+ # # 3) Lemon
32
+ # # 4) Lime
33
+ # # Select a flavor: 4
34
+ # #
35
+ # # => "Lime"
36
+ #
37
+ module Interactive
38
+ include Stdio
39
+
40
+ #
41
+ # Asks the user for input.
42
+ #
43
+ # @param [String] prompt
44
+ # The prompt that will be printed before reading input.
45
+ #
46
+ # @param [String, nil] default
47
+ # The default value to return if no input is given.
48
+ #
49
+ # @param [Boolean] required
50
+ # Requires non-empty input.
51
+ #
52
+ # @return [String]
53
+ # The user input.
54
+ #
55
+ # @example
56
+ # first_name = ask("First name")
57
+ # last_name = ask("Last name")
58
+ #
59
+ # @example Default value:
60
+ # ask("Country", default: "EU")
61
+ # # Country [EU]: <enter>
62
+ # # => "EU"
63
+ #
64
+ # @example Required non-empty input:
65
+ # ask("Email", required: true)
66
+ # # Email: <enter>
67
+ # # Email: bob@example.com<enter>
68
+ # # => "bob@example.com"
69
+ #
70
+ # @api public
71
+ #
72
+ def ask(prompt, default: nil, required: false)
73
+ prompt = prompt.chomp
74
+ prompt << " [#{default}]" if default
75
+ prompt << ": "
76
+
77
+ stdout.print(prompt)
78
+
79
+ loop do
80
+ value = stdin.gets
81
+ value ||= '' # convert nil values (ctrl^D) to an empty String
82
+
83
+ if value.empty?
84
+ if required
85
+ next
86
+ else
87
+ return (default || value)
88
+ end
89
+ else
90
+ return value
91
+ end
92
+ end
93
+ end
94
+
95
+ #
96
+ # Asks the user a yes or no question.
97
+ #
98
+ # @param [String] prompt
99
+ # The prompt that will be printed before reading input.
100
+ #
101
+ # @param [true, false, nil] default
102
+ #
103
+ # @return [Boolean]
104
+ # Specifies whether the user entered Y/yes.
105
+ #
106
+ # @example
107
+ # ask_yes_or_no("Proceed anyways?")
108
+ # # Proceed anyways? (Y/N): Y
109
+ # # => true
110
+ #
111
+ # @example Default value:
112
+ # ask_yes_or_no("Proceed anyways?", default: true)
113
+ # # Proceed anyways? (Y/N) [Y]: <enter>
114
+ # # => true
115
+ #
116
+ # @api public
117
+ #
118
+ def ask_yes_or_no(prompt, default: nil, **kwargs)
119
+ default = case default
120
+ when true then 'Y'
121
+ when false then 'N'
122
+ when nil then nil
123
+ else
124
+ raise(ArgumentError,"invalid default: #{default.inspect}")
125
+ end
126
+
127
+ prompt = "#{prompt} (Y/N)"
128
+
129
+ loop do
130
+ answer = ask(prompt, **kwargs, default: default)
131
+
132
+ case answer.downcase
133
+ when 'y', 'yes'
134
+ return true
135
+ else
136
+ return false
137
+ end
138
+ end
139
+ end
140
+
141
+ #
142
+ # Asks the user to select a choice from a list of options.
143
+ #
144
+ # @param [String] prompt
145
+ # The prompt that will be printed before reading input.
146
+ #
147
+ # @param [Hash{String => String}, Array<String>] choices
148
+ # The choices to select from.
149
+ #
150
+ # @param [Hash{Symbol => Object}] kwargs
151
+ # Additional keyword arguments for {#ask}.
152
+ #
153
+ # @option kwargs [String, nil] default
154
+ # The default option to fallback to, if no input is given.
155
+ #
156
+ # @option kwargs [Boolean] required
157
+ # Requires non-empty input.
158
+ #
159
+ # @return [String]
160
+ # The selected choice.
161
+ #
162
+ # @example Array of choices:
163
+ # ask_multiple_choice("Select a flavor", %w[Apple Orange Lemon Lime])
164
+ # # 1) Apple
165
+ # # 2) Orange
166
+ # # 3) Lemon
167
+ # # 4) Lime
168
+ # # Select a flavor: 4
169
+ # #
170
+ # # => "Lime"
171
+ #
172
+ # @example Hash of choices:
173
+ # ask_multiple_choice("Select an option", {'A' => 'Foo',
174
+ # 'B' => 'Bar',
175
+ # 'X' => 'All of the above'})
176
+ # # A) Foo
177
+ # # B) Bar
178
+ # # X) All of the above
179
+ # # Select an option: X
180
+ # #
181
+ # # => "All of the above"
182
+ #
183
+ # @api public
184
+ #
185
+ def ask_multiple_choice(prompt,choices,**kwargs)
186
+ choices = case choices
187
+ when Array
188
+ Hash[choices.each_with_index.map { |value,i|
189
+ [(i+1).to_s, value]
190
+ }]
191
+ when Hash
192
+ choices
193
+ else
194
+ raise(TypeError,"unsupported choices class #{choices.class}: #{choices.inspect}")
195
+ end
196
+
197
+ prompt = "#{prompt} (#{choices.keys.join(', ')})"
198
+
199
+ loop do
200
+ # print the choices
201
+ choices.each do |choice,value|
202
+ stdout.puts " #{choice}) #{value}"
203
+ end
204
+ stdout.puts
205
+
206
+ # read the choice
207
+ choice = ask(prompt,**kwargs)
208
+
209
+ if choices.has_key?(choice)
210
+ # if a valid choice is given, return the value
211
+ return choices[choice]
212
+ else
213
+ stderr.puts "Invalid selection: #{choice}"
214
+ end
215
+ end
216
+ end
217
+
218
+ #
219
+ # Asks the user for secret input.
220
+ #
221
+ # @param [String] prompt
222
+ # The prompt that will be printed before reading input.
223
+ #
224
+ # @param [Boolean] required
225
+ # Requires non-empty input.
226
+ #
227
+ # @return [String]
228
+ # The user input.
229
+ #
230
+ # @example
231
+ # ask_secret("Password")
232
+ # # Password:
233
+ # # => "s3cr3t"
234
+ #
235
+ # @api public
236
+ #
237
+ def ask_secret(prompt, required: true)
238
+ if stdin.respond_to?(:noecho)
239
+ stdin.noecho do
240
+ ask(prompt, required: required)
241
+ end
242
+ else
243
+ ask(prompt, required: required)
244
+ end
245
+ end
246
+
247
+ end
248
+ end
@@ -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
@@ -84,6 +89,8 @@ module CommandKit
84
89
  #
85
90
  # @note `argv` is splatted into {#run}.
86
91
  #
92
+ # @api public
93
+ #
87
94
  def main(argv=[])
88
95
  run(*argv)
89
96
  return 0
@@ -99,6 +106,8 @@ module CommandKit
99
106
  #
100
107
  # @abstract
101
108
  #
109
+ # @api public
110
+ #
102
111
  def run(*args)
103
112
  end
104
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#readme"
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,6 +11,8 @@ module CommandKit
11
11
  #
12
12
  # Represents a defined option.
13
13
  #
14
+ # @api private
15
+ #
14
16
  class Option
15
17
 
16
18
  # The option's name.
@@ -33,11 +35,6 @@ module CommandKit
33
35
  # @return [OptionValue, nil]
34
36
  attr_reader :value
35
37
 
36
- # The option's description.
37
- #
38
- # @return [String]
39
- attr_reader :desc
40
-
41
38
  # The optional block that will receive the parsed option value.
42
39
  #
43
40
  # @return [Proc, nil]
@@ -126,7 +123,7 @@ module CommandKit
126
123
  end
127
124
 
128
125
  #
129
- # The separator characer between the option and option value.
126
+ # The separator character between the option and option value.
130
127
  #
131
128
  # @return ['=', ' ', nil]
132
129
  #