command_kit 0.5.5 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a99a41f5bf2442e5523f9f7019dcd6b916f1b645786eefa9b0fb2a84aadd28c
4
- data.tar.gz: 3fb73eaa7b3a5730baafd0d0bd02cc567e82020acc96dec6a87bf726666f4250
3
+ metadata.gz: 5bf3011e0c065be0e6c7fd1da97f0c0de87b80674a29e782c339b63b15384992
4
+ data.tar.gz: bcad8e0d7ad0113bee65d2c5eda383f300b9764e3a0b53d90752e551299be0d0
5
5
  SHA512:
6
- metadata.gz: 44673dd7cb91fe176210e319c4055010c4e81af5dabf7eb420b202c1d42637e4844a1f5c99e0d81eeb52d32bfe9221de843b86c8d1f7fc91cc4aece561b46402
7
- data.tar.gz: 2470a9c111f829fc858c03f8b9be2726028d59da449bd68f778f7d87dc3f6bd93051ad4339ea896fa15f81dbcba3fe06af0b3b1b72f4705108a6cbab5157cba1
6
+ metadata.gz: 9c13184c83e668c3afd46ad1ad28a7d78655f1e8602831120ead6f39f2f35f4f6ba08151339e16485ef9111690b7975af4cb6a219787ec3530fb0a822cd2c952
7
+ data.tar.gz: ba6caee506bb4085cab8a6fe8a389006e0423643a1ee35e1f0a26a2ec0e6fc1f5f53a982f3a8247fefebdf2e6fbf9a3c05afc5d78a1209f8921e1566643e30b8
data/.rubocop.yml CHANGED
@@ -161,3 +161,6 @@ Naming/HeredocDelimiterNaming: { Enabled: false }
161
161
 
162
162
  # I prefer to use explicit parenthesis for compound logical statements
163
163
  Style/RedundantParentheses: { Enabled: false }
