command_kit 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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