git 5.0.0.beta.2 → 5.0.0.beta.4

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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +27 -0
  3. data/.github/copilot-instructions.md +3 -8
  4. data/.github/hooks/bin-setup-on-worktree.json +11 -0
  5. data/.github/hooks/run-bin-setup-once.sh +14 -0
  6. data/.github/skills/breaking-change-analysis/SKILL.md +6 -9
  7. data/.github/skills/ci-cd-troubleshooting/SKILL.md +2 -2
  8. data/.github/skills/command-implementation/REFERENCE.md +34 -33
  9. data/.github/skills/command-implementation/SKILL.md +5 -4
  10. data/.github/skills/command-test-conventions/SKILL.md +13 -9
  11. data/.github/skills/command-yard-documentation/SKILL.md +14 -6
  12. data/.github/skills/development-workflow/SKILL.md +20 -14
  13. data/.github/skills/facade-implementation/REFERENCE.md +19 -22
  14. data/.github/skills/facade-implementation/SKILL.md +0 -5
  15. data/.github/skills/facade-test-conventions/SKILL.md +4 -7
  16. data/.github/skills/facade-yard-documentation/SKILL.md +8 -2
  17. data/.github/skills/make-skill-template/SKILL.md +53 -3
  18. data/.github/skills/pr-readiness-review/SKILL.md +22 -2
  19. data/.github/skills/project-context/SKILL.md +16 -23
  20. data/.github/skills/pull-request-review/SKILL.md +2 -2
  21. data/.github/skills/rebase/SKILL.md +148 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +1 -1
  23. data/.github/skills/resolve-feedback/SKILL.md +288 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +3 -3
  25. data/.github/skills/reviewing-skills/SKILL.md +28 -3
  26. data/.github/skills/test-debugging/SKILL.md +20 -19
  27. data/.github/skills/yard-documentation/SKILL.md +671 -483
  28. data/.github/skills/yard-documentation/element-rules.md +162 -0
  29. data/.github/skills-deprecated/README.md +21 -0
  30. data/.github/{skills → skills-deprecated}/extract-command-from-lib/SKILL.md +9 -9
  31. data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/SKILL.md +12 -12
  32. data/.github/workflows/continuous_integration.yml +3 -2
  33. data/.rubocop.yml +7 -39
  34. data/.yard-lint.yml +75 -0
  35. data/CONTRIBUTING.md +186 -174
  36. data/Gemfile +17 -0
  37. data/README.md +86 -23
  38. data/Rakefile +3 -21
  39. data/UPGRADING.md +219 -70
  40. data/docker/test/Dockerfile +32 -0
  41. data/docker/test/docker-compose.yml +0 -0
  42. data/git.gemspec +3 -4
  43. data/lib/git/author.rb +20 -1
  44. data/lib/git/branch.rb +26 -6
  45. data/lib/git/branch_delete_failure.rb +3 -0
  46. data/lib/git/branch_delete_result.rb +6 -3
  47. data/lib/git/branch_info.rb +6 -6
  48. data/lib/git/command_line/base.rb +6 -13
  49. data/lib/git/command_line/capturing.rb +67 -10
  50. data/lib/git/command_line/result.rb +0 -8
  51. data/lib/git/command_line/streaming.rb +45 -5
  52. data/lib/git/command_line.rb +4 -3
  53. data/lib/git/commands/add.rb +61 -53
  54. data/lib/git/commands/am/abort.rb +2 -2
  55. data/lib/git/commands/am/apply.rb +109 -98
  56. data/lib/git/commands/am/continue.rb +2 -2
  57. data/lib/git/commands/am/quit.rb +2 -2
  58. data/lib/git/commands/am/retry.rb +4 -2
  59. data/lib/git/commands/am/show_current_patch.rb +1 -1
  60. data/lib/git/commands/am/skip.rb +2 -2
  61. data/lib/git/commands/am.rb +2 -2
  62. data/lib/git/commands/apply.rb +7 -2
  63. data/lib/git/commands/archive/list_formats.rb +3 -3
  64. data/lib/git/commands/archive.rb +7 -2
  65. data/lib/git/commands/arguments.rb +1370 -359
  66. data/lib/git/commands/base.rb +111 -13
  67. data/lib/git/commands/branch/copy.rb +29 -21
  68. data/lib/git/commands/branch/create.rb +72 -68
  69. data/lib/git/commands/branch/delete.rb +25 -17
  70. data/lib/git/commands/branch/list.rb +76 -60
  71. data/lib/git/commands/branch/move.rb +29 -21
  72. data/lib/git/commands/branch/set_upstream.rb +8 -8
  73. data/lib/git/commands/branch/show_current.rb +3 -3
  74. data/lib/git/commands/branch/unset_upstream.rb +4 -8
  75. data/lib/git/commands/branch.rb +2 -2
  76. data/lib/git/commands/cat_file/batch.rb +36 -20
  77. data/lib/git/commands/cat_file/filtered.rb +12 -12
  78. data/lib/git/commands/cat_file/raw.rb +13 -13
  79. data/lib/git/commands/cat_file.rb +4 -4
  80. data/lib/git/commands/checkout/branch.rb +7 -5
  81. data/lib/git/commands/checkout/files.rb +5 -4
  82. data/lib/git/commands/checkout.rb +4 -4
  83. data/lib/git/commands/checkout_index.rb +5 -4
  84. data/lib/git/commands/clean.rb +6 -4
  85. data/lib/git/commands/clone.rb +5 -4
  86. data/lib/git/commands/commit.rb +5 -4
  87. data/lib/git/commands/commit_tree.rb +5 -4
  88. data/lib/git/commands/config_option_syntax/add.rb +7 -4
  89. data/lib/git/commands/config_option_syntax/get.rb +9 -5
  90. data/lib/git/commands/config_option_syntax/get_all.rb +7 -4
  91. data/lib/git/commands/config_option_syntax/get_color.rb +8 -4
  92. data/lib/git/commands/config_option_syntax/get_color_bool.rb +7 -4
  93. data/lib/git/commands/config_option_syntax/get_regexp.rb +8 -4
  94. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +8 -4
  95. data/lib/git/commands/config_option_syntax/list.rb +8 -4
  96. data/lib/git/commands/config_option_syntax/remove_section.rb +7 -2
  97. data/lib/git/commands/config_option_syntax/rename_section.rb +7 -2
  98. data/lib/git/commands/config_option_syntax/replace_all.rb +7 -2
  99. data/lib/git/commands/config_option_syntax/set.rb +7 -2
  100. data/lib/git/commands/config_option_syntax/unset.rb +7 -4
  101. data/lib/git/commands/config_option_syntax/unset_all.rb +7 -2
  102. data/lib/git/commands/describe.rb +5 -4
  103. data/lib/git/commands/diff.rb +13 -12
  104. data/lib/git/commands/diff_files.rb +7 -6
  105. data/lib/git/commands/diff_index.rb +10 -7
  106. data/lib/git/commands/fetch.rb +6 -4
  107. data/lib/git/commands/fsck.rb +6 -4
  108. data/lib/git/commands/gc.rb +6 -4
  109. data/lib/git/commands/grep.rb +6 -5
  110. data/lib/git/commands/init.rb +6 -4
  111. data/lib/git/commands/log.rb +287 -285
  112. data/lib/git/commands/ls_files.rb +69 -65
  113. data/lib/git/commands/ls_remote.rb +62 -56
  114. data/lib/git/commands/ls_tree.rb +50 -46
  115. data/lib/git/commands/maintenance/register.rb +25 -23
  116. data/lib/git/commands/maintenance/run.rb +7 -2
  117. data/lib/git/commands/maintenance/start.rb +7 -2
  118. data/lib/git/commands/maintenance/stop.rb +7 -2
  119. data/lib/git/commands/maintenance/unregister.rb +7 -2
  120. data/lib/git/commands/merge/abort.rb +2 -2
  121. data/lib/git/commands/merge/continue.rb +2 -2
  122. data/lib/git/commands/merge/quit.rb +2 -2
  123. data/lib/git/commands/merge/start.rb +7 -2
  124. data/lib/git/commands/merge.rb +2 -2
  125. data/lib/git/commands/merge_base.rb +7 -2
  126. data/lib/git/commands/mv.rb +7 -2
  127. data/lib/git/commands/name_rev.rb +7 -2
  128. data/lib/git/commands/pull.rb +7 -2
  129. data/lib/git/commands/push.rb +7 -2
  130. data/lib/git/commands/read_tree.rb +7 -2
  131. data/lib/git/commands/remote/add.rb +7 -2
  132. data/lib/git/commands/remote/get_url.rb +4 -2
  133. data/lib/git/commands/remote/list.rb +4 -2
  134. data/lib/git/commands/remote/prune.rb +4 -2
  135. data/lib/git/commands/remote/remove.rb +4 -2
  136. data/lib/git/commands/remote/rename.rb +4 -2
  137. data/lib/git/commands/remote/set_branches.rb +9 -2
  138. data/lib/git/commands/remote/set_head.rb +12 -5
  139. data/lib/git/commands/remote/set_url.rb +9 -2
  140. data/lib/git/commands/remote/set_url_add.rb +11 -2
  141. data/lib/git/commands/remote/set_url_delete.rb +9 -2
  142. data/lib/git/commands/remote/show.rb +18 -12
  143. data/lib/git/commands/remote/update.rb +19 -12
  144. data/lib/git/commands/remote.rb +2 -2
  145. data/lib/git/commands/repack.rb +67 -63
  146. data/lib/git/commands/reset.rb +51 -44
  147. data/lib/git/commands/rev_parse.rb +135 -128
  148. data/lib/git/commands/revert/abort.rb +2 -2
  149. data/lib/git/commands/revert/continue.rb +7 -2
  150. data/lib/git/commands/revert/quit.rb +2 -2
  151. data/lib/git/commands/revert/skip.rb +2 -2
  152. data/lib/git/commands/revert/start.rb +7 -2
  153. data/lib/git/commands/revert.rb +2 -2
  154. data/lib/git/commands/rm.rb +35 -36
  155. data/lib/git/commands/show.rb +1 -1
  156. data/lib/git/commands/show_ref/exclude_existing.rb +3 -4
  157. data/lib/git/commands/show_ref/exists.rb +15 -13
  158. data/lib/git/commands/show_ref/list.rb +39 -35
  159. data/lib/git/commands/show_ref/verify.rb +29 -27
  160. data/lib/git/commands/show_ref.rb +4 -4
  161. data/lib/git/commands/stash/apply.rb +31 -25
  162. data/lib/git/commands/stash/branch.rb +10 -8
  163. data/lib/git/commands/stash/clear.rb +7 -5
  164. data/lib/git/commands/stash/create.rb +10 -8
  165. data/lib/git/commands/stash/drop.rb +15 -9
  166. data/lib/git/commands/stash/list.rb +9 -5
  167. data/lib/git/commands/stash/pop.rb +21 -12
  168. data/lib/git/commands/stash/push.rb +20 -11
  169. data/lib/git/commands/stash/show.rb +21 -12
  170. data/lib/git/commands/stash/store.rb +19 -10
  171. data/lib/git/commands/stash.rb +2 -2
  172. data/lib/git/commands/status.rb +7 -2
  173. data/lib/git/commands/symbolic_ref/delete.rb +18 -14
  174. data/lib/git/commands/symbolic_ref/read.rb +24 -20
  175. data/lib/git/commands/symbolic_ref/update.rb +20 -17
  176. data/lib/git/commands/symbolic_ref.rb +4 -4
  177. data/lib/git/commands/tag/create.rb +45 -42
  178. data/lib/git/commands/tag/delete.rb +5 -3
  179. data/lib/git/commands/tag/list.rb +47 -44
  180. data/lib/git/commands/tag/verify.rb +12 -12
  181. data/lib/git/commands/tag.rb +2 -2
  182. data/lib/git/commands/update_ref/batch.rb +6 -1
  183. data/lib/git/commands/update_ref/delete.rb +19 -21
  184. data/lib/git/commands/update_ref/update.rb +22 -25
  185. data/lib/git/commands/update_ref.rb +4 -4
  186. data/lib/git/commands/version.rb +14 -11
  187. data/lib/git/commands/worktree/add.rb +50 -51
  188. data/lib/git/commands/worktree/list.rb +17 -17
  189. data/lib/git/commands/worktree/lock.rb +12 -12
  190. data/lib/git/commands/worktree/move.rb +16 -16
  191. data/lib/git/commands/worktree/prune.rb +2 -2
  192. data/lib/git/commands/worktree/remove.rb +2 -2
  193. data/lib/git/commands/worktree/repair.rb +2 -2
  194. data/lib/git/commands/worktree/unlock.rb +2 -2
  195. data/lib/git/commands/write_tree.rb +2 -2
  196. data/lib/git/commands.rb +4 -3
  197. data/lib/git/config.rb +47 -1
  198. data/lib/git/configuring.rb +89 -15
  199. data/lib/git/detached_head_info.rb +4 -1
  200. data/lib/git/diff.rb +2 -5
  201. data/lib/git/diff_file_numstat_info.rb +3 -1
  202. data/lib/git/diff_file_patch_info.rb +3 -1
  203. data/lib/git/diff_file_raw_info.rb +3 -1
  204. data/lib/git/diff_info.rb +8 -15
  205. data/lib/git/diff_path_status.rb +16 -3
  206. data/lib/git/diff_result.rb +3 -1
  207. data/lib/git/diff_stats.rb +15 -0
  208. data/lib/git/dirstat_info.rb +24 -8
  209. data/lib/git/encoding_utils.rb +32 -1
  210. data/lib/git/errors.rb +19 -9
  211. data/lib/git/escaped_path.rb +55 -3
  212. data/lib/git/execution_context/repository.rb +15 -0
  213. data/lib/git/execution_context.rb +76 -74
  214. data/lib/git/file_ref.rb +6 -3
  215. data/lib/git/fsck_object.rb +8 -0
  216. data/lib/git/fsck_result.rb +26 -15
  217. data/lib/git/log.rb +152 -5
  218. data/lib/git/object.rb +284 -8
  219. data/lib/git/parsers/branch.rb +18 -0
  220. data/lib/git/parsers/cat_file.rb +2 -2
  221. data/lib/git/parsers/diff.rb +213 -5
  222. data/lib/git/parsers/fsck.rb +11 -0
  223. data/lib/git/parsers/ls_remote.rb +1 -1
  224. data/lib/git/parsers/stash.rb +51 -0
  225. data/lib/git/parsers/tag.rb +38 -0
  226. data/lib/git/remote.rb +34 -2
  227. data/lib/git/repository/branching.rb +23 -21
  228. data/lib/git/repository/committing.rb +46 -6
  229. data/lib/git/repository/context_helpers.rb +45 -16
  230. data/lib/git/repository/diffing.rb +32 -23
  231. data/lib/git/repository/factories.rb +153 -28
  232. data/lib/git/repository/inspecting.rb +1 -1
  233. data/lib/git/repository/logging.rb +169 -30
  234. data/lib/git/repository/maintenance.rb +1 -1
  235. data/lib/git/repository/merging.rb +3 -3
  236. data/lib/git/repository/object_operations.rb +108 -14
  237. data/lib/git/repository/remote_operations.rb +150 -171
  238. data/lib/git/repository/shared_private.rb +7 -5
  239. data/lib/git/repository/staging.rb +36 -12
  240. data/lib/git/repository/stashing.rb +16 -16
  241. data/lib/git/repository/status_operations.rb +1 -1
  242. data/lib/git/repository/worktree_operations.rb +1 -1
  243. data/lib/git/repository.rb +287 -6
  244. data/lib/git/stash.rb +16 -21
  245. data/lib/git/stash_info.rb +4 -4
  246. data/lib/git/stashes.rb +20 -26
  247. data/lib/git/status.rb +25 -13
  248. data/lib/git/tag_delete_failure.rb +3 -0
  249. data/lib/git/tag_delete_result.rb +6 -3
  250. data/lib/git/tag_info.rb +1 -0
  251. data/lib/git/url.rb +12 -5
  252. data/lib/git/version.rb +6 -4
  253. data/lib/git/version_constraint.rb +4 -0
  254. data/lib/git.rb +400 -314
  255. data/redesign/3_architecture_implementation.md +81 -30
  256. data/redesign/Phase 4 - Step A.md +6 -0
  257. data/redesign/Phase 4 - Step B.md +921 -0
  258. data/redesign/Phase 4 - Step C.md +833 -0
  259. data/redesign/c1a-public-api-scope.tsv +256 -0
  260. data/redesign/phase-4-step-b-test-audit.tsv +485 -0
  261. data/tasks/yard.rake +24 -17
  262. metadata +24 -58
  263. data/commitlint.test +0 -4
  264. data/lib/git/args_builder.rb +0 -103
  265. data/lib/git/command_line_result.rb +0 -8
  266. data/lib/git/deprecation.rb +0 -9
  267. data/lib/git/repository/configuring.rb +0 -351
  268. data/tasks/test.rake +0 -25
  269. /data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -0
  270. /data/.github/{skills → skills-deprecated}/review-backward-compatibility/SKILL.md +0 -0
@@ -19,7 +19,7 @@ module Git
19
19
  #
20
20
  # See {Git::Commands::Init} for a usage example.
21
21
  #
22
- # Example: Defining arguments for a command
22
+ # For example, defining arguments for a command:
23
23
  #
24
24
  # ```ruby
25
25
  # # 1. Definition of expected CLI arguments and their constraints
@@ -62,7 +62,7 @@ module Git
62
62
  # An **argument specification** declares what command inputs are accepted and
63
63
  # their constraints.
64
64
  #
65
- # Example:
65
+ # For example:
66
66
  #
67
67
  # ```text
68
68
  # git branch (--set-upstream-to=<upstream>|-u <upstream>) [<branch-name>]
@@ -74,7 +74,7 @@ module Git
74
74
  # - **Operands**: Arguments identified by position
75
75
  # - **Options**: Arguments identified by name (prefixed with `-` or `--`)
76
76
  #
77
- # Example:
77
+ # For example:
78
78
  #
79
79
  # ```shell
80
80
  # git branch --set-upstream-to=origin/main main
@@ -88,7 +88,7 @@ module Git
88
88
  # A **DSL definition** declares what arguments the {#bind} method accepts and how
89
89
  # they map to CLI arguments.
90
90
  #
91
- # Example:
91
+ # For example:
92
92
  #
93
93
  # ```ruby
94
94
  # Arguments.define do
@@ -103,7 +103,7 @@ module Git
103
103
  # - **Positional arguments**: Arguments identified by position
104
104
  # - **Keyword arguments**: Arguments identified by name
105
105
  #
106
- # Example:
106
+ # For example:
107
107
  #
108
108
  # ```ruby
109
109
  # args_def.bind('main', set_upstream_to: 'origin/main')
@@ -134,11 +134,11 @@ module Git
134
134
  #
135
135
  # Key internal components:
136
136
  #
