gorails 0.1.2 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -1
  3. data/Gemfile.lock +1 -6
  4. data/bin/update-deps +95 -0
  5. data/exe/gorails +2 -1
  6. data/gorails.gemspec +0 -2
  7. data/lib/gorails/commands/railsbytes.rb +10 -10
  8. data/lib/gorails/commands/version.rb +15 -0
  9. data/lib/gorails/commands.rb +2 -5
  10. data/lib/gorails/version.rb +1 -1
  11. data/lib/gorails.rb +11 -20
  12. data/vendor/deps/cli-kit/REVISION +1 -0
  13. data/vendor/deps/cli-kit/lib/cli/kit/args/definition.rb +301 -0
  14. data/vendor/deps/cli-kit/lib/cli/kit/args/evaluation.rb +237 -0
  15. data/vendor/deps/cli-kit/lib/cli/kit/args/parser/node.rb +131 -0
  16. data/vendor/deps/cli-kit/lib/cli/kit/args/parser.rb +128 -0
  17. data/vendor/deps/cli-kit/lib/cli/kit/args/tokenizer.rb +132 -0
  18. data/vendor/deps/cli-kit/lib/cli/kit/args.rb +15 -0
  19. data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +29 -0
  20. data/vendor/deps/cli-kit/lib/cli/kit/command_help.rb +256 -0
  21. data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +141 -0
  22. data/vendor/deps/cli-kit/lib/cli/kit/config.rb +137 -0
  23. data/vendor/deps/cli-kit/lib/cli/kit/core_ext.rb +30 -0
  24. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +165 -0
  25. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +99 -0
  26. data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +94 -0
  27. data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +89 -0
  28. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +95 -0
  29. data/vendor/deps/cli-kit/lib/cli/kit/opts.rb +284 -0
  30. data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +67 -0
  31. data/vendor/deps/cli-kit/lib/cli/kit/sorbet_runtime_stub.rb +142 -0
  32. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +253 -0
  33. data/vendor/deps/cli-kit/lib/cli/kit/support.rb +10 -0
  34. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +350 -0
  35. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +133 -0
  36. data/vendor/deps/cli-kit/lib/cli/kit/version.rb +7 -0
  37. data/vendor/deps/cli-kit/lib/cli/kit.rb +151 -0
  38. data/vendor/deps/cli-ui/REVISION +1 -0
  39. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +180 -0
  40. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +98 -0
  41. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +216 -0
  42. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +116 -0
  43. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +176 -0
  44. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +149 -0
  45. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +112 -0
  46. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +300 -0
  47. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +92 -0
  48. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +58 -0
  49. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +72 -0
  50. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +102 -0
  51. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +534 -0
  52. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +36 -0
  53. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +354 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/sorbet_runtime_stub.rb +143 -0
  55. data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +46 -0
  56. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +292 -0
  57. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +82 -0
  58. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +264 -0
  59. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +53 -0
  60. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +107 -0
  61. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +6 -0
  62. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +37 -0
  63. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +75 -0
  64. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +91 -0
  65. data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +63 -0
  66. data/vendor/deps/cli-ui/lib/cli/ui.rb +356 -0
  67. metadata +58 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ceb227d76224d717af596ca69a9b7f670823bd20ae69e2225e21f4cccd4d349b
4
- data.tar.gz: a836f4816f8c0d4b5db9e6dcb7c53005cd53f107f945d7efe611282f1e4a5137
3
+ metadata.gz: e64a68f47a33d15b16065e2882e19ef2fdf815e484bf5674900b6d214193bd50
4
+ data.tar.gz: ad7d91c0bdb7a3e9a0c41a148e4ab5658a7ad12478b5294c5ffb3de853695abb
5
5
  SHA512:
6
- metadata.gz: '0541289f21a733a8af0168d9360fe50e5bd3c7a5d3a68261e2b293b2ac10e60f600dd04d6cb8167babe3908275abae1d6821da37dbd242d95e43ce6eced8b62d'
7
- data.tar.gz: 659d2cb41d4fca9c4ff76dc4e10f72429547cce50a256af8832f716fae3fcb8e4a6d5a6ac11ff04faa4eb16640ea30c70bebb41b65bba549ad8e525d2f8186d0
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.0] - 2021-12-16
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.2)
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 "net/http"
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
- Bundler.with_original_env do
57
- system "rails app:template LOCATION=\"https://railsbytes.com/script/#{id}\""
58
- end
56
+ handler.option("View source") do |selection|
57
+ puts
58
+ puts byte["script"]
59
59
  end
60
60
 
61
- handler.option("exit") { |selection| exit 0 }
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
@@ -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
- # register :Example, 'example', 'gorails/commands/example'
16
+ register :Version, "version", "gorails/commands/version"
20
17
  register :Help, "help", "gorails/commands/help"
21
18
  end
22
19
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gorails
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.5"
5
5
  end
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/myproject.log"
14
+ LOG_FILE = "/tmp/gorails.log"
17
15
 
18
16
  autoload(:EntryPoint, "gorails/entry_point")
19
17
  autoload(:Commands, "gorails/commands")
20
18
 
21
- autocall(:Config) { CLI::Kit::Config.new(tool_name: TOOL_NAME) }
22
- autocall(:Command) { CLI::Kit::BaseCommand }
23
-
24
- autocall(:Executor) { CLI::Kit::Executor.new(log_file: LOG_FILE) }
25
- autocall(:Resolver) do
26
- CLI::Kit::Resolver.new(
27
- tool_name: TOOL_NAME,
28
- command_registry: Gorails::Commands::Registry
29
- )
30
- end
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