hammer_cli 0.10.2 → 0.11.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
  SHA1:
3
- metadata.gz: 230906bc19a41759805ad05e51cee6244938cc5e
4
- data.tar.gz: 7e4389026de508ce212c3af34823bc1606b6851e
3
+ metadata.gz: f466081646f301304855a180aec480a3c90e1653
4
+ data.tar.gz: b33e6d9d98d4746890ffd72080cd6f3bf272dd3b
5
5
  SHA512:
6
- metadata.gz: 55c6841e58e1645e48eb83e147d662abc847cf99f18df1f5636b6fb9a09be79e133d99429fa626c3e6ca2456003091e7e74f2813ac43dc22ee163c0f2862b54d
7
- data.tar.gz: ddeec7d17a560050722bddc473bbc2ff8bba5e967c3cf3f7772249fcf300ca38d03db2f5d8d75df0be5e1b10025eafeef257bf4b8ca5280d71720537ea0e6a0f
6
+ metadata.gz: 659186808832cd2531ef847a046eded3270789a73a052c8565c0cadbde7de977fe3b3d18bfb2d362ed2458dc4dd8f4ce826a828f78eafceb2815f727b5ee2395
7
+ data.tar.gz: dceeed449044ee96e0fe969bdf4ed2906c6b27b6de91988f70a83ec72a65d218d7e3ef720ced4195420a5697ca9369e1786afc992bac6020d9f7fae597295297
@@ -21,7 +21,7 @@ echo "deb http://deb.theforeman.org/ squeeze nightly" > /etc/apt/sources.list.d/
21
21
  and update the keys
22
22
 
23
23
  ```bash
24
- wget -q http://deb.theforeman.org/foreman.asc -O- | apt-key add -
24
+ wget -q https://deb.theforeman.org/foreman.asc -O- | apt-key add -
25
25
  ```
26
26
 
27
27
  #### Step 2: install hammer core
data/doc/release_notes.md CHANGED
@@ -1,12 +1,17 @@
1
1
  Release notes
2
2
  =============
3
3
 
