command_kit 0.3.0 → 0.4.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +6 -6
  3. data/.rubocop.yml +16 -0
  4. data/ChangeLog.md +30 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +18 -9
  8. data/command_kit.gemspec +0 -1
  9. data/examples/printing/tables.rb +141 -0
  10. data/examples/subcommands/cli/config/get.rb +47 -0
  11. data/examples/subcommands/cli/config/set.rb +44 -0
  12. data/examples/subcommands/cli/config.rb +23 -0
  13. data/examples/subcommands/cli/list.rb +35 -0
  14. data/examples/subcommands/cli/update.rb +47 -0
  15. data/examples/subcommands/cli.rb +55 -0
  16. data/gemspec.yml +3 -3
  17. data/lib/command_kit/bug_report.rb +105 -0
  18. data/lib/command_kit/colors.rb +4 -4
  19. data/lib/command_kit/edit.rb +54 -0
  20. data/lib/command_kit/env/home.rb +1 -1
  21. data/lib/command_kit/env.rb +1 -1
  22. data/lib/command_kit/inflector.rb +1 -1
  23. data/lib/command_kit/options/option.rb +5 -1
  24. data/lib/command_kit/options/option_value.rb +2 -2
  25. data/lib/command_kit/options/parser.rb +2 -2
  26. data/lib/command_kit/options/quiet.rb +1 -1
  27. data/lib/command_kit/options/verbose.rb +2 -2
  28. data/lib/command_kit/options/version.rb +10 -0
  29. data/lib/command_kit/options.rb +1 -1
  30. data/lib/command_kit/os/linux.rb +1 -1
  31. data/lib/command_kit/os.rb +2 -2
  32. data/lib/command_kit/printing/fields.rb +56 -0
  33. data/lib/command_kit/printing/indent.rb +1 -1
  34. data/lib/command_kit/printing/lists.rb +91 -0
  35. data/lib/command_kit/printing/tables/border_style.rb +169 -0
  36. data/lib/command_kit/printing/tables/cell_builder.rb +93 -0
  37. data/lib/command_kit/printing/tables/row_builder.rb +111 -0
  38. data/lib/command_kit/printing/tables/style.rb +198 -0
  39. data/lib/command_kit/printing/tables/table_builder.rb +145 -0
  40. data/lib/command_kit/printing/tables/table_formatter.rb +254 -0
  41. data/lib/command_kit/printing/tables.rb +208 -0
  42. data/lib/command_kit/stdio.rb +5 -1
  43. data/lib/command_kit/version.rb +1 -1
  44. data/lib/command_kit/xdg.rb +1 -1
  45. data/spec/bug_report_spec.rb +266 -0
  46. data/spec/colors_spec.rb +6 -0
  47. data/spec/command_name_spec.rb +1 -1
  48. data/spec/commands_spec.rb +26 -0
  49. data/spec/edit_spec.rb +72 -0
  50. data/spec/options/option_spec.rb +12 -2
  51. data/spec/options/parser_spec.rb +19 -0
  52. data/spec/options/quiet_spec.rb +51 -0
  53. data/spec/options/verbose_spec.rb +51 -0
  54. data/spec/options/version_spec.rb +146 -0
  55. data/spec/pager_spec.rb +1 -1
  56. data/spec/printing/fields_spec.rb +167 -0
  57. data/spec/printing/lists_spec.rb +99 -0
  58. data/spec/printing/tables/border_style.rb +43 -0
  59. data/spec/printing/tables/cell_builer_spec.rb +135 -0
  60. data/spec/printing/tables/row_builder_spec.rb +165 -0
  61. data/spec/printing/tables/style_spec.rb +377 -0
  62. data/spec/printing/tables/table_builder_spec.rb +252 -0
  63. data/spec/printing/tables/table_formatter_spec.rb +1190 -0
  64. data/spec/printing/tables_spec.rb +1069 -0
  65. metadata +39 -7
