gorails 0.1.2 → 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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/Gemfile.lock +1 -1
  4. data/bin/update-deps +95 -0
  5. data/exe/gorails +3 -2
  6. data/lib/gorails/commands/railsbytes.rb +10 -10
  7. data/lib/gorails/commands.rb +1 -4
  8. data/lib/gorails/version.rb +1 -1
  9. data/lib/gorails.rb +11 -20
  10. data/vendor/deps/cli-kit/REVISION +1 -0
  11. data/vendor/deps/cli-kit/lib/cli/kit/args/definition.rb +301 -0
  12. data/vendor/deps/cli-kit/lib/cli/kit/args/evaluation.rb +237 -0
  13. data/vendor/deps/cli-kit/lib/cli/kit/args/parser/node.rb +131 -0
  14. data/vendor/deps/cli-kit/lib/cli/kit/args/parser.rb +128 -0
  15. data/vendor/deps/cli-kit/lib/cli/kit/args/tokenizer.rb +132 -0
  16. data/vendor/deps/cli-kit/lib/cli/kit/args.rb +15 -0
  17. data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +29 -0
  18. data/vendor/deps/cli-kit/lib/cli/kit/command_help.rb +256 -0
  19. data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +141 -0
  20. data/vendor/deps/cli-kit/lib/cli/kit/config.rb +137 -0
  21. data/vendor/deps/cli-kit/lib/cli/kit/core_ext.rb +30 -0
  22. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +165 -0
  23. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +99 -0
  24. data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +94 -0
  25. data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +89 -0
  26. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +95 -0
  27. data/vendor/deps/cli-kit/lib/cli/kit/opts.rb +284 -0
  28. data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +67 -0
  29. data/vendor/deps/cli-kit/lib/cli/kit/sorbet_runtime_stub.rb +142 -0
  30. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +253 -0
  31. data/vendor/deps/cli-kit/lib/cli/kit/support.rb +10 -0
  32. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +350 -0
  33. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +133 -0
  34. data/vendor/deps/cli-kit/lib/cli/kit/version.rb +7 -0
  35. data/vendor/deps/cli-kit/lib/cli/kit.rb +151 -0
  36. data/vendor/deps/cli-ui/REVISION +1 -0
  37. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +180 -0
  38. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +98 -0
  39. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +216 -0
  40. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +116 -0
  41. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +176 -0
  42. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +149 -0
  43. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +112 -0
  44. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +300 -0
  45. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +92 -0
  46. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +58 -0
  47. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +72 -0
  48. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +102 -0
  49. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +534 -0
  50. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +36 -0
  51. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +354 -0
  52. data/vendor/deps/cli-ui/lib/cli/ui/sorbet_runtime_stub.rb +143 -0
  53. data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +46 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +292 -0
  55. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +82 -0
  56. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +264 -0
  57. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +53 -0
  58. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +107 -0
  59. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +6 -0
  60. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +37 -0
  61. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +75 -0
  62. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +91 -0
  63. data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +63 -0
  64. data/vendor/deps/cli-ui/lib/cli/ui.rb +356 -0
  65. metadata +57 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ceb227d76224d717af596ca69a9b7f670823bd20ae69e2225e21f4cccd4d349b
4
- data.tar.gz: a836f4816f8c0d4b5db9e6dcb7c53005cd53f107f945d7efe611282f1e4a5137
3
+ metadata.gz: e70b979a5284596091b39be5e2372914fc9712839b6e1d5a666bc7ac142e61a2
4
+ data.tar.gz: 838162e13a911e26c5654778513229b1411441d968f593bf467168b57d24b985
5
5
  SHA512:
6
- metadata.gz: '0541289f21a733a8af0168d9360fe50e5bd3c7a5d3a68261e2b293b2ac10e60f600dd04d6cb8167babe3908275abae1d6821da37dbd242d95e43ce6eced8b62d'
7
- data.tar.gz: 659d2cb41d4fca9c4ff76dc4e10f72429547cce50a256af8832f716fae3fcb8e4a6d5a6ac11ff04faa4eb16640ea30c70bebb41b65bba549ad8e525d2f8186d0
6
+ metadata.gz: 7b108dc53a37aa0bed0faae758d157c742de83e9f6b30258c42dc36e0a1b1440f9112bde7d5db71bc1ddeb23b7995151b82309eb5e921e05004e8c0cde1e180f
7
+ data.tar.gz: 585422f5c6221e3d3fb23c0aa7a460dc2725e702cfe58ded81566b21b94432087312cd4a29f20b8d58a02f7c5c94984eb79e0ee247c1a70b490c2776499b6bbd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2021-12-16
3
+ ## [0.1.3] - 2022-03-13
4
+
5
+ - Vendor cli-kit and cli-ui for speed
6
+ This also skips loading bundler/setup which looks for a Gemfile
7
+
8
+ ## [0.1.2] - 2022-03-13
9
+
10
+ - Add ability to view and apply individual Railsbytes
11
+
12
+ ## [0.1.1] - 2022-03-13
4
13
 
5
14
  - Initial release
15
+
16
+ ## [0.1.0] - 2021-12-16
17
+
18
+ - Empty release
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gorails (0.1.2)
4
+ gorails (0.1.3)
5
5
  cli-kit (~> 4.0.0)
6
6
  cli-ui (~> 1.5.1)
7
7
 
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("lib")
10
+ unshift_path.call('vendor/deps/cli-ui/lib')
11
+ unshift_path.call('vendor/deps/cli-kit/lib')
12
+ unshift_path.call('lib')
11
13
 
12
- require "bundler/setup"
13
14
  require "gorails"
14
15
 
15
16
  exit(Gorails::ErrorHandler.call do
@@ -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
@@ -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)
@@ -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.3"
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