4
- ### 0.10.2 (2017-05-05)
4
+ ### 0.11.0 (2017-08-01)
5
+ * Add full-help command ([#20181](http://projects.theforeman.org/issues/20181))
6
+ * Replace CSV parser in List Normalizer ([#17135](http://projects.theforeman.org/issues/17135))
7
+ * Make sure assert_match is called in correct context ([#19573](http://projects.theforeman.org/issues/19573))
8
+ * Fix merging .edit.po into .po files ([#17671](http://projects.theforeman.org/issues/17671))
9
+ * Error strings with % in them break messages ([#11178](http://projects.theforeman.org/issues/11178))
10
+ * Log what exception handler is used ([#19470](http://projects.theforeman.org/issues/19470))
11
+ * Interpolate variables in translations correctly ([#19083](http://projects.theforeman.org/issues/19083))
5
12
  * Retry command on session expiry ([#19431](http://projects.theforeman.org/issues/19431))
6
13
  * Prevent fetching of non-CA certificates ([#19391](http://projects.theforeman.org/issues/19391))
7
14
  * Use local ca cert store instead of ssl_ca_path ([#19390](http://projects.theforeman.org/issues/19390))
8
-
9
- ### 0.10.1 (2017-04-24)
10
15
  * Require apipie-bindings >= 0.2.0 ([#19083](http://projects.theforeman.org/issues/19083))
11
16
  * Install server CA cert without root access ([#19083](http://projects.theforeman.org/issues/19083))
12
17
 
@@ -0,0 +1,80 @@
1
+ module HammerCLI
2
+ class CSVParser
3
+
4
+ def initialize
5
+ reset_parser
6
+ end
7
+
8
+ def parse(data)
9
+ return [] if data.nil?
10
+ reset_parser
11
+ data.each_char do |char|
12
+ handle_escape(char) || handle_quoting(char) || handle_comma(char) || add_to_buffer(char)
13
+ end
14
+ raise ArgumentError.new(_("Illegal quoting in %{buffer}") % { :buffer => @raw_buffer }) unless @last_quote.nil?
15
+ clean_buffer
16
+ @value
17
+ end
18
+
19
+ private
20
+
21
+ def handle_comma(char)
22
+ if char == ','
23
+ clean_buffer
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ def handle_quoting(char)
31
+ if @last_quote.nil? && ["'", '"'].include?(char)
32
+ @last_quote = char
33
+ @raw_buffer += char
34
+ true
35
+ elsif @last_quote == char
36
+ @last_quote = nil
37
+ @raw_buffer += char
38
+ true
39
+ elsif @last_quote
40
+ add_to_buffer(char)
41
+ true
42
+ else
43
+ false
44
+ end
45
+ end
46
+
47
+ def handle_escape(char)
48
+ if @escape
49
+ add_to_buffer(char)
50
+ @escape = false
51
+ true
52
+ elsif char == '\\'
53
+ @escape = true
54
+ @raw_buffer += char
55
+ true
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ def add_to_buffer(char)
62
+ @buffer += char
63
+ @raw_buffer += char
64
+ end
65
+
66
+ def reset_parser
67
+ @value = []
68
+ @buffer = ''
69
+ @raw_buffer = ''
70
+ @escape = false
71
+ @last_quote = nil
72
+ end
73
+
74
+ def clean_buffer
75
+ @value << @buffer
76
+ @raw_buffer = ''
77
+ @buffer = ''
78
+ end
79
+ end
80
+ end
@@ -27,6 +27,7 @@ module HammerCLI
27
27
  def handle_exception(e, options={})
28
28
  @options = options
29
29
  handler = mappings.reverse.find { |m| e.class.respond_to?(:"<=") ? e.class <= m[0] : false }
30
+ @logger.debug "Using exception handler #{self.class}##{handler[1]}"
30
31
  return send(handler[1], e) if handler
31
32
  raise e
32
33
  end
@@ -0,0 +1,84 @@
1
+ require 'hammer_cli/abstract'
2
+
3
+ module HammerCLI
4
+ class FullHelpCommand < HammerCLI::AbstractCommand
5
+ option "--md", :flag, _("Format output in markdown")
6
+
7
+ def execute
8
+ @adapter = option_md? ? MDAdapter.new : TxtAdapter.new
9
+ print_heading
10
+ print_help
11
+ HammerCLI::EX_OK
12
+ end
13
+
14
+ private
15
+
16
+ def print_heading
17
+ @adapter.print_heading(_('Hammer CLI help'))
18
+ @adapter.print_toc(HammerCLI::MainCommand)
19
+ end
20
+
21
+ def print_help(name='hammer', command=HammerCLI::MainCommand, desc='')
22
+ @adapter.print_command(name, desc, command.new(name).help)
23
+
24
+ command.recognised_subcommands.each do |sub_cmd|
25
+ print_help(@adapter.command_name(name, sub_cmd.names.first), sub_cmd.subcommand_class, sub_cmd.description)
26
+ end
27
+ end
28
+
29
+ class MDAdapter
30
+ def command_name(parent, command_name)
31
+ "#{parent} #{command_name}"
32
+ end
33
+
34
+ def print_command(name, description, help)
35
+ print_heading(name, name.split.length)
36
+ puts description
37
+ puts
38
+ puts "```"
39
+ puts help
40
+ puts "```"
41
+ puts
42
+ end
43
+
44
+ def print_toc(cmd)
45
+ names = cmd.recognised_subcommands.collect do |sub_cmd|
46
+ sub_cmd.names[0]
47
+ end
48
+ names.sort.each do |name|
49
+ puts "- [%s](#hammer-%s)" % [name, name.gsub(' ', '-')]
50
+ end
51
+ puts
52
+ end
53
+
54
+ def print_heading(text, level=1)
55
+ puts '#'*level + ' ' + text
56
+ end
57
+ end
58
+
59
+ class TxtAdapter
60
+ def command_name(parent, command_name)
61
+ "#{parent} > #{command_name}"
62
+ end
63
+
64
+ def print_command(name, description, help)
65
+ print_heading(name, 2)
66
+ puts description
67
+ puts
68
+ puts help
69
+ puts
70
+ end
71
+
72
+ def print_toc(cmd)
73
+ end
74
+
75
+ def print_heading(text, level=1)
76
+ ch = (level > 1) ? '-' : '='
77
+ puts text
78
+ puts ch * text.length
79
+ end
80
+ end
81
+ end
82
+
83
+ HammerCLI::MainCommand.subcommand "full-help", _("Print help for all hammer commands"), HammerCLI::FullHelpCommand
84
+ end
@@ -1,5 +1,5 @@
1
1
  require 'json'
2
- require 'csv'
2
+ require 'hammer_cli/csv_parser'
3
3
 
4
4
  module HammerCLI
5
5
  module Options
@@ -79,25 +79,14 @@ module HammerCLI
79
79
  end
80
80
  end
81
81
 
82
- CSV_ERROR_MESSAGES = {
83
- /Missing or stray quote/ => _('Missing or stray quote.'),
84
- /Unquoted fields do not allow/ => _('Unquoted fields do not allow \r or \n.'),
85
- /Illegal quoting/ => _('Illegal quoting.'),
86
- /Unclosed quoted field/ => _('Unclosed quoted field.'),
87
- /Field size exceeded/ => _('Field size exceeded.')
88
- }
89
82
 
90
83
  class List < AbstractNormalizer
91
-
92
84
  def description
93
- _("Comma separated list of values. Values containing comma should be double quoted")
85
+ _("Comma separated list of values. Values containing comma should be quoted or escaped with backslash")
94
86
  end
95
87
 
96
88
  def format(val)
97
- (val.is_a?(String) && !val.empty?) ? CSV.parse_line(val) : []
98
- rescue CSV::MalformedCSVError => e
99
- message = CSV_ERROR_MESSAGES.find { |pattern,| pattern.match e.message } || [e.message]
100
- raise ArgumentError.new(message.last)
89
+ (val.is_a?(String) && !val.empty?) ? HammerCLI::CSVParser.new.parse(val) : []
101
90
  end
102
91
  end
103
92
 
@@ -2,8 +2,6 @@ module HammerCLI
2
2
  module Testing
3
3
  module CommandAssertions
4
4
  class CommandExpectation
5
- include MiniTest::Assertions
6
-
7
5
  attr_accessor :expected_out, :expected_err, :expected_exit_code
8
6
 
9
7
  def initialize(expected_out="", expected_err="", expected_exit_code=0)
@@ -12,10 +10,10 @@ module HammerCLI
12
10
  @expected_exit_code = expected_exit_code
13
11
  end
14
12
 
15
- def assert_match(actual_result)
16
- assert_equal_or_match @expected_err, actual_result.err
17
- assert_equal_or_match @expected_out, actual_result.out
18
- assert_exit_code_equal @expected_exit_code, actual_result.exit_code
13
+ def assert_match(test_ctx, actual_result)
14
+ test_ctx.assert_equal_or_match @expected_err, actual_result.err
15
+ test_ctx.assert_equal_or_match @expected_out, actual_result.out
16
+ test_ctx.assert_exit_code_equal @expected_exit_code, actual_result.exit_code
19
17
  end
20
18
  end
21
19
 
@@ -54,15 +52,14 @@ module HammerCLI
54
52
  end
55
53
 
56
54
  def assert_cmd(expectation, actual_result)
57
- expectation.assert_match(actual_result)
55
+ expectation.assert_match(self, actual_result)
58
56
  end
59
57
 
60
58
  def assert_equal_or_match(expected, actual)
61
- case expected
62
- when String
59
+ if expected.is_a? String
63
60
  assert_equal(expected, actual)
64
- when MatcherBase
65
- expected.assert_match(actual)
61
+ elsif expected.respond_to? :assert_match
62
+ expected.assert_match(self, actual)
66
63
  else
67
64
  msg = actual
68
65
  assert_match(expected, actual, msg)
@@ -8,8 +8,8 @@ module HammerCLI
8
8
  @expected = expected
9
9
  end
10
10
 
11
- def assert_match(actual)
12
- assert_equal(@expected, actual)
11
+ def assert_match(test_ctx, actual)
12
+ test_ctx.assert_equal(@expected, actual)
13
13
  end
14
14
  end
15
15
 
@@ -18,9 +18,9 @@ module HammerCLI
18
18
  @expected = FieldMatcher.matcher(label, value)
19
19
  end
20
20
 
21
- def assert_match(actual)
21
+ def assert_match(test_ctx, actual)
22
22
  message = "Regex /#{@expected.source}/ didn't match the output:\n#{actual}"
23
- assert(@expected =~ actual, message)
23
+ test_ctx.assert(@expected =~ actual, message)
24
24
  end
25
25
 
26
26
  def self.matcher(label, value)
@@ -36,7 +36,7 @@ module HammerCLI
36
36
  @ignore_whitespace = options.fetch(:ignore_whitespace, true)
37
37
  end
38
38
 
39
- def assert_match(actual)
39
+ def assert_match(test_ctx, actual)
40
40
  if @ignore_whitespace
41
41
  expected_lines = strip_lines(@expected_lines)
42
42
  actual = strip_lines(actual.split("\n")).join("\n")
@@ -46,7 +46,7 @@ module HammerCLI
46
46
  expected_lines = expected_lines.join("\n")
47
47
 
48
48
  message = "Output didn't contain expected lines:\n" + diff(expected_lines, actual)
49
- assert(actual.include?(expected_lines), message)
49
+ test_ctx.assert(actual.include?(expected_lines), message)
50
50
  end
51
51
 
52
52
  protected
@@ -64,9 +64,9 @@ module HammerCLI
64
64
  end
65
65
  end
66
66
 
67
- def assert_match(actual)
67
+ def assert_match(test_ctx, actual)
68
68
  @line_matchers.each do |matcher|
69
- matcher.assert_match(actual)
69
+ matcher.assert_match(test_ctx, actual)
70
70
  end
71
71
  end
72
72
  end
@@ -76,7 +76,7 @@ module HammerCLI
76
76
  @expected_values = expected
77
77
  end
78
78
 
79
- def assert_match(actual)
79
+ def assert_match(test_ctx, actual)
80
80
  message = [
81
81
  "Regex didn't match the output.",
82
82
  "Expected regex:",
@@ -87,7 +87,7 @@ module HammerCLI
87
87
  actual
88
88
  ].join("\n")
89
89
 
90
- assert(line_regexp =~ actual, message)
90
+ test_ctx.assert(line_regexp =~ actual, message)
91
91
  end
92
92
 
93
93
  protected
@@ -8,10 +8,11 @@ class String
8
8
  name = name[0]
9
9
  params[name.to_s] || params[name.to_sym]
10
10
  end
11
-
12
- self.gsub(/%[<]([^>]*)[>]/, '%').gsub(/%[{]([^}]*)[}]/, '%s') % array_params
11
+ self.gsub(/%[<]([^>]*)[>]/, '%')
12
+ .gsub(/%[{]([^}]*)[}]/, '%s')
13
+ .gsub(/\%(\W?[^bBdiouxXeEfgGaAcps])/, '%%\1') % array_params
13
14
  else
14
- self % params
15
+ self.gsub(/\%(\W?[^bBdiouxXeEfgGaAcps])/, '%%\1') % params
15
16
  end
16
17
  end
17
18
 
@@ -1,5 +1,5 @@
1
1
  module HammerCLI
2
2
  def self.version
3
- @version ||= Gem::Version.new '0.10.2'
3
+ @version ||= Gem::Version.new '0.11.0'
4
4
  end
5
5
  end
data/lib/hammer_cli.rb CHANGED
@@ -21,4 +21,4 @@ require 'hammer_cli/apipie'
21
21
  # extend MainCommand
22
22
  require 'hammer_cli/shell'
23
23
  require 'hammer_cli/defaults'
24
-
24
+ require 'hammer_cli/full_help'
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,54 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+ require 'hammer_cli/csv_parser'
3
+
4
+ describe HammerCLI::CSVParser do
5
+
6
+ describe 'parse' do
7
+ let(:parser) { HammerCLI::CSVParser.new }
8
+
9
+ it "parses nil" do
10
+ parser.parse(nil).must_equal []
11
+ end
12
+
13
+ it "parses empty string" do
14
+ parser.parse('').must_equal ['']
15
+ end
16
+
17
+ it "parses single value" do
18
+ parser.parse('a').must_equal ['a']
19
+ end
20
+
21
+ it "parses a dquoted string" do
22
+ parser.parse('\"a').must_equal ['"a']
23
+ end
24
+
25
+ it "parses a quoted string" do
26
+ parser.parse("Mary\\'s").must_equal ["Mary's"]
27
+ end
28
+
29
+ it "should parse a comma separated string" do
30
+ parser.parse("a,b,c").must_equal ['a', 'b', 'c']
31
+ end
32
+
33
+ it "parses a string with escaped comma" do
34
+ parser.parse('a\,b,c').must_equal ['a,b', 'c']
35
+ end
36
+
37
+ it "should parse a comma separated string with quotes" do
38
+ parser.parse('a,b,\\"c\\"').must_equal ['a', 'b', '"c"']
39
+ end
40
+
41
+ it "parses a comma separated string with values including comma" do
42
+ parser.parse('a,b,"c,d"').must_equal ['a', 'b', 'c,d']
43
+ end
44
+
45
+ it "parses a comma separated string with values including comma (dquotes)" do
46
+ parser.parse("a,b,'c,d'").must_equal ['a', 'b', 'c,d']
47
+ end
48
+
49
+ it "raises quoting error" do
50
+ err = proc { parser.parse('1,"3,4""s') }.must_raise ArgumentError
51
+ err.message.must_equal "Illegal quoting in \"3,4\"\"s"
52
+ end
53
+ end
54
+ end
@@ -51,7 +51,8 @@ describe HammerCLI::ExceptionHandler do
51
51
  ex = RestClient::ResourceNotFound.new
52
52
  output.default_adapter = :silent
53
53
  handler.handle_exception(ex)
54
- @log_output.readline.strip.must_match /ERROR Exception : (Resource )?Not Found/
54
+ assert_match /Using exception handler HammerCLI::ExceptionHandler#handle_not_found/, @log_output.readline.strip
55
+ assert_match /ERROR Exception : (Resource )?Not Found/, @log_output.readline.strip
55
56
  end
56
57
 
57
58
  end
@@ -38,8 +38,12 @@ describe HammerCLI::Options::Normalizers do
38
38
  formatter.format('a,b,"c,d"').must_equal ['a', 'b', 'c,d']
39
39
  end
40
40
 
41
+ it "should parse a comma separated string with values including comma (doublequotes)" do
42
+ formatter.format("a,b,'c,d'").must_equal ['a', 'b', 'c,d']
43
+ end
44
+
41
45
  it "should parse a comma separated string containig double quotes" do
42
- formatter.format('a,b,""c""').must_equal ['a', 'b', '"c"']
46
+ formatter.format('a,b,\"c\"').must_equal ['a', 'b', '"c"']
43
47
  end
44
48
 
45
49
  it "should catch quoting errors" do
@@ -15,6 +15,7 @@ describe String do
15
15
  let(:str) { "AA%<a>s BB%<b>s" }
16
16
  let(:curly_str) { "AA%{a} BB%{b}" }
17
17
  let(:pos_str) { "AA%s BB%s" }
18
+ let(:str_with_percent) { "Error: AA%<a>s BB%<b>s <%# template error %> verify this %>" }
18
19
 
19
20
  it "should not fail without expected parameters" do
20
21
  str.format({}).must_equal 'AA BB'
@@ -36,6 +37,10 @@ describe String do
36
37
  curly_str.format(:a => 'A', :b => 'B').must_equal 'AAA BBB'
37
38
  end
38
39
 
40
+ it "should not fail due to presence of percent chars in string" do
41
+ str_with_percent.format({}).must_equal 'Error: AA BB <%# template error %> verify this %>'
42
+ end
43
+
39
44
  end
40
45
 
41
46
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hammer_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Bačovský
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-05-05 00:00:00.000000000 Z
12
+ date: 2017-08-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: clamp
@@ -188,11 +188,13 @@ files:
188
188
  - lib/hammer_cli/completer.rb
189
189
  - lib/hammer_cli/connection.rb
190
190
  - lib/hammer_cli/context.rb
191
+ - lib/hammer_cli/csv_parser.rb
191
192
  - lib/hammer_cli/defaults.rb
192
193
  - lib/hammer_cli/defaults_commands.rb
193
194
  - lib/hammer_cli/exception_handler.rb
194
195
  - lib/hammer_cli/exceptions.rb
195
196
  - lib/hammer_cli/exit_codes.rb
197
+ - lib/hammer_cli/full_help.rb
196
198
  - lib/hammer_cli/help/builder.rb
197
199
  - lib/hammer_cli/help/text_builder.rb
198
200
  - lib/hammer_cli/i18n.rb
@@ -263,6 +265,7 @@ files:
263
265
  - test/unit/ca_cert_manager_test.rb
264
266
  - test/unit/completer_test.rb
265
267
  - test/unit/connection_test.rb
268
+ - test/unit/csv_parser_test.rb
266
269
  - test/unit/defaults_test.rb
267
270
  - test/unit/exception_handler_test.rb
268
271
  - test/unit/fixtures/apipie/architectures.json
@@ -329,16 +332,16 @@ signing_key:
329
332
  specification_version: 4
330
333
  summary: Universal command-line interface
331
334
  test_files:
332
- - test/unit/apipie/command_test.rb
333
335
  - test/unit/apipie/option_builder_test.rb
334
336
  - test/unit/apipie/option_definition_test.rb
335
337
  - test/unit/apipie/api_connection_test.rb
338
+ - test/unit/apipie/command_test.rb
336
339
  - test/unit/options/matcher_test.rb
337
- - test/unit/options/normalizers_test.rb
338
340
  - test/unit/options/option_collector_test.rb
339
341
  - test/unit/options/option_definition_test.rb
340
342
  - test/unit/options/sources/command_line_test.rb
341
343
  - test/unit/options/sources/saved_defaults_test.rb
344
+ - test/unit/options/normalizers_test.rb
342
345
  - test/unit/output/adapter/abstract_test.rb
343
346
  - test/unit/output/adapter/base_test.rb
344
347
  - test/unit/output/adapter/csv_test.rb
@@ -346,16 +349,14 @@ test_files:
346
349
  - test/unit/output/adapter/table_test.rb
347
350
  - test/unit/output/adapter/yaml_test.rb
348
351
  - test/unit/output/definition_test.rb
349
- - test/unit/output/dsl_test.rb
350
352
  - test/unit/output/field_filter_test.rb
351
353
  - test/unit/output/fields_test.rb
352
354
  - test/unit/output/formatters_test.rb
353
355
  - test/unit/output/output_test.rb
354
356
  - test/unit/output/record_collection_test.rb
357
+ - test/unit/output/dsl_test.rb
355
358
  - test/unit/abstract_test.rb
356
359
  - test/unit/completer_test.rb
357
- - test/unit/connection_test.rb
358
- - test/unit/defaults_test.rb
359
360
  - test/unit/fixtures/apipie/architectures.json
360
361
  - test/unit/fixtures/apipie/documented.json
361
362
  - test/unit/fixtures/defaults/defaults.yml
@@ -374,10 +375,13 @@ test_files:
374
375
  - test/unit/option_builder_test.rb
375
376
  - test/unit/settings_test.rb
376
377
  - test/unit/test_helper.rb
377
- - test/unit/utils_test.rb
378
+ - test/unit/ca_cert_manager_test.rb
378
379
  - test/unit/validator_test.rb
380
+ - test/unit/connection_test.rb
381
+ - test/unit/defaults_test.rb
382
+ - test/unit/csv_parser_test.rb
379
383
  - test/unit/exception_handler_test.rb
380
- - test/unit/ca_cert_manager_test.rb
384
+ - test/unit/utils_test.rb
381
385
  - test/functional/defaults_test.rb
382
386
  - test/functional/help_test.rb
383
387
  - test/functional/test_helper.rb