cli-kit 3.0.0 → 4.0.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 +5 -5
- data/.github/CODEOWNERS +1 -0
- data/.github/dependabot.yml +10 -0
- data/.github/probots.yml +2 -0
- data/.github/workflows/ruby.yml +32 -0
- data/.rubocop.yml +13 -15
- data/Gemfile +3 -2
- data/Gemfile.lock +35 -30
- data/README.md +102 -4
- data/Rakefile +27 -0
- data/TODO.md +5 -0
- data/bin/console +3 -3
- data/bin/test_gen +31 -0
- data/bin/testunit +2 -2
- data/cli-kit.gemspec +8 -7
- data/dev.yml +1 -1
- data/examples/README.md +21 -0
- data/examples/minimal/example.rb +22 -0
- data/examples/single-file/example.rb +71 -0
- data/examples/todo-list/README.md +1 -0
- data/gen/lib/gen/commands/help.rb +4 -4
- data/gen/lib/gen/generator.rb +12 -10
- data/gen/lib/gen.rb +5 -1
- data/gen/template/Gemfile +6 -0
- data/gen/template/bin/testunit +23 -0
- data/gen/template/bin/update-deps +2 -1
- data/gen/template/lib/__app__/commands/example.rb +0 -1
- data/gen/template/test/example_test.rb +17 -0
- data/gen/template/test/test_helper.rb +22 -0
- data/lib/cli/kit/base_command.rb +14 -8
- data/lib/cli/kit/command_registry.rb +1 -1
- data/lib/cli/kit/config.rb +39 -4
- data/lib/cli/kit/error_handler.rb +56 -36
- data/lib/cli/kit/executor.rb +39 -10
- data/lib/cli/kit/ini.rb +16 -7
- data/lib/cli/kit/logger.rb +82 -0
- data/lib/cli/kit/resolver.rb +2 -2
- data/lib/cli/kit/support/test_helper.rb +244 -0
- data/lib/cli/kit/support.rb +9 -0
- data/lib/cli/kit/system.rb +61 -22
- data/lib/cli/kit/util.rb +189 -0
- data/lib/cli/kit/version.rb +1 -1
- data/lib/cli/kit.rb +4 -1
- metadata +36 -20
- data/.travis.yml +0 -5
data/gen/lib/gen.rb
CHANGED
|
@@ -8,7 +8,10 @@ module Gen
|
|
|
8
8
|
|
|
9
9
|
TOOL_NAME = 'cli-kit'
|
|
10
10
|
ROOT = File.expand_path('../../..', __FILE__)
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
TOOL_CONFIG_PATH = File.expand_path(File.join('~', '.config', TOOL_NAME))
|
|
13
|
+
LOG_FILE = File.join(TOOL_CONFIG_PATH, 'logs', 'log.log')
|
|
14
|
+
DEBUG_LOG_FILE = File.join(TOOL_CONFIG_PATH, 'logs', 'debug.log')
|
|
12
15
|
|
|
13
16
|
autoload(:Generator, 'gen/generator')
|
|
14
17
|
|
|
@@ -17,6 +20,7 @@ module Gen
|
|
|
17
20
|
|
|
18
21
|
autocall(:Config) { CLI::Kit::Config.new(tool_name: TOOL_NAME) }
|
|
19
22
|
autocall(:Command) { CLI::Kit::BaseCommand }
|
|
23
|
+
autocall(:Logger) { CLI::Kit::Logger.new(debug_log_file: DEBUG_LOG_FILE) }
|
|
20
24
|
|
|
21
25
|
autocall(:Executor) { CLI::Kit::Executor.new(log_file: LOG_FILE) }
|
|
22
26
|
autocall(:Resolver) do
|
data/gen/template/Gemfile
CHANGED
|
@@ -2,3 +2,9 @@ source 'https://rubygems.org'
|
|
|
2
2
|
|
|
3
3
|
gem 'cli-kit', '~> __cli-kit-version__'
|
|
4
4
|
gem 'cli-ui', '~> __cli-ui-version__'
|
|
5
|
+
|
|
6
|
+
group :test do
|
|
7
|
+
gem 'mocha', '~> 1.5.0', require: false
|
|
8
|
+
gem 'minitest', '>= 5.0.0', require: false
|
|
9
|
+
gem 'minitest-reporters', require: false
|
|
10
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
|
|
6
|
+
root = File.expand_path('../..', __FILE__)
|
|
7
|
+
TEST_ROOT = root + '/test'
|
|
8
|
+
|
|
9
|
+
$LOAD_PATH.unshift(TEST_ROOT)
|
|
10
|
+
|
|
11
|
+
def test_files
|
|
12
|
+
Dir.glob(TEST_ROOT + "/**/*_test.rb")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if ARGV.empty?
|
|
16
|
+
test_files.each { |f| require(f) }
|
|
17
|
+
exit 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# A list of files is presumed to be specified
|
|
21
|
+
ARGV.each do |a|
|
|
22
|
+
require a.sub(%r{^test/}, '')
|
|
23
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
$LOAD_PATH.unshift(File.expand_path("../../vendor/deps/cli-ui/lib", __FILE__))
|
|
4
4
|
require 'open3'
|
|
@@ -39,6 +39,7 @@ deps.each do |dep|
|
|
|
39
39
|
bail(
|
|
40
40
|
"dependency is not checked out: {{yellow:#{dep}}}.\n" \
|
|
41
41
|
" This repo {{bold_blue:(github.com/shopify/#{dep})}} must be cloned at {{bold_blue:#{path}}} for this script to succeed.\n" \
|
|
42
|
+
" Currently, SOURCE_ROOT is set to {{bold_blue:#{source_path}}}.\n" \
|
|
42
43
|
" Alternatively, you can set {{bold_blue:SOURCE_ROOT}} to a directory containing {{yellow:#{dep}}}.\n" \
|
|
43
44
|
" {{bold_blue:SOURCE_ROOT}} defaults to {{bold_blue:../}}."
|
|
44
45
|
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module __App__
|
|
4
|
+
class ExampleTest < MiniTest::Test
|
|
5
|
+
include CLI::Kit::Support::TestHelper
|
|
6
|
+
|
|
7
|
+
def test_example
|
|
8
|
+
CLI::Kit::System.fake("ls -al", stdout: "a\nb", success: true)
|
|
9
|
+
|
|
10
|
+
out, = CLI::Kit::System.capture2('ls', '-al')
|
|
11
|
+
assert_equal %w(a b), out.split("\n")
|
|
12
|
+
|
|
13
|
+
errors = assert_all_commands_run(should_raise: false)
|
|
14
|
+
assert_nil errors, "expected command to run successfully"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
begin
|
|
2
|
+
addpath = lambda do |p|
|
|
3
|
+
path = File.expand_path("../../#{p}", __FILE__)
|
|
4
|
+
$LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
|
5
|
+
end
|
|
6
|
+
addpath.call("lib")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'cli/kit'
|
|
10
|
+
|
|
11
|
+
require 'fileutils'
|
|
12
|
+
require 'tmpdir'
|
|
13
|
+
require 'tempfile'
|
|
14
|
+
|
|
15
|
+
require 'rubygems'
|
|
16
|
+
require 'bundler/setup'
|
|
17
|
+
|
|
18
|
+
CLI::UI::StdoutRouter.enable
|
|
19
|
+
|
|
20
|
+
require 'minitest/autorun'
|
|
21
|
+
require "minitest/unit"
|
|
22
|
+
require 'mocha/minitest'
|
data/lib/cli/kit/base_command.rb
CHANGED
|
@@ -17,22 +17,28 @@ module CLI
|
|
|
17
17
|
|
|
18
18
|
def self.call(args, command_name)
|
|
19
19
|
cmd = new
|
|
20
|
-
stats_tags =
|
|
21
|
-
stats_tags << "subcommand:#{args.first}" if args && args.first && cmd.has_subcommands?
|
|
20
|
+
stats_tags = cmd.stats_tags(args, command_name)
|
|
22
21
|
begin
|
|
23
|
-
statsd_increment(
|
|
24
|
-
statsd_time(
|
|
22
|
+
statsd_increment('cli.command.invoked', tags: stats_tags)
|
|
23
|
+
statsd_time('cli.command.time', tags: stats_tags) do
|
|
25
24
|
cmd.call(args, command_name)
|
|
26
25
|
end
|
|
27
|
-
statsd_increment(
|
|
28
|
-
rescue => e
|
|
29
|
-
statsd_increment(
|
|
26
|
+
statsd_increment('cli.command.success', tags: stats_tags)
|
|
27
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
28
|
+
statsd_increment('cli.command.exception', tags: stats_tags + ["exception:#{e.class}"])
|
|
30
29
|
raise e
|
|
31
30
|
end
|
|
32
31
|
end
|
|
33
32
|
|
|
33
|
+
def stats_tags(args, command_name)
|
|
34
|
+
tags = ["task:#{self.class}"]
|
|
35
|
+
tags << "command:#{command_name}" if command_name
|
|
36
|
+
tags << "subcommand:#{args.first}" if args&.first && has_subcommands?
|
|
37
|
+
tags
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
def call(_args, _command_name)
|
|
35
|
-
raise NotImplementedError
|
|
41
|
+
raise NotImplementedError, "#{self.class.name} must implement #{__method__}"
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def has_subcommands?
|
data/lib/cli/kit/config.rb
CHANGED
|
@@ -18,13 +18,27 @@ module CLI
|
|
|
18
18
|
# `name` : the name of the config value you are looking for
|
|
19
19
|
#
|
|
20
20
|
# #### Returns
|
|
21
|
-
# `value` : the value of the config variable (
|
|
21
|
+
# `value` : the value of the config variable (nil if none)
|
|
22
22
|
#
|
|
23
23
|
# #### Example Usage
|
|
24
24
|
# `config.get('name.of.config')`
|
|
25
25
|
#
|
|
26
|
-
def get(section, name)
|
|
27
|
-
all_configs.dig("[#{section}]", name) ||
|
|
26
|
+
def get(section, name, default: nil)
|
|
27
|
+
all_configs.dig("[#{section}]", name) || default
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Coalesce and enforce the value of a config to a boolean
|
|
31
|
+
def get_bool(section, name, default: false)
|
|
32
|
+
case get(section, name, default: default)
|
|
33
|
+
when 'true'
|
|
34
|
+
true
|
|
35
|
+
when 'false'
|
|
36
|
+
false
|
|
37
|
+
when default
|
|
38
|
+
default
|
|
39
|
+
else
|
|
40
|
+
raise CLI::Kit::Abort, "Invalid config: #{section}.#{name} is expected to be true or false"
|
|
41
|
+
end
|
|
28
42
|
end
|
|
29
43
|
|
|
30
44
|
# Sets the config value in the config file
|
|
@@ -43,6 +57,27 @@ module CLI
|
|
|
43
57
|
write_config
|
|
44
58
|
end
|
|
45
59
|
|
|
60
|
+
# Unsets a config value in the config file
|
|
61
|
+
#
|
|
62
|
+
# #### Parameters
|
|
63
|
+
# `section` : the section of the config you are deleting
|
|
64
|
+
# `name` : the name of the config you are deleting
|
|
65
|
+
#
|
|
66
|
+
# #### Example Usage
|
|
67
|
+
# `config.unset('section', 'name.of.config')`
|
|
68
|
+
#
|
|
69
|
+
def unset(section, name)
|
|
70
|
+
set(section, name, nil)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Gets the hash for the entire section
|
|
74
|
+
#
|
|
75
|
+
# #### Parameters
|
|
76
|
+
# `section` : the section of the config you are getting
|
|
77
|
+
#
|
|
78
|
+
# #### Example Usage
|
|
79
|
+
# `config.get_section('section')`
|
|
80
|
+
#
|
|
46
81
|
def get_section(section)
|
|
47
82
|
(all_configs["[#{section}]"] || {}).dup
|
|
48
83
|
end
|
|
@@ -83,7 +118,7 @@ module CLI
|
|
|
83
118
|
|
|
84
119
|
def ini
|
|
85
120
|
@ini ||= CLI::Kit::Ini
|
|
86
|
-
.new(file, default_section:
|
|
121
|
+
.new(file, default_section: '[global]', convert_types: false)
|
|
87
122
|
.tap(&:parse)
|
|
88
123
|
end
|
|
89
124
|
|
|
@@ -4,9 +4,10 @@ require 'English'
|
|
|
4
4
|
module CLI
|
|
5
5
|
module Kit
|
|
6
6
|
class ErrorHandler
|
|
7
|
-
def initialize(log_file:, exception_reporter:)
|
|
7
|
+
def initialize(log_file:, exception_reporter:, tool_name: nil)
|
|
8
8
|
@log_file = log_file
|
|
9
9
|
@exception_reporter_or_proc = exception_reporter || NullExceptionReporter
|
|
10
|
+
@tool_name = tool_name
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
module NullExceptionReporter
|
|
@@ -20,65 +21,84 @@ module CLI
|
|
|
20
21
|
handle_abort(&block)
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
def handle_exception(error)
|
|
25
|
+
if (notify_with = exception_for_submission(error))
|
|
26
|
+
logs = begin
|
|
27
|
+
File.read(@log_file)
|
|
28
|
+
rescue => e
|
|
29
|
+
"(#{e.class}: #{e.message})"
|
|
30
|
+
end
|
|
31
|
+
exception_reporter.report(notify_with, logs)
|
|
32
|
+
end
|
|
27
33
|
end
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
CLI::Kit::EXIT_SUCCESS
|
|
32
|
-
rescue CLI::Kit::GenericAbort => e
|
|
33
|
-
is_bug = e.is_a?(CLI::Kit::Bug) || e.is_a?(CLI::Kit::BugSilent)
|
|
34
|
-
is_silent = e.is_a?(CLI::Kit::AbortSilent) || e.is_a?(CLI::Kit::BugSilent)
|
|
35
|
-
|
|
36
|
-
print_error_message(e) unless is_silent
|
|
37
|
-
(@exception = e) if is_bug
|
|
38
|
-
|
|
39
|
-
CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
|
40
|
-
rescue Interrupt
|
|
41
|
-
$stderr.puts(format_error_message("Interrupt"))
|
|
42
|
-
return CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
|
43
|
-
end
|
|
35
|
+
# maybe we can get rid of this.
|
|
36
|
+
attr_writer :exception
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
notify_with = nil
|
|
38
|
+
private
|
|
47
39
|
|
|
40
|
+
def exception_for_submission(error)
|
|
48
41
|
case error
|
|
49
42
|
when nil # normal, non-error termination
|
|
43
|
+
nil
|
|
50
44
|
when Interrupt # ctrl-c
|
|
45
|
+
nil
|
|
51
46
|
when CLI::Kit::Abort, CLI::Kit::AbortSilent # Not a bug
|
|
47
|
+
nil
|
|
52
48
|
when SignalException
|
|
53
|
-
skip =
|
|
54
|
-
|
|
55
|
-
notify_with = error
|
|
56
|
-
end
|
|
49
|
+
skip = ['SIGTERM', 'SIGHUP', 'SIGINT']
|
|
50
|
+
skip.include?(error.message) ? nil : error
|
|
57
51
|
when SystemExit # "exit N" called
|
|
58
52
|
case error.status
|
|
59
53
|
when CLI::Kit::EXIT_SUCCESS # submit nothing if it was `exit 0`
|
|
54
|
+
nil
|
|
60
55
|
when CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
|
61
56
|
# if it was `exit 30`, translate the exit code to 1, and submit nothing.
|
|
62
57
|
# 30 is used to signal normal failures that are not indicative of bugs.
|
|
63
58
|
# However, users should see it presented as 1.
|
|
64
|
-
exit
|
|
59
|
+
exit(1)
|
|
65
60
|
else
|
|
66
61
|
# A weird termination status happened. `error.exception "message"` will maintain backtrace
|
|
67
62
|
# but allow us to set a message
|
|
68
|
-
|
|
63
|
+
error.exception("abnormal termination status: #{error.status}")
|
|
69
64
|
end
|
|
70
65
|
else
|
|
71
|
-
|
|
66
|
+
error
|
|
72
67
|
end
|
|
68
|
+
end
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
def install!
|
|
71
|
+
at_exit { handle_exception(@exception || $ERROR_INFO) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def handle_abort
|
|
75
|
+
yield
|
|
76
|
+
CLI::Kit::EXIT_SUCCESS
|
|
77
|
+
rescue CLI::Kit::GenericAbort => e
|
|
78
|
+
is_bug = e.is_a?(CLI::Kit::Bug) || e.is_a?(CLI::Kit::BugSilent)
|
|
79
|
+
is_silent = e.is_a?(CLI::Kit::AbortSilent) || e.is_a?(CLI::Kit::BugSilent)
|
|
80
|
+
|
|
81
|
+
print_error_message(e) unless is_silent
|
|
82
|
+
(@exception = e) if is_bug
|
|
83
|
+
|
|
84
|
+
CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
|
85
|
+
rescue Interrupt
|
|
86
|
+
stderr_puts_message('Interrupt')
|
|
87
|
+
CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
|
88
|
+
rescue Errno::ENOSPC
|
|
89
|
+
message = if @tool_name
|
|
90
|
+
"Your disk is full - {{command:#{@tool_name}}} requires free space to operate"
|
|
91
|
+
else
|
|
92
|
+
'Your disk is full - free space is required to operate'
|
|
81
93
|
end
|
|
94
|
+
stderr_puts_message(message)
|
|
95
|
+
CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def stderr_puts_message(message)
|
|
99
|
+
$stderr.puts(format_error_message(message))
|
|
100
|
+
rescue Errno::EPIPE
|
|
101
|
+
nil
|
|
82
102
|
end
|
|
83
103
|
|
|
84
104
|
def exception_reporter
|
data/lib/cli/kit/executor.rb
CHANGED
|
@@ -1,37 +1,66 @@
|
|
|
1
1
|
require 'cli/kit'
|
|
2
2
|
require 'English'
|
|
3
|
+
require 'fileutils'
|
|
3
4
|
|
|
4
5
|
module CLI
|
|
5
6
|
module Kit
|
|
6
7
|
class Executor
|
|
7
8
|
def initialize(log_file:)
|
|
9
|
+
FileUtils.mkpath(File.dirname(log_file))
|
|
8
10
|
@log_file = log_file
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def call(command, command_name, args)
|
|
12
|
-
with_traps
|
|
14
|
+
with_traps do
|
|
15
|
+
with_logging do |id|
|
|
16
|
+
command.call(args, command_name)
|
|
17
|
+
rescue => e
|
|
18
|
+
begin
|
|
19
|
+
$stderr.puts "This command ran with ID: #{id}"
|
|
20
|
+
$stderr.puts 'Please include this information in any issues/report along with relevant logs'
|
|
21
|
+
rescue SystemCallError
|
|
22
|
+
# Outputting to stderr is best-effort. Avoid raising another error when outputting debug info so that
|
|
23
|
+
# we can detect and log the original error, which may even be the source of this error.
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
raise e
|
|
27
|
+
end
|
|
28
|
+
end
|
|
13
29
|
end
|
|
14
30
|
|
|
15
31
|
private
|
|
16
32
|
|
|
17
33
|
def with_logging(&block)
|
|
18
34
|
return yield unless @log_file
|
|
19
|
-
CLI::UI.log_output_to(@log_file
|
|
35
|
+
CLI::UI.log_output_to(@log_file) do
|
|
36
|
+
CLI::UI::StdoutRouter.with_id(on_streams: [CLI::UI::StdoutRouter.duplicate_output_to]) do |id|
|
|
37
|
+
block.call(id)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
20
40
|
end
|
|
21
41
|
|
|
22
|
-
def with_traps
|
|
42
|
+
def with_traps(&block)
|
|
23
43
|
twrap('QUIT', method(:quit_handler)) do
|
|
24
|
-
twrap('INFO', method(:info_handler))
|
|
25
|
-
yield
|
|
26
|
-
end
|
|
44
|
+
twrap('INFO', method(:info_handler), &block)
|
|
27
45
|
end
|
|
28
46
|
end
|
|
29
47
|
|
|
30
48
|
def twrap(signal, handler)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
return yield unless Signal.list.key?(signal)
|
|
50
|
+
|
|
51
|
+
begin
|
|
52
|
+
begin
|
|
53
|
+
prev_handler = trap(signal, handler)
|
|
54
|
+
installed = true
|
|
55
|
+
rescue ArgumentError
|
|
56
|
+
# If we couldn't install a signal handler because the signal is
|
|
57
|
+
# reserved, remember not to uninstall it later.
|
|
58
|
+
installed = false
|
|
59
|
+
end
|
|
60
|
+
yield
|
|
61
|
+
ensure
|
|
62
|
+
trap(signal, prev_handler) if installed
|
|
63
|
+
end
|
|
35
64
|
end
|
|
36
65
|
|
|
37
66
|
def quit_handler(_sig)
|
data/lib/cli/kit/ini.rb
CHANGED
|
@@ -15,8 +15,12 @@ module CLI
|
|
|
15
15
|
class Ini
|
|
16
16
|
attr_accessor :ini
|
|
17
17
|
|
|
18
|
-
def initialize(path = nil, default_section: nil, convert_types: true)
|
|
19
|
-
@config =
|
|
18
|
+
def initialize(path = nil, config: nil, default_section: nil, convert_types: true)
|
|
19
|
+
@config = if path && File.exist?(path)
|
|
20
|
+
File.readlines(path)
|
|
21
|
+
elsif config
|
|
22
|
+
config.lines
|
|
23
|
+
end
|
|
20
24
|
@ini = {}
|
|
21
25
|
@current_key = nil
|
|
22
26
|
@default_section = default_section
|
|
@@ -39,28 +43,33 @@ module CLI
|
|
|
39
43
|
|
|
40
44
|
# Otherwise set the values
|
|
41
45
|
else
|
|
42
|
-
k, v = l.split('=').map(&:strip)
|
|
46
|
+
k, v = l.split('=', 2).map(&:strip)
|
|
43
47
|
set_val(k, v)
|
|
44
48
|
end
|
|
45
49
|
end
|
|
46
50
|
@ini
|
|
47
51
|
end
|
|
48
52
|
|
|
53
|
+
def git_format
|
|
54
|
+
to_ini(@ini, git_format: true).flatten.join("\n")
|
|
55
|
+
end
|
|
56
|
+
|
|
49
57
|
def to_s
|
|
50
58
|
to_ini(@ini).flatten.join("\n")
|
|
51
59
|
end
|
|
52
60
|
|
|
53
61
|
private
|
|
54
62
|
|
|
55
|
-
def to_ini(h)
|
|
63
|
+
def to_ini(h, git_format: false)
|
|
64
|
+
optional_tab = git_format ? "\t" : ''
|
|
56
65
|
str = []
|
|
57
66
|
h.each do |k, v|
|
|
58
67
|
if section_designator?(k)
|
|
59
|
-
str <<
|
|
68
|
+
str << '' unless str.empty? || git_format
|
|
60
69
|
str << k
|
|
61
|
-
str << to_ini(v)
|
|
70
|
+
str << to_ini(v, git_format: git_format)
|
|
62
71
|
else
|
|
63
|
-
str << "#{k} = #{v}"
|
|
72
|
+
str << "#{optional_tab}#{k} = #{v}"
|
|
64
73
|
end
|
|
65
74
|
end
|
|
66
75
|
str
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module CLI
|
|
5
|
+
module Kit
|
|
6
|
+
class Logger
|
|
7
|
+
MAX_LOG_SIZE = 5 * 1024 * 1000 # 5MB
|
|
8
|
+
MAX_NUM_LOGS = 10
|
|
9
|
+
|
|
10
|
+
# Constructor for CLI::Kit::Logger
|
|
11
|
+
#
|
|
12
|
+
# @param debug_log_file [String] path to the file where debug logs should be stored
|
|
13
|
+
def initialize(debug_log_file:, env_debug_name: 'DEBUG')
|
|
14
|
+
FileUtils.mkpath(File.dirname(debug_log_file))
|
|
15
|
+
@debug_logger = ::Logger.new(debug_log_file, MAX_NUM_LOGS, MAX_LOG_SIZE)
|
|
16
|
+
@env_debug_name = env_debug_name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Functionally equivalent to Logger#info
|
|
20
|
+
# Also logs to the debug file, taking into account CLI::UI::StdoutRouter.current_id
|
|
21
|
+
#
|
|
22
|
+
# @param msg [String] the message to log
|
|
23
|
+
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
|
24
|
+
def info(msg, debug: true)
|
|
25
|
+
$stdout.puts CLI::UI.fmt(msg)
|
|
26
|
+
@debug_logger.info(format_debug(msg)) if debug
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Functionally equivalent to Logger#warn
|
|
30
|
+
# Also logs to the debug file, taking into account CLI::UI::StdoutRouter.current_id
|
|
31
|
+
#
|
|
32
|
+
# @param msg [String] the message to log
|
|
33
|
+
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
|
34
|
+
def warn(msg, debug: true)
|
|
35
|
+
$stdout.puts CLI::UI.fmt("{{yellow:#{msg}}}")
|
|
36
|
+
@debug_logger.warn(format_debug(msg)) if debug
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Functionally equivalent to Logger#error
|
|
40
|
+
# Also logs to the debug file, taking into account CLI::UI::StdoutRouter.current_id
|
|
41
|
+
#
|
|
42
|
+
# @param msg [String] the message to log
|
|
43
|
+
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
|
44
|
+
def error(msg, debug: true)
|
|
45
|
+
$stderr.puts CLI::UI.fmt("{{red:#{msg}}}")
|
|
46
|
+
@debug_logger.error(format_debug(msg)) if debug
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Functionally equivalent to Logger#fatal
|
|
50
|
+
# Also logs to the debug file, taking into account CLI::UI::StdoutRouter.current_id
|
|
51
|
+
#
|
|
52
|
+
# @param msg [String] the message to log
|
|
53
|
+
# @param debug [Boolean] determines if the debug logger will receive the log (default true)
|
|
54
|
+
def fatal(msg, debug: true)
|
|
55
|
+
$stderr.puts CLI::UI.fmt("{{red:{{bold:Fatal:}} #{msg}}}")
|
|
56
|
+
@debug_logger.fatal(format_debug(msg)) if debug
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Similar to Logger#debug, however will not output to STDOUT unless DEBUG env var is set
|
|
60
|
+
# Logs to the debug file, taking into account CLI::UI::StdoutRouter.current_id
|
|
61
|
+
#
|
|
62
|
+
# @param msg [String] the message to log
|
|
63
|
+
def debug(msg)
|
|
64
|
+
$stdout.puts CLI::UI.fmt(msg) if debug?
|
|
65
|
+
@debug_logger.debug(format_debug(msg))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def format_debug(msg)
|
|
71
|
+
msg = CLI::UI.fmt(msg)
|
|
72
|
+
return msg unless CLI::UI::StdoutRouter.current_id
|
|
73
|
+
"[#{CLI::UI::StdoutRouter.current_id[:id]}] #{msg}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def debug?
|
|
77
|
+
val = ENV[@env_debug_name]
|
|
78
|
+
val && val != '0' && val != ''
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/cli/kit/resolver.rb
CHANGED
|
@@ -25,7 +25,7 @@ module CLI
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
27
|
def command_not_found(name)
|
|
28
|
-
CLI::UI::Frame.open(
|
|
28
|
+
CLI::UI::Frame.open('Command not found', color: :red, timing: false) do
|
|
29
29
|
$stderr.puts(CLI::UI.fmt("{{command:#{@tool_name} #{name}}} was not found"))
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -43,7 +43,7 @@ module CLI
|
|
|
43
43
|
|
|
44
44
|
# If we have any matches left, tell the user
|
|
45
45
|
if possible_matches.any?
|
|
46
|
-
CLI::UI::Frame.open(
|
|
46
|
+
CLI::UI::Frame.open('{{bold:Did you mean?}}', timing: false, color: :blue) do
|
|
47
47
|
possible_matches.each do |possible_match|
|
|
48
48
|
$stderr.puts CLI::UI.fmt("{{command:#{@tool_name} #{possible_match}}}")
|
|
49
49
|
end
|