@@ -0,0 +1,105 @@
1
+ require 'command_kit/exception_handler'
2
+ require 'command_kit/printing'
3
+
4
+ module CommandKit
5
+ #
6
+ # Adds an exception handler to print a bug report when an unhandled exception
7
+ # is raised by `run`.
8
+ #
9
+ # @since 0.4.0
10
+ #
11
+ module BugReport
12
+ include ExceptionHandler
13
+ include Printing
14
+
15
+ #
16
+ # @api private
17
+ #
18
+ module ModuleMethods
19
+ #
20
+ # Extends {ClassMethods} or {ModuleMethods}, depending on whether
21
+ # {Options} is being included into a class or a module.
22
+ #
23
+ # @param [Class, Module] context
24
+ # The class or module which is including {Options}.
25
+ #
26
+ def included(context)
27
+ super(context)
28
+
29
+ if context.class == Module
30
+ context.extend ModuleMethods
31
+ else
32
+ context.extend ClassMethods
33
+ end
34
+ end
35
+ end
36
+
37
+ extend ModuleMethods
38
+
39
+ #
40
+ # Defines class-level methods.
41
+ #
42
+ module ClassMethods
43
+ #
44
+ # Gets or sets the bug report URL.
45
+ #
46
+ # @param [String, nil] new_url
47
+ # The new bug report URL.
48
+ #
49
+ # @return [String, nil]
50
+ # The bug report URL.
51
+ #
52
+ # @example
53
+ # bug_report_url 'https://github.com/user/repo/issues/new'
54
+ #
55
+ def bug_report_url(new_url=nil)
56
+ if new_url
57
+ @bug_report_url = new_url
58
+ else
59
+ @bug_report_url || if superclass.kind_of?(ClassMethods)
60
+ superclass.bug_report_url
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ #
67
+ # The bug report URL.
68
+ #
69
+ # @return [String, nil]
70
+ #
71
+ def bug_report_url
72
+ self.class.bug_report_url
73
+ end
74
+
75
+ #
76
+ # Overrides {#on_exception} to print a bug report for unhandled exceptions
77
+ # and then exit with `-1`.
78
+ #
79
+ # @param [Exception] error
80
+ # The unhandled exception.
81
+ #
82
+ def on_exception(error)
83
+ print_bug_report(error)
84
+ exit(-1)
85
+ end
86
+
87
+ #
88
+ # Prints a bug report for the unhandled exception.
89
+ #
90
+ # @param [Exception] error
91
+ # The unhandled exception.
92
+ #
93
+ def print_bug_report(error)
94
+ url = bug_report_url
95
+
96
+ stderr.puts
97
+ stderr.puts "Oops! Looks like you have found a bug. Please report it!"
98
+ stderr.puts url if url
99
+ stderr.puts
100
+ stderr.puts '```'
101
+ print_exception(error)
102
+ stderr.puts '```'
103
+ end
104
+ end
105
+ end
@@ -998,14 +998,14 @@ module CommandKit
998
998
  # @return [Boolean]
999
999
  #
1000
1000
  # @note
1001
- # When the env variable `TERM` is set to `dumb`, it will disable color
1002
- # output. Color output will also be disabled if the given stream is not
1003
- # a TTY.
1001
+ # When the env variable `TERM` is set to `dumb` or when the `NO_COLOR`
1002
+ # env variable is set, it will disable color output. Color output will
1003
+ # also be disabled if the given stream is not a TTY.
1004
1004
  #
1005
1005
  # @api public
1006
1006
  #
1007
1007
  def ansi?(stream=stdout)
1008
- env['TERM'] != 'dumb' && stream.tty?
1008
+ env['TERM'] != 'dumb' && !env['NO_COLOR'] && stream.tty?
1009
1009
  end
1010
1010
 