137
- # - +@ordered_definitions+: Array tracking all definitions in definition order
138
- # - +@option_definitions+: Hash mapping option names to their definitions
139
- # - +@operand_definitions+: Array of operand (positional argument) definitions
140
- # - +@alias_map+: Maps option aliases to their primary names
141
- # - +BUILDERS+: Hash of lambdas that convert values to CLI arguments by type
137
+ # - `@ordered_definitions`: Array tracking all definitions in definition order
138
+ # - `@option_definitions`: Hash mapping option names to their definitions
139
+ # - `@operand_definitions`: Array of operand (positional argument) definitions
140
+ # - `@alias_map`: Maps option aliases to their primary names
141
+ # - `BUILDERS`: Hash of lambdas that convert values to CLI arguments by type
142
142
  # - {OperandAllocator}: Handles Ruby-like operand allocation
143
143
  #
144
144
  # ## Argument Ordering
@@ -253,7 +253,7 @@ module Git
253
253
  #
254
254
  # Supported by: {#flag_option}, {#value_option}, {#flag_or_value_option}.
255
255
  #
256
- # Note: {#literal} and {#execution_option} do not support these validation parameters.
256
+ # Note that {#literal} and {#execution_option} do not support these validation parameters.
257
257
  #
258
258
  # These parameters affect **output generation** (what CLI arguments are
259
259
  # produced):
@@ -385,7 +385,7 @@ module Git
385
385
  # Only `value_option` with `as_operand: true` and `execution_option` are allowed
386
386
  # after the boundary because they do not produce flag-prefixed output.
387
387
  #
388
- # For example, this will raise +ArgumentError+ during definition:
388
+ # For example, this will raise `ArgumentError` during definition:
389
389
  #
390
390
  # Arguments.define do
391
391
  # literal '--'
@@ -428,7 +428,7 @@ module Git
428
428
  # `Pathname`, etc. — can be passed as a value without the DSL needing to know
429
429
  # about the type.
430
430
  #
431
- # Note: `flag_option` values control *presence or absence* of a flag and are not
431
+ # Note that `flag_option` values control *presence or absence* of a flag and are not
432
432
  # stringified. `custom_option` builders receive the raw value and are responsible
433
433
  # for producing CLI strings themselves.
434
434
  #
@@ -593,24 +593,25 @@ module Git
593
593
  class Arguments
594
594
  # Define a new Arguments instance using the DSL
595
595
  #
596
- # @yield [] block evaluated in the context of the new Arguments instance via
597
- # +instance_eval+, so DSL methods ({#flag_option}, {#operand}, etc.) are called
598
- # directly without an explicit receiver
599
- #
600
- # @return [Arguments] The configured Arguments instance
601
- #
602
596
  # @example Basic flag
603
597
  # args_def = Arguments.define do
604
598
  # flag_option :verbose
605
599
  # end
606
600
  # args_def.bind(verbose: true).to_a # => ['--verbose']
607
601
  #
602
+ # @return [Arguments] the configured Arguments instance
603
+ #
604
+ # @yield [] block evaluated in the context of the new Arguments instance via
605
+ # `instance_eval`, so DSL methods ({#flag_option}, {#operand}, etc.) are called
606
+ # directly without an explicit receiver
607
+ #
608
608
  def self.define(&block)
609
609
  args = new
610
610
  args.instance_eval(&block) if block
611
611
  args
612
612
  end
613
613
 
614
+ # Initialize an empty Arguments definition
614
615
  def initialize
615
616
  @option_definitions = {}
616
617
  @alias_map = {} # Maps alias keys to primary keys
@@ -626,54 +627,6 @@ module Git
626
627
 
627
628
  # Define a boolean flag option (--flag when true)
628
629
  #
629
- # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
630
- #
631
- # @param as [String, Array<String>, nil] custom argument(s) to output (e.g., '-r' or ['--amend', '--no-edit'])
632
- #
633
- # @param negatable [Boolean] when true, registers a companion `no_<name>` key that emits
634
- # `--no-<flag>` when set to `true`. Both keys use standard boolean semantics: `true`
635
- # emits the flag, `false` or absent emits nothing. A conflict is automatically registered
636
- # between the two keys so that `name: true, no_name: true` raises at bind time.
637
- # The primary key must be snake_case (e.g. `:verify`, `:three_way`). When `as:` is
638
- # given, it must be a long-form (`--flag`) String; Arrays and short-form flags (e.g.
639
- # `-S`) are not compatible with `negatable: true` because the synthesized companion is
640
- # always `--no-<flag>`.
641
- #
642
- # @param required [Boolean] whether the option must be provided (the key must be present
643
- # in opts). When combined with +negatable: true+, a `requires_one_of [name, no_name]`
644
- # group is automatically registered so that either the primary or companion key satisfies
645
- # the requirement (e.g. `bind(no_verify: true)` satisfies `required: true` for `:verify`).
646
- # Note that under the companion-key model, `bind(verify: false)` does **not** satisfy
647
- # the requirement because `false` is treated as absent.
648
- #
649
- # @param allow_nil [Boolean] whether nil is allowed when required is true. Defaults to true.
650
- # When false with required: true, raises ArgumentError if value is nil.
651
- # Cannot be combined with +negatable: true+ and +required: true+ — raises ArgumentError
652
- # at definition time (nil is already caught by the auto +requires_one_of+ group).
653
- #
654
- # @param max_times [Integer, nil] maximum number of times the flag may be repeated (default: nil).
655
- # When set, the caller may pass a positive Integer up to this limit to emit the flag
656
- # multiple times (e.g. `force: 2` emits `--force --force`). Must be an Integer >= 2;
657
- # 0 and 1 raise ArgumentError at definition time. When nil (the default), only boolean
658
- # values are accepted.
659
- #
660
- # @return [void]
661
- #
662
- # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
663
- #
664
- # @raise [ArgumentError] if max_times is not nil and not an Integer >= 2
665
- #
666
- # @raise [ArgumentError] if negatable: true and the primary key is not snake_case
667
- #
668
- # @raise [ArgumentError] if negatable: true and the generated `no_<name>` key collides
669
- # with an already-registered key
670
- #
671
- # @raise [ArgumentError] if negatable: true and as: is an Array
672
- #
673
- # @raise [ArgumentError] if negatable: true and as: is not a long-form (`--flag`) String
674
- #
675
- # @raise [ArgumentError] if negatable: true and required: true and allow_nil: false
676
- #
677
630
  # @example Basic flag
678
631
  # args_def = Arguments.define do
679
632
  # flag_option :force
@@ -722,6 +675,54 @@ module Git
722
675
  # args_def.bind() #=> raise ArgumentError, "Required options not provided: :force"
723
676
  # args_def.bind(force: nil) #=> raise ArgumentError, "Required options cannot be nil: :force"
724
677
  #
678
+ # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
679
+ #
680
+ # @param as [String, Array<String>, nil] custom argument(s) to output (e.g., '-r' or ['--amend', '--no-edit'])
681
+ #
682
+ # @param negatable [Boolean] when true, registers a companion `no_<name>` key that emits
683
+ # `--no-<flag>` when set to `true`. Both keys use standard boolean semantics: `true`
684
+ # emits the flag, `false` or absent emits nothing. A conflict is automatically registered
685
+ # between the two keys so that `name: true, no_name: true` raises at bind time.
686
+ # The primary key must be snake_case (e.g. `:verify`, `:three_way`). When `as:` is
687
+ # given, it must be a long-form (`--flag`) String; Arrays and short-form flags (e.g.
688
+ # `-S`) are not compatible with `negatable: true` because the synthesized companion is
689
+ # always `--no-<flag>`.
690
+ #
691
+ # @param required [Boolean] whether the option must be provided (the key must be present
692
+ # in opts). When combined with `negatable: true`, a `requires_one_of [name, no_name]`
693
+ # group is automatically registered so that either the primary or companion key satisfies
694
+ # the requirement (e.g. `bind(no_verify: true)` satisfies `required: true` for `:verify`).
695
+ # Note that under the companion-key model, `bind(verify: false)` does **not** satisfy
696
+ # the requirement because `false` is treated as absent.
697
+ #
698
+ # @param allow_nil [Boolean] whether nil is allowed when required is true (defaults to true)
699
+ #
700
+ # When false with required: true, raises ArgumentError if value is nil.
701
+ # Cannot be combined with `negatable: true` and `required: true` — raises ArgumentError
702
+ # at definition time (nil is already caught by the auto `requires_one_of` group).
703
+ #
704
+ # @param max_times [Integer, nil] maximum number of times the flag may be repeated (default: nil)
705
+ # When set, the caller may pass a positive Integer up to this limit to emit the flag
706
+ # multiple times (e.g. `force: 2` emits `--force --force`). Must be an Integer >= 2;
707
+ # 0 and 1 raise ArgumentError at definition time. When nil (the default), only boolean
708
+ # values are accepted.
709
+ #
710
+ # @return [void]
711
+ #
712
+ # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
713
+ #
714
+ # @raise [ArgumentError] if max_times is not nil and not an Integer >= 2
715
+ #
716
+ # @raise [ArgumentError] if negatable: true and the primary key is not snake_case
717
+ #
718
+ # @raise [ArgumentError] if negatable: true and the generated `no_<name>` key collides
719
+ # with an already-registered key
720
+ #
721
+ # @raise [ArgumentError] if negatable: true and as: is an Array
722
+ #
723
+ # @raise [ArgumentError] if negatable: true and as: is not a long-form (`--flag`) String
724
+ #
725
+ # @raise [ArgumentError] if negatable: true and required: true and allow_nil: false
725
726
  def flag_option(names, as: nil, negatable: false, required: false, allow_nil: true, max_times: nil)
726
727
  primary = Array(names).first
727
728
  validate_max_times!(primary, max_times)
@@ -743,40 +744,6 @@ module Git
743
744
  # - **Inline**: `--flag=value` (single argument with `inline: true`)
744
745
  # - **Operand**: `value` (no flag, just the value with `as_operand: true`)
745
746
  #
746
- # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
747
- #
748
- # @param as [String, nil] custom option string (arrays not supported for value types)
749
- #
750
- # @param type [Class, Array<Class>, nil] expected type(s) for validation. Raises ArgumentError with
751
- # descriptive message if value doesn't match.
752
- #
753
- # @param inline [Boolean] when true, outputs --flag=value as single argument instead of
754
- # --flag value as separate arguments (default: false). Cannot be combined with as_operand:.
755
- #
756
- # @param as_operand [Boolean] when true, outputs value as operand without flag
757
- # (default: false). Cannot be combined with inline:.
758
- #
759
- # @param allow_empty [Boolean] whether to include the option even when value is an empty string.
760
- # When false (default), empty strings are skipped entirely. When true, the option and empty
761
- # value are included in the output.
762
- #
763
- # @param repeatable [Boolean] whether to allow multiple values. When true, accepts an array
764
- # of values and repeats the option for each value. A single value or nil is also accepted.
765
- # Behavior varies by output mode (see examples below).
766
- #
767
- # @param required [Boolean] when true, the option key must be present in the provided options hash.
768
- # Raises ArgumentError if the key is missing. Defaults to false.
769
- #
770
- # @param allow_nil [Boolean] when false (with required: true), the value cannot be nil.
771
- # Raises ArgumentError if a nil value is provided. Defaults to true.
772
- #
773
- # @return [void]
774
- #
775
- # @raise [ArgumentError] if inline: and as_operand: are both true
776
- #
777
- # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
778
- # (unless as_operand: true)
779
- #
780
747
  # @example Basic value (default mode)
781
748
  # args_def = Arguments.define do
782
749
  # value_option :branch
@@ -860,6 +827,39 @@ module Git
860
827
  # args_def.bind(message: nil) #=> raise ArgumentError, "Required options cannot be nil: :message"
861
828
  # args_def.bind() #=> raise ArgumentError, "Required options not provided: :message"
862
829
  #
830
+ # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
831
+ #
832
+ # @param as [String, nil] custom option string (arrays not supported for value types)
833
+ #
834
+ # @param type [Class, Array<Class>, nil] expected type(s) for validation. Raises ArgumentError with
835
+ # descriptive message if value doesn't match.
836
+ #
837
+ # @param inline [Boolean] when true, outputs --flag=value as single argument instead of
838
+ # --flag value as separate arguments (default: false). Cannot be combined with as_operand:.
839
+ #
840
+ # @param as_operand [Boolean] when true, outputs value as operand without flag
841
+ # (default: false). Cannot be combined with inline:.
842
+ #
843
+ # @param allow_empty [Boolean] whether to include the option even when value is an empty string.
844
+ # When false (default), empty strings are skipped entirely. When true, the option and empty
845
+ # value are included in the output.
846
+ #
847
+ # @param repeatable [Boolean] whether to allow multiple values. When true, accepts an array
848
+ # of values and repeats the option for each value. A single value or nil is also accepted.
849
+ # Behavior varies by output mode (see examples below).
850
+ #
851
+ # @param required [Boolean] when true, the option key must be present in the provided options hash.
852
+ # Raises ArgumentError if the key is missing. Defaults to false.
853
+ #
854
+ # @param allow_nil [Boolean] when false (with required: true), the value cannot be nil.
855
+ # Raises ArgumentError if a nil value is provided. Defaults to true.
856
+ #
857
+ # @return [void]
858
+ #
859
+ # @raise [ArgumentError] if inline: and as_operand: are both true
860
+ #
861
+ # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
862
+ # (unless as_operand: true)
863
863
  def value_option(names, as: nil, type: nil, inline: false, as_operand: false,
864
864
  allow_empty: false, repeatable: false, required: false, allow_nil: true)
865
865
  validate_value_modifiers!(names, inline, as_operand)
@@ -879,55 +879,6 @@ module Git
879
879
  # e.g., --flag value or --flag=value if inline: true)
880
880
  # - Nothing when value is nil
881
881
  #
882
- # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
883
- #
884
- # @param as [String, nil] custom option string
885
- #
886
- # @param type [Class, Array<Class>, nil] expected type(s) for validation
887
- #
888
- # @param negatable [Boolean] when true, registers a companion `no_<name>` key that emits
889
- # `--no-<flag>` when set to `true`. The positive key retains flag-or-value semantics;
890
- # the negative key is boolean-only (accepts only `true`/`false`/`nil`). A conflict is
891
- # automatically registered so that `name: true, no_name: true` raises at bind time.
892
- # The primary key must be snake_case. When `as:` is given, it must be a long-form
893
- # (`--flag`) String; Arrays and short-form flags (e.g. `-S`) are not compatible with
894
- # `negatable: true` because the synthesized companion is always `--no-<flag>`.
895
- #
896
- # @param inline [Boolean] when true, outputs --flag=value instead of --flag value (default: false)
897
- #
898
- # @param repeatable [Boolean] when true, accepts an Array of values and repeats the option
899
- # for each element. Each element must be +true+, +false+, or a non-nil object (which is
900
- # stringified via +#to_s+); nil elements raise ArgumentError at bind time.
901
- # A single (non-Array) value is also accepted. Default false.
902
- #
903
- # @param required [Boolean] whether the option must be provided (the key must be present
904
- # in opts). When combined with +negatable: true+, a `requires_one_of [name, no_name]`
905
- # group is automatically registered so that either side satisfies the requirement. Note
906
- # that `bind(name: false)` does **not** satisfy the requirement because `false` is
907
- # treated as absent under the companion-key model.
908
- #
909
- # @param allow_nil [Boolean] whether nil is allowed when required is true. Defaults to true.
910
- # Cannot be combined with +negatable: true+ and +required: true+ — raises ArgumentError
911
- # at definition time (nil is already caught by the auto +requires_one_of+ group).
912
- #
913
- # @return [void]
914
- #
915
- # @raise [ArgumentError] at bind time if +repeatable: true+ is used and any
916
- # Array element is nil
917
- #
918
- # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
919
- #
920
- # @raise [ArgumentError] if negatable: true and the primary key is not snake_case
921
- #
922
- # @raise [ArgumentError] if negatable: true and the generated `no_<name>` key collides
923
- # with an already-registered key
924
- #
925
- # @raise [ArgumentError] if negatable: true and as: is an Array
926
- #
927
- # @raise [ArgumentError] if negatable: true and as: is not a long-form (`--flag`) String
928
- #
929
- # @raise [ArgumentError] if negatable: true and required: true and allow_nil: false
930
- #
931
882
  # @example Basic flag or value (new capability - not possible with old DSL)
932
883
  # args_def = Arguments.define do
933
884
  # flag_or_value_option :contains
@@ -977,6 +928,54 @@ module Git
977
928
  # args_def.bind(recurse_submodules: [nil])
978
929
  # # => raise_error ArgumentError, /Invalid value for flag_or_inline_value/
979
930
  #
931
+ # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
932
+ #
933
+ # @param as [String, nil] custom option string
934
+ #
935
+ # @param type [Class, Array<Class>, nil] expected type(s) for validation
936
+ #
937
+ # @param negatable [Boolean] when true, registers a companion `no_<name>` key that emits
938
+ # `--no-<flag>` when set to `true`. The positive key retains flag-or-value semantics;
939
+ # the negative key is boolean-only (accepts only `true`/`false`/`nil`). A conflict is
940
+ # automatically registered so that `name: true, no_name: true` raises at bind time.
941
+ # The primary key must be snake_case. When `as:` is given, it must be a long-form
942
+ # (`--flag`) String; Arrays and short-form flags (e.g. `-S`) are not compatible with
943
+ # `negatable: true` because the synthesized companion is always `--no-<flag>`.
944
+ #
945
+ # @param inline [Boolean] when true, outputs --flag=value instead of --flag value (default: false)
946
+ #
947
+ # @param repeatable [Boolean] when true, accepts an Array of values and repeats the option
948
+ # for each element. Each element must be `true`, `false`, or a non-nil object (which is
949
+ # stringified via `#to_s`); nil elements raise ArgumentError at bind time.
950
+ # A single (non-Array) value is also accepted. Default false.
951
+ #
952
+ # @param required [Boolean] whether the option must be provided (the key must be present
953
+ # in opts). When combined with `negatable: true`, a `requires_one_of [name, no_name]`
954
+ # group is automatically registered so that either side satisfies the requirement. Note
955
+ # that `bind(name: false)` does **not** satisfy the requirement because `false` is
956
+ # treated as absent under the companion-key model.
957
+ #
958
+ # @param allow_nil [Boolean] whether nil is allowed when required is true. Defaults to true.
959
+ # Cannot be combined with `negatable: true` and `required: true` — raises ArgumentError
960
+ # at definition time (nil is already caught by the auto `requires_one_of` group).
961
+ #
962
+ # @return [void]
963
+ #
964
+ # @raise [ArgumentError] at bind time if `repeatable: true` is used and any
965
+ # Array element is nil
966
+ #
967
+ # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
968
+ #
969
+ # @raise [ArgumentError] if negatable: true and the primary key is not snake_case
970
+ #
971
+ # @raise [ArgumentError] if negatable: true and the generated `no_<name>` key collides
972
+ # with an already-registered key
973
+ #
974
+ # @raise [ArgumentError] if negatable: true and as: is an Array
975
+ #
976
+ # @raise [ArgumentError] if negatable: true and as: is not a long-form (`--flag`) String
977
+ #
978
+ # @raise [ArgumentError] if negatable: true and required: true and allow_nil: false
980
979
  def flag_or_value_option(names, as: nil, type: nil, negatable: false, inline: false,
981
980
  repeatable: false, required: false, allow_nil: true)