164
+
165
+ # I prefer to call `super()` with explicit arguments
166
+ Style/SuperArguments: { Enabled: false }
data/ChangeLog.md CHANGED
@@ -1,3 +1,20 @@
1
+ ### 0.6.0 / 2024-06-19
2
+
3
+ * Added {CommandKit::Interactive#ask_multiline}.
4
+ * Added {CommandKit::Open}.
5
+ * Added {CommandKit::Options::VerboseLevel}.
6
+
7
+ ### 0.5.6 / 2024-06-19
8
+
9
+ #### CommandKit::Inflector
10
+
11
+ * Fixed {CommandKit::Inflector.camelize} to convert `foo-1234-5678` to
12
+ `Foo_1234_5678`.
13
+
14
+ #### CommandKit::Printing::Indent
15
+
16
+ * Micro-optimization to {CommandKit::Printing::Indent#puts}.
17
+
1
18
  ### 0.5.5 / 2024-04-08
2
19
 
3
20
  #### CommandKit::Interactive
data/README.md CHANGED
@@ -229,6 +229,7 @@ end
229
229
  * [CommandKit::Help::Man](https://rubydoc.info/gems/command_kit/CommandKit/Help/Man)
230
230
  * [CommandKit::Interactive](https://rubydoc.info/gems/command_kit/CommandKit/Interactive)
231
231
  * [CommandKit::Main](https://rubydoc.info/gems/command_kit/CommandKit/Main)
232
+ * [CommandKit::Open](https://rubydoc.info/gems/command_kit/CommandKit/Open)
232
233
  * [CommandKit::Options](https://rubydoc.info/gems/command_kit/CommandKit/Options)
233
234
  * [CommandKit::Options::Quiet](https://rubydoc.info/gems/command_kit/CommandKit/Options/Quiet)
234
235
  * [CommandKit::Options::Verbose](https://rubydoc.info/gems/command_kit/CommandKit/Options/Verbose)
@@ -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/interactive'
6
+
7
+ class InteractiveCmd < CommandKit::Command
8
+
9
+ include CommandKit::Interactive
10
+
11
+ description 'Demonstrates interactive prompt input'
12
+
13
+ def run
14
+ you_entered = ->(result) {
15
+ puts "You entered: #{result.inspect}"
16
+ puts
17
+ }
18
+
19
+ you_entered[ask("Single-line input")]
20
+ you_entered[ask_secret("Secret input")]
21
+ you_entered[ask_yes_or_no("Yes or no prompt")]
22
+ you_entered[ask_multiple_choice("Multiple choice", %w[Red Green Blue])]
23
+ you_entered[ask_multiline('Multi-line comment 1')]
24
+ end
25
+
26
+ end
27
+
28
+ if __FILE__ == $0
29
+ InteractiveCmd.start
30
+ end
@@ -101,6 +101,8 @@ module CommandKit
101
101
  if (word = scanner.scan(/[A-Za-z\d]+/))
102
102
  word.capitalize!
103
103
  new_string << word
104
+ elsif (numbers = scanner.scan(/[_-]\d+/))
105
+ new_string << "_#{numbers[1..]}"
104
106
  elsif scanner.scan(/[_-]+/)
105
107
  # skip
106
108
  elsif scanner.scan(%r{/})
@@ -34,6 +34,15 @@ module CommandKit
34
34
  # #
35
35
  # # => "Lime"
36
36
  #
37
+ # ### Prompt for multi-line input
38
+ #
39
+ # ask_multiline('Comment')
40
+ # # Comment (Press Ctrl^D to exit):
41
+ # # foo bar
42
+ # # baz qux
43
+ # # Ctrl^D
44
+ # # => "foo bar\nbaz qux\n"
45
+ #
37
46
  module Interactive
38
47
  include Stdio
39
48
 
@@ -244,5 +253,83 @@ module CommandKit
244
253
  end
245
254
  end
246
255
 
256
+ #
257
+ # Asks the user for multi-line text input.
258
+ #
259
+ # @param [String] prompt
260
+ # The prompt that will be printed before reading input.
261
+ #
262
+ # @param [String, nil] help
263
+ # Optional help instructions on how to exit from reading.
264
+ #
265
+ # @param [String, nil] default
266
+ # The default value to return if no input is given.
267
+ #
268
+ # @param [Boolean] required
269
+ # Requires non-empty input.
270
+ #
271
+ # @param [:double_newline, :ctrl_d] terminator
272
+ # Indicates how the input should be terminated.
273
+ # Defaults to `:ctrl_d` which indicates `Ctrl^D`.
274
+ #
275
+ # @return [String]
276
+ # The user input.
277
+ #
278
+ # @example
279
+ # ask_multiline('Comment')
280
+ # # Comment (Press Ctrl^D to exit):
281
+ # # foo bar
282
+ # # baz qux
283
+ # # Ctrl^D
284
+ # # => "foo bar\nbaz qux\n"
285
+ #
286
+ # @example Terminate input on a double newline:
287
+ # ask_multiline('Comment', terminator: :double_newline)
288
+ # # Comment (Enter two empty lines to exit):
289
+ # # foo bar
290
+ # # baz qux
291
+ # #
292
+ # # => "foo bar\nbaz qux\n"
293
+ #
294
+ # @api public
295
+ #
296
+ # @since 0.6.0
297
+ #
298
+ def ask_multiline(prompt, help: nil,
299
+ default: nil,
300
+ required: false,
301
+ terminator: :ctrl_d)
302
+ case terminator
303
+ when :ctrl_d
304
+ eos = nil
305
+ help ||= 'Press Ctrl^D to exit'
306
+ when :double_newline
307
+ eos = "#{$/}#{$/}"
308
+ help ||= 'Press Enter twice to exit'
309
+ else
310
+ raise(ArgumentError,"invalid terminator: #{terminator.inspect}")
311
+ end
312
+
313
+ prompt = "#{prompt.chomp} (#{help})"
314
+ prompt << " [#{default}]" if default
315
+ prompt << ": "
316
+
317
+ loop do
318
+ stdout.puts(prompt)
319
+
320
+ value = stdin.gets(eos)
321
+ value ||= '' # convert nil values (ctrl^D) to an empty String
322
+
323
+ if value.empty?
324
+ if required
325
+ next
326
+ else
327
+ return (default || value)
328
+ end
329
+ else
330
+ return value
331
+ end
332
+ end
333
+ end
247
334
  end
248
335
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'stdio'
4
+ require_relative 'printing'
5
+
6
+ module CommandKit
7
+ #
8
+ # Adds helper methods for opening files.
9
+ #
10
+ # ## Features
11
+ #
12
+ # * Prints `No such file or directory` error if the given file does not exist.
13
+ # * Handles `-` file paths which indicate input should be read from STDIN or
14
+ # output written to STDOUT.
15
+ #
16
+ # ## Examples
17
+ #
18
+ # include CommandKit::Open
19
+ #
20
+ # def run(path)
21
+ # open(path) do |file|
22
+ # # ...
23
+ # end
24
+ # end
25
+ #
26
+ # @since 0.6.0
27
+ #
28
+ module Open
29
+ include Stdio
30
+ include Printing
31
+
32
+ #
33
+ # Opens a file for reading or writing.
34
+ #
35
+ # @param [String] path
36
+ # The path of the given file. May be `"-"` to indicate that input should
37
+ # be read from STDINT or output written to STDOUT.
38
+ #
39
+ # @param [String] mode
40
+ # The mode to open the file with.
41
+ #
42
+ # @yield [file]
43
+ # * If the given path is `"-"`.
44
+ # * and the given mode contains `w` or `a`, then {#stdout} will be
45
+ # yielded.
46
+ # * and no mode is given or if the mode contains `r`, then {#stdin} will
47
+ # be yielded.
48
+ # * Otherwise, the newly opened file.
49
+ #
50
+ # @yieldparam [File, IO] file
51
+ # The newly opened file, or {#stdin} / {#stdout} if the given path is
52
+ # `"-"`.
53
+ #
54
+ # @return [File, IO, Object]
55
+ # * If no block is given, the newly opened file, or {#stdin} / {#stdout}
56
+ # if the given path is `"-"`, will be returned.
57
+ # * If a block was given, then the return value of the block will be
58
+ # returned.
59
+ #
60
+ # @example Opening a file for reading:
61
+ # open(path)
62
+ # # => #<File:...>
63
+ #
64
+ # @example Temporarily opening a file for reading:
65
+ # open(path) do |file|
66
+ # # ...
67
+ # end
68
+ #
69
+ # @example Opening a file for writing:
70
+ # open(path,'w')
71
+ # # => #<File:...>
72
+ #
73
+ # @example Temporarily opening a file for writing:
74
+ # open(path,'w') do |output|
75
+ # output
76
+ # end
77
+ #
78
+ # @api public
79
+ #
80
+ # @since 0.6.0
81
+ #
82
+ def open(path,mode='r',&block)
83
+ if path == '-'
84
+ io = case mode
85
+ when /[wa]/ then stdout
86
+ else stdin
87
+ end
88
+
89
+ if block_given?
90
+ return yield(io)
91
+ else
92
+ return io
93
+ end
94
+ end
95
+
96
+ begin
97
+ File.open(path,mode,&block)
98
+ rescue Errno::ENOENT
99
+ print_error "No such file or directory: #{path}"
100
+ exit(1)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../options'
4
+
5
+ module CommandKit
6
+ module Options
7
+ #
8
+ # Defines a `-v`,`--verbose` option that can be specified multiple times to
9
+ # increase the verbosity level.
10
+ #
11
+ # ## Examples
12
+ #
13
+ # include CommandKit::Options::VerboseLevel
14
+ #
15
+ # def run(*argv)
16
+ # # ...
17
+ # case verbose
18
+ # when 1
19
+ # puts "verbose output"
20
+ # when 2
21
+ # puts " extra verbose output"
22
+ # end
23
+ # # ...
24
+ # end
25
+ #
26
+ # @since 0.6.0
27
+ #
28
+ module VerboseLevel
29
+ include Options
30
+
31
+ #
32
+ # @api private
33
+ #
34
+ module ModuleMethods
35
+ #
36
+ # Defines a `-v, --verbose` option or extends {ModuleMethods}, depending
37
+ # on whether {Options::Verbose} is being included into a class or a
38
+ # module.
39
+ #
40
+ # @param [Class, Module] context
41
+ # The class or module including {Verbose}.
42
+ #
43
+ def included(context)
44
+ super(context)
45
+
46
+ if context.class == Module
47
+ context.extend ModuleMethods
48
+ else
49
+ context.option :verbose, short: '-v', desc: 'Increases the verbosity level' do
50
+ @verbose += 1
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ extend ModuleMethods
57
+
58
+ # The verbosity level.
59
+ #
60
+ # @return [Integer]
61
+ attr_reader :verbose
62
+
63
+ #
64
+ # Initializes the command and sets {#verbose} to 0.
65
+ #
66
+ # @param [Hash{Symbol => Object}] kwargs
67
+ # Additional keyword arguments.
68
+ #
69
+ def initialize(**kwargs)
70
+ super(**kwargs)
71
+
72
+ @verbose = 0
73
+ end
74
+
75
+ #
76
+ # Determines if verbose mode is enabled.
77
+ #
78
+ # @return [Boolean]
79
+ #
80
+ # @api public
81
+ #
82
+ def verbose?
83
+ @verbose > 0
84
+ end
85
+ end
86
+ end
87
+ end
@@ -26,6 +26,7 @@ module CommandKit
26
26
  #
27
27
  def initialize(**kwargs)
28
28
  @indent = 0
29
+ @indent_padding = String.new
29
30
 
30
31
  super(**kwargs)
31
32
  end
@@ -71,8 +72,10 @@ module CommandKit
71
72
 
72
73
  begin
73
74
  @indent += n
75
+ @indent_padding << (' ' * n)
74
76
  yield
75
77
  ensure
78
+ @indent_padding.slice!(original_indent,n)
76
79
  @indent = original_indent
77
80
  end
78
81
  else
@@ -90,9 +93,7 @@ module CommandKit
90
93
  #
91
94
  def puts(*lines)
92
95
  if (@indent > 0 && !lines.empty?)
93
- padding = " " * @indent
94
-
95
- super(*lines.map { |line| "#{padding}#{line}" })
96
+ super(*lines.map { |line| "#{@indent_padding}#{line}" })
96
97
  else
97
98
  super(*lines)
98
99
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module CommandKit
4
4
  # command_kit version
5
- VERSION = "0.5.5"
5
+ VERSION = "0.6.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Postmodern
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-09 00:00:00.000000000 Z
11
+ date: 2024-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -48,6 +48,7 @@ files:
48
48
  - command_kit.gemspec
49
49
  - examples/colors.rb
50
50
  - examples/command.rb
51
+ - examples/interactive.rb
51
52
  - examples/pager.rb
52
53
  - examples/printing/tables.rb
53
54
  - examples/subcommands/cli.rb
@@ -90,6 +91,7 @@ files:
90
91
  - lib/command_kit/interactive.rb
91
92
  - lib/command_kit/main.rb
92
93
  - lib/command_kit/man.rb
94
+ - lib/command_kit/open.rb
93
95
  - lib/command_kit/open_app.rb
94
96
  - lib/command_kit/options.rb
95
97
  - lib/command_kit/options/option.rb
@@ -97,6 +99,7 @@ files:
97
99
  - lib/command_kit/options/parser.rb
98
100
  - lib/command_kit/options/quiet.rb
99
101
  - lib/command_kit/options/verbose.rb
102
+ - lib/command_kit/options/verbose_level.rb
100
103
  - lib/command_kit/options/version.rb
101
104
  - lib/command_kit/os.rb
102
105
  - lib/command_kit/os/linux.rb
@@ -144,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
147
  - !ruby/object:Gem::Version
145
148
  version: '0'
146
149
  requirements: []
147
- rubygems_version: 3.4.10
150
+ rubygems_version: 3.5.9
148
151
  signing_key:
149
152
  specification_version: 4
150
153
  summary: An all-in-one modular Ruby CLI toolkit