command_kit 0.2.2 → 0.4.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +4 -5
  3. data/.rubocop.yml +14 -1
  4. data/ChangeLog.md +82 -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/gemspec.yml +3 -3
  11. data/lib/command_kit/arguments/argument.rb +2 -2
  12. data/lib/command_kit/arguments.rb +27 -2
  13. data/lib/command_kit/bug_report.rb +105 -0
  14. data/lib/command_kit/colors.rb +488 -15
  15. data/lib/command_kit/command.rb +1 -2
  16. data/lib/command_kit/edit.rb +54 -0
  17. data/lib/command_kit/env.rb +1 -1
  18. data/lib/command_kit/file_utils.rb +46 -0
  19. data/lib/command_kit/options/option.rb +45 -22
  20. data/lib/command_kit/options/option_value.rb +2 -2
  21. data/lib/command_kit/options/parser.rb +1 -4
  22. data/lib/command_kit/options/quiet.rb +1 -1
  23. data/lib/command_kit/options/verbose.rb +2 -2
  24. data/lib/command_kit/options/version.rb +10 -0
  25. data/lib/command_kit/options.rb +89 -14
  26. data/lib/command_kit/os.rb +1 -1
  27. data/lib/command_kit/printing/fields.rb +56 -0
  28. data/lib/command_kit/printing/indent.rb +1 -1
  29. data/lib/command_kit/printing/lists.rb +91 -0
  30. data/lib/command_kit/printing/tables/border_style.rb +169 -0
  31. data/lib/command_kit/printing/tables/cell_builder.rb +93 -0
  32. data/lib/command_kit/printing/tables/row_builder.rb +111 -0
  33. data/lib/command_kit/printing/tables/style.rb +198 -0
  34. data/lib/command_kit/printing/tables/table_builder.rb +145 -0
  35. data/lib/command_kit/printing/tables/table_formatter.rb +254 -0
  36. data/lib/command_kit/printing/tables.rb +208 -0
  37. data/lib/command_kit/program_name.rb +9 -0
  38. data/lib/command_kit/stdio.rb +5 -1
  39. data/lib/command_kit/version.rb +1 -1
  40. data/spec/arguments_spec.rb +33 -0
  41. data/spec/bug_report_spec.rb +266 -0
  42. data/spec/colors_spec.rb +232 -195
  43. data/spec/command_name_spec.rb +1 -1
  44. data/spec/command_spec.rb +2 -2
  45. data/spec/edit_spec.rb +72 -0
  46. data/spec/file_utils_spec.rb +59 -0
  47. data/spec/fixtures/template.erb +5 -0
  48. data/spec/options/option_spec.rb +48 -2
  49. data/spec/options/parser_spec.rb +0 -10
  50. data/spec/options/quiet_spec.rb +51 -0
  51. data/spec/options/verbose_spec.rb +51 -0
  52. data/spec/options/version_spec.rb +146 -0
  53. data/spec/options_spec.rb +46 -0
  54. data/spec/pager_spec.rb +1 -1
  55. data/spec/printing/fields_spec.rb +167 -0
  56. data/spec/printing/lists_spec.rb +99 -0
  57. data/spec/printing/tables/border_style.rb +43 -0
  58. data/spec/printing/tables/cell_builer_spec.rb +135 -0
  59. data/spec/printing/tables/row_builder_spec.rb +165 -0
  60. data/spec/printing/tables/style_spec.rb +377 -0
  61. data/spec/printing/tables/table_builder_spec.rb +252 -0
  62. data/spec/printing/tables/table_formatter_spec.rb +1180 -0
  63. data/spec/printing/tables_spec.rb +1069 -0
  64. data/spec/program_name_spec.rb +8 -0
  65. metadata +36 -7
@@ -0,0 +1,46 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+
4
+ module CommandKit
5
+ #
6
+ # File manipulation related methods.
7
+ #
8
+ # @since 0.3.0
9
+ #
10
+ # @api public
11
+ #
12
+ module FileUtils
13
+ include ::FileUtils
14
+
15
+ #
16
+ # Renders an erb file and optionally writes it out to a destination file.
17
+ #
18
+ # @param [String] source
19
+ # The path to the erb template file.
20
+ #
21
+ # @param [String, nil] dest
22
+ # The path to the destination file.
23
+ #
24
+ # @return [String, nil]
25
+ # If no destination path argument is given, the rendered erb template
26
+ # String will be returned.
27
+ #
28
+ # @example Rendering a ERB template and saving it's output:
29
+ # erb File.join(template_dir,'README.md.erb'), 'README.md'
30
+ #
31
+ # @example Rendering a ERB template and capturing it's output:
32
+ # output = erb(File.join(template_dir,'_partial.erb'))
33
+ #
34
+ def erb(source,dest=nil)
35
+ erb = ERB.new(File.read(source), trim_mode: '-')
36
+ result = erb.result(binding)
37
+
38
+ if dest
39
+ File.write(dest,result)
40
+ return
41
+ else
42
+ return result
43
+ end
44
+ end
45
+ end
46
+ end
@@ -40,6 +40,13 @@ module CommandKit
40
40
  # @return [Proc, nil]
