command_kit 0.1.0.pre2 → 0.2.1

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 (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
  #