982
981
  if negatable
@@ -995,31 +994,6 @@ module Git
995
994
  # This is useful for git options like --trailer that take key=value pairs
996
995
  # and can be repeated. Accepts Hash or Array of arrays for flexible input.
997
996
  #
998
- # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
999
- #
1000
- # @param as [String, nil] custom option string (e.g., '--trailer')
1001
- #
1002
- # @param key_separator [String] separator between key and value (default: '=')
1003
- #
1004
- # @param inline [Boolean] when true, outputs --flag=key=value instead of --flag key=value
1005
- #
1006
- # @param required [Boolean] whether the option must be provided (key must exist in opts).
1007
- # Note: empty hash/array is considered "present" and produces no output without error.
1008
- #
1009
- # @param allow_nil [Boolean] whether nil is allowed when required is true
1010
- #
1011
- # @return [void]
1012
- #
1013
- # @raise [ArgumentError] at bind time if array input is not a [key, value] pair or array of pairs
1014
- #
1015
- # @raise [ArgumentError] at bind time if a sub-array has more than 2 elements
1016
- #
1017
- # @raise [ArgumentError] at bind time if a key is nil, empty, or contains the separator
1018
- #
1019
- # @raise [ArgumentError] at bind time if a value is a Hash or Array (non-scalar)
1020
- #
1021
- # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
1022
- #
1023
997
  # @example Basic key-value (like --trailer)
1024
998
  # args_def = Arguments.define do
1025
999
  # key_value_option :trailers, as: '--trailer'
@@ -1070,6 +1044,30 @@ module Git
1070
1044
  # args_def.bind(trailers: []).to_a # => []
1071
1045
  # args_def.bind(trailers: nil).to_a # => []
1072
1046
  #
1047
+ # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
1048
+ #
1049
+ # @param as [String, nil] custom option string (e.g., '--trailer')
1050
+ #
1051
+ # @param key_separator [String] separator between key and value (default: '=')
1052
+ #
1053
+ # @param inline [Boolean] when true, outputs --flag=key=value instead of --flag key=value
1054
+ #
1055
+ # @param required [Boolean] whether the option must be provided (key must exist in opts).
1056
+ # Note: empty hash/array is considered "present" and produces no output without error.
1057
+ #
1058
+ # @param allow_nil [Boolean] whether nil is allowed when required is true
1059
+ #
1060
+ # @return [void]
1061
+ #
1062
+ # @raise [ArgumentError] at bind time if array input is not a [key, value] pair or array of pairs
1063
+ #
1064
+ # @raise [ArgumentError] at bind time if a sub-array has more than 2 elements
1065
+ #
1066
+ # @raise [ArgumentError] at bind time if a key is nil, empty, or contains the separator
1067
+ #
1068
+ # @raise [ArgumentError] at bind time if a value is a Hash or Array (non-scalar)
1069
+ #
1070
+ # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
1073
1071
  def key_value_option(names, as: nil, key_separator: '=', inline: false, required: false, allow_nil: true)
1074
1072
  option_type = inline ? :inline_key_value : :key_value
