gorails 0.1.2 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/Gemfile.lock +1 -6
- data/bin/update-deps +95 -0
- data/exe/gorails +2 -1
- data/gorails.gemspec +0 -2
- data/lib/gorails/commands/railsbytes.rb +10 -10
- data/lib/gorails/commands/version.rb +15 -0
- data/lib/gorails/commands.rb +2 -5
- data/lib/gorails/version.rb +1 -1
- data/lib/gorails.rb +11 -20
- 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 +58 -29
@@ -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
|