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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e64a68f47a33d15b16065e2882e19ef2fdf815e484bf5674900b6d214193bd50
|
4
|
+
data.tar.gz: ad7d91c0bdb7a3e9a0c41a148e4ab5658a7ad12478b5294c5ffb3de853695abb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e11a575cd81bfaaadd2ae294dc2b03a9382f70cada35820da6abeabda074cbd2ad93c58b896bf3b2073524ebfa07c2eb63d94ce8f5d5d4e9a341637be0c04eb1
|
7
|
+
data.tar.gz: d4c107348f4ed57cae042618a3ff2483129c0cadf5aed553acaed1affc78d161c3dacb1ba7b29904024944da50350c00c9a3f8613df9485f89c077430a05d886
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
## [0.1.
|
3
|
+
## [0.1.4] - 2022-03-13
|
4
|
+
|
5
|
+
- Add `version` command
|
6
|
+
|
7
|
+
## [0.1.3] - 2022-03-13
|
8
|
+
|
9
|
+
- Vendor cli-kit and cli-ui for speed
|
10
|
+
This also skips loading bundler/setup which looks for a Gemfile
|
11
|
+
|
12
|
+
## [0.1.2] - 2022-03-13
|
13
|
+
|
14
|
+
- Add ability to view and apply individual Railsbytes
|
15
|
+
|
16
|
+
## [0.1.1] - 2022-03-13
|
4
17
|
|
5
18
|
- Initial release
|
19
|
+
|
20
|
+
## [0.1.0] - 2021-12-16
|
21
|
+
|
22
|
+
- Empty release
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gorails (0.1.
|
5
|
-
cli-kit (~> 4.0.0)
|
6
|
-
cli-ui (~> 1.5.1)
|
4
|
+
gorails (0.1.5)
|
7
5
|
|
8
6
|
GEM
|
9
7
|
remote: https://rubygems.org/
|
@@ -11,9 +9,6 @@ GEM
|
|
11
9
|
ansi (1.5.0)
|
12
10
|
ast (2.4.2)
|
13
11
|
builder (3.2.4)
|
14
|
-
cli-kit (4.0.0)
|
15
|
-
cli-ui (>= 1.1.4)
|
16
|
-
cli-ui (1.5.1)
|
17
12
|
metaclass (0.0.4)
|
18
13
|
minitest (5.15.0)
|
19
14
|
minitest-reporters (1.5.0)
|
data/bin/update-deps
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../../vendor/deps/cli-ui/lib", __FILE__))
|
4
|
+
require 'open3'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
def fmt(tag, msg)
|
8
|
+
# Not really CLI::UI.fmt compatible: no nesting support, for example.
|
9
|
+
# While we could pull in CLI::UI here, it makes it more difficult to
|
10
|
+
# bootstrap a new project and to fix a broken vendor.
|
11
|
+
fmt_msg = msg
|
12
|
+
.gsub(/{{yellow:(.*?)}}/, "\x1b[33m\\1\x1b[31m")
|
13
|
+
.gsub(/{{green:(.*?)}}/, "\x1b[32m\\1\x1b[31m")
|
14
|
+
.gsub(/{{blue:(.*?)}}/, "\x1b[34m\\1\x1b[31m")
|
15
|
+
.gsub(/{{bold_blue:(.*?)}}/, "\x1b[1;34m\\1\x1b[0;31m")
|
16
|
+
.gsub(/{{bold_green:(.*?)}}/, "\x1b[1;32m\\1\x1b[0;31m")
|
17
|
+
"\x1b[1;31m[#{tag}] \x1b[0;31m#{fmt_msg}\x1b[0m"
|
18
|
+
end
|
19
|
+
|
20
|
+
def bail(msg)
|
21
|
+
STDERR.puts(fmt("ERROR", msg))
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def warn(msg)
|
26
|
+
STDERR.puts(fmt("WARNING", msg))
|
27
|
+
end
|
28
|
+
|
29
|
+
def source_path
|
30
|
+
File.expand_path(ENV.fetch('SOURCE_ROOT', File.expand_path('../../..', __FILE__)))
|
31
|
+
end
|
32
|
+
|
33
|
+
deps = %w(cli-ui cli-kit)
|
34
|
+
|
35
|
+
deps.each do |dep|
|
36
|
+
path = File.expand_path(dep, source_path)
|
37
|
+
|
38
|
+
unless Dir.exist?(path)
|
39
|
+
bail(
|
40
|
+
"dependency is not checked out: {{yellow:#{dep}}}.\n" \
|
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" \
|
43
|
+
" Alternatively, you can set {{bold_blue:SOURCE_ROOT}} to a directory containing {{yellow:#{dep}}}.\n" \
|
44
|
+
" {{bold_blue:SOURCE_ROOT}} defaults to {{bold_blue:../}}."
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
head_sha = nil
|
49
|
+
dirty = false
|
50
|
+
|
51
|
+
Dir.chdir(path) do
|
52
|
+
_, _, stat = Open3.capture3('git fetch origin main')
|
53
|
+
bail("couldn't git fetch in dependency: {{yellow:#{dep}}}") unless stat.success?
|
54
|
+
|
55
|
+
head_sha, stat = Open3.capture2('git rev-parse HEAD')
|
56
|
+
bail("couldn't determine HEAD: {{yellow:#{dep}}}") unless stat.success?
|
57
|
+
head_sha.chomp!
|
58
|
+
|
59
|
+
fetch_head_sha, stat = Open3.capture2('git rev-parse FETCH_HEAD')
|
60
|
+
bail("couldn't determine FETCH_HEAD: {{yellow:#{dep}}}") unless stat.success?
|
61
|
+
fetch_head_sha.chomp!
|
62
|
+
|
63
|
+
git_status, stat = Open3.capture2('git status --porcelain')
|
64
|
+
bail("couldn't determine git status: {{yellow:#{dep}}}") unless stat.success?
|
65
|
+
|
66
|
+
if head_sha != fetch_head_sha
|
67
|
+
warn(
|
68
|
+
"Copying files from {{yellow:#{path}}} to satisfy dependency {{yellow:#{dep}}}.\n" \
|
69
|
+
" However, the repo at {{yellow:#{path}}} isn't up to date.\n" \
|
70
|
+
" The checked-out revision is {{yellow:#{head_sha[0..8]}}}, and "\
|
71
|
+
"{{yellow:origin/master}} is {{yellow:#{fetch_head_sha[0..8]}}}.\n" \
|
72
|
+
" Unless you know what you're doing, you should {{green:cd}} to that repo and {{green:git pull}}, then run this again."
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
unless git_status.chomp.empty?
|
77
|
+
dirty = true
|
78
|
+
warn("importing uncommitted changes from dependency: {{yellow:#{dep}}}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
depdir = File.expand_path("../../vendor/deps/#{dep}", __FILE__)
|
83
|
+
FileUtils.rm_rf(depdir)
|
84
|
+
FileUtils.mkdir_p(depdir)
|
85
|
+
dstlib = File.expand_path('lib', depdir)
|
86
|
+
srclib = File.expand_path('lib', path)
|
87
|
+
|
88
|
+
FileUtils.cp_r(srclib, dstlib)
|
89
|
+
|
90
|
+
rev = head_sha
|
91
|
+
rev << " (dirty)" if dirty
|
92
|
+
rev << "\n"
|
93
|
+
|
94
|
+
File.write("#{depdir}/REVISION", rev)
|
95
|
+
end
|
data/exe/gorails
CHANGED
@@ -7,9 +7,10 @@ unshift_path = ->(path) {
|
|
7
7
|
p = File.expand_path("../../#{path}", __FILE__)
|
8
8
|
$LOAD_PATH.unshift(p) unless $LOAD_PATH.include?(p)
|
9
9
|
}
|
10
|
+
unshift_path.call("vendor/deps/cli-ui/lib")
|
11
|
+
unshift_path.call("vendor/deps/cli-kit/lib")
|
10
12
|
unshift_path.call("lib")
|
11
13
|
|
12
|
-
require "bundler/setup"
|
13
14
|
require "gorails"
|
14
15
|
|
15
16
|
exit(Gorails::ErrorHandler.call do
|
data/gorails.gemspec
CHANGED
@@ -29,8 +29,6 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
30
|
spec.require_paths = ["lib"]
|
31
31
|
|
32
|
-
spec.add_dependency "cli-kit", "~> 4.0.0"
|
33
|
-
spec.add_dependency "cli-ui", "~> 1.5.1"
|
34
32
|
spec.add_development_dependency "standard"
|
35
33
|
|
36
34
|
# For more information and examples about making a new gem, checkout our
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "gorails"
|
2
|
-
require "
|
2
|
+
require "bundler"
|
3
3
|
require "json"
|
4
|
+
require "net/http"
|
4
5
|
|
5
6
|
module Gorails
|
6
7
|
module Commands
|
@@ -44,21 +45,20 @@ module Gorails
|
|
44
45
|
puts
|
45
46
|
|
46
47
|
CLI::UI::Prompt.ask("What would you like to do?") do |handler|
|
47
|
-
handler.option("View source") do |selection|
|
48
|
-
puts
|
49
|
-
puts byte["script"]
|
50
|
-
end
|
51
|
-
|
52
48
|
handler.option("Apply Railsbyte") do |selection|
|
53
49
|
puts
|
54
50
|
puts "Running Railsbyte..."
|
51
|
+
puts
|
52
|
+
|
53
|
+
system "rails app:template LOCATION=\"https://railsbytes.com/script/#{id}\""
|
54
|
+
end
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
handler.option("View source") do |selection|
|
57
|
+
puts
|
58
|
+
puts byte["script"]
|
59
59
|
end
|
60
60
|
|
61
|
-
handler.option("
|
61
|
+
handler.option("Exit") { |selection| exit 0 }
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "gorails"
|
2
|
+
|
3
|
+
module Gorails
|
4
|
+
module Commands
|
5
|
+
class Version < Gorails::Command
|
6
|
+
def call(_args, _name)
|
7
|
+
puts Gorails::VERSION
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.help
|
11
|
+
"Prints the version.\nUsage: {{command:#{Gorails::TOOL_NAME} version}}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/gorails/commands.rb
CHANGED
@@ -2,10 +2,7 @@ require "gorails"
|
|
2
2
|
|
3
3
|
module Gorails
|
4
4
|
module Commands
|
5
|
-
Registry = CLI::Kit::CommandRegistry.new(
|
6
|
-
default: "help",
|
7
|
-
contextual_resolver: nil
|
8
|
-
)
|
5
|
+
Registry = CLI::Kit::CommandRegistry.new(default: "help")
|
9
6
|
|
10
7
|
def self.register(const, cmd, path)
|
11
8
|
autoload(const, path)
|
@@ -16,7 +13,7 @@ module Gorails
|
|
16
13
|
register :Jobs, "jobs", "gorails/commands/jobs"
|
17
14
|
register :Jumpstart, "jumpstart", "gorails/commands/jumpstart"
|
18
15
|
register :Railsbytes, "railsbytes", "gorails/commands/railsbytes"
|
19
|
-
|
16
|
+
register :Version, "version", "gorails/commands/version"
|
20
17
|
register :Help, "help", "gorails/commands/help"
|
21
18
|
end
|
22
19
|
end
|
data/lib/gorails/version.rb
CHANGED
data/lib/gorails.rb
CHANGED
@@ -9,30 +9,21 @@ CLI::UI::StdoutRouter.enable
|
|
9
9
|
module Gorails
|
10
10
|
class Error < StandardError; end
|
11
11
|
|
12
|
-
extend CLI::Kit::Autocall
|
13
|
-
|
14
12
|
TOOL_NAME = "gorails"
|
15
13
|
ROOT = File.expand_path("../..", __FILE__)
|
16
|
-
LOG_FILE = "/tmp/
|
14
|
+
LOG_FILE = "/tmp/gorails.log"
|
17
15
|
|
18
16
|
autoload(:EntryPoint, "gorails/entry_point")
|
19
17
|
autoload(:Commands, "gorails/commands")
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
autocall(:ErrorHandler) do
|
33
|
-
CLI::Kit::ErrorHandler.new(
|
34
|
-
log_file: LOG_FILE,
|
35
|
-
exception_reporter: nil
|
36
|
-
)
|
37
|
-
end
|
19
|
+
Config = CLI::Kit::Config.new(tool_name: TOOL_NAME)
|
20
|
+
Command = CLI::Kit::BaseCommand
|
21
|
+
|
22
|
+
Executor = CLI::Kit::Executor.new(log_file: LOG_FILE)
|
23
|
+
Resolver = CLI::Kit::Resolver.new(
|
24
|
+
tool_name: TOOL_NAME,
|
25
|
+
command_registry: Gorails::Commands::Registry
|
26
|
+
)
|
27
|
+
|
28
|
+
ErrorHandler = CLI::Kit::ErrorHandler.new(log_file: LOG_FILE)
|
38
29
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
635eab627526f34c7ce15d2c894633246493b1a4
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'cli/kit'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module Kit
|
6
|
+
module Args
|
7
|
+
class Definition
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
Error = Class.new(Args::Error)
|
11
|
+
ConflictingFlag = Class.new(Error)
|
12
|
+
InvalidFlag = Class.new(Error)
|
13
|
+
InvalidLookup = Class.new(Error)
|
14
|
+
InvalidPosition = Class.new(Error)
|
15
|
+
|
16
|
+
sig { returns(T::Array[Flag]) }
|
17
|
+
attr_reader :flags
|
18
|
+
|
19
|
+
sig { returns(T::Array[Option]) }
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
sig { returns(T::Array[Position]) }
|
23
|
+
attr_reader :positions
|
24
|
+
|
25
|
+
sig { params(name: Symbol, short: T.nilable(String), long: T.nilable(String), desc: T.nilable(String)).void }
|
26
|
+
def add_flag(name, short: nil, long: nil, desc: nil)
|
27
|
+
short, long = strip_prefixes_and_validate(short, long)
|
28
|
+
flag = Flag.new(name: name, short: short, long: long, desc: desc)
|
29
|
+
add_resolution(flag)
|
30
|
+
@flags << flag
|
31
|
+
end
|
32
|
+
|
33
|
+
sig do
|
34
|
+
params(
|
35
|
+
name: Symbol, short: T.nilable(String), long: T.nilable(String),
|
36
|
+
desc: T.nilable(String), default: T.any(NilClass, String, T.proc.returns(String)),
|
37
|
+
required: T::Boolean, multi: T::Boolean,
|
38
|
+
).void
|
39
|
+
end
|
40
|
+
def add_option(name, short: nil, long: nil, desc: nil, default: nil, required: false, multi: false)
|
41
|
+
short, long = strip_prefixes_and_validate(short, long)
|
42
|
+
option = Option.new(
|
43
|
+
name: name, short: short, long: long, desc: desc, default: default,
|
44
|
+
required: required, multi: multi,
|
45
|
+
)
|
46
|
+
add_resolution(option)
|
47
|
+
@options << option
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { params(name: Symbol, required: T::Boolean, multiple: T::Boolean, desc: T.nilable(String)).void }
|
51
|
+
def add_position(name, required:, multiple:, desc: nil)
|
52
|
+
index = @positions.size
|
53
|
+
position = Position.new(name: name, desc: desc, required: required, multiple: multiple, index: index)
|
54
|
+
validate_order(position)
|
55
|
+
add_name_resolution(position)
|
56
|
+
@positions << position
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { void }
|
60
|
+
def initialize
|
61
|
+
@flags = []
|
62
|
+
@options = []
|
63
|
+
@by_short = {}
|
64
|
+
@by_long = {}
|
65
|
+
@by_name = {}
|
66
|
+
@positions = []
|
67
|
+
end
|
68
|
+
|
69
|
+
class Flag
|
70
|
+
extend T::Sig
|
71
|
+
|
72
|
+
sig { returns(Symbol) }
|
73
|
+
attr_reader :name
|
74
|
+
|
75
|
+
sig { returns(T.nilable(String)) }
|
76
|
+
attr_reader :short
|
77
|
+
|
78
|
+
sig { returns(T.nilable(String)) }
|
79
|
+
attr_reader :long
|
80
|
+
|
81
|
+
sig { returns(T.nilable(String)) }
|
82
|
+
attr_reader :desc
|
83
|
+
|
84
|
+
sig { returns(String) }
|
85
|
+
def as_written_by_user
|
86
|
+
long ? "--#{long}" : "-#{short}"
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(name: Symbol, short: T.nilable(String), long: T.nilable(String), desc: T.nilable(String)).void }
|
90
|
+
def initialize(name:, short: nil, long: nil, desc: nil)
|
91
|
+
if long&.start_with?('-') || short&.start_with?('-')
|
92
|
+
raise(ArgumentError, 'invalid - prefix')
|
93
|
+
end
|
94
|
+
|
95
|
+
@name = name
|
96
|
+
@short = short
|
97
|
+
@long = long
|
98
|
+
@desc = desc
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Position
|
103
|
+
extend T::Sig
|
104
|
+
|
105
|
+
sig { returns(Symbol) }
|
106
|
+
attr_reader :name
|
107
|
+
|
108
|
+
sig { returns(T.nilable(String)) }
|
109
|
+
attr_reader :desc
|
110
|
+
|
111
|
+
sig { returns(Integer) }
|
112
|
+
attr_reader :index
|
113
|
+
|
114
|
+
sig do
|
115
|
+
params(name: Symbol, desc: T.nilable(String), required: T::Boolean, multiple: T::Boolean, index: Integer)
|
116
|
+
.void
|
117
|
+
end
|
118
|
+
def initialize(name:, desc:, required:, multiple:, index:)
|
119
|
+
raise(ArgumentError, 'Cannot be required and multiple') if required && multiple
|
120
|
+
|
121
|
+
@name = name
|
122
|
+
@desc = desc
|
123
|
+
@required = required
|
124
|
+
@multiple = multiple
|
125
|
+
@index = index
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { returns(T::Boolean) }
|
129
|
+
def required?
|
130
|
+
@required
|
131
|
+
end
|
132
|
+
|
133
|
+
sig { returns(T::Boolean) }
|
134
|
+
def multiple?
|
135
|
+
@multiple
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { returns(T::Boolean) }
|
139
|
+
def optional?
|
140
|
+
!required?
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class Option < Flag
|
145
|
+
extend T::Sig
|
146
|
+
|
147
|
+
sig { returns(T.nilable(String)) }
|
148
|
+
def default
|
149
|
+
if @default.is_a?(Proc)
|
150
|
+
@default.call
|
151
|
+
else
|
152
|
+
@default
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
sig { returns(T::Boolean) }
|
157
|
+
def dynamic_default?
|
158
|
+
@default.is_a?(Proc)
|
159
|
+
end
|
160
|
+
|
161
|
+
sig { returns(T::Boolean) }
|
162
|
+
attr_reader :required
|
163
|
+
|
164
|
+
sig { returns(T::Boolean) }
|
165
|
+
attr_reader :multi
|
166
|
+
|
167
|
+
sig do
|
168
|
+
params(
|
169
|
+
name: Symbol, short: T.nilable(String), long: T.nilable(String),
|
170
|
+
desc: T.nilable(String), default: T.any(NilClass, String, T.proc.returns(String)),
|
171
|
+
required: T::Boolean, multi: T::Boolean,
|
172
|
+
).void
|
173
|
+
end
|
174
|
+
def initialize(name:, short: nil, long: nil, desc: nil, default: nil, required: false, multi: false)
|
175
|
+
if multi && (default || required)
|
176
|
+
raise(ArgumentError, 'multi-valued options cannot have a default or required value')
|
177
|
+
end
|
178
|
+
|
179
|
+
super(name: name, short: short, long: long, desc: desc)
|
180
|
+
@default = default
|
181
|
+
@required = required
|
182
|
+
@multi = multi
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
sig { params(name: Symbol).returns(T.nilable(Flag)) }
|
187
|
+
def lookup_flag(name)
|
188
|
+
flagopt = @by_name[name]
|
189
|
+
if flagopt.class == Flag
|
190
|
+
flagopt
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
sig { params(name: Symbol).returns(T.nilable(Option)) }
|
195
|
+
def lookup_option(name)
|
196
|
+
flagopt = @by_name[name]
|
197
|
+
if flagopt.class == Option
|
198
|
+
flagopt
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
sig { params(name: String).returns(T.any(Flag, Option, NilClass)) }
|
203
|
+
def lookup_short(name)
|
204
|
+
raise(InvalidLookup, "invalid '-' prefix") if name.start_with?('-')
|
205
|
+
|
206
|
+
@by_short[name]
|
207
|
+
end
|
208
|
+
|
209
|
+
sig { params(name: String).returns(T.any(Flag, Option, NilClass)) }
|
210
|
+
def lookup_long(name)
|
211
|
+
raise(InvalidLookup, "invalid '-' prefix") if name.start_with?('-')
|
212
|
+
|
213
|
+
@by_long[name]
|
214
|
+
end
|
215
|
+
|
216
|
+
sig { params(name: Symbol).returns(T.nilable(Position)) }
|
217
|
+
def lookup_position(name)
|
218
|
+
position = @by_name[name]
|
219
|
+
if position.class == Position
|
220
|
+
position
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
sig { params(position: Position).void }
|
227
|
+
def validate_order(position)
|
228
|
+
if @positions.last&.multiple?
|
229
|
+
raise(InvalidPosition, 'Cannot have any more positional arguments after multiple')
|
230
|
+
elsif @positions.last&.optional? && !position.optional?
|
231
|
+
raise(InvalidPosition, 'Cannot have any more required positional arguments after optional ones')
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
sig { params(short: String).returns(String) }
|
236
|
+
def strip_short_prefix(short)
|
237
|
+
unless short.match?(/^-[^-]/)
|
238
|
+
raise(InvalidFlag, "Short flag '#{short}' does not start with '-'")
|
239
|
+
end
|
240
|
+
if short.size != 2
|
241
|
+
raise(InvalidFlag, 'Short flag must be a single character')
|
242
|
+
end
|
243
|
+
|
244
|
+
short.sub(/^-/, '')
|
245
|
+
end
|
246
|
+
|
247
|
+
sig { params(long: String).returns(String) }
|
248
|
+
def strip_long_prefix(long)
|
249
|
+
unless long.match?(/^--[^-]/)
|
250
|
+
raise(InvalidFlag, "Long flag '#{long}' does not start with '--'")
|
251
|
+
end
|
252
|
+
|
253
|
+
long.sub(/^--/, '')
|
254
|
+
end
|
255
|
+
|
256
|
+
sig do
|
257
|
+
params(short: T.nilable(String), long: T.nilable(String))
|
258
|
+
.returns([T.nilable(String), T.nilable(String)])
|
259
|
+
end
|
260
|
+
def strip_prefixes_and_validate(short, long)
|
261
|
+
if short.nil? && long.nil?
|
262
|
+
raise(Error, 'One or more of short and long must be specified')
|
263
|
+
end
|
264
|
+
|
265
|
+
short = strip_short_prefix(short) if short
|
266
|
+
long = strip_long_prefix(long) if long
|
267
|
+
|
268
|
+
[short, long]
|
269
|
+
end
|
270
|
+
|
271
|
+
sig { params(flagopt: Flag).void }
|
272
|
+
def add_resolution(flagopt)
|
273
|
+
if flagopt.short
|
274
|
+
if (existing = @by_short[flagopt.short])
|
275
|
+
raise(ConflictingFlag, "Short flag '#{flagopt.short}' already defined by #{existing.name}")
|
276
|
+
end
|
277
|
+
|
278
|
+
@by_short[flagopt.short] = flagopt
|
279
|
+
end
|
280
|
+
if flagopt.long
|
281
|
+
if (existing = @by_long[flagopt.long])
|
282
|
+
raise(ConflictingFlag, "Long flag '#{flagopt.long}' already defined by #{existing.name}")
|
283
|
+
end
|
284
|
+
|
285
|
+
@by_long[flagopt.long] = flagopt
|
286
|
+
end
|
287
|
+
add_name_resolution(flagopt)
|
288
|
+
end
|
289
|
+
|
290
|
+
sig { params(arg: T.any(Flag, Position)).void }
|
291
|
+
def add_name_resolution(arg)
|
292
|
+
if (existing = @by_name[arg.name])
|
293
|
+
raise(ConflictingFlag, "Flag '#{arg.name}' already defined by #{existing.name}")
|
294
|
+
end
|
295
|
+
|
296
|
+
@by_name[arg.name] = arg
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|