1075
1073
  register_option(names, type: option_type, as: as, key_separator: key_separator,
@@ -1082,10 +1080,6 @@ module Git
1082
1080
  # This allows precise control over argument ordering, which is important for
1083
1081
  # git commands where argument position matters.
1084
1082
  #
1085
- # @param flag_string [String] the static flag string (e.g., '--', '--no-progress')
1086
- #
1087
- # @return [void]
1088
- #
1089
1083
  # @example Static flag for subcommand mode
1090
1084
  # args_def = Arguments.define do
1091
1085
  # literal '--delete'
@@ -1104,6 +1098,9 @@ module Git
1104
1098
  # args_def.bind('HEAD', 'file.txt', force: true).to_a
1105
1099
  # # => ['--force', 'HEAD', '--', 'file.txt']
1106
1100
  #
1101
+ # @param flag_string [String] the static flag string (e.g., '--', '--no-progress')
1102
+ #
1103
+ # @return [void]
1107
1104
  def literal(flag_string)
1108
1105
  @ordered_definitions << { kind: :static, flag: flag_string }
1109
1106
  @past_separator = true if flag_string == '--'
@@ -1123,16 +1120,6 @@ module Git
1123
1120
  # defined before it are always validated for option-like values (starting with
1124
1121
  # `-`), regardless of whether the terminator will ultimately be emitted.
1125
1122
  #
1126
- # @param as [String] the CLI token to emit as the options terminator
1127
- # (default `'--'`). Some commands use a different terminator; for example,
1128
- # `git rev-parse` uses `'--end-of-options'`.
1129
- #
1130
- # @return [void]
1131
- #
1132
- # @raise [ArgumentError] if called more than once per definition block
1133
- #
1134
- # @raise [ArgumentError] if a flag-producing option is defined after this call
1135
- #
1136
1123
  # @example Basic usage (git checkout tree-ish -- pathspecs)
1137
1124
  # args_def = Arguments.define do
1138
1125
  # flag_option :force
@@ -1154,6 +1141,15 @@ module Git
1154
1141
  # args_def.bind('HEAD').to_a # => ['--end-of-options', 'HEAD']
1155
1142
  # args_def.bind.to_a # => []
1156
1143
  #
1144
+ # @param as [String] the CLI token to emit as the options terminator
1145
+ # (default `'--'`). Some commands use a different terminator; for example,
1146
+ # `git rev-parse` uses `'--end-of-options'`.
1147
+ #
1148
+ # @return [void]
1149
+ #
1150
+ # @raise [ArgumentError] if called more than once per definition block
1151
+ #
1152
+ # @raise [ArgumentError] if a flag-producing option is defined after this call
1157
1153
  def end_of_options(as: '--')
1158
1154
  raise ArgumentError, 'end_of_options cannot be declared twice' if @end_of_options_declared
1159
1155
 
@@ -1165,6 +1161,15 @@ module Git
1165
1161
 
1166
1162
  # Define a custom option with a custom builder block
1167
1163
  #
1164
+ # @example Custom transformation (e.g., formatting a Date value)
1165
+ # args_def = Arguments.define do
1166
+ # custom_option :since do |val|
1167
+ # val ? "--since=#{val.strftime('%Y-%m-%d')}" : nil
1168
+ # end
1169
+ # end
1170
+ # args_def.bind(since: Date.new(2024, 1, 1)).to_a # => ['--since=2024-01-01']
1171
+ # args_def.bind.to_a # => []
1172
+ #
1168
1173
  # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
1169
1174
  #
1170
1175
  # @param required [Boolean] whether the option must be provided (key must exist in opts)
@@ -1172,6 +1177,10 @@ module Git
1172
1177
  # @param allow_nil [Boolean] whether nil is allowed when required is true. Defaults to true.
1173
1178
  # When false with required: true, raises ArgumentError if value is nil.
1174
1179
  #
1180
+ # @return [void]
1181
+ #
1182
+ # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
1183
+ #
1175
1184
  # @yield [value] block that receives the option value and returns the CLI argument(s)
1176
1185
  #
1177
1186
  # @yieldparam value [Object] the bound value for this option
@@ -1179,19 +1188,6 @@ module Git
1179
1188
  # @yieldreturn [String, Array<String>, nil] the CLI argument(s) to emit;
1180
1189
  # nil or an empty array emits nothing
1181
1190
  #
1182
- # @return [void]
1183
- #
1184
- # @raise [ArgumentError] if defined after an `end_of_options` or `literal '--'` boundary
1185
- #
1186
- # @example Custom transformation (e.g., formatting a Date value)
1187
- # args_def = Arguments.define do
1188
- # custom_option :since do |val|
1189
- # val ? "--since=#{val.strftime('%Y-%m-%d')}" : nil
1190
- # end
1191
- # end
1192
- # args_def.bind(since: Date.new(2024, 1, 1)).to_a # => ['--since=2024-01-01']
1193
- # args_def.bind.to_a # => []
1194
- #
1195
1191
  def custom_option(names, required: false, allow_nil: true, &block)
1196
1192
  register_option(names, type: :custom, builder: block, required: required, allow_nil: allow_nil)
1197
1193
  end
@@ -1202,10 +1198,6 @@ module Git
1202
1198
  # their values are still accessible on the {Bound} object. This is useful for options that
1203
1199
  # control Ruby-side execution context (e.g., working directory) rather than git flags.
1204
1200
  #
1205
- # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
1206
- #
1207
- # @return [void]
1208
- #
1209
1201
  # @example Chdir option forwarded to execution context, not emitted as a CLI flag
1210
1202
  # args_def = Arguments.define do
1211
1203
  # flag_option :verbose
@@ -1215,6 +1207,9 @@ module Git
1215
1207
  # bound.to_a # => ['--verbose'] # :chdir is not included
1216
1208
  # bound[:chdir] # => '/tmp' # still accessible on the Bound object
1217
1209
  #
1210
+ # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
1211
+ #
1212
+ # @return [void]
1218
1213
  def execution_option(names)
1219
1214
  register_option(names, type: :execution_option)
1220
1215
  end
@@ -1231,22 +1226,12 @@ module Git
1231
1226
  # `[]`, or `''`. `false` is always treated as absent for all option types.
1232
1227
  #
1233
1228
  # An ArgumentError is raised at definition time if any name given to
1234
- # +conflicts+ is not a known option or operand, catching typos early.
1229
+ # `conflicts` is not a known option or operand, catching typos early.
1235
1230
  #
1236
1231
  # The error message has the general form:
1237
1232
  #
1238
1233
  # "cannot specify :name1 and :name2"
1239
1234
  #
1240
- # @param names [Array<Symbol>] the option/operand names that conflict within
1241
- # this group
1242
- #
1243
- # @return [void]
1244
- #
1245
- # @raise [ArgumentError] if any name is not a known option or operand
1246
- #
1247
- # @raise [ArgumentError] if more than one argument in the same conflict group
1248
- # is present when building arguments
1249
- #
1250
1235
  # @example Option-only conflict group
1251
1236
  # args_def = Arguments.define do
1252
1237
  # flag_option :gpg_sign
@@ -1270,6 +1255,15 @@ module Git
1270
1255
  # args_def.bind('main', 'file.txt', merge: true)
1271
1256
  # # => raise ArgumentError, 'cannot specify :merge and :tree_ish'
1272
1257
  #
1258
+ # @param names [Array<Symbol>] the option/operand names that conflict within
1259
+ # this group
1260
+ #
1261
+ # @return [void]
1262
+ #
1263
+ # @raise [ArgumentError] if any name is not a known option or operand
1264
+ #
1265
+ # @raise [ArgumentError] if more than one argument in the same conflict group
1266
+ # is present when binding arguments (during {#bind})
1273
1267
  def conflicts(*names)
1274
1268
  names.each do |name|
1275
1269
  sym = name.to_sym
@@ -1303,16 +1297,6 @@ module Git
1303
1297
  #
1304
1298
  # "cannot specify :name1=value1 with :name2=value2"
1305
1299
  #
1306
- # @param pairs [Hash] keyword pairs mapping argument name to forbidden value
1307
- #
1308
- # @return [void]
1309
- #
1310
- # @raise [ArgumentError] if any name in +pairs+ is not a known option or
1311
- # operand
1312
- #
1313
- # @raise [ArgumentError] during {#bind} if all names are present and all
1314
- # values exactly match the declared tuple
1315
- #
1316
1300
  # @example Reject only the contradictory negatable flag combinations
1317
1301
  # args_def = Arguments.define do
1318
1302
  # flag_option :all, negatable: true
@@ -1331,6 +1315,21 @@ module Git
1331
1315
  # args_def.bind(all: true, no_ignore_removal: true).to_a # => ['--all', '--no-ignore-removal']
1332
1316
  # args_def.bind(no_all: true, ignore_removal: true).to_a # => ['--no-all', '--ignore-removal']
1333
1317
  #
1318
+ # @param pairs [Hash{Symbol => Object}] keyword pairs mapping argument name to forbidden value
1319
+ #
1320
+ # Each key must be a known option or operand name. During {#bind}, an
1321
+ # `ArgumentError` is raised when all names are present and all values
1322
+ # exactly match the declared tuple.
1323
+ #
1324
+ # @option pairs [Object] :"argument_name" the value forbidden for the argument with that name
1325
+ #
1326
+ # @return [void]
1327
+ #
1328
+ # @raise [ArgumentError] if any name in `pairs` is not a known option or
1329
+ # operand
1330
+ #
1331
+ # @raise [ArgumentError] during {#bind} if all names are present and all
1332
+ # values exactly match the declared tuple
1334
1333
  def forbid_values(**pairs)
1335
1334
  raise ArgumentError, 'forbid_values must be given at least one name-value pair' if pairs.empty?
1336
1335
 
@@ -1379,22 +1378,6 @@ module Git
1379
1378
  #
1380
1379
  # ":trigger requires at least one of :name1, :name2"
1381
1380
  #
1382
- # @param names [Array<Symbol>] the option/operand names where at least one
1383
- # must be present
1384
- #
1385
- # @option kwargs [Symbol] :when optional trigger argument; when given, the check is
1386
- # only performed if the trigger argument is present
1387
- #
1388
- # @return [void]
1389
- #
1390
- # @raise [ArgumentError] if no names are given
1391
- #
1392
- # @raise [ArgumentError] if any name (or the `when:` trigger) is not a known
1393
- # option or operand
1394
- #
1395
- # @raise [ArgumentError] if none of the arguments in the group is present
1396
- # when binding arguments (and the trigger, if any, is present)
1397
- #
1398
1381
  # @example At-least-one of two keyword options (unconditional)
1399
1382
  # args_def = Arguments.define do
1400
1383
  # value_option :pathspec_from_file, inline: true
@@ -1442,6 +1425,21 @@ module Git
1442
1425
  # # => raise ArgumentError, ':annotate requires at least one of :message, :file'
1443
1426
  # args_def.bind # trigger absent — no error
1444
1427
  #
1428
+ # @param names [Array<Symbol>] the option/operand names where at least one
1429
+ # must be present
1430
+ #
1431
+ # @option kwargs [Symbol] :when optional trigger argument; when given, the check is
1432
+ # only performed if the trigger argument is present
1433
+ #
1434
+ # @return [void]
1435
+ #
1436
+ # @raise [ArgumentError] if no names are given
1437
+ #
1438
+ # @raise [ArgumentError] if any name (or the `when:` trigger) is not a known
1439
+ # option or operand
1440
+ #
1441
+ # @raise [ArgumentError] if none of the arguments in the group is present
1442
+ # when binding arguments (and the trigger, if any, is present)
1445
1443
  def requires_one_of(*names, **kwargs)
1446
1444
  condition = kwargs.delete(:when)
1447
1445
  raise ArgumentError, "requires_one_of: unknown keyword arguments: #{kwargs.keys.inspect}" unless kwargs.empty?
@@ -1479,17 +1477,6 @@ module Git
1479
1477
  # "at least one of :a, :b, :c must be provided" # zero present
1480
1478
  # "cannot specify :a and :b" # two or more present
1481
1479
  #
1482
- # @param names [Array<Symbol>] the option/operand names where exactly one
1483
- # must be present
1484
- #
1485
- # @return [void]
1486
- #
1487
- # @raise [ArgumentError] if any name is not a known option or operand
1488
- #
1489
- # @raise [ArgumentError] at bind time if none of the arguments in the group is present
1490
- #
1491
- # @raise [ArgumentError] at bind time if more than one argument in the group is present
1492
- #
1493
1480
  # @example Mode flags where exactly one must be supplied
1494
1481
  # args_def = Arguments.define do
1495
1482
  # flag_option :mode_a
@@ -1503,6 +1490,16 @@ module Git
1503
1490
  # args_def.bind(mode_a: true, mode_c: true)
1504
1491
  # # => raise ArgumentError, 'cannot specify :mode_a and :mode_c'
1505
1492
  #
1493
+ # @param names [Array<Symbol>] the option/operand names where exactly one
1494
+ # must be present
1495
+ #
1496
+ # @return [void]
1497
+ #
1498
+ # @raise [ArgumentError] if any name is not a known option or operand
1499
+ #
1500
+ # @raise [ArgumentError] at bind time if none of the arguments in the group is present
1501
+ #
1502
+ # @raise [ArgumentError] at bind time if more than one argument in the group is present
1506
1503
  def requires_exactly_one_of(*names)
1507
1504
  requires_one_of(*names)
1508
1505
  conflicts(*names)
@@ -1529,20 +1526,6 @@ module Git
1529
1526
  #
1530
1527
  # ":trigger requires :name"
1531
1528
  #
1532
- # @param name [Symbol] the option/operand name that must be present
1533
- #
1534
- # @option kwargs [Symbol] :when the trigger argument; when present, *name* must also be present
1535
- #
1536
- # @return [void]
1537
- #
1538
- # @raise [ArgumentError] if `when:` is not provided
1539
- #
1540
- # @raise [ArgumentError] if *name* or the `when:` trigger is not a known option
1541
- # or operand
1542
- #
1543
- # @raise [ArgumentError] if the trigger is present and *name* is absent when
1544
- # binding arguments
1545
- #
1546
1529
  # @example Require pathspec_from_file when pathspec_file_nul is present
1547
1530
  # args_def = Arguments.define do
1548
1531
  # flag_option :pathspec_file_nul
@@ -1564,6 +1547,19 @@ module Git
1564
1547
  # args_def.bind(ignore_missing: true)
1565
1548
  # # => raise ArgumentError, ':ignore_missing requires :dry_run'
1566
1549
  #
1550
+ # @param name [Symbol] the option/operand name that must be present
1551
+ #
1552
+ # @option kwargs [Symbol] :when the trigger argument; when present, *name* must also be present
1553
+ #
1554
+ # @return [void]
1555
+ #
1556
+ # @raise [ArgumentError] if `when:` is not provided
1557
+ #
1558
+ # @raise [ArgumentError] if *name* or the `when:` trigger is not a known option
1559
+ # or operand
1560
+ #
1561
+ # @raise [ArgumentError] if the trigger is present and *name* is absent when
1562
+ # binding arguments
1567
1563
  def requires(name, **kwargs)
1568
1564
  condition = kwargs.delete(:when)
1569
1565
  raise ArgumentError, 'requires: `when:` keyword is required' unless condition
@@ -1585,27 +1581,6 @@ module Git
1585
1581
  # `allow_empty: true` is set on the option. For `repeatable: true` options
1586
1582
  # each element of the array is validated individually.
1587
1583
  #
1588
- # @param name [Symbol] the option name (primary or alias); must refer to a
1589
- # previously defined {#value_option} or {#flag_or_value_option}
1590
- #
1591
- # @param in [#each] accepted values enumerable. Each value is coerced with
1592
- # +to_s+ and compared as a string.
1593
- #
1594
- # For \\{#flag_or_value_option} variants (including +negatable: true+),
1595
- # boolean values (+true+ / +false+) are skipped by this check because they
1596
- # control flag-emission behavior rather than representing candidate string
1597
- # values.
1598
- #
1599
- # @return [void]
1600
- #
1601
- # @raise [ArgumentError] if +name+ is not a known option at definition time
1602
- #
1603
- # @raise [ArgumentError] if +name+ refers to a non-value option (e.g., a flag)
1604
- #
1605
- # @raise [ArgumentError] during {#bind} if the bound value is not in the
1606
- # accepted set, with a message of the form:
1607
- # `"Invalid value for :name: expected one of [...], got \"actual\""`
1608
- #
1609
1584
  # @example Constrain chmod to '+x' or '-x'
1610
1585
  # args_def = Arguments.define do
1611
1586
  # value_option :chmod, inline: true
@@ -1625,16 +1600,36 @@ module Git
1625
1600
  # args_def.bind(cleanup: 'compact')
1626
1601
  # # => raise ArgumentError, 'Invalid value for :cleanup: expected one of ["verbatim", "whitespace", "strip"], got "compact"'
1627
1602
  #
1628
- # @example Repeatable option — each element is validated
1629
- # args_def = Arguments.define do
1630
- # value_option :strategy, inline: true, repeatable: true
1631
- # allowed_values :strategy, in: %w[ours theirs]
1632
- # end
1633
- # args_def.bind(strategy: %w[ours theirs]).to_a
1634
- # # => ['--strategy=ours', '--strategy=theirs']
1635
- # args_def.bind(strategy: %w[ours other])
1636
- # # => raise ArgumentError, 'Invalid value for :strategy: expected one of ["ours", "theirs"], got "other"'
1603
+ # @example Repeatable option — each element is validated
1604
+ # args_def = Arguments.define do
1605
+ # value_option :strategy, inline: true, repeatable: true
1606
+ # allowed_values :strategy, in: %w[ours theirs]
1607
+ # end
1608
+ # args_def.bind(strategy: %w[ours theirs]).to_a
1609
+ # # => ['--strategy=ours', '--strategy=theirs']
1610
+ # args_def.bind(strategy: %w[ours other])
1611
+ # # => raise ArgumentError, 'Invalid value for :strategy: expected one of ["ours", "theirs"], got "other"'
1612
+ #
1613
+ # @param name [Symbol] the option name (primary or alias); must refer to a
1614
+ # previously defined {#value_option} or {#flag_or_value_option}
1615
+ #
1616
+ # @param in [#each] accepted values enumerable. Each value is coerced with
1617
+ # `to_s` and compared as a string.
1618
+ #
1619
+ # For {#flag_or_value_option} variants (including `negatable: true`),
1620
+ # boolean values (`true` / `false`) are skipped by this check because they
1621
+ # control flag-emission behavior rather than representing candidate string
1622
+ # values.
1623
+ #
1624
+ # @return [void]
1625
+ #
1626
+ # @raise [ArgumentError] if `name` is not a known option at definition time
1637
1627
  #
1628
+ # @raise [ArgumentError] if `name` refers to a non-value option (e.g., a flag)
1629
+ #
1630
+ # @raise [ArgumentError] during {#bind} if the bound value is not in the
1631
+ # accepted set, with a message of the form:
1632
+ # `"Invalid value for :name: expected one of [...], got \"actual\""`
1638
1633
  def allowed_values(name, in:)
1639
1634
  sym = name.to_sym
1640
1635
  defn = validate_allowed_values_definition!(sym)
@@ -1650,31 +1645,6 @@ module Git
1650
1645
  # required operands after a repeatable are filled from the end, and the
1651
1646
  # repeatable gets whatever remains in the middle.
1652
1647
  #
1653
- # @param name [Symbol] the operand name (used in error messages)
1654
- #
1655
- # @param required [Boolean] whether the argument is required. For repeatable
1656
- # operands, this means at least one value must be provided.
1657
- #
1658
- # @param repeatable [Boolean] whether the argument accepts multiple values
1659
- # (like Ruby's splat operator *args). Only one repeatable operand is
1660
- # allowed per definition; attempting to define a second will raise an
1661
- # ArgumentError.
1662
- #
1663
- # @param default [Object] the default value if not provided. For repeatable
1664
- # operands, this should be an array (e.g., `default: ['.']`).
1665
- #
1666
- # @param allow_nil [Boolean] whether nil is a valid value for a required
1667
- # operand. When true, nil consumes the operand slot but is omitted
1668
- # from output. This is useful for commands like `git checkout` where
1669
- # the tree-ish is required to consume a slot but may be nil to restore
1670
- # from the index. Defaults to false.
1671
- #
1672
- # @param skip_cli [Boolean] whether this operand participates in binding,
1673
- # validation, and accessors but is omitted from CLI argv emission.
1674
- # Defaults to false.
1675
- #
1676
- # @return [void]
1677
- #
1678
1648
  # @example Required operand (like `def clone(repository)`)
1679
1649
  # args_def = Arguments.define do
1680
1650
  # operand :repository, required: true
@@ -1755,10 +1725,33 @@ module Git
1755
1725
  # args_def.bind(nil, 'file.rb').to_a
1756
1726
  # # => ['--', 'file.rb']
1757
1727
  #
1758
- # @raise [ArgumentError] during {#bind} if the operand appears before a '--'
1759
- # boundary (or no boundary exists) and the bound value starts with '-'
1728
+ # @param name [Symbol] the operand name (used in error messages)
1729
+ #
1730
+ # @param required [Boolean] whether the argument is required. For repeatable
1731
+ # operands, this means at least one value must be provided.
1732
+ #
1733
+ # @param repeatable [Boolean] whether the argument accepts multiple values
1734
+ # (like Ruby's splat operator *args). Only one repeatable operand is
1735
+ # allowed per definition; attempting to define a second will raise an
1736
+ # ArgumentError.
1737
+ #
1738
+ # @param default [Object] the default value if not provided. For repeatable
1739
+ # operands, this should be an array (e.g., `default: ['.']`).
1740
+ #
1741
+ # @param allow_nil [Boolean] whether nil is a valid value for a required
1742
+ # operand. When true, nil consumes the operand slot but is omitted
1743
+ # from output. This is useful for commands like `git checkout` where
1744
+ # the tree-ish is required to consume a slot but may be nil to restore
1745
+ # from the index. Defaults to false.
1746
+ #
1747
+ # @param skip_cli [Boolean] whether this operand participates in binding,
1748
+ # validation, and accessors but is omitted from CLI argv emission.
1749
+ # Defaults to false.
1760
1750
  #
1751
+ # @return [void]
1761
1752
  #
1753
+ # @raise [ArgumentError] during {#bind} if the operand appears before a '--'
1754
+ # boundary (or no boundary exists) and the bound value starts with '-'
1762
1755
  def operand(name, required: false, repeatable: false, default: nil, allow_nil: false,
1763
1756
  skip_cli: false)
1764
1757
  validate_single_repeatable!(name) if repeatable
@@ -1773,16 +1766,6 @@ module Git
1773
1766
  # - Automatically normalizes option aliases to their canonical names
1774
1767
  # - Supports splatting via `to_ary` for seamless use with `command(*bound)`
1775
1768
  #
1776
- # @param positionals [Array] positional argument values
1777
- #
1778
- # @param opts [Hash] the keyword options
1779
- #
1780
- # @return [Bound] a frozen object with accessor methods for all arguments
1781
- #
1782
- # @raise [ArgumentError] if unsupported options are provided or validation fails
1783
- #
1784
- # @raise [ArgumentError] if an operand value before a '--' boundary starts with '-'
1785
- #
1786
1769
  # @example Simple splatting (same behavior as build)
1787
1770
  # def call(*, **)
1788
1771
  # @execution_context.command_capturing(*ARGS.bind(*, **))
@@ -1806,6 +1789,17 @@ module Git
1806
1789
  # bound_args = args_def.bind(hash: 'abc123')
1807
1790
  # bound_args[:hash] # => 'abc123'
1808
1791
  #
1792
+ # @param positionals [Array] positional argument values
1793
+ #
1794
+ # @param opts [Hash] the keyword options
1795
+ #
1796
+ # @option opts [Object] :"option_name" value for any option registered in this argument definition
1797
+ #
1798
+ # @return [Bound] a frozen object with accessor methods for all arguments
1799
+ #
1800
+ # @raise [ArgumentError] if unsupported options are provided or validation fails
1801
+ #
1802
+ # @raise [ArgumentError] if an operand value before a '--' boundary starts with '-'
1809
1803
  def bind(*positionals, **opts)
1810
1804
  normalized_opts = validate_and_normalize_options!(opts)
1811
1805
  allocated_positionals = allocate_and_validate_positionals(positionals)
@@ -1848,7 +1842,9 @@ module Git
1848
1842
  # Run all cross-field validations on bound inputs
1849
1843
  #
1850
1844
  # @param normalized_opts [Hash] normalized keyword options
1845
+ #
1851
1846
  # @param allocated_positionals [Hash] allocated positional arguments
1847
+ #
1852
1848
  # @return [void]
1853
1849
  #
1854
1850
  def validate_bind_inputs!(normalized_opts, allocated_positionals)
@@ -1862,7 +1858,7 @@ module Git
1862
1858
  #
1863
1859
  # @param types [Array<Symbol>] the option types to match
1864
1860
  #
1865
- # @return [Array<Symbol>]
1861
+ # @return [Array<Symbol>] matching option names
1866
1862
  #
1867
1863
  def option_names_by_type(*types)
1868
1864
  @option_definitions.each_with_object([]) do |(name, definition), names|
@@ -1873,7 +1869,11 @@ module Git
1873
1869
  # Validate and normalize keyword options
1874
1870
  #
1875
1871
  # @param opts [Hash] raw keyword options
1872
+ #
1873
+ # @option opts [Object] :"option_name" value for any option registered in this argument definition
1874
+ #
1876
1875
  # @return [Hash] normalized options with aliases resolved
1876
+ #
1877
1877
  # @raise [ArgumentError] if options are unsupported, conflicting, or invalid
1878
1878
  #
1879
1879
  def validate_and_normalize_options!(opts)
@@ -1888,6 +1888,7 @@ module Git
1888
1888
  # Build a hash of all option values for the Bound object
1889
1889
  #
1890
1890
  # @param normalized_opts [Hash] the normalized options
1891
+ #
1891
1892
  # @return [Hash{Symbol => Object}] option values with defaults applied
1892
1893
  def build_options_hash(normalized_opts)
1893
1894
  result = {}
@@ -1900,6 +1901,7 @@ module Git
1900
1901
  # Get the default value for an option when not provided
1901
1902
  #
1902
1903
  # @param name [Symbol] the option name
1904
+ #
1903
1905
  # @return [Object] the default value (false for flags, nil for values)
1904
1906
  def default_option_value(name)
1905
1907
  definition = @option_definitions[name]
@@ -1912,7 +1914,9 @@ module Git
1912
1914
  # Determine the internal option type based on inline and as_operand modifiers
1913
1915
  #
1914
1916
  # @param inline [Boolean] whether to use inline format (--flag=value)
1917
+ #
1915
1918
  # @param as_operand [Boolean] whether to output as operand (positional argument)
1919
+ #
1916
1920
  # @return [Symbol] the internal option type
1917
1921
  #
1918
1922
  def determine_value_option_type(inline, as_operand)
@@ -1928,8 +1932,11 @@ module Git
1928
1932
  # Validate value modifier combinations
1929
1933
  #
1930
1934
  # @param names [Symbol, Array<Symbol>] the option name(s)
1935
+ #
1931
1936
  # @param inline [Boolean] whether inline: true was specified
1937
+ #
1932
1938
  # @param as_operand [Boolean] whether as_operand: true was specified
1939
+ #
1933
1940
  # @raise [ArgumentError] if invalid modifier combination is used
1934
1941
  #
1935
1942
  def validate_value_modifiers!(names, inline, as_operand)
@@ -1940,7 +1947,31 @@ module Git
1940
1947
  # Register an option with optional aliases
1941
1948
  #
1942
1949
  # @param names [Symbol, Array<Symbol>] the option name(s), first is primary
1950
+ #
1943
1951
  # @param definition [Hash] the option definition
1952
+ #
1953
+ # @option definition [Symbol] :type the DSL option type (e.g. `:flag`, `:value`)
1954
+ #
1955
+ # @option definition [String, Array<String>, nil] :as the CLI flag name override
1956
+ #
1957
+ # @option definition [Class, nil] :expected_type the required Ruby class for the value
1958
+ #
1959
+ # @option definition [Proc, nil] :validator a callable that validates the value
1960
+ #
1961
+ # @option definition [Boolean] :required (false) whether the option is required
1962
+ #
1963
+ # @option definition [Boolean] :allow_nil whether nil is a valid value
1964
+ #
1965
+ # @option definition [Proc, nil] :builder a custom argument builder callable
1966
+ #
1967
+ # @option definition [Integer, nil] :max_times maximum repeat count for flag options
1968
+ #
1969
+ # @option definition [Boolean] :allow_empty whether empty values should be emitted
1970
+ #
1971
+ # @option definition [Boolean] :repeatable whether the option accepts multiple values
1972
+ #
1973
+ # @option definition [String] :key_separator separator used by key-value options
1974
+ #
1944
1975
  # @return [void]
1945
1976
  #
1946
1977
  def register_option(names, **definition)
@@ -1955,15 +1986,34 @@ module Git
1955
1986
  store_option(primary, keys, definition)
1956
1987
  end
1957
1988
 
1989
+ # Store option in the option definitions and alias map
1990
+ #
1991
+ # @param primary [Symbol] the primary option name
1992
+ #
1993
+ # @param keys [Array<Symbol>] all alias keys including the primary
1994
+ #
1995
+ # @param definition [Hash] the option definition hash
1996
+ #
1997
+ # @return [void]
1998
+ #
1999
+ # @api private
1958
2000
  def store_option(primary, keys, definition)
1959
2001
  @option_definitions[primary] = definition
1960
2002
  keys.each { |key| @alias_map[key] = primary }
1961
2003
  @ordered_definitions << { kind: :option, name: primary }
1962
2004
  end
1963
2005
 
1964
- # Raise if any of +keys+ collides with a previously synthesized +no_<name>+
2006
+ # Raise if any of `keys` collides with a previously synthesized `no_<name>`
1965
2007
  # companion entry. This catches the case where a user declares
1966
- # +flag_option :foo, negatable: true+ followed by +flag_option :no_foo+.
2008
+ # `flag_option :foo, negatable: true` followed by `flag_option :no_foo`.
2009
+ #
2010
+ # @param keys [Array<Symbol>] the option alias keys to check
2011
+ #
2012
+ # @return [void]
2013
+ #
2014
+ # @raise [ArgumentError] if any key is already registered as a negatable companion
2015
+ #
2016
+ # @api private
1967
2017
  def validate_no_companion_collision!(keys)
1968
2018
  keys.each do |key|
1969
2019
  next unless @negatable_companions.include?(key)
@@ -1973,11 +2023,19 @@ module Git
1973
2023
  end
1974
2024
  end
1975
2025
 
1976
- # Raise if the +keys+ array contains duplicate entries.
2026
+ # Raise if the `keys` array contains duplicate entries
1977
2027
  #
1978
- # Duplicate aliases in a single declaration (e.g. +flag_option %i[foo foo]+)
2028
+ # Duplicate aliases in a single declaration (e.g. `flag_option %i[foo foo]`)
1979
2029
  # are a programming mistake and would silently overwrite each other in
1980
- # +@alias_map+. Catching them at definition time makes the error obvious.
2030
+ # `@alias_map`. Catching them at definition time makes the error obvious.
2031
+ #
2032
+ # @param keys [Array<Symbol>] the option alias keys to check for duplicates
2033
+ #
2034
+ # @return [void]
2035
+ #
2036
+ # @raise [ArgumentError] if the keys array contains duplicate entries
2037
+ #
2038
+ # @api private
1981
2039
  def validate_no_duplicate_aliases!(keys)
1982
2040
  seen = Set.new
1983
2041
  keys.each do |key|
@@ -1985,6 +2043,17 @@ module Git
1985
2043
  end
1986
2044
  end
1987
2045
 
2046
+ # Validate that max_times is nil or an Integer >= 2
2047
+ #
2048
+ # @param option_name [Symbol] the option name (for error messages)
2049
+ #
2050
+ # @param max_times [Integer, nil] the max_times value to validate
2051
+ #
2052
+ # @return [void]
2053
+ #
2054
+ # @raise [ArgumentError] if max_times is not nil and not an Integer >= 2
2055
+ #
2056
+ # @api private
1988
2057
  def validate_max_times!(option_name, max_times)
1989
2058
  return if max_times.nil?
1990
2059
 
@@ -1995,9 +2064,23 @@ module Git
1995
2064
 
1996
2065
  # Register two companion :flag entries for a negatable flag option
1997
2066
  #
1998
- # Registers a positive entry for +names+ and a boolean-only negative entry for
1999
- # <tt>:no_<primary></tt>. An automatic conflict is added so that both being
2067
+ # Registers a positive entry for `names` and a boolean-only negative entry for
2068
+ # `:no_<primary>`. An automatic conflict is added so that both being
2000
2069
  # true at bind time raises ArgumentError.
2070
+ #
2071
+ # @param names [Symbol, Array<Symbol>] the option name(s); first is primary
2072
+ #
2073
+ # @param as [String, nil] the CLI flag string, or nil to derive from name
2074
+ #
2075
+ # @param required [Boolean] whether at least one of the pair must be provided
2076
+ #
2077
+ # @param allow_nil [Boolean] whether nil is a permitted bound value
2078
+ #
2079
+ # @param max_times [Integer, nil] maximum number of times the flag may be repeated
2080
+ #
2081
+ # @return [void]
2082
+ #
2083
+ # @api private
2001
2084
  def register_negatable_flag_pair(names, as:, required:, allow_nil:, max_times:)
2002
2085
  primary = Array(names).first
2003
2086
  validate_negatable_allow_nil!(primary, required: required, allow_nil: allow_nil)
@@ -2010,6 +2093,24 @@ module Git
2010
2093
 
2011
2094
  # Register a positive flag-or-value entry and a boolean-only negative companion
2012
2095
  # entry for a negatable flag-or-value option
2096
+ #
2097
+ # @param names [Symbol, Array<Symbol>] the option name(s); first is primary
2098
+ #
2099
+ # @param as [String, nil] the CLI flag string, or nil to derive from name
2100
+ #
2101
+ # @param type [Class, Array<Class>, nil] expected Ruby type(s) for the value
2102
+ #
2103
+ # @param inline [Boolean] whether to use inline format (--flag=value)
2104
+ #
2105
+ # @param repeatable [Boolean] whether the option may be given multiple times
2106
+ #
2107
+ # @param required [Boolean] whether at least one of the pair must be provided
2108
+ #
2109
+ # @param allow_nil [Boolean] whether nil is a permitted bound value
2110
+ #
2111
+ # @return [void]
2112
+ #
2113
+ # @api private
2013
2114
  def register_negatable_flag_or_value_pair(names, as:, type:, inline:, repeatable:, required:, allow_nil:)
2014
2115
  primary = Array(names).first
2015
2116
  validate_negatable_allow_nil!(primary, required: required, allow_nil: allow_nil)
@@ -2022,6 +2123,18 @@ module Git
2022
2123
  end
2023
2124
 
2024
2125
  # Run shared validations for a negatable option before registering either side
2126
+ #
2127
+ # @param primary [Symbol] the primary option name
2128
+ #
2129
+ # @param names [Symbol, Array<Symbol>] the option name(s) including aliases
2130
+ #
2131
+ # @param as [String, nil] the CLI flag string, or nil to derive from name
2132
+ #
2133
+ # @return [void]
2134
+ #
2135
+ # @raise [ArgumentError] if any negatable precondition is violated
2136
+ #
2137
+ # @api private
2025
2138
  def prepare_negatable!(primary, names, as)
2026
2139
  validate_negatable_primary_key!(primary)
2027
2140
  validate_negatable_as_not_array!(primary, as)
@@ -2031,8 +2144,18 @@ module Git
2031
2144
  validate_no_companion_in_alias_list!(no_name, Array(names))
2032
2145
  end
2033
2146
 
2034
- # Register the synthesized +no_<primary>+ flag entry, the auto-conflict, and
2035
- # (when +required: true+) the auto requires_one_of group
2147
+ # Register the synthesized `no_<primary>` flag entry, the auto-conflict, and
2148
+ # (when `required: true`) the auto requires_one_of group
2149
+ #
2150
+ # @param primary [Symbol] the primary option name
2151
+ #
2152
+ # @param as [String, nil] the positive CLI flag string used to derive the negative form
2153
+ #
2154
+ # @param required [Boolean] whether an auto requires_one_of group should be added
2155
+ #
2156
+ # @return [void]
2157
+ #
2158
+ # @api private
2036
2159
  def register_negative_companion(primary, as:, required:)
2037
2160
  no_name = :"no_#{primary}"
2038
2161
  positive_flag = as || default_arg_spec(primary)
@@ -2059,6 +2182,8 @@ module Git
2059
2182
  #
2060
2183
  # @param allow_nil [Boolean] whether nil is allowed
2061
2184
  #
2185
+ # @return [void]
2186
+ #
2062
2187
  # @raise [ArgumentError] if `required: true` and `allow_nil: false` are combined
2063
2188
  # with `negatable: true`
2064
2189
  #
@@ -2070,8 +2195,15 @@ module Git
2070
2195
  '(nil is caught by the auto requires_one_of group, not allow_nil)'
2071
2196
  end
2072
2197
 
2198
+ # Validate that the primary key is snake_case so a meaningful no_ companion can be generated
2199
+ #
2200
+ # @param key [Symbol] the primary option name to validate
2201
+ #
2202
+ # @return [void]
2203
+ #
2073
2204
  # @raise [ArgumentError] if key is not snake_case
2074
2205
  #
2206
+ # @api private
2075
2207
  def validate_negatable_primary_key!(key)
2076
2208
  return if key.to_s.match?(/\A[a-z][a-z0-9_]*\z/)
2077
2209
 
@@ -2085,6 +2217,16 @@ module Git
2085
2217
  # Arrays for as: are not compatible with negatable: true regardless of the
2086
2218
  # underlying option type — the synthesized +--no-<flag>+ form has no sensible
2087
2219
  # mapping when the positive form expands to multiple CLI tokens.
2220
+ #
2221
+ # @param primary [Symbol] the primary option name (for error messages)
2222
+ #
2223
+ # @param as [Object] the as: parameter value to check
2224
+ #
2225
+ # @return [void]
2226
+ #
2227
+ # @raise [ArgumentError] if as: is an Array
2228
+ #
2229
+ # @api private
2088
2230
  def validate_negatable_as_not_array!(primary, as)
2089
2231
  return unless as.is_a?(Array)
2090
2232
 
@@ -2096,6 +2238,16 @@ module Git
2096
2238
  # Negation requires a long-form flag because the synthesized companion is
2097
2239
  # always +--no-<flag>+; deriving it from a short flag would yield a
2098
2240
  # nonexistent git form like +--no-S+.
2241
+ #
2242
+ # @param primary [Symbol] the primary option name (for error messages)
2243
+ #
2244
+ # @param as [String, nil] the as: parameter value to check
2245
+ #
2246
+ # @return [void]
2247
+ #
2248
+ # @raise [ArgumentError] if as: is not nil and not a long-form flag
2249
+ #
2250
+ # @api private
2099
2251
  def validate_negatable_as_long_form!(primary, as)
2100
2252
  return if as.nil?
2101
2253
  return if as.is_a?(String) && as.start_with?('--')
@@ -2106,6 +2258,13 @@ module Git
2106
2258
 
2107
2259
  # Raise if the generated no_ companion key is already registered
2108
2260
  #
2261
+ # @param no_name [Symbol] the synthesized companion key (e.g. :no_force)
2262
+ #
2263
+ # @return [void]
2264
+ #
2265
+ # @raise [ArgumentError] if the companion key is already in the alias map
2266
+ #
2267
+ # @api private
2109
2268
  def validate_no_negatable_collision!(no_name)
2110
2269
  return unless @alias_map.key?(no_name)
2111
2270
 
@@ -2113,11 +2272,21 @@ module Git
2113
2272
  "negatable: true would register :#{no_name} but that key is already registered"
2114
2273
  end
2115
2274
 
2116
- # Raise if the synthesized companion key appears in the same declaration's alias list.
2275
+ # Raise if the synthesized companion key appears in the same declaration's alias list
2117
2276
  #
2118
- # This catches e.g. +flag_option %i[foo no_foo], negatable: true+ where :no_foo
2277
+ # This catches e.g. `flag_option %i[foo no_foo], negatable: true` where `:no_foo`
2119
2278
  # is listed as an alias and would be silently overwritten when the companion is
2120
- # registered, corrupting @alias_map and @option_definitions.
2279
+ # registered, corrupting `@alias_map` and `@option_definitions`.
2280
+ #
2281
+ # @param no_name [Symbol] the synthesized companion key (e.g. :no_force)
2282
+ #
2283
+ # @param keys [Array<Symbol>] the alias list from the current declaration
2284
+ #
2285
+ # @return [void]
2286
+ #
2287
+ # @raise [ArgumentError] if the companion key is already listed as an alias
2288
+ #
2289
+ # @api private
2121
2290
  def validate_no_companion_in_alias_list!(no_name, keys)
2122
2291
  return unless keys.include?(no_name)
2123
2292
 
@@ -2129,7 +2298,11 @@ module Git
2129
2298
  # Validate that flag-producing options are not defined after a '--' boundary
2130
2299
  #
2131
2300
  # @param type [Symbol] the option type
2301
+ #
2132
2302
  # @param option_name [Symbol] the primary option name
2303
+ #
2304
+ # @return [void]
2305
+ #
2133
2306
  # @raise [ArgumentError] if a flag-producing option is defined after '--'
2134
2307
  #
2135
2308
  def validate_option_after_separator!(type, option_name)
@@ -2141,6 +2314,17 @@ module Git
2141
2314
  'boundary because its flags would be treated as operands by git'
2142
2315
  end
2143
2316
 
2317
+ # Apply a type-checking validator to a definition when expected_type is set
2318
+ #
2319
+ # @param definition [Hash] the option definition hash (mutated in place)
2320
+ #
2321
+ # @param option_name [Symbol] the option name (for error messages)
2322
+ #
2323
+ # @return [void]
2324
+ #
2325
+ # @raise [ArgumentError] if both `expected_type` and `validator` are specified
2326
+ #
2327
+ # @api private
2144
2328
  def apply_type_validator!(definition, option_name)
2145
2329
  return unless definition[:expected_type]
2146
2330
 
@@ -2152,6 +2336,17 @@ module Git
2152
2336
  definition[:validator] = create_type_validator(option_name, definition[:expected_type])
2153
2337
  end
2154
2338
 
2339
+ # Validate that array as: values are only used with flag-type options
2340
+ #
2341
+ # @param definition [Hash] the option definition hash
2342
+ #
2343
+ # @param option_name [Symbol] the primary option name (for error messages)
2344
+ #
2345
+ # @return [void]
2346
+ #
2347
+ # @raise [ArgumentError] if as: is an Array and the type is not :flag
2348
+ #
2349
+ # @api private
2155
2350
  def validate_as_parameter!(definition, option_name)
2156
2351
  return unless definition[:as].is_a?(Array)
2157
2352
 
@@ -2165,7 +2360,9 @@ module Git
2165
2360
  # Build arguments by iterating over definitions in their defined order
2166
2361
  #
2167
2362
  # @param allocated_positionals [Hash] the allocated positional values
2363
+ #
2168
2364
  # @param normalized_opts [Hash] normalized keyword options
2365
+ #
2169
2366
  # @return [Array<String>] the command-line arguments
2170
2367
  #
2171
2368
  def build_ordered_arguments(allocated_positionals, normalized_opts)
@@ -2185,9 +2382,13 @@ module Git
2185
2382
  # Build a single definition entry and append to args
2186
2383
  #
2187
2384
  # @param args [Array<String>] the argument array to append to
2385
+ #
2188
2386
  # @param entry [Hash] the definition entry with :kind and name/flag
2387
+ #
2189
2388
  # @param normalized_opts [Hash] normalized keyword options
2389
+ #
2190
2390
  # @param allocated_positionals [Hash] the allocated positional values
2391
+ #
2191
2392
  # @return [void]
2192
2393
  #
2193
2394
  def build_entry(args, entry, normalized_opts, allocated_positionals)
@@ -2209,6 +2410,7 @@ module Git
2209
2410
  # follows it, or strip it
2210
2411
  #
2211
2412
  # @param args [Array] the built argument array (may contain END_OF_OPTIONS_MARKER)
2413
+ #
2212
2414
  # @return [Array<String>] the argument array with the marker resolved
2213
2415
  #
2214
2416
  def resolve_end_of_options_marker(args)
@@ -2226,6 +2428,7 @@ module Git
2226
2428
  # Allocate positionals and perform validation, returning the allocation hash
2227
2429
  #
2228
2430
  # @param positionals [Array] positional argument values
2431
+ #
2229
2432
  # @return [Hash] allocation of positional names to values
2230
2433
  #
2231
2434
  def allocate_and_validate_positionals(positionals)
@@ -2245,8 +2448,11 @@ module Git
2245
2448
  # Build a single positional argument
2246
2449
  #
2247
2450
  # @param args [Array<String>] the argument array to append to
2451
+ #
2248
2452
  # @param name [Symbol] the positional argument name
2453
+ #
2249
2454
  # @param allocation [Hash] the allocated positional values
2455
+ #
2250
2456
  # @return [void]
2251
2457
  #
2252
2458
  def build_single_positional(args, name, allocation)
@@ -2257,6 +2463,15 @@ module Git
2257
2463
  append_positional_to_args(args, value, definition)
2258
2464
  end
2259
2465
 
2466
+ # Raise if a second repeatable operand is declared
2467
+ #
2468
+ # @param name [Symbol] the name of the operand being added
2469
+ #
2470
+ # @return [void]
2471
+ #
2472
+ # @raise [ArgumentError] if a repeatable operand is already registered
2473
+ #
2474
+ # @api private
2260
2475
  def validate_single_repeatable!(name)
2261
2476
  existing_repeatable = @operand_definitions.find { |d| d[:repeatable] }
2262
2477
  return unless existing_repeatable
@@ -2266,6 +2481,23 @@ module Git
2266
2481
  "cannot add :#{name} as repeatable"
2267
2482
  end
2268
2483
 
2484
+ # Append a positional operand definition to the internal arrays
2485
+ #
2486
+ # @param name [Symbol] the operand name
2487
+ #
2488
+ # @param required [Boolean] whether the operand is required
2489
+ #
2490
+ # @param repeatable [Boolean] whether the operand may accept multiple values
2491
+ #
2492
+ # @param default [Object] the default value when the operand is not provided
2493
+ #
2494
+ # @param allow_nil [Boolean] whether nil is a permitted value
2495
+ #
2496
+ # @param skip_cli [Boolean] whether to omit this operand from the CLI array
2497
+ #
2498
+ # @return [void]
2499
+ #
2500
+ # @api private
2269
2501
  def add_operand_definition(name, required, repeatable, default, allow_nil, skip_cli)
2270
2502
  @operand_definitions << {
2271
2503
  name: name, required: required, repeatable: repeatable,
@@ -2274,6 +2506,9 @@ module Git
2274
2506
  @ordered_definitions << { kind: :operand, name: name }
2275
2507
  end
2276
2508
 
2509
+ # Maps option types to their build strategy (Symbol method name or lambda)
2510
+ #
2511
+ # @api private
2277
2512
  BUILDERS = {
2278
2513
  flag: :build_flag,
2279
2514
  value: lambda do |args, arg_spec, value, definition|
@@ -2316,6 +2551,19 @@ module Git
2316
2551
  }.freeze
2317
2552
  private_constant :BUILDERS
2318
2553
 
2554
+ # Build a single option's CLI tokens and append them to args
2555
+ #
2556
+ # @param args [Array<String>] the argument array to append to
2557
+ #
2558
+ # @param name [Symbol] the canonical option name
2559
+ #
2560
+ # @param definition [Hash] the option definition hash
2561
+ #
2562
+ # @param value [Object] the bound value for the option
2563
+ #
2564
+ # @return [void]
2565
+ #
2566
+ # @api private
2319
2567
  def build_option(args, name, definition, value)
2320
2568
  return if should_skip_option?(value, definition)
2321
2569
 
@@ -2334,6 +2582,7 @@ module Git
2334
2582
  # multi-character options use double dash (--force)
2335
2583
  #
2336
2584
  # @param name [Symbol] the option name
2585
+ #
2337
2586
  # @return [String] the argument specification (e.g., '-f' or '--force')
2338
2587
  #
2339
2588
  def default_arg_spec(name)
@@ -2344,12 +2593,26 @@ module Git
2344
2593
  # Check if an argument specification is for a short (single-character) option
2345
2594
  #
2346
2595
  # @param arg_spec [String] the argument specification
2596
+ #
2347
2597
  # @return [Boolean] true if this is a short option (single dash, single char)
2348
2598
  #
2349
2599
  def short_option?(arg_spec)
2350
2600
  arg_spec.is_a?(String) && arg_spec.match?(/\A-[^-]\z/)
2351
2601
  end
2352
2602
 
2603
+ # Build key_value option tokens and append to args
2604
+ #
2605
+ # @param args [Array<String>] the argument array to append to
2606
+ #
2607
+ # @param arg_spec [String] the CLI flag string
2608
+ #
2609
+ # @param value [Hash, Array] the key-value input
2610
+ #
2611
+ # @param definition [Hash] the option definition hash
2612
+ #
2613
+ # @return [void]
2614
+ #
2615
+ # @api private
2353
2616
  def build_key_value(args, arg_spec, value, definition)
2354
2617
  sep = definition[:key_separator] || '='
2355
2618
  option_name = definition[:aliases].first
@@ -2362,6 +2625,19 @@ module Git
2362
2625
  end
2363
2626
  end
2364
2627
 
2628
+ # Build inline_key_value option tokens and append to args
2629
+ #
2630
+ # @param args [Array<String>] the argument array to append to
2631
+ #
2632
+ # @param arg_spec [String] the CLI flag string
2633
+ #
2634
+ # @param value [Hash, Array] the key-value input
2635
+ #
2636
+ # @param definition [Hash] the option definition hash
2637
+ #
2638
+ # @return [void]
2639
+ #
2640
+ # @api private
2365
2641
  def build_inline_key_value(args, arg_spec, value, definition)
2366
2642
  sep = definition[:key_separator] || '='
2367
2643
  option_name = definition[:aliases].first
@@ -2379,6 +2655,17 @@ module Git
2379
2655
  # Short options (single-char) use no separator: -n3
2380
2656
  # Long options (multi-char) use = separator: --name=value
2381
2657
  #
2658
+ # @param args [Array<String>] the argument array to append to
2659
+ #
2660
+ # @param arg_spec [String] the CLI flag string
2661
+ #
2662
+ # @param value [Object] the bound value
2663
+ #
2664
+ # @param definition [Hash] the option definition hash
2665
+ #
2666
+ # @return [void]
2667
+ #
2668
+ # @api private
2382
2669
  def build_inline_value(args, arg_spec, value, definition)
2383
2670
  sep = inline_value_separator(arg_spec)
2384
2671
  if definition[:repeatable]
@@ -2390,6 +2677,17 @@ module Git
2390
2677
 
2391
2678
  # Build flag or inline value option with POSIX-compliant formatting
2392
2679
  #
2680
+ # @param args [Array<String>] the argument array to append to
2681
+ #
2682
+ # @param arg_spec [String] the CLI flag string
2683
+ #
2684
+ # @param value [Boolean, Object] the bound value
2685
+ #
2686
+ # @param definition [Hash] the option definition hash
2687
+ #
2688
+ # @return [void]
2689
+ #
2690
+ # @api private
2393
2691
  def build_flag_or_inline_value(args, arg_spec, value, definition)
2394
2692
  each_flag_or_value_value(value, definition, 'flag_or_inline_value') do |v|
2395
2693
  next if v == false
@@ -2400,6 +2698,17 @@ module Git
2400
2698
 
2401
2699
  # Build flag or value option
2402
2700
  #
2701
+ # @param args [Array<String>] the argument array to append to
2702
+ #
2703
+ # @param arg_spec [String] the CLI flag string
2704
+ #
2705
+ # @param value [Boolean, Object] the bound value
2706
+ #
2707
+ # @param definition [Hash] the option definition hash
2708
+ #
2709
+ # @return [void]
2710
+ #
2711
+ # @api private
2403
2712
  def build_flag_or_value(args, arg_spec, value, definition)
2404
2713
  each_flag_or_value_value(value, definition, 'flag_or_value') do |v|
2405
2714
  next if v == false
@@ -2412,6 +2721,21 @@ module Git
2412
2721
  end
2413
2722
  end
2414
2723
 
2724
+ # Iterate over each individual flag-or-value element, validating and yielding
2725
+ #
2726
+ # @param value [Object] the bound value (scalar or array for repeatable)
2727
+ #
2728
+ # @param definition [Hash] the option definition hash
2729
+ #
2730
+ # @param option_type [String] the option type name (for error messages)
2731
+ #
2732
+ # @return [void]
2733
+ #
2734
+ # @yield [value] each validated element
2735
+ #
2736
+ # @yieldparam value [Object] the validated scalar value (true, false, or a non-nil object)
2737
+ #
2738
+ # @api private
2415
2739
  def each_flag_or_value_value(value, definition, option_type)
2416
2740
  values = definition[:repeatable] ? Array(value) : [value]
2417
2741
  values.each do |v|
@@ -2423,10 +2747,19 @@ module Git
2423
2747
  # Validate that a flag_or_value element is not nil.
2424
2748
  #
2425
2749
  # Boolean values (true/false) control flag presence/absence. Any other non-nil
2426
- # object is accepted and converted to a CLI argument string via +#to_s+.
2750
+ # object is accepted and converted to a CLI argument string via `#to_s`.
2427
2751
  # Nil is rejected only within repeatable arrays — non-repeatable nil values are
2428
- # filtered out earlier by +should_skip_option?+ and never reach here.
2752
+ # filtered out earlier by `should_skip_option?` and never reach here.
2753
+ #
2754
+ # @param value [Object] the element value to validate
2755
+ #
2756
+ # @param option_type [String] the option type name (for error messages)
2757
+ #
2758
+ # @return [void]
2759
+ #
2760
+ # @raise [ArgumentError] if value is nil
2429
2761
  #
2762
+ # @api private
2430
2763
  def validate_flag_or_value_type!(value, option_type)
2431
2764
  return unless value.nil?
2432
2765
 
@@ -2442,6 +2775,7 @@ module Git
2442
2775
  # - Long options (double dash like --name): = separator (--name=value)
2443
2776
  #
2444
2777
  # @param arg_spec [String] the argument specification
2778
+ #
2445
2779
  # @return [String] empty string ('') for short options, '=' for long options;
2446
2780
  # never returns nil, safe to concatenate directly
2447
2781
  #
@@ -2449,11 +2783,35 @@ module Git
2449
2783
  short_option?(arg_spec) ? '' : '='
2450
2784
  end
2451
2785
 
2786
+ # Build flag option tokens and append to args
2787
+ #
2788
+ # @param args [Array<String>] the argument array to append to
2789
+ #
2790
+ # @param arg_spec [String, Array<String>] the CLI flag string(s)
2791
+ #
2792
+ # @param value [Boolean, Integer] the bound flag value
2793
+ #
2794
+ # @param definition [Hash] the option definition hash
2795
+ #
2796
+ # @return [void]
2797
+ #
2798
+ # @api private
2452
2799
  def build_flag(args, arg_spec, value, definition)
2453
2800
  count = normalize_flag_value!(value, definition)
2454
2801
  append_repeated_flag(args, arg_spec, count)
2455
2802
  end
2456
2803
 
2804
+ # Append a flag to the args array the given number of times
2805
+ #
2806
+ # @param args [Array<String>] the argument array to append to
2807
+ #
2808
+ # @param arg_spec [String, Array<String>] the CLI flag string(s)
2809
+ #
2810
+ # @param count [Integer] the number of times to append the flag
2811
+ #
2812
+ # @return [void]
2813
+ #
2814
+ # @api private
2457
2815
  def append_repeated_flag(args, arg_spec, count)
2458
2816
  return if count <= 0
2459
2817
 
@@ -2462,6 +2820,17 @@ module Git
2462
2820
  end
2463
2821
  end
2464
2822
 
2823
+ # Normalize a flag bound value to a repeat count integer
2824
+ #
2825
+ # @param value [Boolean, Integer, nil] the bound flag value
2826
+ #
2827
+ # @param definition [Hash] the option definition hash
2828
+ #
2829
+ # @return [Integer] the number of times the flag should be emitted
2830
+ #
2831
+ # @raise [ArgumentError] if the value is not valid for this flag type
2832
+ #
2833
+ # @api private
2465
2834
  def normalize_flag_value!(value, definition)
2466
2835
  return 1 if value == true
2467
2836
  return 0 if value.nil? || value == false
@@ -2476,15 +2845,50 @@ module Git
2476
2845
  raise ArgumentError, "Invalid value for :#{option_name}: expected true, false, or a positive Integer"
2477
2846
  end
2478
2847
 
2848
+ # Raise a type error for a flag option that received a non-boolean value without max_times
2849
+ #
2850
+ # @param value [Object] the invalid value
2851
+ #
2852
+ # @param definition [Hash] the option definition hash
2853
+ #
2854
+ # @return [void]
2855
+ #
2856
+ # @raise [ArgumentError] always
2857
+ #
2858
+ # @api private
2479
2859
  def raise_flag_type_boolean_error!(value, definition)
2480
2860
  raise_flag_boolean_error!(definition[:aliases].first, value)
2481
2861
  end
2482
2862
 
2863
+ # Raise an ArgumentError describing a non-boolean value for a boolean-only flag option
2864
+ #
2865
+ # @param option_name [Symbol] the option name (for the error message)
2866
+ #
2867
+ # @param value [Object] the invalid value received
2868
+ #
2869
+ # @return [void]
2870
+ #
2871
+ # @raise [ArgumentError] always
2872
+ #
2873
+ # @api private
2483
2874
  def raise_flag_boolean_error!(option_name, value)
2484
2875
  raise ArgumentError,
2485
2876
  "flag_option :#{option_name} expects a boolean value, got #{value.inspect} (#{value.class})"
2486
2877
  end
2487
2878
 
2879
+ # Validate and return an integer flag repeat count within the max_times limit
2880
+ #
2881
+ # @param value [Integer] the integer value to validate
2882
+ #
2883
+ # @param option_name [Symbol] the option name (for error messages)
2884
+ #
2885
+ # @param max_times [Integer] the maximum allowed repeat count
2886
+ #
2887
+ # @return [Integer] the validated repeat count
2888
+ #
2889
+ # @raise [ArgumentError] if value is not a positive Integer or exceeds max_times
2890
+ #
2891
+ # @api private
2488
2892
  def normalize_flag_integer_value!(value, option_name, max_times)
2489
2893
  raise ArgumentError, "Invalid value for :#{option_name}: expected a positive Integer" if value <= 0
2490
2894
 
@@ -2493,6 +2897,19 @@ module Git
2493
2897
  value
2494
2898
  end
2495
2899
 
2900
+ # Raise an ArgumentError when a flag repeat count exceeds max_times
2901
+ #
2902
+ # @param option_name [Symbol] the option name (for the error message)
2903
+ #
2904
+ # @param value [Integer] the value that exceeded the limit
2905
+ #
2906
+ # @param max_times [Integer] the configured maximum
2907
+ #
2908
+ # @return [void]
2909
+ #
2910
+ # @raise [ArgumentError] always
2911
+ #
2912
+ # @api private
2496
2913
  def raise_max_times_exceeded!(option_name, value, max_times)
2497
2914
  raise ArgumentError,
2498
2915
  "#{option_name}: #{value} exceeds max_times: #{max_times} for :#{option_name}"
@@ -2504,6 +2921,7 @@ module Git
2504
2921
  # For long options (--force), transforms to --no-force
2505
2922
  #
2506
2923
  # @param arg_spec [String] the argument specification
2924
+ #
2507
2925
  # @return [String] the negated flag
2508
2926
  #
2509
2927
  def negate_flag(arg_spec)
@@ -2516,6 +2934,15 @@ module Git
2516
2934
  end
2517
2935
  end
2518
2936
 
2937
+ # Determine whether an option value should be skipped during CLI build
2938
+ #
2939
+ # @param value [Object] the bound option value
2940
+ #
2941
+ # @param definition [Hash] the option definition hash
2942
+ #
2943
+ # @return [Boolean] true if the option should be omitted from the CLI array
2944
+ #
2945
+ # @api private
2519
2946
  def should_skip_option?(value, definition)
2520
2947
  return true if value.nil?
2521
2948
  return true if value == false && %i[flag_or_inline_value flag_or_value].include?(definition[:type])
@@ -2526,6 +2953,14 @@ module Git
2526
2953
 
2527
2954
  # For value_as_operand, empty arrays always skip regardless of allow_empty
2528
2955
  # (allow_empty only applies to empty strings, not empty arrays)
2956
+ #
2957
+ # @param value [Array] the array value to evaluate
2958
+ #
2959
+ # @param definition [Hash] the option definition hash
2960
+ #
2961
+ # @return [Boolean] true if the option should be skipped
2962
+ #
2963
+ # @api private
2529
2964
  def skip_value_as_operand_array?(value, definition)
2530
2965
  return value.empty? if definition[:type] == :value_as_operand
2531
2966
 
@@ -2540,6 +2975,7 @@ module Git
2540
2975
  # - Single array pair: ['key', 'value']
2541
2976
  #
2542
2977
  # @param value [Hash, Array] the input value
2978
+ #
2543
2979
  # @return [Array<Array>] array of [key, value] pairs
2544
2980
  #
2545
2981
  def normalize_key_value_pairs(value)
@@ -2552,12 +2988,28 @@ module Git
2552
2988
  end
2553
2989
  end
2554
2990
 
2991
+ # Normalize a Hash to an array of [key, value] pairs
2992
+ #
2993
+ # @param hash [Hash] the input hash
2994
+ #
2995
+ # @return [Array<Array>] array of [key, value] pairs
2996
+ #
2997
+ # @api private
2555
2998
  def normalize_hash_to_pairs(hash)
2556
2999
  hash.flat_map do |k, v|
2557
3000
  v.is_a?(Array) ? v.map { |val| [k, val] } : [[k, v]]
2558
3001
  end
2559
3002
  end
2560
3003
 
3004
+ # Normalize an Array to an array of [key, value] pairs
3005
+ #
3006
+ # @param array [Array] the input array
3007
+ #
3008
+ # @return [Array<Array>] array of [key, value] pairs
3009
+ #
3010
+ # @raise [ArgumentError] if the array is not a pair or array of pairs
3011
+ #
3012
+ # @api private
2561
3013
  def normalize_array_to_pairs(array)
2562
3014
  # Check if it's a single [key, value] pair or array of pairs
2563
3015
  if array.size == 2 && !array.first.is_a?(Array)
@@ -2573,9 +3025,14 @@ module Git
2573
3025
  # Validate that a key-value pair array has at most 2 elements
2574
3026
  #
2575
3027
  # @param pair [Array] the pair to validate
3028
+ #
2576
3029
  # @param option_name [Symbol] the option name for error messages
3030
+ #
3031
+ # @return [void]
3032
+ #
2577
3033
  # @raise [ArgumentError] if pair has more than 2 elements
2578
3034
  #
3035
+ # @api private
2579
3036
  def validate_key_value_pair_size!(pair, option_name)
2580
3037
  return unless pair.is_a?(Array) && pair.size > 2
2581
3038
 
@@ -2586,10 +3043,16 @@ module Git
2586
3043
  # Validate a key for key_value options
2587
3044
  #
2588
3045
  # @param key [Object] the key to validate
3046
+ #
2589
3047
  # @param separator [String] the key-value separator
3048
+ #
2590
3049
  # @param option_name [Symbol] the option name for error messages
3050
+ #
3051
+ # @return [void]
3052
+ #
2591
3053
  # @raise [ArgumentError] if key is nil, empty, or contains the separator
2592
3054
  #
3055
+ # @api private
2593
3056
  def validate_key_value_key!(key, separator, option_name)
2594
3057
  key_str = key.to_s
2595
3058
  raise ArgumentError, "key_value :#{option_name} requires a non-empty key" if key.nil? || key_str.empty?
@@ -2603,9 +3066,14 @@ module Git
2603
3066
  # Validate a value for key_value options
2604
3067
  #
2605
3068
  # @param value [Object] the value to validate
3069
+ #
2606
3070
  # @param option_name [Symbol] the option name for error messages
3071
+ #
3072
+ # @return [void]
3073
+ #
2607
3074
  # @raise [ArgumentError] if value is a Hash or Array (non-scalar)
2608
3075
  #
3076
+ # @api private
2609
3077
  def validate_key_value_value!(value, option_name)
2610
3078
  return if value.nil?
2611
3079
  return unless value.is_a?(Hash) || value.is_a?(Array)
@@ -2615,6 +3083,13 @@ module Git
2615
3083
  "got #{value.class}: #{value.inspect}"
2616
3084
  end
2617
3085
 
3086
+ # Flatten a single-element array wrapper if the first element is itself an array
3087
+ #
3088
+ # @param positionals [Array] the raw positional values from the splat
3089
+ #
3090
+ # @return [Array] normalized positionals array
3091
+ #
3092
+ # @api private
2618
3093
  def normalize_positionals(positionals)
2619
3094
  # Flatten if first element is an array (allows both splat and array syntax)
2620
3095
  positionals = positionals.first if positionals.size == 1 && positionals.first.is_a?(Array)
@@ -2624,22 +3099,59 @@ module Git
2624
3099
  # Allocate positional arguments to definitions following Ruby semantics
2625
3100
  # Returns [allocation_hash, consumed_count] where consumed_count is the
2626
3101
  # number of non-nil positionals that were consumed by definitions.
3102
+ #
3103
+ # @param positionals [Array] the positional argument values
3104
+ #
3105
+ # @return [Array(Hash, Integer)] tuple of allocation hash and consumed count
3106
+ #
3107
+ # @api private
2627
3108
  def allocate_positionals(positionals)
2628
3109
  OperandAllocator.new(@operand_definitions).allocate(positionals)
2629
3110
  end
2630
3111
 
3112
+ # Append a positional argument's value(s) to the CLI args array
3113
+ #
3114
+ # @param args [Array<String>] the argument array to append to
3115
+ #
3116
+ # @param value [Object] the positional value
3117
+ #
3118
+ # @param definition [Hash] the operand definition hash
3119
+ #
3120
+ # @return [void]
3121
+ #
3122
+ # @api private
2631
3123
  def append_positional_to_args(args, value, definition)
2632
3124
  return if positional_value_empty?(value, definition)
2633
3125
 
2634
3126
  append_positional_value(args, value, definition[:repeatable])
2635
3127
  end
2636
3128
 
3129
+ # Return true if a positional value is empty and should be skipped
3130
+ #
3131
+ # @param value [Object] the positional value
3132
+ #
3133
+ # @param definition [Hash] the operand definition hash
3134
+ #
3135
+ # @return [Boolean] true if the value should be skipped
3136
+ #
3137
+ # @api private
2637
3138
  def positional_value_empty?(value, definition)
2638
3139
  return true if value.nil?
2639
3140
 
2640
3141
  definition[:repeatable] && value.respond_to?(:empty?) && value.empty?
2641
3142
  end
2642
3143
 
3144
+ # Append a positional value to the args array, expanding arrays when repeatable
3145
+ #
3146
+ # @param args [Array<String>] the argument array to append to
3147
+ #
3148
+ # @param value [Object] the positional value
3149
+ #
3150
+ # @param repeatable [Boolean] whether the operand accepts multiple values
3151
+ #
3152
+ # @return [void]
3153
+ #
3154
+ # @api private
2643
3155
  def append_positional_value(args, value, repeatable)
2644
3156
  if repeatable
2645
3157
  args.concat(Array(value).map(&:to_s))
@@ -2648,6 +3160,17 @@ module Git
2648
3160
  end
2649
3161
  end
2650
3162
 
3163
+ # Raise if more positional values were provided than definitions can absorb
3164
+ #
3165
+ # @param positionals [Array] the full list of provided positional values
3166
+ #
3167
+ # @param consumed_count [Integer] the number of positionals consumed by definitions
3168
+ #
3169
+ # @return [void]
3170
+ #
3171
+ # @raise [ArgumentError] if extra positional values remain after allocation
3172
+ #
3173
+ # @api private
2651
3174
  def check_unexpected_positionals(positionals, consumed_count)
2652
3175
  provided_count = positionals.compact.size
2653
3176
 
@@ -2658,6 +3181,17 @@ module Git
2658
3181
  raise ArgumentError, "Unexpected positional arguments: #{unexpected.join(', ')}"
2659
3182
  end
2660
3183
 
3184
+ # Raise if a required positional argument is missing or empty
3185
+ #
3186
+ # @param value [Object] the allocated positional value
3187
+ #
3188
+ # @param definition [Hash] the operand definition hash
3189
+ #
3190
+ # @return [void]
3191
+ #
3192
+ # @raise [ArgumentError] if a required positional is missing
3193
+ #
3194
+ # @api private
2661
3195
  def validate_required_positional(value, definition)
2662
3196
  return unless definition[:required]
2663
3197
  return if definition[:allow_nil] && value.nil?
@@ -2668,6 +3202,17 @@ module Git
2668
3202
  raise ArgumentError, "#{definition[:name]} is required"
2669
3203
  end
2670
3204
 
3205
+ # Raise if a repeatable positional array contains nil values
3206
+ #
3207
+ # @param value [Object] the allocated positional value
3208
+ #
3209
+ # @param definition [Hash] the operand definition hash
3210
+ #
3211
+ # @return [void]
3212
+ #
3213
+ # @raise [ArgumentError] if the array contains nil elements
3214
+ #
3215
+ # @api private
2671
3216
  def validate_no_nil_values!(value, definition)
2672
3217
  return unless definition[:repeatable]
2673
3218
  return if value.nil? # Allow nil as "not provided"
@@ -2686,6 +3231,9 @@ module Git
2686
3231
  # a hyphen, which could be misinterpreted as a git option.
2687
3232
  #
2688
3233
  # @param allocation [Hash{Symbol => Object}] the allocated operand values
3234
+ #
3235
+ # @return [void]
3236
+ #
2689
3237
  # @raise [ArgumentError] if any pre-separator operand value starts with '-'
2690
3238
  #
2691
3239
  def validate_no_option_like_operands!(allocation)
@@ -2717,6 +3265,7 @@ module Git
2717
3265
  # Check if an operand is configured with skip_cli: true
2718
3266
  #
2719
3267
  # @param name [Symbol] the operand name
3268
+ #
2720
3269
  # @return [Boolean] true if operand has skip_cli enabled
2721
3270
  #
2722
3271
  def operand_skip_cli?(name)
@@ -2730,6 +3279,7 @@ module Git
2730
3279
  # even when its runtime `--` may be suppressed by {#resolve_end_of_options_marker}.
2731
3280
  #
2732
3281
  # @param defn [Hash] a definition entry from @ordered_definitions
3282
+ #
2733
3283
  # @return [Boolean] true if this definition is an active '--' boundary
2734
3284
  #
2735
3285
  def separator_boundary_active?(defn)
@@ -2742,7 +3292,8 @@ module Git
2742
3292
  # Check if a definition is a literal '--' static flag
2743
3293
  #
2744
3294
  # @param defn [Hash] the entry definition
2745
- # @return [Boolean]
3295
+ #
3296
+ # @return [Boolean] true if the definition emits a literal `--` boundary
2746
3297
  #
2747
3298
  def literal_separator_flag?(defn)
2748
3299
  defn[:kind] == :static && defn[:flag] == '--'
@@ -2751,7 +3302,11 @@ module Git
2751
3302
  # Check that a single operand value does not look like a command-line option
2752
3303
  #
2753
3304
  # @param name [Symbol] the operand name
3305
+ #
2754
3306
  # @param value [Object] the operand value
3307
+ #
3308
+ # @return [void]
3309
+ #
2755
3310
  # @raise [ArgumentError] if the value starts with '-'
2756
3311
  #
2757
3312
  def check_operand_not_option_like(name, value)
@@ -2763,12 +3318,32 @@ module Git
2763
3318
  end
2764
3319
  end
2765
3320
 
3321
+ # Raise if a single operand string value looks like a command-line option
3322
+ #
3323
+ # @param name [Symbol] the operand name (for the error message)
3324
+ #
3325
+ # @param value [String] the option-like string value
3326
+ #
3327
+ # @return [void]
3328
+ #
2766
3329
  # @raise [ArgumentError] if the string value starts with '-'
3330
+ #
3331
+ # @api private
2767
3332
  def raise_option_like_error(name, value)
2768
3333
  raise ArgumentError, "operand :#{name} value '#{value}' looks like a command-line option"
2769
3334
  end
2770
3335
 
3336
+ # Raise if any element of an operand array looks like a command-line option
3337
+ #
3338
+ # @param name [Symbol] the operand name (for the error message)
3339
+ #
3340
+ # @param values [Array] the array of operand values to check
3341
+ #
3342
+ # @return [void]
3343
+ #
2771
3344
  # @raise [ArgumentError] if any array element starts with '-'
3345
+ #
3346
+ # @api private
2772
3347
  def raise_option_like_array_error(name, values)
2773
3348
  invalid = values.select { |v| v.is_a?(String) && v.start_with?('-') }
2774
3349
  return if invalid.empty?
@@ -2783,12 +3358,24 @@ module Git
2783
3358
  # arrays are valid values that should be passed through.
2784
3359
  #
2785
3360
  # @param value [Object] the value to check
3361
+ #
2786
3362
  # @return [Boolean] true if the value is nil
2787
3363
  #
2788
3364
  def value_empty?(value)
2789
3365
  value.nil?
2790
3366
  end
2791
3367
 
3368
+ # Raise if opts contains any keys not registered in the alias map
3369
+ #
3370
+ # @param opts [Hash] the raw keyword options to check
3371
+ #
3372
+ # @option opts [Object] :"option_name" value for any registered option name
3373
+ #
3374
+ # @return [void]
3375
+ #
3376
+ # @raise [ArgumentError] if any option key is not registered
3377
+ #
3378
+ # @api private
2792
3379
  def validate_unsupported_options!(opts)
2793
3380
  unsupported = opts.keys - @alias_map.keys
2794
3381
  return if unsupported.empty?
@@ -2796,6 +3383,17 @@ module Git
2796
3383
  raise ArgumentError, "Unsupported options: #{unsupported.map(&:inspect).join(', ')}"
2797
3384
  end
2798
3385
 
3386
+ # Raise if the caller provided more than one alias from the same option group
3387
+ #
3388
+ # @param opts [Hash] the raw keyword options
3389
+ #
3390
+ # @option opts [Object] :"option_name" value for any registered option name
3391
+ #
3392
+ # @return [void]
3393
+ #
3394
+ # @raise [ArgumentError] if conflicting aliases are both present
3395
+ #
3396
+ # @api private
2799
3397
  def validate_conflicting_aliases!(opts)
2800
3398
  @option_definitions.each_value do |definition|
2801
3399
  aliases = definition[:aliases]
@@ -2808,16 +3406,45 @@ module Git
2808
3406
  end
2809
3407
  end
2810
3408
 
3409
+ # Rekey opts so every alias maps to its primary option name
3410
+ #
3411
+ # @param opts [Hash] the raw keyword options
3412
+ #
3413
+ # @option opts [Object] :"option_name" value for any registered option name
3414
+ #
3415
+ # @return [Hash] a new hash with all keys resolved to primary names
3416
+ #
3417
+ # @api private
2811
3418
  def normalize_aliases(opts)
2812
3419
  opts.transform_keys { |key| @alias_map[key] || key }
2813
3420
  end
2814
3421
 
3422
+ # Raise if any required options are missing or nil when allow_nil is false
3423
+ #
3424
+ # @param opts [Hash] the normalized keyword options
3425
+ #
3426
+ # @option opts [Object] :"option_name" value for any registered option name
3427
+ #
3428
+ # @return [void]
3429
+ #
3430
+ # @raise [ArgumentError] if required options are missing or nil
3431
+ #
3432
+ # @api private
2815
3433
  def validate_required_options!(opts)
2816
3434
  missing, nil_not_allowed = collect_required_option_errors(opts)
2817
3435
  raise_missing_options_error(missing) if missing.any?
2818
3436
  raise_nil_options_error(nil_not_allowed) if nil_not_allowed.any?
2819
3437
  end
2820
3438
 
3439
+ # Collect missing and nil-not-allowed option names from opts
3440
+ #
3441
+ # @param opts [Hash] the normalized keyword options
3442
+ #
3443
+ # @option opts [Object] :"option_name" value for any registered option name
3444
+ #
3445
+ # @return [Array(Array<Symbol>, Array<Symbol>)] tuple of missing and nil-invalid names
3446
+ #
3447
+ # @api private
2821
3448
  def collect_required_option_errors(opts)
2822
3449
  missing = []
2823
3450
  nil_not_allowed = []
@@ -2830,14 +3457,41 @@ module Git
2830
3457
  [missing, nil_not_allowed]
2831
3458
  end
2832
3459
 
3460
+ # Raise an error listing required options that were not provided
3461
+ #
3462
+ # @param missing [Array<Symbol>] the names of missing required options
3463
+ #
3464
+ # @return [void]
3465
+ #
3466
+ # @raise [ArgumentError] always
3467
+ #
3468
+ # @api private
2833
3469
  def raise_missing_options_error(missing)
2834
3470
  raise ArgumentError, "Required options not provided: #{missing.map(&:inspect).join(', ')}"
2835
3471
  end
2836
3472
 
3473
+ # Raise an error listing required options that were provided as nil
3474
+ #
3475
+ # @param nil_not_allowed [Array<Symbol>] the names of options given nil when not allowed
3476
+ #
3477
+ # @return [void]
3478
+ #
3479
+ # @raise [ArgumentError] always
3480
+ #
3481
+ # @api private
2837
3482
  def raise_nil_options_error(nil_not_allowed)
2838
3483
  raise ArgumentError, "Required options cannot be nil: #{nil_not_allowed.map(&:inspect).join(', ')}"
2839
3484
  end
2840
3485
 
3486
+ # Run validators and allowed_values checks for each option present in opts
3487
+ #
3488
+ # @param opts [Hash] the normalized keyword options
3489
+ #
3490
+ # @option opts [Object] :"option_name" value for any registered option name
3491
+ #
3492
+ # @return [void]
3493
+ #
3494
+ # @api private
2841
3495
  def validate_option_values!(opts)
2842
3496
  @option_definitions.each do |name, definition|
2843
3497
  next unless opts.key?(name)
@@ -2846,11 +3500,37 @@ module Git
2846
3500
  end
2847
3501
  end
2848
3502
 
3503
+ # Run the validator and allowed_values check for a single option value
3504
+ #
3505
+ # @param name [Symbol] the option name
3506
+ #
3507
+ # @param value [Object] the option value to validate
3508
+ #
3509
+ # @param definition [Hash] the option definition hash
3510
+ #
3511
+ # @return [void]
3512
+ #
3513
+ # @raise [ArgumentError] if validation fails
3514
+ #
3515
+ # @api private
2849
3516
  def validate_single_option!(name, value, definition)
2850
3517
  run_validator!(name, value, definition[:validator]) if definition[:validator]
2851
3518
  check_allowed_values!(name, value, definition) if definition[:allowed_values]
2852
3519
  end
2853
3520
 
3521
+ # Call the validator proc and raise if it returns an error
3522
+ #
3523
+ # @param name [Symbol] the option name (for the default error message)
3524
+ #
3525
+ # @param value [Object] the option value to validate
3526
+ #
3527
+ # @param validator [Proc] the validator callable
3528
+ #
3529
+ # @return [void]
3530
+ #
3531
+ # @raise [ArgumentError] if the validator returns any value other than `true`
3532
+ #
3533
+ # @api private
2854
3534
  def run_validator!(name, value, validator)
2855
3535
  result = validator.call(value)
2856
3536
  return if result == true
@@ -2859,6 +3539,19 @@ module Git
2859
3539
  raise ArgumentError, error_msg
2860
3540
  end
2861
3541
 
3542
+ # Check that the option value is within the declared allowed_values set
3543
+ #
3544
+ # @param name [Symbol] the option name
3545
+ #
3546
+ # @param value [Object] the option value to check
3547
+ #
3548
+ # @param definition [Hash] the option definition hash
3549
+ #
3550
+ # @return [void]
3551
+ #
3552
+ # @raise [ArgumentError] if the value is not in the allowed set
3553
+ #
3554
+ # @api private
2862
3555
  def check_allowed_values!(name, value, definition)
2863
3556
  allowed = definition[:allowed_values]
2864
3557
  type = definition[:type]
@@ -2869,6 +3562,17 @@ module Git
2869
3562
  end
2870
3563
  end
2871
3564
 
3565
+ # Convert an allowed_values enumerable to a frozen array of strings
3566
+ #
3567
+ # @param sym [Symbol] the option name (for error messages)
3568
+ #
3569
+ # @param values [Enumerable] the allowed values to coerce
3570
+ #
3571
+ # @return [Array<String>] a frozen array of stringified allowed values
3572
+ #
3573
+ # @raise [ArgumentError] if values is not Enumerable or is empty
3574
+ #
3575
+ # @api private
2872
3576
  def coerce_allowed_values_set!(sym, values)
2873
3577
  unless values.respond_to?(:map)
2874
3578
  raise ArgumentError,
@@ -2880,6 +3584,15 @@ module Git
2880
3584
  arr.freeze
2881
3585
  end
2882
3586
 
3587
+ # Look up and validate the option definition for an allowed_values declaration
3588
+ #
3589
+ # @param sym [Symbol] the option or alias name
3590
+ #
3591
+ # @return [Hash] the option definition hash
3592
+ #
3593
+ # @raise [ArgumentError] if sym is unknown or not a value option type
3594
+ #
3595
+ # @api private
2883
3596
  def validate_allowed_values_definition!(sym)
2884
3597
  primary = @alias_map[sym]
2885
3598
  defn = primary && @option_definitions[primary]
@@ -2895,6 +3608,23 @@ module Git
2895
3608
  defn
2896
3609
  end
2897
3610
 
3611
+ # Check each element of a repeatable option value against the allowed set
3612
+ #
3613
+ # @param name [Symbol] the option name
3614
+ #
3615
+ # @param values [Object] the repeatable value (array or scalar)
3616
+ #
3617
+ # @param allowed [Array<String>] the allowed string values
3618
+ #
3619
+ # @param allow_empty [Boolean] whether empty strings are permitted
3620
+ #
3621
+ # @param type [Symbol] the option type (for boolean skip logic)
3622
+ #
3623
+ # @return [void]
3624
+ #
3625
+ # @raise [ArgumentError] if any element is not in the allowed set
3626
+ #
3627
+ # @api private
2898
3628
  def check_repeatable_allowed_values!(name, values, allowed, allow_empty, type)
2899
3629
  Array(values).each do |v|
2900
3630
  next if skip_allowed_values_check?(v, allow_empty, type)
@@ -2906,6 +3636,23 @@ module Git
2906
3636
  end
2907
3637
  end
2908
3638
 
3639
+ # Check a single option value against the allowed set
3640
+ #
3641
+ # @param name [Symbol] the option name
3642
+ #
3643
+ # @param value [Object] the value to check
3644
+ #
3645
+ # @param allowed [Array<String>] the allowed string values
3646
+ #
3647
+ # @param allow_empty [Boolean] whether empty strings are permitted
3648
+ #
3649
+ # @param type [Symbol] the option type (for boolean skip logic)
3650
+ #
3651
+ # @return [void]
3652
+ #
3653
+ # @raise [ArgumentError] if the value is not in the allowed set
3654
+ #
3655
+ # @api private
2909
3656
  def check_single_allowed_value!(name, value, allowed, allow_empty, type)
2910
3657
  return if skip_allowed_values_check?(value, allow_empty, type)
2911
3658
 
@@ -2915,6 +3662,17 @@ module Git
2915
3662
  "Invalid value for :#{name}: expected one of #{allowed.inspect}, got #{value.inspect}"
2916
3663
  end
2917
3664
 
3665
+ # Return true if an allowed_values check should be bypassed for this value
3666
+ #
3667
+ # @param value [Object] the value being checked
3668
+ #
3669
+ # @param allow_empty [Boolean] whether empty strings should be skipped
3670
+ #
3671
+ # @param type [Symbol] the option type (for flag_or_value boolean skip)
3672
+ #
3673
+ # @return [Boolean] true if the check should be skipped
3674
+ #
3675
+ # @api private
2918
3676
  def skip_allowed_values_check?(value, allow_empty, type)
2919
3677
  return true if value.nil?
2920
3678
  # Only skip boolean values for flag_or_value option types where true/false carry
@@ -2926,6 +3684,15 @@ module Git
2926
3684
  false
2927
3685
  end
2928
3686
 
3687
+ # Build a lambda that validates a value is one of the expected types
3688
+ #
3689
+ # @param option_name [Symbol] the option name (for error messages)
3690
+ #
3691
+ # @param expected_type [Class, Array<Class>] the expected Ruby type(s)
3692
+ #
3693
+ # @return [Proc] a validator lambda
3694
+ #
3695
+ # @api private
2929
3696
  def create_type_validator(option_name, expected_type)
2930
3697
  types = Array(expected_type)
2931
3698
 
@@ -2940,6 +3707,19 @@ module Git
2940
3707
  end
2941
3708
  end
2942
3709
 
3710
+ # Raise if any conflicting argument names are both present in the bound values
3711
+ #
3712
+ # @param opts [Hash] normalized keyword options
3713
+ #
3714
+ # @param allocated_positionals [Hash] the allocated positional values
3715
+ #
3716
+ # @option opts [Object] :"option_name" value for any registered option name
3717
+ #
3718
+ # @return [void]
3719
+ #
3720
+ # @raise [ArgumentError] if conflicting arguments are both present
3721
+ #
3722
+ # @api private
2943
3723
  def validate_conflicts!(opts, allocated_positionals = {})
2944
3724
  @conflicts.each do |conflict_group|
2945
3725
  provided = conflict_group.select { |name| conflict_present?(name, opts, allocated_positionals) }
@@ -2955,6 +3735,18 @@ module Git
2955
3735
  # For registered keyword options only looks in opts; positional slots use
2956
3736
  # allocated_positionals. This prevents a positional operand that shares a
2957
3737
  # name with a keyword option from spuriously triggering keyword conflicts.
3738
+ #
3739
+ # @param name [Symbol] the argument name to check
3740
+ #
3741
+ # @param opts [Hash] normalized keyword options
3742
+ #
3743
+ # @param allocated_positionals [Hash] the allocated positional values
3744
+ #
3745
+ # @option opts [Object] :"option_name" value for any registered option name
3746
+ #
3747
+ # @return [Boolean] true if the named argument has a present bound value
3748
+ #
3749
+ # @api private
2958
3750
  def conflict_present?(name, opts, allocated_positionals)
2959
3751
  canonical_name = @alias_map[name] || name
2960
3752
  value = if @option_definitions.key?(canonical_name)
@@ -2968,7 +3760,13 @@ module Git
2968
3760
  # Validate that no bound values match a forbidden exact-value tuple
2969
3761
  #
2970
3762
  # @param opts [Hash] normalized keyword options (aliases already resolved)
3763
+ #
2971
3764
  # @param allocated_positionals [Hash] the allocated positional values
3765
+ #
3766
+ # @option opts [Object] :"option_name" value for any registered option name
3767
+ #
3768
+ # @return [void]
3769
+ #
2972
3770
  # @raise [ArgumentError] if all names in a forbidden tuple are present with
2973
3771
  # their declared values
2974
3772
  #
@@ -2988,9 +3786,14 @@ module Git
2988
3786
  # key never triggers a forbidden-values match.
2989
3787
  #
2990
3788
  # @param tuple [Hash{Symbol => Object}] canonical name → forbidden value
3789
+ #
2991
3790
  # @param opts [Hash] normalized keyword options
3791
+ #
2992
3792
  # @param allocated_positionals [Hash] the allocated positional values
2993
- # @return [Boolean]
3793
+ #
3794
+ # @option opts [Object] :"option_name" value for any registered option name
3795
+ #
3796
+ # @return [Boolean] true if all tuple entries match the bound values
2994
3797
  #
2995
3798
  def forbidden_tuple_matches?(tuple, opts, allocated_positionals)
2996
3799
  tuple.all? do |name, forbidden_value|
@@ -3013,7 +3816,13 @@ module Git
3013
3816
  # :single — true when declared via `requires` (affects error message wording)
3014
3817
  #
3015
3818
  # @param opts [Hash] normalized keyword options (aliases already resolved)
3819
+ #
3016
3820
  # @param allocated_positionals [Hash] the allocated positional values
3821
+ #
3822
+ # @option opts [Object] :"option_name" value for any registered option name
3823
+ #
3824
+ # @return [void]
3825
+ #
3017
3826
  # @raise [ArgumentError] if none of the arguments in any applicable group is present
3018
3827
  #
3019
3828
  def validate_requires_one_of!(opts, allocated_positionals = {})
@@ -3025,9 +3834,15 @@ module Git
3025
3834
  # Validate a single requires_one_of entry
3026
3835
  #
3027
3836
  # @param entry [Hash] the group entry with :names, :condition, :single keys
3837
+ #
3028
3838
  # @param opts [Hash] normalized keyword options
3839
+ #
3029
3840
  # @param allocated_positionals [Hash] the allocated positional values
3030
3841
  #
3842
+ # @option opts [Object] :"option_name" value for any registered option name
3843
+ #
3844
+ # @return [void]
3845
+ #
3031
3846
  def validate_requires_one_of_entry!(entry, opts, allocated_positionals)
3032
3847
  condition = entry[:condition]
3033
3848
 
@@ -3042,8 +3857,11 @@ module Git
3042
3857
  # Build the error message for a failed requires_one_of check
3043
3858
  #
3044
3859
  # @param names [Array<Symbol>] the required argument names
3860
+ #
3045
3861
  # @param condition [Symbol, nil] the trigger argument name, or nil for unconditional
3862
+ #
3046
3863
  # @param single [Boolean] true when declared via `requires` (single required arg)
3864
+ #
3047
3865
  # @return [String] the error message
3048
3866
  #
3049
3867
  def requires_one_of_error_message(names, condition, single)
@@ -3057,6 +3875,9 @@ module Git
3057
3875
  # Validate a single name used in a requires_one_of declaration
3058
3876
  #
3059
3877
  # @param sym [Symbol] the name to validate
3878
+ #
3879
+ # @return [void]
3880
+ #
3060
3881
  # @raise [ArgumentError] if sym is not a known option or operand
3061
3882
  #
3062
3883
  def validate_requires_one_of_name!(sym)
@@ -3066,6 +3887,9 @@ module Git
3066
3887
  # Validate a single name used in a requires or conditional requires_one_of declaration
3067
3888
  #
3068
3889
  # @param sym [Symbol] the name to validate
3890
+ #
3891
+ # @return [void]
3892
+ #
3069
3893
  # @raise [ArgumentError] if sym is not a known option or operand
3070
3894
  #
3071
3895
  def validate_requires_name!(sym)
@@ -3079,6 +3903,7 @@ module Git
3079
3903
  # the name is used directly.
3080
3904
  #
3081
3905
  # @param names [Array<Symbol, String>] raw argument names
3906
+ #
3082
3907
  # @return [Array<Symbol>] canonical, deduplicated names
3083
3908
  #
3084
3909
  def canonicalize_requires_names(names)
@@ -3092,6 +3917,7 @@ module Git
3092
3917
  # Validate and canonicalize the `when:` condition for requires/requires_one_of
3093
3918
  #
3094
3919
  # @param condition [Symbol, nil] the raw trigger argument name
3920
+ #
3095
3921
  # @return [Symbol, nil] canonical trigger name, or nil when condition is nil
3096
3922
  #
3097
3923
  def resolve_requires_condition(condition)
@@ -3105,7 +3931,8 @@ module Git
3105
3931
  # Return true if the given name refers to a defined option or operand
3106
3932
  #
3107
3933
  # @param name [Symbol] the argument name to look up
3108
- # @return [Boolean]
3934
+ #
3935
+ # @return [Boolean] true if the name is an option, alias, or operand
3109
3936
  def known_argument?(name)
3110
3937
  @alias_map.key?(name) || @operand_definitions.any? { |d| d[:name] == name }
3111
3938
  end
@@ -3119,7 +3946,8 @@ module Git
3119
3946
  # emit tokens for non-empty arrays even when every element is '' or false.
3120
3947
  #
3121
3948
  # @param value [Object] the argument value to test
3122
- # @return [Boolean]
3949
+ #
3950
+ # @return [Boolean] true if the value counts as present for validation
3123
3951
  def argument_present?(value)
3124
3952
  return false if value.nil?
3125
3953
  return false if value == false
@@ -3144,8 +3972,6 @@ module Git
3144
3972
  # generated to avoid overriding built-in `Object` methods. Use hash-style
3145
3973
  # access (`bound[:nil]`) when the flag name is reserved.
3146
3974
  #
3147
- # @api private
3148
- #
3149
3975
  # @example Accessing bound arguments
3150
3976
  # args_def = Arguments.define do
3151
3977
  # flag_option :force
@@ -3174,6 +4000,8 @@ module Git
3174
4000
  # bound = args_def.bind(hash: 'abc123')
3175
4001
  # bound[:hash] # => 'abc123'
3176
4002
  #
4003
+ # @api private
4004
+ #
3177
4005
  class Bound
3178
4006
  # Names that cannot have accessor methods defined (would override Object methods)
3179
4007
  RESERVED_NAMES = (Object.instance_methods + [:to_ary]).freeze
@@ -3181,7 +4009,7 @@ module Git
3181
4009
  # Canonical frozen empty hash returned by {#execution_options} when no
3182
4010
  # non-nil execution options are present.
3183
4011
  #
3184
- # @return [Hash{Symbol => Object}]
4012
+ # @return [Hash{Symbol => Object}] frozen empty execution options hash
3185
4013
  EMPTY_EXECUTION_OPTIONS = {}.freeze
3186
4014
 
3187
4015
  # Execution options and values for command execution.
@@ -3190,15 +4018,22 @@ module Git
3190
4018
  # excludes options with nil values.
3191
4019
  #
3192
4020
  # @return [Hash{Symbol => Object}] frozen hash of execution option values
3193
- # @!attribute [r] execution_options
3194
4021
  attr_reader :execution_options
3195
4022
 
4023
+ # Initialize a new frozen Bound object with accessor methods for all defined arguments
4024
+ #
3196
4025
  # @param args_array [Array<String>] the CLI argument array (frozen)
4026
+ #
3197
4027
  # @param options [Hash{Symbol => Object}] normalized options hash (frozen)
4028
+ #
3198
4029
  # @param positionals [Hash{Symbol => Object}] positional arguments hash (frozen)
4030
+ #
3199
4031
  # @param execution_option_names [Array<Symbol>] option names declared via {Arguments#execution_option}
4032
+ #
3200
4033
  # @param flag_names [Array<Symbol>] option names declared via {Arguments#flag_option}
3201
4034
  #
4035
+ # @option options [Object] :"option_name" bound value for any registered option name
4036
+ #
3202
4037
  def initialize(args_array, options, positionals, execution_option_names = [], flag_names = [])
3203
4038
  @args_array = args_array.freeze
3204
4039
  @options = options.freeze
@@ -3242,6 +4077,7 @@ module Git
3242
4077
  # accessor methods defined.
3243
4078
  #
3244
4079
  # @param key [Symbol] the option or positional name
4080
+ #
3245
4081
  # @return [Object, nil] the value, or nil if not found
3246
4082
  def [](key)
3247
4083
  return @options[key] if @options.key?(key)
@@ -3252,6 +4088,13 @@ module Git
3252
4088
 
3253
4089
  private
3254
4090
 
4091
+ # Build the execution_options hash from the given option names
4092
+ #
4093
+ # @param execution_option_names [Array<Symbol>] option names declared as execution options
4094
+ #
4095
+ # @return [Hash{Symbol => Object}] frozen hash of non-nil execution option values
4096
+ #
4097
+ # @api private
3255
4098
  def build_execution_options(execution_option_names)
3256
4099
  result = execution_option_names.each_with_object({}) do |name, values|
3257
4100
  value = @options[name]
@@ -3267,6 +4110,7 @@ module Git
3267
4110
  # by {#initialize} after all plain accessors have been set up.
3268
4111
  #
3269
4112
  # @param name [Symbol] the option or positional name
4113
+ #
3270
4114
  # @param source [Hash] the hash to read from (@options or @positionals)
3271
4115
  #
3272
4116
  def define_accessor(name, source)
@@ -3291,6 +4135,13 @@ module Git
3291
4135
  end
3292
4136
  end
3293
4137
 
4138
+ # Return true if a flag value represents a truthy flag state
4139
+ #
4140
+ # @param value [Boolean, Integer, nil] the flag value
4141
+ #
4142
+ # @return [Boolean] true if the flag value represents an enabled flag
4143
+ #
4144
+ # @api private
3294
4145
  def flag_predicate?(value)
3295
4146
  return value.positive? if value.is_a?(Integer)
3296
4147
 
@@ -3312,8 +4163,10 @@ module Git
3312
4163
  end
3313
4164
 
3314
4165
  # Allocate values to definitions
4166
+ #
3315
4167
  # @param values [Array] the positional argument values
3316
- # @return [Array(Hash, Integer)] [allocation_hash, consumed_count]
4168
+ #
4169
+ # @return [Array(Hash, Integer)] tuple of allocation hash and consumed count
3317
4170
  def allocate(values)
3318
4171
  allocation = {}
3319
4172
  repeatable_index = @definitions.find_index { |d| d[:repeatable] }
@@ -3333,6 +4186,14 @@ module Git
3333
4186
  # - Required positionals at the END are reserved first
3334
4187
  # - Leading positionals get remaining values left-to-right
3335
4188
  # - Optional positionals are skipped when there aren't enough values
4189
+ #
4190
+ # @param values [Array] the positional argument values
4191
+ #
4192
+ # @param allocation [Hash] the allocation hash to populate
4193
+ #
4194
+ # @return [Integer] the number of non-nil positionals consumed
4195
+ #
4196
+ # @api private
3336
4197
  def allocate_without_repeatable(values, allocation)
3337
4198
  trailing = count_trailing_required
3338
4199
  leading_defs = @definitions[0...(@definitions.size - trailing)]
@@ -3346,6 +4207,11 @@ module Git
3346
4207
  consumed + allocate_trailing(allocation, trailing_defs, trailing_values)
3347
4208
  end
3348
4209
 
4210
+ # Count the number of trailing required definitions
4211
+ #
4212
+ # @return [Integer] number of required definitions at the end of @definitions
4213
+ #
4214
+ # @api private
3349
4215
  def count_trailing_required
3350
4216
  count = 0
3351
4217
  @definitions.reverse_each do |d|
@@ -3356,12 +4222,29 @@ module Git
3356
4222
  count
3357
4223
  end
3358
4224
 
4225
+ # Return true if a definition is required (required and has no default)
4226
+ #
4227
+ # @param definition [Hash] the operand definition hash
4228
+ #
4229
+ # @return [Boolean] true if the operand must be provided and has no default
4230
+ #
4231
+ # @api private
3359
4232
  def required?(definition)
3360
4233
  definition[:required] && definition[:default].nil?
3361
4234
  end
3362
4235
 
3363
4236
  # Allocate leading positionals (those before any trailing required)
3364
4237
  # Required positionals consume values; optional ones only consume if extras available
4238
+ #
4239
+ # @param allocation [Hash] the allocation hash to populate
4240
+ #
4241
+ # @param definitions [Array<Hash>] the leading operand definitions
4242
+ #
4243
+ # @param values [Array] the values available for leading allocation
4244
+ #
4245
+ # @return [Integer] the number of non-nil positionals consumed
4246
+ #
4247
+ # @api private
3365
4248
  def allocate_leading(allocation, definitions, values)
3366
4249
  return 0 if definitions.empty?
3367
4250
 
@@ -3369,6 +4252,17 @@ module Git
3369
4252
  state.allocate(allocation)
3370
4253
  end
3371
4254
 
4255
+ # Allocate trailing required positionals from the end of the values array
4256
+ #
4257
+ # @param allocation [Hash] the allocation hash to populate
4258
+ #
4259
+ # @param definitions [Array<Hash>] the trailing operand definitions
4260
+ #
4261
+ # @param values [Array] the values remaining for trailing allocation
4262
+ #
4263
+ # @return [Integer] the number of non-nil positionals consumed
4264
+ #
4265
+ # @api private
3372
4266
  def allocate_trailing(allocation, definitions, values)
3373
4267
  consumed = 0
3374
4268
  definitions.each_with_index do |definition, index|
@@ -3378,6 +4272,17 @@ module Git
3378
4272
  consumed
3379
4273
  end
3380
4274
 
4275
+ # Allocate values when a repeatable definition is present
4276
+ #
4277
+ # @param values [Array] the positional argument values
4278
+ #
4279
+ # @param allocation [Hash] the allocation hash to populate
4280
+ #
4281
+ # @param repeatable_index [Integer] the index of the repeatable definition
4282
+ #
4283
+ # @return [Integer] total non-nil positionals consumed
4284
+ #
4285
+ # @api private
3381
4286
  def allocate_with_repeatable(values, allocation, repeatable_index)
3382
4287
  parts = split_around_repeatable(repeatable_index)
3383
4288
  slices = calculate_repeatable_slices(values, parts)
@@ -3391,6 +4296,15 @@ module Git
3391
4296
  pre_consumed + repeatable_consumed + post_consumed
3392
4297
  end
3393
4298
 
4299
+ # Calculate start/end indices for pre, repeatable, and post slices
4300
+ #
4301
+ # @param values [Array] the full positional values array
4302
+ #
4303
+ # @param parts [Hash] the pre/repeatable/post definition parts
4304
+ #
4305
+ # @return [Hash] slice indices: :pre_values, :var_start, :var_end, :post_start
4306
+ #
4307
+ # @api private
3394
4308
  def calculate_repeatable_slices(values, parts)
3395
4309
  post_required_count = count_required(parts[:post])
3396
4310
  pre_available = [values.size - post_required_count, 0].max
@@ -3405,10 +4319,24 @@ module Git
3405
4319
  }
3406
4320
  end
3407
4321
 
4322
+ # Count required definitions in the given list
4323
+ #
4324
+ # @param definitions [Array<Hash>] the operand definitions to count
4325
+ #
4326
+ # @return [Integer] the number of required definitions
4327
+ #
4328
+ # @api private
3408
4329
  def count_required(definitions)
3409
4330
  definitions.count { |d| required?(d) }
3410
4331
  end
3411
4332
 
4333
+ # Split @definitions into pre, repeatable, and post parts
4334
+ #
4335
+ # @param repeatable_index [Integer] the index of the repeatable definition
4336
+ #
4337
+ # @return [Hash] parts hash with :pre, :repeatable, and :post keys
4338
+ #
4339
+ # @api private
3412
4340
  def split_around_repeatable(repeatable_index)
3413
4341
  {
3414
4342
  pre: @definitions[0...repeatable_index],
@@ -3419,6 +4347,16 @@ module Git
3419
4347
 
3420
4348
  # Allocate pre-repeatable positionals with Ruby-like semantics
3421
4349
  # (required get values first, optional only if extra values available)
4350
+ #
4351
+ # @param allocation [Hash] the allocation hash to populate
4352
+ #
4353
+ # @param definitions [Array<Hash>] the pre-repeatable operand definitions
4354
+ #
4355
+ # @param values [Array] the values available for pre-repeatable allocation
4356
+ #
4357
+ # @return [Integer] the number of non-nil positionals consumed
4358
+ #
4359
+ # @api private
3422
4360
  def allocate_pre_repeatable_smart(allocation, definitions, values)
3423
4361
  return 0 if definitions.empty?
3424
4362
 
@@ -3426,6 +4364,21 @@ module Git
3426
4364
  state.allocate(allocation)
3427
4365
  end
3428
4366
 
4367
+ # Allocate the repeatable definition's slice of values
4368
+ #
4369
+ # @param allocation [Hash] the allocation hash to populate
4370
+ #
4371
+ # @param definition [Hash] the repeatable operand definition
4372
+ #
4373
+ # @param values [Array] the full values array
4374
+ #
4375
+ # @param start_idx [Integer] the start index of the repeatable slice
4376
+ #
4377
+ # @param end_idx [Integer] the end index (exclusive) of the repeatable slice
4378
+ #
4379
+ # @return [Integer] the number of non-nil values consumed by the repeatable
4380
+ #
4381
+ # @api private
3429
4382
  def allocate_repeatable(allocation, definition, values, start_idx, end_idx)
3430
4383
  repeatable_values = values[start_idx...end_idx] || []
3431
4384
  allocation[definition[:name]] =
@@ -3437,6 +4390,19 @@ module Git
3437
4390
  repeatable_values.compact.size
3438
4391
  end
3439
4392
 
4393
+ # Allocate post-repeatable positionals from the end of the values array
4394
+ #
4395
+ # @param allocation [Hash] the allocation hash to populate
4396
+ #
4397
+ # @param definitions [Array<Hash>] the post-repeatable operand definitions
4398
+ #
4399
+ # @param values [Array] the full values array
4400
+ #
4401
+ # @param post_start [Integer] the index where post definitions begin
4402
+ #
4403
+ # @return [Integer] the number of non-nil positionals consumed
4404
+ #
4405
+ # @api private
3440
4406
  def allocate_post_repeatable(allocation, definitions, values, post_start)
3441
4407
  consumed = 0
3442
4408
  definitions.each_with_index do |definition, offset|
@@ -3449,8 +4415,19 @@ module Git
3449
4415
  end
3450
4416
 
3451
4417
  # Encapsulates state for allocating leading positionals
4418
+ #
3452
4419
  # @api private
3453
4420
  class LeadingAllocationState
4421
+ # Initialize allocation state for the given leading definitions and values
4422
+ #
4423
+ # @param definitions [Array<Hash>] the leading operand definitions
4424
+ #
4425
+ # @param values [Array] the values available for allocation
4426
+ #
4427
+ # @param required_check [Method] callable returning true if a definition is required
4428
+ #
4429
+ # @return [void]
4430
+ #
3454
4431
  def initialize(definitions, values, required_check)
3455
4432
  @definitions = definitions
3456
4433
  @values = values
@@ -3475,6 +4452,15 @@ module Git
3475
4452
 
3476
4453
  private
3477
4454
 
4455
+ # Allocate a single definition, dispatching to required or optional branch
4456
+ #
4457
+ # @param allocation [Hash] the allocation hash to populate
4458
+ #
4459
+ # @param definition [Hash] the operand definition to allocate
4460
+ #
4461
+ # @return [void]
4462
+ #
4463
+ # @api private
3478
4464
  def allocate_one(allocation, definition)
3479
4465
  if @required_check.call(definition)
3480
4466
  allocate_required(allocation, definition)
@@ -3483,12 +4469,30 @@ module Git
3483
4469
  end
3484
4470
  end
3485
4471
 
4472
+ # Allocate a required definition, consuming the next value unconditionally
4473
+ #
4474
+ # @param allocation [Hash] the allocation hash to populate
4475
+ #
4476
+ # @param definition [Hash] the operand definition to allocate
4477
+ #
4478
+ # @return [void]
4479
+ #
4480
+ # @api private
3486
4481
  def allocate_required(allocation, definition)
3487
4482
  allocation[definition[:name]] = value_or_default(definition)
3488
4483
  @consumed += 1 if @val_idx < @values.size
3489
4484
  @val_idx += 1
3490
4485
  end
3491
4486
 
4487
+ # Allocate an optional definition, consuming only if extra values remain
4488
+ #
4489
+ # @param allocation [Hash] the allocation hash to populate
4490
+ #
4491
+ # @param definition [Hash] the operand definition to allocate
4492
+ #
4493
+ # @return [void]
4494
+ #
4495
+ # @api private
3492
4496
  def allocate_optional(allocation, definition)
3493
4497
  if @opt_idx < @extra_for_optionals
3494
4498
  allocation[definition[:name]] = value_or_default(definition)
@@ -3500,6 +4504,13 @@ module Git
3500
4504
  @opt_idx += 1
3501
4505
  end
3502
4506
 
4507
+ # Return the current value from @values or the definition default if exhausted
4508
+ #
4509
+ # @param definition [Hash] the operand definition
4510
+ #
4511
+ # @return [Object] the value or default
4512
+ #
4513
+ # @api private
3503
4514
  def value_or_default(definition)
3504
4515
  @val_idx < @values.size ? @values[@val_idx] : definition[:default]
3505
4516
  end