1011
1011
  #
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_kit/env'
4
+
5
+ module CommandKit
6
+ #
7
+ # Allows invoking the `EDITOR` environment variable.
8
+ #
9
+ # ## Environment Variables
10
+ #
11
+ # * `EDITOR` - The preferred editor command.
12
+ #
13
+ # ## Example
14
+ #
15
+ # if options[:edit]
16
+ # edit CONFIG_FILE
17
+ # end
18
+ #
19
+ # @since 0.4.0
20
+ #
21
+ module Edit
22
+ include Env
23
+
24
+ #
25
+ # The `EDITOR` environment variable.
26
+ #
27
+ # @return [String]
28
+ # The `EDITOR` environment variable, or `"nano"` if `EDITOR` was not set.
29
+ #
30
+ # @api semipublic
31
+ #
32
+ def editor
33
+ env['EDITOR'] || 'nano'
34
+ end
35
+
36
+ #
37
+ # Invokes the preferred editor with the additional arguments.
38
+ #
39
+ # @param [Array] arguments
40
+ # The additional arguments to pass to the editor command.
41
+ #
42
+ # @return [Boolean, nil]
43
+ # Indicates whether the editor successfully launched and exited.
44
+ # If the {#editor} command was not installed, `nil` will be returned.
45
+ #
46
+ # @api public
47
+ #
48
+ def edit(*arguments)
49
+ if editor
50
+ system(editor,*arguments.map(&:to_s))
51
+ end
52
+ end
53
+ end
54
+ end
@@ -20,7 +20,7 @@ module CommandKit
20
20
  module ModuleMethods
21
21
  #
22
22
  # Extends {ClassMethods} or {ModuleMethods}, depending on whether
23
- # {Env::Home} is being included into a class or a module..
23
+ # {Env::Home} is being included into a class or a module.
24
24
  #
25
25
  # @param [Class, Module] context
26
26
  # The class or module which is including {Home}.
@@ -7,7 +7,7 @@ module CommandKit
7
7
  # class MyCmd
8
8
  # include CommandKit::Env
9
9
  #
10
- # def main
10
+ # def run
11
11
  # home = env['HOME']
12
12
  # # ...
13
13
  # end
@@ -103,7 +103,7 @@ module CommandKit
103
103
  new_string << word
104
104
  elsif scanner.scan(/[_-]+/)
105
105
  # skip
