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,133 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module Util
|
7
|
+
class << self
|
8
|
+
extend T::Sig
|
9
|
+
#
|
10
|
+
# Converts an integer representing bytes into a human readable format
|
11
|
+
#
|
12
|
+
sig { params(bytes: Integer, precision: Integer, space: T::Boolean).returns(String) }
|
13
|
+
def to_filesize(bytes, precision: 2, space: false)
|
14
|
+
to_si_scale(bytes, 'B', precision: precision, space: space, factor: 1024)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts a number to a human readable format on the SI scale
|
18
|
+
#
|
19
|
+
sig do
|
20
|
+
params(number: Numeric, unit: String, factor: Integer, precision: Integer,
|
21
|
+
space: T::Boolean).returns(String)
|
22
|
+
end
|
23
|
+
def to_si_scale(number, unit = '', factor: 1000, precision: 2, space: false)
|
24
|
+
raise ArgumentError, 'factor should only be 1000 or 1024' unless [1000, 1024].include?(factor)
|
25
|
+
|
26
|
+
small_scale = ['m', 'µ', 'n', 'p', 'f', 'a', 'z', 'y']
|
27
|
+
big_scale = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
|
28
|
+
negative = number < 0
|
29
|
+
number = number.abs.to_f
|
30
|
+
|
31
|
+
if number == 0.0 || number.between?(1, factor)
|
32
|
+
prefix = ''
|
33
|
+
scale = 0
|
34
|
+
else
|
35
|
+
scale = Math.log(number, factor).floor
|
36
|
+
if number < 1
|
37
|
+
index = [-scale - 1, small_scale.length].min
|
38
|
+
scale = -(index + 1)
|
39
|
+
prefix = T.must(small_scale[index])
|
40
|
+
else
|
41
|
+
index = [scale - 1, big_scale.length].min
|
42
|
+
scale = index + 1
|
43
|
+
prefix = T.must(big_scale[index])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
divider = (factor**scale)
|
48
|
+
fnum = (number / divider.to_f).round(precision)
|
49
|
+
|
50
|
+
# Trim useless decimal
|
51
|
+
fnum = fnum.to_i if (fnum.to_i.to_f * divider.to_f) == number
|
52
|
+
|
53
|
+
fnum = -fnum if negative
|
54
|
+
if space
|
55
|
+
prefix = ' ' + prefix
|
56
|
+
end
|
57
|
+
|
58
|
+
"#{fnum}#{prefix}#{unit}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Dir.chdir, when invoked in block form, complains when we call chdir
|
62
|
+
# again recursively. There's no apparent good reason for this, so we
|
63
|
+
# simply implement our own block form of Dir.chdir here.
|
64
|
+
sig do
|
65
|
+
type_parameters(:T).params(dir: String, block: T.proc.returns(T.type_parameter(:T)))
|
66
|
+
.returns(T.type_parameter(:T))
|
67
|
+
end
|
68
|
+
def with_dir(dir, &block)
|
69
|
+
prev = Dir.pwd
|
70
|
+
begin
|
71
|
+
Dir.chdir(dir)
|
72
|
+
yield
|
73
|
+
ensure
|
74
|
+
Dir.chdir(prev)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Must call retry_after on the result in order to execute the block
|
79
|
+
#
|
80
|
+
# Example usage:
|
81
|
+
#
|
82
|
+
# CLI::Kit::Util.begin do
|
83
|
+
# might_raise_if_costly_prep_not_done()
|
84
|
+
# end.retry_after(ExpectedError) do
|
85
|
+
# costly_prep()
|
86
|
+
# end
|
87
|
+
sig do
|
88
|
+
type_parameters(:T).params(block_that_might_raise: T.proc.returns(T.type_parameter(:T)))
|
89
|
+
.returns(Retrier[T.type_parameter(:T)])
|
90
|
+
end
|
91
|
+
def begin(&block_that_might_raise)
|
92
|
+
Retrier.new(block_that_might_raise)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class Retrier
|
97
|
+
extend T::Sig
|
98
|
+
extend T::Generic
|
99
|
+
|
100
|
+
BlockReturnType = type_member
|
101
|
+
|
102
|
+
sig { params(block_that_might_raise: T.proc.returns(BlockReturnType)).void }
|
103
|
+
def initialize(block_that_might_raise)
|
104
|
+
@block_that_might_raise = block_that_might_raise
|
105
|
+
end
|
106
|
+
|
107
|
+
sig do
|
108
|
+
params(
|
109
|
+
exception: T.class_of(Exception),
|
110
|
+
retries: Integer,
|
111
|
+
before_retry: T.nilable(T.proc.params(e: Exception).void)
|
112
|
+
).returns(BlockReturnType)
|
113
|
+
end
|
114
|
+
def retry_after(exception = StandardError, retries: 1, &before_retry)
|
115
|
+
@block_that_might_raise.call
|
116
|
+
rescue exception => e
|
117
|
+
raise if (retries -= 1) < 0
|
118
|
+
|
119
|
+
if before_retry
|
120
|
+
if before_retry.arity == 0
|
121
|
+
T.cast(before_retry, T.proc.void).call
|
122
|
+
else
|
123
|
+
T.cast(before_retry, T.proc.params(e: Exception).void).call(e)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
retry
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private_constant :Retrier
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/ui'
|
3
|
+
|
4
|
+
unless defined?(T)
|
5
|
+
require('cli/kit/sorbet_runtime_stub')
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'cli/kit/core_ext'
|
9
|
+
|
10
|
+
module CLI
|
11
|
+
module Kit
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
autoload :Args, 'cli/kit/args'
|
15
|
+
autoload :BaseCommand, 'cli/kit/base_command'
|
16
|
+
autoload :CommandRegistry, 'cli/kit/command_registry'
|
17
|
+
autoload :CommandHelp, 'cli/kit/command_help'
|
18
|
+
autoload :Config, 'cli/kit/config'
|
19
|
+
autoload :ErrorHandler, 'cli/kit/error_handler'
|
20
|
+
autoload :Executor, 'cli/kit/executor'
|
21
|
+
autoload :Ini, 'cli/kit/ini'
|
22
|
+
autoload :Levenshtein, 'cli/kit/levenshtein'
|
23
|
+
autoload :Logger, 'cli/kit/logger'
|
24
|
+
autoload :Opts, 'cli/kit/opts'
|
25
|
+
autoload :Resolver, 'cli/kit/resolver'
|
26
|
+
autoload :Support, 'cli/kit/support'
|
27
|
+
autoload :System, 'cli/kit/system'
|
28
|
+
autoload :Util, 'cli/kit/util'
|
29
|
+
|
30
|
+
EXIT_FAILURE_BUT_NOT_BUG = 30
|
31
|
+
EXIT_BUG = 1
|
32
|
+
EXIT_SUCCESS = 0
|
33
|
+
|
34
|
+
# Abort, Bug, AbortSilent, and BugSilent are four ways of immediately bailing
|
35
|
+
# on command-line execution when an unrecoverable error occurs.
|
36
|
+
#
|
37
|
+
# Note that these don't inherit from StandardError, and so are not caught by
|
38
|
+
# a bare `rescue => e`.
|
39
|
+
#
|
40
|
+
# * Abort prints its message in red and exits 1;
|
41
|
+
# * Bug additionally submits the exception to the exception_reporter passed to
|
42
|
+
# `CLI::Kit::ErrorHandler.new`
|
43
|
+
# * AbortSilent and BugSilent do the same as above, but do not print
|
44
|
+
# messages before exiting.
|
45
|
+
#
|
46
|
+
# Treat these like panic() in Go:
|
47
|
+
# * Don't rescue them. Use a different Exception class if you plan to recover;
|
48
|
+
# * Provide a useful message, since it will be presented in brief to the
|
49
|
+
# user, and will be useful for debugging.
|
50
|
+
# * Avoid using it if it does actually make sense to recover from an error.
|
51
|
+
#
|
52
|
+
# Additionally:
|
53
|
+
# * Do not subclass these.
|
54
|
+
# * Only use AbortSilent or BugSilent if you prefer to print a more
|
55
|
+
# contextualized error than Abort or Bug would present to the user.
|
56
|
+
# * In general, don't attach a message to AbortSilent or BugSilent.
|
57
|
+
# * Never raise GenericAbort directly.
|
58
|
+
# * Think carefully about whether Abort or Bug is more appropriate. Is this
|
59
|
+
# a bug in the tool? Or is it just user error, transient network
|
60
|
+
# failure, etc.?
|
61
|
+
# * One case where it's ok to rescue (cli-kit internals or tests aside):
|
62
|
+
# 1. rescue Abort or Bug
|
63
|
+
# 2. Print a contextualized error message
|
64
|
+
# 3. Re-raise AbortSilent or BugSilent respectively.
|
65
|
+
#
|
66
|
+
# These aren't the only exceptions that can carry this 'bug' and 'silent'
|
67
|
+
# metadata, however:
|
68
|
+
#
|
69
|
+
# If you raise an exception with `CLI::Kit.raise(..., bug: x, silent: y)`,
|
70
|
+
# those last two (optional) keyword arguments will attach the metadata to
|
71
|
+
# whatever exception you raise. This is interpreted later in the
|
72
|
+
# ErrorHandler to decide how to print output and whether to submit the
|
73
|
+
# exception to bugsnag.
|
74
|
+
GenericAbort = Class.new(Exception) # rubocop:disable Lint/InheritException
|
75
|
+
|
76
|
+
class Abort < GenericAbort # bug:false; silent: false
|
77
|
+
extend(T::Sig)
|
78
|
+
|
79
|
+
sig { returns(T::Boolean) }
|
80
|
+
def bug?
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Bug < GenericAbort # bug:true; silent:false
|
86
|
+
end
|
87
|
+
|
88
|
+
class BugSilent < GenericAbort # bug:true; silent:true
|
89
|
+
extend(T::Sig)
|
90
|
+
|
91
|
+
sig { returns(T::Boolean) }
|
92
|
+
def silent?
|
93
|
+
true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class AbortSilent < GenericAbort # bug:false; silent:true
|
98
|
+
extend(T::Sig)
|
99
|
+
|
100
|
+
sig { returns(T::Boolean) }
|
101
|
+
def bug?
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
sig { returns(T::Boolean) }
|
106
|
+
def silent?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Mirrors the API of Kernel#raise, but with the addition of a few new
|
112
|
+
# optional keyword arguments. `bug` and `silent` attach metadata to the
|
113
|
+
# exception being raised, which is interpreted later in the ErrorHandler to
|
114
|
+
# decide what to print and whether to submit to bugsnag.
|
115
|
+
#
|
116
|
+
# `depth` is used to trim leading elements of the backtrace. If you wrap
|
117
|
+
# this method in your own wrapper, you'll want to pass `depth: 2`, for
|
118
|
+
# example.
|
119
|
+
sig do
|
120
|
+
params(
|
121
|
+
exception: T.any(Class, String, Exception),
|
122
|
+
string: T.untyped,
|
123
|
+
array: T.nilable(T::Array[String]),
|
124
|
+
cause: T.nilable(Exception),
|
125
|
+
bug: T.nilable(T::Boolean),
|
126
|
+
silent: T.nilable(T::Boolean),
|
127
|
+
depth: Integer,
|
128
|
+
).returns(T.noreturn)
|
129
|
+
end
|
130
|
+
def self.raise(
|
131
|
+
# default arguments
|
132
|
+
exception = T.unsafe(nil), string = T.unsafe(nil), array = T.unsafe(nil), cause: $ERROR_INFO,
|
133
|
+
# new arguments
|
134
|
+
bug: nil, silent: nil, depth: 1
|
135
|
+
)
|
136
|
+
if array
|
137
|
+
T.unsafe(Kernel).raise(exception, string, array, cause: cause)
|
138
|
+
elsif string
|
139
|
+
T.unsafe(Kernel).raise(exception, string, Kernel.caller(depth), cause: cause)
|
140
|
+
elsif exception.is_a?(String)
|
141
|
+
T.unsafe(Kernel).raise(RuntimeError, exception, Kernel.caller(depth), cause: cause)
|
142
|
+
else
|
143
|
+
T.unsafe(Kernel).raise(exception, exception.message, Kernel.caller(depth), cause: cause)
|
144
|
+
end
|
145
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
146
|
+
e.bug!(bug) unless bug.nil?
|
147
|
+
e.silent!(silent) unless silent.nil?
|
148
|
+
Kernel.raise(e, cause: cause)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
c9b77b8073f9242ba8d0c8c5651264810b23cb0d
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/ui'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
module ANSI
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
ESC = "\x1b"
|
10
|
+
|
11
|
+
# ANSI escape sequences (like \x1b[31m) have zero width.
|
12
|
+
# when calculating the padding width, we must exclude them.
|
13
|
+
# This also implements a basic version of utf8 character width calculation like
|
14
|
+
# we could get for real from something like utf8proc.
|
15
|
+
#
|
16
|
+
sig { params(str: String).returns(Integer) }
|
17
|
+
def self.printing_width(str)
|
18
|
+
zwj = T.let(false, T::Boolean)
|
19
|
+
strip_codes(str).codepoints.reduce(0) do |acc, cp|
|
20
|
+
if zwj
|
21
|
+
zwj = false
|
22
|
+
next acc
|
23
|
+
end
|
24
|
+
case cp
|
25
|
+
when 0x200d # zero-width joiner
|
26
|
+
zwj = true
|
27
|
+
acc
|
28
|
+
when "\n"
|
29
|
+
acc
|
30
|
+
else
|
31
|
+
acc + 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Strips ANSI codes from a str
|
37
|
+
#
|
38
|
+
# ==== Attributes
|
39
|
+
#
|
40
|
+
# - +str+ - The string from which to strip codes
|
41
|
+
#
|
42
|
+
sig { params(str: String).returns(String) }
|
43
|
+
def self.strip_codes(str)
|
44
|
+
str.gsub(/\x1b\[[\d;]+[A-z]|\r/, '')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns an ANSI control sequence
|
48
|
+
#
|
49
|
+
# ==== Attributes
|
50
|
+
#
|
51
|
+
# - +args+ - Argument to pass to the ANSI control sequence
|
52
|
+
# - +cmd+ - ANSI control sequence Command
|
53
|
+
#
|
54
|
+
sig { params(args: String, cmd: String).returns(String) }
|
55
|
+
def self.control(args, cmd)
|
56
|
+
ESC + '[' + args + cmd
|
57
|
+
end
|
58
|
+
|
59
|
+
# https://en.wikipedia.org/wiki/ANSI_escape_code#graphics
|
60
|
+
sig { params(params: String).returns(String) }
|
61
|
+
def self.sgr(params)
|
62
|
+
control(params, 'm')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Cursor Movement
|
66
|
+
|
67
|
+
# Move the cursor up n lines
|
68
|
+
#
|
69
|
+
# ==== Attributes
|
70
|
+
#
|
71
|
+
# * +n+ - number of lines by which to move the cursor up
|
72
|
+
#
|
73
|
+
sig { params(n: Integer).returns(String) }
|
74
|
+
def self.cursor_up(n = 1)
|
75
|
+
return '' if n.zero?
|
76
|
+
|
77
|
+
control(n.to_s, 'A')
|
78
|
+
end
|
79
|
+
|
80
|
+
# Move the cursor down n lines
|
81
|
+
#
|
82
|
+
# ==== Attributes
|
83
|
+
#
|
84
|
+
# * +n+ - number of lines by which to move the cursor down
|
85
|
+
#
|
86
|
+
sig { params(n: Integer).returns(String) }
|
87
|
+
def self.cursor_down(n = 1)
|
88
|
+
return '' if n.zero?
|
89
|
+
|
90
|
+
control(n.to_s, 'B')
|
91
|
+
end
|
92
|
+
|
93
|
+
# Move the cursor forward n columns
|
94
|
+
#
|
95
|
+
# ==== Attributes
|
96
|
+
#
|
97
|
+
# * +n+ - number of columns by which to move the cursor forward
|
98
|
+
#
|
99
|
+
sig { params(n: Integer).returns(String) }
|
100
|
+
def self.cursor_forward(n = 1)
|
101
|
+
return '' if n.zero?
|
102
|
+
|
103
|
+
control(n.to_s, 'C')
|
104
|
+
end
|
105
|
+
|
106
|
+
# Move the cursor back n columns
|
107
|
+
#
|
108
|
+
# ==== Attributes
|
109
|
+
#
|
110
|
+
# * +n+ - number of columns by which to move the cursor back
|
111
|
+
#
|
112
|
+
sig { params(n: Integer).returns(String) }
|
113
|
+
def self.cursor_back(n = 1)
|
114
|
+
return '' if n.zero?
|
115
|
+
|
116
|
+
control(n.to_s, 'D')
|
117
|
+
end
|
118
|
+
|
119
|
+
# Move the cursor to a specific column
|
120
|
+
#
|
121
|
+
# ==== Attributes
|
122
|
+
#
|
123
|
+
# * +n+ - The column to move to
|
124
|
+
#
|
125
|
+
sig { params(n: Integer).returns(String) }
|
126
|
+
def self.cursor_horizontal_absolute(n = 1)
|
127
|
+
cmd = control(n.to_s, 'G')
|
128
|
+
cmd += cursor_back if CLI::UI::OS.current.shift_cursor_back_on_horizontal_absolute?
|
129
|
+
cmd
|
130
|
+
end
|
131
|
+
|
132
|
+
# Show the cursor
|
133
|
+
#
|
134
|
+
sig { returns(String) }
|
135
|
+
def self.show_cursor
|
136
|
+
control('', '?25h')
|
137
|
+
end
|
138
|
+
|
139
|
+
# Hide the cursor
|
140
|
+
#
|
141
|
+
sig { returns(String) }
|
142
|
+
def self.hide_cursor
|
143
|
+
control('', '?25l')
|
144
|
+
end
|
145
|
+
|
146
|
+
# Save the cursor position
|
147
|
+
#
|
148
|
+
sig { returns(String) }
|
149
|
+
def self.cursor_save
|
150
|
+
control('', 's')
|
151
|
+
end
|
152
|
+
|
153
|
+
# Restore the saved cursor position
|
154
|
+
#
|
155
|
+
sig { returns(String) }
|
156
|
+
def self.cursor_restore
|
157
|
+
control('', 'u')
|
158
|
+
end
|
159
|
+
|
160
|
+
# Move to the next line
|
161
|
+
#
|
162
|
+
sig { returns(String) }
|
163
|
+
def self.next_line
|
164
|
+
cursor_down + cursor_horizontal_absolute
|
165
|
+
end
|
166
|
+
|
167
|
+
# Move to the previous line
|
168
|
+
#
|
169
|
+
sig { returns(String) }
|
170
|
+
def self.previous_line
|
171
|
+
cursor_up + cursor_horizontal_absolute
|
172
|
+
end
|
173
|
+
|
174
|
+
sig { returns(String) }
|
175
|
+
def self.clear_to_end_of_line
|
176
|
+
control('', 'K')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/ui'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
class Color
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(String) }
|
10
|
+
attr_reader :sgr, :code
|
11
|
+
|
12
|
+
sig { returns(Symbol) }
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# Creates a new color mapping
|
16
|
+
# Signatures can be found here:
|
17
|
+
# https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
18
|
+
#
|
19
|
+
# ==== Attributes
|
20
|
+
#
|
21
|
+
# * +sgr+ - The color signature
|
22
|
+
# * +name+ - The name of the color
|
23
|
+
#
|
24
|
+
sig { params(sgr: String, name: Symbol).void }
|
25
|
+
def initialize(sgr, name)
|
26
|
+
@sgr = sgr
|
27
|
+
@code = CLI::UI::ANSI.sgr(sgr)
|
28
|
+
@name = name
|
29
|
+
end
|
30
|
+
|
31
|
+
RED = new('31', :red)
|
32
|
+
GREEN = new('32', :green)
|
33
|
+
YELLOW = new('33', :yellow)
|
34
|
+
# default blue is low-contrast against black in some default terminal color scheme
|
35
|
+
BLUE = new('94', :blue) # 9x = high-intensity fg color x
|
36
|
+
MAGENTA = new('35', :magenta)
|
37
|
+
CYAN = new('36', :cyan)
|
38
|
+
RESET = new('0', :reset)
|
39
|
+
BOLD = new('1', :bold)
|
40
|
+
WHITE = new('97', :white)
|
41
|
+
|
42
|
+
# 240 is very dark gray; 255 is very light gray. 244 is somewhat dark.
|
43
|
+
GRAY = new('38;5;244', :grey)
|
44
|
+
|
45
|
+
MAP = {
|
46
|
+
red: RED,
|
47
|
+
green: GREEN,
|
48
|
+
yellow: YELLOW,
|
49
|
+
blue: BLUE,
|
50
|
+
magenta: MAGENTA,
|
51
|
+
cyan: CYAN,
|
52
|
+
reset: RESET,
|
53
|
+
bold: BOLD,
|
54
|
+
gray: GRAY,
|
55
|
+
}.freeze
|
56
|
+
|
57
|
+
class InvalidColorName < ArgumentError
|
58
|
+
extend T::Sig
|
59
|
+
|
60
|
+
sig { params(name: Symbol).void }
|
61
|
+
def initialize(name)
|
62
|
+
super
|
63
|
+
@name = name
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { returns(String) }
|
67
|
+
def message
|
68
|
+
keys = Color.available.map(&:inspect).join(',')
|
69
|
+
"invalid color: #{@name.inspect} " \
|
70
|
+
"-- must be one of CLI::UI::Color.available (#{keys})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Looks up a color code by name
|
75
|
+
#
|
76
|
+
# ==== Raises
|
77
|
+
# Raises a InvalidColorName if the color is not available
|
78
|
+
# You likely need to add it to the +MAP+ or you made a typo
|
79
|
+
#
|
80
|
+
# ==== Returns
|
81
|
+
# Returns a color code
|
82
|
+
#
|
83
|
+
sig { params(name: T.any(Symbol, String)).returns(Color) }
|
84
|
+
def self.lookup(name)
|
85
|
+
MAP.fetch(name.to_sym)
|
86
|
+
rescue KeyError
|
87
|
+
raise InvalidColorName, name
|
88
|
+
end
|
89
|
+
|
90
|
+
# All available colors by name
|
91
|
+
#
|
92
|
+
sig { returns(T::Array[Symbol]) }
|
93
|
+
def self.available
|
94
|
+
MAP.keys
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|