gorails 0.1.0 → 0.1.3
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 +4 -4
- data/CHANGELOG.md +14 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +65 -0
- data/README.md +41 -12
- data/bin/update-deps +95 -0
- data/exe/gorails +18 -0
- data/gorails.gemspec +4 -3
- data/lib/gorails/commands/episodes.rb +25 -0
- data/lib/gorails/commands/example.rb +19 -0
- data/lib/gorails/commands/help.rb +21 -0
- data/lib/gorails/commands/jobs.rb +25 -0
- data/lib/gorails/commands/jumpstart.rb +29 -0
- data/lib/gorails/commands/railsbytes.rb +67 -0
- data/lib/gorails/commands.rb +19 -0
- data/lib/gorails/entry_point.rb +10 -0
- data/lib/gorails/version.rb +1 -1
- data/lib/gorails.rb +22 -1
- data/vendor/deps/cli-kit/REVISION +1 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/definition.rb +301 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/evaluation.rb +237 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/parser/node.rb +131 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/parser.rb +128 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args/tokenizer.rb +132 -0
- data/vendor/deps/cli-kit/lib/cli/kit/args.rb +15 -0
- data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +29 -0
- data/vendor/deps/cli-kit/lib/cli/kit/command_help.rb +256 -0
- data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +141 -0
- data/vendor/deps/cli-kit/lib/cli/kit/config.rb +137 -0
- data/vendor/deps/cli-kit/lib/cli/kit/core_ext.rb +30 -0
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +165 -0
- data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +99 -0
- data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +94 -0
- data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +89 -0
- data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +95 -0
- data/vendor/deps/cli-kit/lib/cli/kit/opts.rb +284 -0
- data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +67 -0
- data/vendor/deps/cli-kit/lib/cli/kit/sorbet_runtime_stub.rb +142 -0
- data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +253 -0
- data/vendor/deps/cli-kit/lib/cli/kit/support.rb +10 -0
- data/vendor/deps/cli-kit/lib/cli/kit/system.rb +350 -0
- data/vendor/deps/cli-kit/lib/cli/kit/util.rb +133 -0
- data/vendor/deps/cli-kit/lib/cli/kit/version.rb +7 -0
- data/vendor/deps/cli-kit/lib/cli/kit.rb +151 -0
- data/vendor/deps/cli-ui/REVISION +1 -0
- data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +180 -0
- data/vendor/deps/cli-ui/lib/cli/ui/color.rb +98 -0
- data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +216 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +116 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +176 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +149 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +112 -0
- data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +300 -0
- data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +92 -0
- data/vendor/deps/cli-ui/lib/cli/ui/os.rb +58 -0
- data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +72 -0
- data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +102 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +534 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +36 -0
- data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +354 -0
- data/vendor/deps/cli-ui/lib/cli/ui/sorbet_runtime_stub.rb +143 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +46 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +292 -0
- data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +82 -0
- data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +264 -0
- data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +53 -0
- data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +107 -0
- data/vendor/deps/cli-ui/lib/cli/ui/version.rb +6 -0
- data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +37 -0
- data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +75 -0
- data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +91 -0
- data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +63 -0
- data/vendor/deps/cli-ui/lib/cli/ui.rb +356 -0
- metadata +114 -5
@@ -0,0 +1,137 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module CLI
|
6
|
+
module Kit
|
7
|
+
class Config
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
XDG_CONFIG_HOME = 'XDG_CONFIG_HOME'
|
11
|
+
|
12
|
+
sig { params(tool_name: String).void }
|
13
|
+
def initialize(tool_name:)
|
14
|
+
@tool_name = tool_name
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the config corresponding to `name` from the config file
|
18
|
+
# `false` is returned if it doesn't exist
|
19
|
+
#
|
20
|
+
# #### Parameters
|
21
|
+
# `section` : the section of the config value you are looking for
|
22
|
+
# `name` : the name of the config value you are looking for
|
23
|
+
#
|
24
|
+
# #### Returns
|
25
|
+
# `value` : the value of the config variable (nil if none)
|
26
|
+
#
|
27
|
+
# #### Example Usage
|
28
|
+
# `config.get('name.of.config')`
|
29
|
+
#
|
30
|
+
sig { params(section: String, name: String, default: T.nilable(String)).returns(T.nilable(String)) }
|
31
|
+
def get(section, name, default: nil)
|
32
|
+
all_configs.dig("[#{section}]", name) || default
|
33
|
+
end
|
34
|
+
|
35
|
+
# Coalesce and enforce the value of a config to a boolean
|
36
|
+
sig { params(section: String, name: String, default: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
|
37
|
+
def get_bool(section, name, default: false)
|
38
|
+
case get(section, name)
|
39
|
+
when 'true'
|
40
|
+
true
|
41
|
+
when 'false'
|
42
|
+
false
|
43
|
+
when nil
|
44
|
+
default
|
45
|
+
else
|
46
|
+
raise CLI::Kit::Abort, "Invalid config: #{section}.#{name} is expected to be true or false"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sets the config value in the config file
|
51
|
+
#
|
52
|
+
# #### Parameters
|
53
|
+
# `section` : the section of the config you are setting
|
54
|
+
# `name` : the name of the config you are setting
|
55
|
+
# `value` : the value of the config you are setting
|
56
|
+
#
|
57
|
+
# #### Example Usage
|
58
|
+
# `config.set('section', 'name.of.config', 'value')`
|
59
|
+
#
|
60
|
+
sig { params(section: String, name: String, value: T.nilable(T.any(String, T::Boolean))).void }
|
61
|
+
def set(section, name, value)
|
62
|
+
all_configs["[#{section}]"] ||= {}
|
63
|
+
case value
|
64
|
+
when nil
|
65
|
+
T.must(all_configs["[#{section}]"]).delete(name)
|
66
|
+
else
|
67
|
+
T.must(all_configs["[#{section}]"])[name] = value.to_s
|
68
|
+
end
|
69
|
+
write_config
|
70
|
+
end
|
71
|
+
|
72
|
+
# Unsets a config value in the config file
|
73
|
+
#
|
74
|
+
# #### Parameters
|
75
|
+
# `section` : the section of the config you are deleting
|
76
|
+
# `name` : the name of the config you are deleting
|
77
|
+
#
|
78
|
+
# #### Example Usage
|
79
|
+
# `config.unset('section', 'name.of.config')`
|
80
|
+
#
|
81
|
+
sig { params(section: String, name: String).void }
|
82
|
+
def unset(section, name)
|
83
|
+
set(section, name, nil)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Gets the hash for the entire section
|
87
|
+
#
|
88
|
+
# #### Parameters
|
89
|
+
# `section` : the section of the config you are getting
|
90
|
+
#
|
91
|
+
# #### Example Usage
|
92
|
+
# `config.get_section('section')`
|
93
|
+
#
|
94
|
+
sig { params(section: String).returns(T::Hash[String, String]) }
|
95
|
+
def get_section(section)
|
96
|
+
(all_configs["[#{section}]"] || {}).dup
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { returns(String) }
|
100
|
+
def to_s
|
101
|
+
ini.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
# The path on disk at which the configuration is stored:
|
105
|
+
# `$XDG_CONFIG_HOME/<toolname>/config`
|
106
|
+
# if ENV['XDG_CONFIG_HOME'] is not set, we default to ~/.config, e.g.:
|
107
|
+
# ~/.config/tool/config
|
108
|
+
#
|
109
|
+
sig { returns(String) }
|
110
|
+
def file
|
111
|
+
config_home = ENV.fetch(XDG_CONFIG_HOME, '~/.config')
|
112
|
+
File.expand_path(File.join(@tool_name, 'config'), config_home)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
sig { returns(T::Hash[String, T::Hash[String, String]]) }
|
118
|
+
def all_configs
|
119
|
+
ini.ini
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { returns(CLI::Kit::Ini) }
|
123
|
+
def ini
|
124
|
+
@ini ||= CLI::Kit::Ini.new(file).tap(&:parse)
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { void }
|
128
|
+
def write_config
|
129
|
+
all_configs.each do |section, sub_config|
|
130
|
+
all_configs.delete(section) if sub_config.empty?
|
131
|
+
end
|
132
|
+
FileUtils.mkdir_p(File.dirname(file))
|
133
|
+
File.write(file, to_s)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Exception
|
5
|
+
extend(T::Sig)
|
6
|
+
|
7
|
+
# You'd think instance variables @bug and @silent would work here. They
|
8
|
+
# don't. I'm not sure why. If you, the reader, want to take some time to
|
9
|
+
# figure it out, go ahead and refactor to that.
|
10
|
+
|
11
|
+
sig { returns(T::Boolean) }
|
12
|
+
def bug?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { returns(T::Boolean) }
|
17
|
+
def silent?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(bug: T::Boolean).void }
|
22
|
+
def bug!(bug = true)
|
23
|
+
singleton_class.define_method(:bug?) { bug }
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(silent: T::Boolean).void }
|
27
|
+
def silent!(silent = true)
|
28
|
+
singleton_class.define_method(:silent?) { silent }
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module CLI
|
6
|
+
module Kit
|
7
|
+
class ErrorHandler
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
ExceptionReporterOrProc = T.type_alias do
|
11
|
+
T.any(T.class_of(ExceptionReporter), T.proc.returns(T.class_of(ExceptionReporter)))
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { params(override_exception_handler: T.proc.params(arg0: Exception).returns(Integer)).void }
|
15
|
+
attr_writer :override_exception_handler
|
16
|
+
|
17
|
+
sig do
|
18
|
+
params(
|
19
|
+
log_file: T.nilable(String),
|
20
|
+
exception_reporter: ExceptionReporterOrProc,
|
21
|
+
tool_name: T.nilable(String),
|
22
|
+
dev_mode: T::Boolean,
|
23
|
+
).void
|
24
|
+
end
|
25
|
+
def initialize(log_file: nil, exception_reporter: NullExceptionReporter, tool_name: nil, dev_mode: false)
|
26
|
+
@log_file = log_file
|
27
|
+
@exception_reporter_or_proc = exception_reporter
|
28
|
+
@tool_name = tool_name
|
29
|
+
@dev_mode = dev_mode
|
30
|
+
end
|
31
|
+
|
32
|
+
class ExceptionReporter
|
33
|
+
extend T::Sig
|
34
|
+
extend T::Helpers
|
35
|
+
abstract!
|
36
|
+
|
37
|
+
sig { abstract.params(exception: T.nilable(Exception), logs: T.nilable(String)).void }
|
38
|
+
def self.report(exception, logs = nil); end
|
39
|
+
end
|
40
|
+
|
41
|
+
class NullExceptionReporter < ExceptionReporter
|
42
|
+
extend T::Sig
|
43
|
+
|
44
|
+
sig { override.params(_exception: T.nilable(Exception), _logs: T.nilable(String)).void }
|
45
|
+
def self.report(_exception, _logs = nil)
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { params(block: T.proc.void).returns(Integer) }
|
51
|
+
def call(&block)
|
52
|
+
# @at_exit_exception is set if handle_abort decides to submit an error.
|
53
|
+
# $ERROR_INFO is set if we terminate because of a signal.
|
54
|
+
at_exit { report_exception(@at_exit_exception || $ERROR_INFO) }
|
55
|
+
triage_all_exceptions(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { params(error: T.nilable(Exception)).void }
|
59
|
+
def report_exception(error)
|
60
|
+
if (notify_with = exception_for_submission(error))
|
61
|
+
logs = nil
|
62
|
+
if @log_file
|
63
|
+
logs = begin
|
64
|
+
File.read(@log_file)
|
65
|
+
rescue => e
|
66
|
+
"(#{e.class}: #{e.message})"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
exception_reporter.report(notify_with, logs)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
SIGNALS_THAT_ARENT_BUGS = [
|
74
|
+
'SIGTERM', 'SIGHUP', 'SIGINT',
|
75
|
+
].freeze
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Run the program, handling any errors that occur.
|
80
|
+
#
|
81
|
+
# Errors are printed to stderr unless they're #silent?, and are reported
|
82
|
+
# to bugsnag (by setting @at_exit_exeption for our at_exit handler) if
|
83
|
+
# they're #bug?
|
84
|
+
#
|
85
|
+
# Returns an exit status for the program.
|
86
|
+
sig { params(block: T.proc.void).returns(Integer) }
|
87
|
+
def triage_all_exceptions(&block)
|
88
|
+
begin
|
89
|
+
block.call
|
90
|
+
CLI::Kit::EXIT_SUCCESS
|
91
|
+
rescue Interrupt => e # Ctrl-C
|
92
|
+
# transform message, prevent bugsnag
|
93
|
+
exc = e.exception('Interrupt')
|
94
|
+
CLI::Kit.raise(exc, bug: false)
|
95
|
+
rescue Errno::ENOSPC => e
|
96
|
+
# transform message, prevent bugsnag
|
97
|
+
message = if @tool_name
|
98
|
+
"Your disk is full - {{command:#{@tool_name}}} requires free space to operate"
|
99
|
+
else
|
100
|
+
'Your disk is full - free space is required to operate'
|
101
|
+
end
|
102
|
+
exc = e.exception(message)
|
103
|
+
CLI::Kit.raise(exc, bug: false)
|
104
|
+
end
|
105
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
106
|
+
@at_exit_exception = e if e.bug?
|
107
|
+
|
108
|
+
if (eh = @override_exception_handler)
|
109
|
+
return eh.call(e)
|
110
|
+
end
|
111
|
+
|
112
|
+
raise(e) if @dev_mode && e.bug?
|
113
|
+
|
114
|
+
stderr_puts(e.message) unless e.silent?
|
115
|
+
e.bug? ? CLI::Kit::EXIT_BUG : CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
116
|
+
end
|
117
|
+
|
118
|
+
sig { params(error: T.nilable(Exception)).returns(T.nilable(Exception)) }
|
119
|
+
def exception_for_submission(error)
|
120
|
+
# happens on normal non-error termination
|
121
|
+
return(nil) if error.nil?
|
122
|
+
|
123
|
+
return(nil) unless error.bug?
|
124
|
+
|
125
|
+
case error
|
126
|
+
when SignalException
|
127
|
+
SIGNALS_THAT_ARENT_BUGS.include?(error.message) ? nil : error
|
128
|
+
when SystemExit # "exit N" called
|
129
|
+
case error.status
|
130
|
+
when CLI::Kit::EXIT_SUCCESS # submit nothing if it was `exit 0`
|
131
|
+
nil
|
132
|
+
when CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
133
|
+
# if it was `exit 30`, translate the exit code to 1, and submit
|
134
|
+
# nothing. 30 is used to signal normal failures that are not
|
135
|
+
# indicative of bugs. However, users should see it presented as 1.
|
136
|
+
exit(1)
|
137
|
+
else
|
138
|
+
# A weird termination status happened. `error.exception "message"`
|
139
|
+
# will maintain backtrace but allow us to set a message
|
140
|
+
error.exception("abnormal termination status: #{error.status}")
|
141
|
+
end
|
142
|
+
else
|
143
|
+
error
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
sig { params(message: String).void }
|
148
|
+
def stderr_puts(message)
|
149
|
+
$stderr.puts(CLI::UI.fmt("{{red:#{message}}}"))
|
150
|
+
rescue Errno::EPIPE
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
sig { returns(T.class_of(ExceptionReporter)) }
|
155
|
+
def exception_reporter
|
156
|
+
case @exception_reporter_or_proc
|
157
|
+
when Proc
|
158
|
+
@exception_reporter_or_proc.call
|
159
|
+
else
|
160
|
+
@exception_reporter_or_proc
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
require 'English'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module CLI
|
7
|
+
module Kit
|
8
|
+
class Executor
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(log_file: String).void }
|
12
|
+
def initialize(log_file:)
|
13
|
+
FileUtils.mkpath(File.dirname(log_file))
|
14
|
+
@log_file = log_file
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(command: T.class_of(CLI::Kit::BaseCommand), command_name: String, args: T::Array[String]).void }
|
18
|
+
def call(command, command_name, args)
|
19
|
+
with_traps do
|
20
|
+
with_logging do |id|
|
21
|
+
command.call(args, command_name)
|
22
|
+
rescue => e
|
23
|
+
begin
|
24
|
+
$stderr.puts "This command ran with ID: #{id}"
|
25
|
+
$stderr.puts 'Please include this information in any issues/report along with relevant logs'
|
26
|
+
rescue SystemCallError
|
27
|
+
# Outputting to stderr is best-effort. Avoid raising another error when outputting debug info so that
|
28
|
+
# we can detect and log the original error, which may even be the source of this error.
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
sig do
|
39
|
+
type_parameters(:T).params(block: T.proc.params(id: String).returns(T.type_parameter(:T)))
|
40
|
+
.returns(T.type_parameter(:T))
|
41
|
+
end
|
42
|
+
def with_logging(&block)
|
43
|
+
CLI::UI.log_output_to(@log_file) do
|
44
|
+
CLI::UI::StdoutRouter.with_id(on_streams: [CLI::UI::StdoutRouter.duplicate_output_to].compact) do |id|
|
45
|
+
block.call(id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
|
51
|
+
def with_traps(&block)
|
52
|
+
twrap('QUIT', method(:quit_handler)) do
|
53
|
+
twrap('INFO', method(:info_handler), &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
sig do
|
58
|
+
type_parameters(:T).params(signal: String, handler: Method,
|
59
|
+
block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T))
|
60
|
+
end
|
61
|
+
def twrap(signal, handler, &block)
|
62
|
+
return yield unless Signal.list.key?(signal)
|
63
|
+
|
64
|
+
begin
|
65
|
+
begin
|
66
|
+
prev_handler = trap(signal, handler)
|
67
|
+
installed = true
|
68
|
+
rescue ArgumentError
|
69
|
+
# If we couldn't install a signal handler because the signal is
|
70
|
+
# reserved, remember not to uninstall it later.
|
71
|
+
installed = false
|
72
|
+
end
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
trap(signal, prev_handler) if installed
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
sig { params(_sig: T.untyped).void }
|
80
|
+
def quit_handler(_sig)
|
81
|
+
z = caller
|
82
|
+
CLI::UI.raw do
|
83
|
+
$stderr.puts('SIGQUIT: quit')
|
84
|
+
$stderr.puts(z)
|
85
|
+
end
|
86
|
+
exit(CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG)
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(_sig: T.untyped).void }
|
90
|
+
def info_handler(_sig)
|
91
|
+
z = caller
|
92
|
+
CLI::UI.raw do
|
93
|
+
$stderr.puts('SIGINFO:')
|
94
|
+
$stderr.puts(z)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
# INI is a language similar to JSON or YAML, but simplied
|
7
|
+
# The spec is here: https://en.wikipedia.org/wiki/INI_file
|
8
|
+
# This parser includes supports for 2 very basic uses
|
9
|
+
# - Sections
|
10
|
+
# - Key Value Pairs (within and outside of the sections)
|
11
|
+
#
|
12
|
+
# [global]
|
13
|
+
# key = val
|
14
|
+
#
|
15
|
+
# Nothing else is supported right now
|
16
|
+
# See the ini_test.rb file for more examples
|
17
|
+
#
|
18
|
+
class Ini
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
sig { returns(T::Hash[String, T::Hash[String, String]]) }
|
22
|
+
attr_accessor :ini
|
23
|
+
|
24
|
+
sig do
|
25
|
+
params(path: T.nilable(String), config: T.nilable(String), default_section: String).void
|
26
|
+
end
|
27
|
+
def initialize(path = nil, config: nil, default_section: '[global]')
|
28
|
+
@config = if path && File.exist?(path)
|
29
|
+
File.readlines(path)
|
30
|
+
elsif config
|
31
|
+
config.lines
|
32
|
+
end
|
33
|
+
@ini = {}
|
34
|
+
@current_key = default_section
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { returns(T::Hash[String, T::Hash[String, String]]) }
|
38
|
+
def parse
|
39
|
+
return @ini if @config.nil?
|
40
|
+
|
41
|
+
@config.each do |l|
|
42
|
+
l.strip!
|
43
|
+
|
44
|
+
if section_designator?(l)
|
45
|
+
@current_key = l
|
46
|
+
else
|
47
|
+
k, v = l.split('=', 2).map(&:strip)
|
48
|
+
set_val(k, v) if k && v
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@ini
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { returns(String) }
|
56
|
+
def git_format
|
57
|
+
to_ini(git_format: true)
|
58
|
+
end
|
59
|
+
|
60
|
+
sig { returns(String) }
|
61
|
+
def to_s
|
62
|
+
to_ini
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
sig { params(git_format: T::Boolean).returns(String) }
|
68
|
+
def to_ini(git_format: false)
|
69
|
+
optional_tab = git_format ? "\t" : ''
|
70
|
+
str = []
|
71
|
+
@ini.each do |section_designator, section|
|
72
|
+
str << '' unless str.empty? || git_format
|
73
|
+
str << section_designator
|
74
|
+
section.each do |k, v|
|
75
|
+
str << "#{optional_tab}#{k} = #{v}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
str.join("\n")
|
79
|
+
end
|
80
|
+
|
81
|
+
sig { params(key: String, val: String).void }
|
82
|
+
def set_val(key, val)
|
83
|
+
current_key = @current_key
|
84
|
+
@ini[current_key] ||= {}
|
85
|
+
@ini[current_key][key] = val
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { params(k: String).returns(T::Boolean) }
|
89
|
+
def section_designator?(k)
|
90
|
+
k.start_with?('[') && k.end_with?(']')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# typed: true
|
2
|
+
# Copyright (c) 2014-2016 Yuki Nishijima
|
3
|
+
|
4
|
+
# MIT License
|
5
|
+
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
require 'cli/kit'
|
26
|
+
|
27
|
+
module CLI
|
28
|
+
module Kit
|
29
|
+
module Levenshtein
|
30
|
+
extend T::Sig
|
31
|
+
|
32
|
+
# This code is based directly on the Text gem implementation
|
33
|
+
# Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
|
34
|
+
#
|
35
|
+
# Returns a value representing the "cost" of transforming str1 into str2
|
36
|
+
sig { params(str1: String, str2: String).returns(Integer) }
|
37
|
+
def distance(str1, str2)
|
38
|
+
n = str1.length
|
39
|
+
m = str2.length
|
40
|
+
return m if n.zero?
|
41
|
+
return n if m.zero?
|
42
|
+
|
43
|
+
d = (0..m).to_a
|
44
|
+
x = 0
|
45
|
+
|
46
|
+
# to avoid duplicating an enumerable object, create it outside of the loop
|
47
|
+
str2_codepoints = str2.codepoints
|
48
|
+
|
49
|
+
str1.each_codepoint.with_index(1) do |char1, i|
|
50
|
+
j = 0
|
51
|
+
while j < m
|
52
|
+
cost = char1 == str2_codepoints[j] ? 0 : 1
|
53
|
+
x = min3(
|
54
|
+
T.must(d[j + 1]) + 1, # insertion
|
55
|
+
i + 1, # deletion
|
56
|
+
T.must(d[j]) + cost # substitution
|
57
|
+
)
|
58
|
+
d[j] = i
|
59
|
+
i = x
|
60
|
+
|
61
|
+
j += 1
|
62
|
+
end
|
63
|
+
d[m] = x
|
64
|
+
end
|
65
|
+
|
66
|
+
x
|
67
|
+
end
|
68
|
+
module_function :distance
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# detects the minimum value out of three arguments. This method is
|
73
|
+
# faster than `[a, b, c].min` and puts less GC pressure.
|
74
|
+
# See https://github.com/yuki24/did_you_mean/pull/1 for a performance
|
75
|
+
# benchmark.
|
76
|
+
sig { params(a: Integer, b: Integer, c: Integer).returns(Integer) }
|
77
|
+
def min3(a, b, c)
|
78
|
+
if a < b && a < c
|
79
|
+
a
|
80
|
+
elsif b < c
|
81
|
+
b
|
82
|
+
else
|
83
|
+
c
|
84
|
+
end
|
85
|
+
end
|
86
|
+
module_function :min3
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|