106
- elsif scanner.scan(/\//)
106
+ elsif scanner.scan(%r{/})
107
107
  new_string << '::'
108
108
  else
109
109
  raise(ArgumentError,"cannot convert string to CamelCase: #{scanner.string.inspect}")
@@ -154,7 +154,11 @@ module CommandKit
154
154
  # The usage strings.
155
155
  #
156
156
  def usage
157
- [*@short, "#{@long}#{separator}#{@value && @value.usage}"]
157
+ if equals? && (@value && @value.optional?)
158
+ [*@short, "#{@long}[#{separator}#{@value && @value.usage}]"]
159
+ else
160
+ [*@short, "#{@long}#{separator}#{@value && @value.usage}"]
161
+ end
158
162
  end
159
163
 
160
164
  #
@@ -19,7 +19,7 @@ module CommandKit
19
19
  class OptionValue < Arguments::ArgumentValue
20
20
 
21
21
  # Maps OptionParser types to USAGE strings.
22
- USAGES = {
22
+ DEFAULT_USAGES = {
23
23
  # NOTE: NilClass and Object are intentionally omitted
24
24
  Date => 'DATE',
25
25
  DateTime => 'DATE_TIME',
@@ -90,7 +90,7 @@ module CommandKit
90
90
  # The given type was not a Class, Hash, Array, or Regexp.
91
91
  #
92
92
  def self.default_usage(type)
93
- USAGES.fetch(type) do
93
+ DEFAULT_USAGES.fetch(type) do
94
94
  case type
95
95
  when Hash then type.keys.join('|')
96
96
  when Array then type.join('|')
@@ -22,7 +22,7 @@ module CommandKit
22
22
  # end
23
23
  # end
24
24
  #
25
- # def main(*argv)
25
+ # def run(*argv)
26
26
  # if @custom
27
27
  # puts "Custom mode enabled"
28
28
  # end
@@ -50,7 +50,7 @@ module CommandKit
50
50
 
51
51
  if context.class == Module
52
52
  context.extend ModuleMethods
53
- else
53
+ elsif context.usage.nil?
54
54
  context.usage '[options]'
55
55
  end
56
56
  end
@@ -9,7 +9,7 @@ module CommandKit
9
9
  #
10
10
  # include CommandKit::Options::Quiet
11
11
  #
12
- # def main(*argv)
12
+ # def run(*argv)
13
13
  # # ...
14
14
  # puts "verbose output" unless quiet?
15
15
  # # ...
@@ -7,9 +7,9 @@ module CommandKit
7
7
  #
8
8
  # ## Examples
9
9
  #
10
- # include Options::Verbose
10
+ # include CommandKit::Options::Verbose
11
11
  #
12
- # def main(*argv)
12
+ # def run(*argv)
13
13
  # # ...
14
14
  # puts "verbose output" if verbose?
15
15
  # # ...
@@ -5,6 +5,16 @@ module CommandKit
5
5
  #
6
6
  # Defines a version option.
7
7
  #
8
+ # ## Examples
9
+ #
10
+ # include CommandKit::Options::Version
11
+ #
12
+ # version '0.1.0'
13
+ #
14
+ # def run(*argv)
15
+ # # ...
16
+ # end
17
+ #
8
18
  module Version
9
19
  #
10
20
  # Includes {Options}, extends {Version::ClassMethods}, and defines a
@@ -247,7 +247,7 @@ module CommandKit
247
247
 
248
248
  super(**kwargs)
249
249
 
250
- define_option_categories
250
+ define_option_categories()
251
251
  end
252
252
 
253
253
  #
@@ -36,7 +36,7 @@ module CommandKit
36
36
  module ModuleMethods
37
37
  #
38
38
  # Extends {ClassMethods} or {ModuleMethods}, depending on whether
39
- # {OS} is being included into a class or a module..
39
+ # {OS} is being included into a class or a module.
40
40
  #
41
41
  # @param [Class, Module] context
42
42
  # The class or module which is including {OS}.
@@ -11,7 +11,7 @@ module CommandKit
11
11
  #
12
12
  # include CommandKit::OS
13
13
  #
14
- # def main(*argv)
14
+ # def run(*argv)
15
15
  # if linux?
16
16
  # # ...
17
17
  # elsif macos?
@@ -32,7 +32,7 @@ module CommandKit
32
32
  module ModuleMethods
33
33
  #
34
34
  # Extends {ClassMethods} or {ModuleMethods}, depending on whether
35
- # {OS} is being included into a class or a module..
35
+ # {OS} is being included into a class or a module.
36
36
  #
37
37
  # @param [Class, Module] context
38
38
  # The class or module which is including {OS}.
@@ -0,0 +1,56 @@
1
+ require 'command_kit/printing/indent'
2
+
3
+ module CommandKit
4
+ module Printing
5
+ #
6
+ # Supports printing aligned key/value fields.
7
+ #
8
+ # @since 0.4.0
9
+ #
10
+ module Fields
11
+ include Indent
12
+
13
+ #
14
+ # Prints a Hash as left-justified `:` separated fields.
15
+ #
16
+ # @param [Hash, Array<(Object, Object)>] fields
17
+ # The fields to print.
18
+ #
19
+ # @example
20
+ # print_fields('Name' => 'foo', 'Version' => '0.1.0')
21
+ # # Name: foo
22
+ # # Version: 0.1.0
23
+ #
24
+ # @api public
25
+ #
26
+ def print_fields(fields)
27
+ max_length = 0
28
+
29
+ fields = fields.map { |name,value|
30
+ name = name.to_s
31
+ value = value.to_s
32
+ max_length = name.length if name.length > max_length
33
+
34
+ [name, value]
35
+ }
36
+
37
+ fields.each do |name,value|
38
+ first_line, *rest = value.to_s.lines(chomp: true)
39
+
40
+ # print the first line with the header
41
+ header = "#{name}:".ljust(max_length + 1)
42
+ puts "#{header} #{first_line}"
43
+
44
+ # indent and print the rest of the lines
45
+ indent(max_length + 2) do
46
+ rest.each do |line|
47
+ puts line
48
+ end
49
+ end
50
+ end
51
+
52
+ return nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -7,7 +7,7 @@ module CommandKit
7
7
  #
8
8
  # include Printing::Indent
9
9
  #
10
- # def main
10
+ # def run
11
11
  # puts "regular output:"
12
12
  #
13
13
  # indent(4) do
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_kit/printing/indent'
4
+
5
+ module CommandKit
6
+ module Printing
7
+ #
8
+ # Methods for printing lists.
9
+ #
10
+ # ## Examples
11
+ #
12
+ # include Printing::Lists
13
+ #
14
+ # def main
15
+ # print_list %w[foo bar baz]
16
+ # # * foo
17
+ # # * bar
18
+ # # * baz
19
+ #
20
+ # list = ['item 1', 'item 2', ['sub-item 1', 'sub-item 2']]
21
+ # print_list(list)
22
+ # # * item 1
23
+ # # * item 2
24
+ # # * sub-item 1
25
+ # # * sub-item 2
26
+ #
27
+ # print_list %w[foo bar baz], bullet: '-'
28
+ # # - foo
29
+ # # - bar
30
+ # # - baz
31
+ # end
32
+ #
33
+ # @since 0.4.0
34
+ #
35
+ module Lists
36
+ include Indent
37
+
38
+ #
39
+ # Prints a bulleted list of items.
40
+ #
41
+ # @param [Array<#to_s, Array>] list
42
+ # The list of items to print.
43
+ #
44
+ # @param [String] bullet
45
+ # The bullet character to use for line item.
46
+ #
47
+ # @example
48
+ # print_list %w[foo bar baz]
49
+ # # * foo
50
+ # # * bar
51
+ # # * baz
52
+ #
53
+ # @example print a nested list:
54
+ # list = ['item 1', 'item 2', ['sub-item 1', 'sub-item 2']]
55
+ # print_list(list)
56
+ # # * item 1
57
+ # # * item 2
58
+ # # * sub-item 1
59
+ # # * sub-item 2
60
+ #
61
+ # @example with a custom bullet character:
62
+ # print_list %w[foo bar baz], bullet: '-'
63
+ # # - foo
64
+ # # - bar
65
+ # # - baz
66
+ #
67
+ # @since 0.4.0
68
+ #
69
+ def print_list(list, bullet: '*')
70
+ list.each do |item|
71
+ case item
72
+ when Array
73
+ indent { print_list(item, bullet: bullet) }
74
+ else
75
+ first_line, *rest = item.to_s.lines(chomp: true)
76
+
77
+ # print the bullet only on the first list
78
+ puts "#{bullet} #{first_line}"
79
+
80
+ # indent the remaining lines
81
+ indent(bullet.length + 1) do
82
+ rest.each do |line|
83
+ puts line
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,169 @@
1
+ module CommandKit
2
+ module Printing
3
+ module Tables
4
+ #
5
+ # Represents the table's border style.
6
+ #
7
+ # @api private
8
+ #
9
+ class BorderStyle
10
+
11
+ # The top-left-corner border character.
12
+ #
13
+ # @return [String]
14
+ attr_reader :top_left_corner
15
+
16
+ # The top-border character.
17
+ #
18
+ # @return [String]
19
+ attr_reader :top_border
20
+
21
+ # The top-joined-border character.
22
+ #
23
+ # @return [String]
24
+ attr_reader :top_joined_border
25
+
26
+ # The top-right-corner border character.
27
+ #
28
+ # @return [String]
29
+ attr_reader :top_right_corner
30
+
31
+ # The left-hand-side border character.
32
+ #
33
+ # @return [String]
34
+ attr_reader :left_border
35
+
36
+ # The left-hand-side-joined-border character.
37
+ #
38
+ # @return [String]
39
+ attr_reader :left_joined_border
40
+
41
+ # The horizontal-separator character.
42
+ #
43
+ # @return [String]
44
+ attr_reader :horizontal_separator
45
+
46
+ # The vertical-separator character.
47
+ #
48
+ # @return [String]
49
+ attr_reader :vertical_separator
50
+
51
+ # The inner-joined border character.
52
+ #
53
+ # @return [String]
54
+ attr_reader :inner_joined_border
55
+
56
+ # The right-hand-side border character.
57
+ #
58
+ # @return [String]
59
+ attr_reader :right_border
60
+
61
+ # The right-hand-side joined border character.
62
+ #
63
+ # @return [String]
64
+ attr_reader :right_joined_border
65
+
66
+ # The bottom border character.
67
+ #
68
+ # @return [String]
69
+ attr_reader :bottom_border
70
+
71
+ # The bottom-left-corner border character.
72
+ #
73
+ # @return [String]
74
+ attr_reader :bottom_left_corner
75
+
76
+ # The bottom-joined border character.
77
+ #
78
+ # @return [String]
79
+ attr_reader :bottom_joined_border
80
+
81
+ # The bottom-right-corner border character.
82
+ #
83
+ # @return [String]
84
+ attr_reader :bottom_right_corner
85
+
86
+ #
87
+ # Initializes the border style.
88
+ #
89
+ # @param [String] top_left_corner
90
+ # The top-left-corner border character.
91
+ #
92
+ # @param [String] top_border
93
+ # The top-border character.
94
+ #
95
+ # @param [String] top_joined_border
96
+ # The top-joined-border character.
97
+ #
98
+ # @param [String] top_right_corner
99
+ # The top-right-corner border character.
100
+ #
101
+ # @param [String] left_border
102
+ # The left-hand-side border character.
103
+ #
104
+ # @param [String] left_joined_border
105
+ # The left-hand-side-joined-border character.
106
+ #
107
+ # @param [String] horizontal_separator
108
+ # The horizontal-separator character.
109
+ #
110
+ # @param [String] vertical_separator
111
+ # The vertical-separator character.
112
+ #
113
+ # @param [String] inner_joined_border
114
+ # The inner-joined border character.
115
+ #
116
+ # @param [String] right_border
117
+ # The right-hand-side border character.
118
+ #
119
+ # @param [String] right_joined_border
120
+ # The right-hand-side joined border character.
121
+ #
122
+ # @param [String] bottom_border
123
+ # The bottom border character.
124
+ #
125
+ # @param [String] bottom_left_corner
126
+ # The bottom-left-corner border character.
127
+ #
128
+ # @param [String] bottom_joined_border
129
+ # The bottom-joined border character.
130
+ #
131
+ # @param [String] bottom_right_corner
132
+ # The bottom-right-corner border character.
133
+ #
134
+ def initialize(top_left_corner: ' ',
135
+ top_border: ' ',
136
+ top_joined_border: ' ',
137
+ top_right_corner: ' ',
138
+ left_border: ' ',
139
+ left_joined_border: ' ',
140
+ horizontal_separator: ' ',
141
+ vertical_separator: ' ',
142
+ inner_joined_border: ' ',
143
+ right_border: ' ',
144
+ right_joined_border: ' ',
145
+ bottom_border: ' ',
146
+ bottom_left_corner: ' ',
147
+ bottom_joined_border: ' ',
148
+ bottom_right_corner: ' ')
149
+ @top_left_corner = top_left_corner
150
+ @top_border = top_border
151
+ @top_joined_border = top_joined_border
152
+ @top_right_corner = top_right_corner
153
+ @left_border = left_border
154
+ @left_joined_border = left_joined_border
155
+ @horizontal_separator = horizontal_separator
156
+ @vertical_separator = vertical_separator
157
+ @inner_joined_border = inner_joined_border
158
+ @right_border = right_border
159
+ @right_joined_border = right_joined_border
160
+ @bottom_border = bottom_border
161
+ @bottom_left_corner = bottom_left_corner
162
+ @bottom_joined_border = bottom_joined_border
163
+ @bottom_right_corner = bottom_right_corner
164
+ end
165
+
166
+ end
167
+ end
168
+ end
169
+ end