41
41
  attr_reader :block
42
42
 
43
+ # The optional category to group the option under.
44
+ #
45
+ # @return [String, nil]
46
+ #
47
+ # @since 0.3.0
48
+ attr_reader :category
49
+
43
50
  #
44
51
  # Initializes the option.
45
52
  #
@@ -65,9 +72,12 @@ module CommandKit
65
72
  # @option value [String, nil] usage
66
73
  # The usage string for the option's value.
67
74
  #
68
- # @param [String] desc
75
+ # @param [String, Array<String>] desc
69
76
  # The description for the option.
70
77
  #
78
+ # @param [String, nil] category
79
+ # The optional category to group the option under.
80
+ #
71
81
  # @yield [(value)]
72
82
  # If a block is given, it will be called when the option is parsed.
73
83
  #
@@ -78,25 +88,27 @@ module CommandKit
78
88
  # The `value` keyword argument was not a `Hash`, `true`, `false`, or
79
89
  # `nil`.
80
90
  #
81
- def initialize(name, short: nil,
82
- long: self.class.default_long_opt(name),
83
- equals: false,
84
- value: nil,
85
- desc: ,
91
+ def initialize(name, short: nil,
92
+ long: self.class.default_long_opt(name),
93
+ equals: false,
94
+ value: nil,
95
+ desc: ,
96
+ category: nil,
86
97
  &block)
87
- @name = name
88
- @short = short
89
- @long = long
90
- @equals = equals
91
- @value = case value
92
- when Hash then OptionValue.new(**value)
93
- when true then OptionValue.new()
94
- when false, nil then nil
95
- else
96
- raise(TypeError,"value: keyword must be Hash, true, false, or nil")
97
- end
98
- @desc = desc
99
- @block = block
98
+ @name = name
99
+ @short = short
100
+ @long = long
101
+ @equals = equals
102
+ @value = case value
103
+ when Hash then OptionValue.new(**value)
104
+ when true then OptionValue.new()
105
+ when false, nil then nil
106
+ else
107
+ raise(TypeError,"value: keyword must be Hash, true, false, or nil")
108
+ end
109
+ @desc = desc
110
+ @category = category
111
+ @block = block
100
112
  end
101
113
 
102
114
  #
@@ -142,7 +154,11 @@ module CommandKit
142
154
  # The usage strings.
143
155
  #
144
156
  def usage
145
- [*@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
146
162
  end
147
163
 
148
164
  #
@@ -179,7 +195,7 @@ module CommandKit
179
195
  #
180
196
  # The option description.
181
197
  #
182
- # @return [String]
198
+ # @return [String, Array<String>]
183
199
  #
184
200
  # @note
185
201
  # If {#default_value} returns a value, the description will contain the
@@ -187,7 +203,14 @@ module CommandKit
187
203
  #
188
204
  def desc
189
205
  if (value = default_value)
190
- "#{@desc} (Default: #{value})"
206
+ default_text = "(Default: #{value})"
207
+
208
+ case @desc
209
+ when Array
210
+ @desc + [default_text]
211
+ else
212
+ "#{@desc} #{default_text}"
213
+ end
191
214
  else
192
215
  @desc
193
216
  end
@@ -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
@@ -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)
@@ -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
@@ -23,6 +23,25 @@ module CommandKit
23
23
  # @bar = arg.split(':')
24
24
  # end
25
25
  #
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
+ #
26
45
  # ### initialize and using instance variables
27
46
  #
28
47
  # option :number, value: {type: Integer},
@@ -111,9 +130,12 @@ module CommandKit
111
130
  # @option value [String, nil] usage
112
131
  # The usage string for the option's value.
113
132
  #
114
- # @option kwargs [String] desc
133
+ # @option kwargs [String, Array<String>] desc
115
134
  # The description for the option.
116
135
  #
136
+ # @option kwargs [String, nil] category
137
+ # The optional category to group the option under.
138
+ #
117
139
  # @yield [(value)]
118
140
  # If a block is given, it will be passed the parsed option value.
119
141
  #
@@ -129,6 +151,19 @@ module CommandKit
129
151
  # @example Define an option:
130
152
  # option :foo, desc: "Foo option"
131
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
+ #
132
167
  # @example With a custom short option:
133
168
  # option :foo, short: '-f',
134
169
  # desc: "Foo option"
@@ -212,19 +247,7 @@ module CommandKit
212
247
 
213
248
  super(**kwargs)
214
249
 
215
- self.class.options.each_value do |option|
216
- default_value = option.default_value
217
-
218
- @options[option.name] = default_value unless default_value.nil?
219
-
220
- option_parser.on(*option.usage,option.type,option.desc) do |arg,*captures|
221
- @options[option.name] = arg
222
-
223
- if option.block
224
- instance_exec(*arg,*captures,&option.block)
225
- end
226
- end
227
- end
250
+ define_option_categories()
228
251
  end
229
252
 
230
253
  #
@@ -237,5 +260,57 @@ module CommandKit
237
260
  help_options
238
261
  help_arguments
239
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)
312
+ end
313
+ end
314
+ end
240
315
  end
241
316
  end
@@ -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?
@@ -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