git 5.0.0.beta.3 → 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.
- checksums.yaml +4 -4
- data/.dockerignore +27 -0
- data/.github/copilot-instructions.md +3 -8
- data/.github/hooks/bin-setup-on-worktree.json +11 -0
- data/.github/hooks/run-bin-setup-once.sh +14 -0
- data/.github/skills/breaking-change-analysis/SKILL.md +6 -9
- data/.github/skills/ci-cd-troubleshooting/SKILL.md +2 -2
- data/.github/skills/command-implementation/REFERENCE.md +34 -33
- data/.github/skills/command-implementation/SKILL.md +5 -4
- data/.github/skills/command-test-conventions/SKILL.md +13 -9
- data/.github/skills/command-yard-documentation/SKILL.md +14 -6
- data/.github/skills/development-workflow/SKILL.md +20 -14
- data/.github/skills/facade-implementation/REFERENCE.md +19 -22
- data/.github/skills/facade-implementation/SKILL.md +0 -5
- data/.github/skills/facade-test-conventions/SKILL.md +4 -7
- data/.github/skills/facade-yard-documentation/SKILL.md +8 -2
- data/.github/skills/make-skill-template/SKILL.md +53 -3
- data/.github/skills/pr-readiness-review/SKILL.md +22 -2
- data/.github/skills/project-context/SKILL.md +16 -23
- data/.github/skills/pull-request-review/SKILL.md +2 -2
- data/.github/skills/rebase/SKILL.md +148 -0
- data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +1 -1
- data/.github/skills/resolve-feedback/SKILL.md +288 -0
- data/.github/skills/review-arguments-dsl/CHECKLIST.md +3 -3
- data/.github/skills/reviewing-skills/SKILL.md +28 -3
- data/.github/skills/test-debugging/SKILL.md +20 -19
- data/.github/skills/yard-documentation/SKILL.md +671 -483
- data/.github/skills/yard-documentation/element-rules.md +162 -0
- data/.github/skills-deprecated/README.md +21 -0
- data/.github/{skills → skills-deprecated}/extract-command-from-lib/SKILL.md +9 -9
- data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/SKILL.md +12 -12
- data/.github/workflows/continuous_integration.yml +3 -2
- data/.rubocop.yml +7 -39
- data/.yard-lint.yml +75 -0
- data/CONTRIBUTING.md +186 -174
- data/Gemfile +17 -0
- data/README.md +47 -12
- data/Rakefile +3 -21
- data/UPGRADING.md +219 -70
- data/docker/test/Dockerfile +32 -0
- data/docker/test/docker-compose.yml +0 -0
- data/git.gemspec +3 -4
- data/lib/git/author.rb +20 -1
- data/lib/git/branch.rb +26 -6
- data/lib/git/branch_delete_failure.rb +3 -0
- data/lib/git/branch_delete_result.rb +6 -3
- data/lib/git/branch_info.rb +6 -6
- data/lib/git/command_line/base.rb +6 -13
- data/lib/git/command_line/capturing.rb +67 -10
- data/lib/git/command_line/result.rb +0 -8
- data/lib/git/command_line/streaming.rb +45 -5
- data/lib/git/command_line.rb +4 -3
- data/lib/git/commands/add.rb +61 -53
- data/lib/git/commands/am/abort.rb +2 -2
- data/lib/git/commands/am/apply.rb +109 -98
- data/lib/git/commands/am/continue.rb +2 -2
- data/lib/git/commands/am/quit.rb +2 -2
- data/lib/git/commands/am/retry.rb +4 -2
- data/lib/git/commands/am/show_current_patch.rb +1 -1
- data/lib/git/commands/am/skip.rb +2 -2
- data/lib/git/commands/am.rb +2 -2
- data/lib/git/commands/apply.rb +7 -2
- data/lib/git/commands/archive/list_formats.rb +3 -3
- data/lib/git/commands/archive.rb +7 -2
- data/lib/git/commands/arguments.rb +1370 -359
- data/lib/git/commands/base.rb +111 -13
- data/lib/git/commands/branch/copy.rb +29 -21
- data/lib/git/commands/branch/create.rb +72 -68
- data/lib/git/commands/branch/delete.rb +25 -17
- data/lib/git/commands/branch/list.rb +76 -60
- data/lib/git/commands/branch/move.rb +29 -21
- data/lib/git/commands/branch/set_upstream.rb +8 -8
- data/lib/git/commands/branch/show_current.rb +3 -3
- data/lib/git/commands/branch/unset_upstream.rb +4 -8
- data/lib/git/commands/branch.rb +2 -2
- data/lib/git/commands/cat_file/batch.rb +36 -20
- data/lib/git/commands/cat_file/filtered.rb +12 -12
- data/lib/git/commands/cat_file/raw.rb +13 -13
- data/lib/git/commands/cat_file.rb +4 -4
- data/lib/git/commands/checkout/branch.rb +7 -5
- data/lib/git/commands/checkout/files.rb +5 -4
- data/lib/git/commands/checkout.rb +4 -4
- data/lib/git/commands/checkout_index.rb +5 -4
- data/lib/git/commands/clean.rb +6 -4
- data/lib/git/commands/clone.rb +5 -4
- data/lib/git/commands/commit.rb +5 -4
- data/lib/git/commands/commit_tree.rb +5 -4
- data/lib/git/commands/config_option_syntax/add.rb +7 -4
- data/lib/git/commands/config_option_syntax/get.rb +9 -5
- data/lib/git/commands/config_option_syntax/get_all.rb +7 -4
- data/lib/git/commands/config_option_syntax/get_color.rb +8 -4
- data/lib/git/commands/config_option_syntax/get_color_bool.rb +7 -4
- data/lib/git/commands/config_option_syntax/get_regexp.rb +8 -4
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +8 -4
- data/lib/git/commands/config_option_syntax/list.rb +8 -4
- data/lib/git/commands/config_option_syntax/remove_section.rb +7 -2
- data/lib/git/commands/config_option_syntax/rename_section.rb +7 -2
- data/lib/git/commands/config_option_syntax/replace_all.rb +7 -2
- data/lib/git/commands/config_option_syntax/set.rb +7 -2
- data/lib/git/commands/config_option_syntax/unset.rb +7 -4
- data/lib/git/commands/config_option_syntax/unset_all.rb +7 -2
- data/lib/git/commands/describe.rb +5 -4
- data/lib/git/commands/diff.rb +13 -12
- data/lib/git/commands/diff_files.rb +7 -6
- data/lib/git/commands/diff_index.rb +10 -7
- data/lib/git/commands/fetch.rb +6 -4
- data/lib/git/commands/fsck.rb +6 -4
- data/lib/git/commands/gc.rb +6 -4
- data/lib/git/commands/grep.rb +6 -5
- data/lib/git/commands/init.rb +6 -4
- data/lib/git/commands/log.rb +287 -285
- data/lib/git/commands/ls_files.rb +69 -65
- data/lib/git/commands/ls_remote.rb +62 -56
- data/lib/git/commands/ls_tree.rb +50 -46
- data/lib/git/commands/maintenance/register.rb +25 -23
- data/lib/git/commands/maintenance/run.rb +7 -2
- data/lib/git/commands/maintenance/start.rb +7 -2
- data/lib/git/commands/maintenance/stop.rb +7 -2
- data/lib/git/commands/maintenance/unregister.rb +7 -2
- data/lib/git/commands/merge/abort.rb +2 -2
- data/lib/git/commands/merge/continue.rb +2 -2
- data/lib/git/commands/merge/quit.rb +2 -2
- data/lib/git/commands/merge/start.rb +7 -2
- data/lib/git/commands/merge.rb +2 -2
- data/lib/git/commands/merge_base.rb +7 -2
- data/lib/git/commands/mv.rb +7 -2
- data/lib/git/commands/name_rev.rb +7 -2
- data/lib/git/commands/pull.rb +7 -2
- data/lib/git/commands/push.rb +7 -2
- data/lib/git/commands/read_tree.rb +7 -2
- data/lib/git/commands/remote/add.rb +7 -2
- data/lib/git/commands/remote/get_url.rb +4 -2
- data/lib/git/commands/remote/list.rb +4 -2
- data/lib/git/commands/remote/prune.rb +4 -2
- data/lib/git/commands/remote/remove.rb +4 -2
- data/lib/git/commands/remote/rename.rb +4 -2
- data/lib/git/commands/remote/set_branches.rb +9 -2
- data/lib/git/commands/remote/set_head.rb +12 -5
- data/lib/git/commands/remote/set_url.rb +9 -2
- data/lib/git/commands/remote/set_url_add.rb +11 -2
- data/lib/git/commands/remote/set_url_delete.rb +9 -2
- data/lib/git/commands/remote/show.rb +18 -12
- data/lib/git/commands/remote/update.rb +19 -12
- data/lib/git/commands/remote.rb +2 -2
- data/lib/git/commands/repack.rb +67 -63
- data/lib/git/commands/reset.rb +51 -44
- data/lib/git/commands/rev_parse.rb +135 -128
- data/lib/git/commands/revert/abort.rb +2 -2
- data/lib/git/commands/revert/continue.rb +7 -2
- data/lib/git/commands/revert/quit.rb +2 -2
- data/lib/git/commands/revert/skip.rb +2 -2
- data/lib/git/commands/revert/start.rb +7 -2
- data/lib/git/commands/revert.rb +2 -2
- data/lib/git/commands/rm.rb +35 -36
- data/lib/git/commands/show.rb +1 -1
- data/lib/git/commands/show_ref/exclude_existing.rb +3 -4
- data/lib/git/commands/show_ref/exists.rb +15 -13
- data/lib/git/commands/show_ref/list.rb +39 -35
- data/lib/git/commands/show_ref/verify.rb +29 -27
- data/lib/git/commands/show_ref.rb +4 -4
- data/lib/git/commands/stash/apply.rb +31 -25
- data/lib/git/commands/stash/branch.rb +10 -8
- data/lib/git/commands/stash/clear.rb +7 -5
- data/lib/git/commands/stash/create.rb +10 -8
- data/lib/git/commands/stash/drop.rb +15 -9
- data/lib/git/commands/stash/list.rb +9 -5
- data/lib/git/commands/stash/pop.rb +21 -12
- data/lib/git/commands/stash/push.rb +20 -11
- data/lib/git/commands/stash/show.rb +21 -12
- data/lib/git/commands/stash/store.rb +19 -10
- data/lib/git/commands/stash.rb +2 -2
- data/lib/git/commands/status.rb +7 -2
- data/lib/git/commands/symbolic_ref/delete.rb +18 -14
- data/lib/git/commands/symbolic_ref/read.rb +24 -20
- data/lib/git/commands/symbolic_ref/update.rb +20 -17
- data/lib/git/commands/symbolic_ref.rb +4 -4
- data/lib/git/commands/tag/create.rb +45 -42
- data/lib/git/commands/tag/delete.rb +5 -3
- data/lib/git/commands/tag/list.rb +47 -44
- data/lib/git/commands/tag/verify.rb +12 -12
- data/lib/git/commands/tag.rb +2 -2
- data/lib/git/commands/update_ref/batch.rb +6 -1
- data/lib/git/commands/update_ref/delete.rb +19 -21
- data/lib/git/commands/update_ref/update.rb +22 -25
- data/lib/git/commands/update_ref.rb +4 -4
- data/lib/git/commands/version.rb +14 -11
- data/lib/git/commands/worktree/add.rb +50 -51
- data/lib/git/commands/worktree/list.rb +17 -17
- data/lib/git/commands/worktree/lock.rb +12 -12
- data/lib/git/commands/worktree/move.rb +16 -16
- data/lib/git/commands/worktree/prune.rb +2 -2
- data/lib/git/commands/worktree/remove.rb +2 -2
- data/lib/git/commands/worktree/repair.rb +2 -2
- data/lib/git/commands/worktree/unlock.rb +2 -2
- data/lib/git/commands/write_tree.rb +2 -2
- data/lib/git/commands.rb +4 -3
- data/lib/git/config.rb +47 -1
- data/lib/git/configuring.rb +89 -15
- data/lib/git/detached_head_info.rb +4 -1
- data/lib/git/diff.rb +2 -5
- data/lib/git/diff_file_numstat_info.rb +3 -1
- data/lib/git/diff_file_patch_info.rb +3 -1
- data/lib/git/diff_file_raw_info.rb +3 -1
- data/lib/git/diff_info.rb +8 -15
- data/lib/git/diff_path_status.rb +16 -3
- data/lib/git/diff_result.rb +3 -1
- data/lib/git/diff_stats.rb +15 -0
- data/lib/git/dirstat_info.rb +24 -8
- data/lib/git/encoding_utils.rb +32 -1
- data/lib/git/errors.rb +19 -9
- data/lib/git/escaped_path.rb +55 -3
- data/lib/git/execution_context/repository.rb +15 -0
- data/lib/git/execution_context.rb +76 -74
- data/lib/git/file_ref.rb +6 -3
- data/lib/git/fsck_object.rb +8 -0
- data/lib/git/fsck_result.rb +26 -15
- data/lib/git/log.rb +152 -5
- data/lib/git/object.rb +284 -8
- data/lib/git/parsers/branch.rb +18 -0
- data/lib/git/parsers/cat_file.rb +2 -2
- data/lib/git/parsers/diff.rb +213 -5
- data/lib/git/parsers/fsck.rb +11 -0
- data/lib/git/parsers/ls_remote.rb +1 -1
- data/lib/git/parsers/stash.rb +51 -0
- data/lib/git/parsers/tag.rb +38 -0
- data/lib/git/remote.rb +34 -2
- data/lib/git/repository/branching.rb +23 -21
- data/lib/git/repository/committing.rb +46 -6
- data/lib/git/repository/context_helpers.rb +45 -16
- data/lib/git/repository/diffing.rb +32 -23
- data/lib/git/repository/factories.rb +153 -28
- data/lib/git/repository/inspecting.rb +1 -1
- data/lib/git/repository/logging.rb +169 -30
- data/lib/git/repository/maintenance.rb +1 -1
- data/lib/git/repository/merging.rb +3 -3
- data/lib/git/repository/object_operations.rb +108 -14
- data/lib/git/repository/remote_operations.rb +150 -171
- data/lib/git/repository/shared_private.rb +7 -5
- data/lib/git/repository/staging.rb +36 -12
- data/lib/git/repository/stashing.rb +16 -16
- data/lib/git/repository/status_operations.rb +1 -1
- data/lib/git/repository/worktree_operations.rb +1 -1
- data/lib/git/repository.rb +123 -63
- data/lib/git/stash.rb +16 -21
- data/lib/git/stash_info.rb +4 -4
- data/lib/git/stashes.rb +20 -26
- data/lib/git/status.rb +21 -7
- data/lib/git/tag_delete_failure.rb +3 -0
- data/lib/git/tag_delete_result.rb +6 -3
- data/lib/git/tag_info.rb +1 -0
- data/lib/git/url.rb +12 -5
- data/lib/git/version.rb +6 -4
- data/lib/git/version_constraint.rb +4 -0
- data/lib/git.rb +398 -314
- data/redesign/3_architecture_implementation.md +59 -29
- data/redesign/Phase 4 - Step B.md +921 -0
- data/redesign/Phase 4 - Step C.md +833 -0
- data/redesign/c1a-public-api-scope.tsv +256 -0
- data/redesign/phase-4-step-b-test-audit.tsv +485 -0
- data/tasks/yard.rake +24 -17
- metadata +24 -57
- data/commitlint.test +0 -4
- data/lib/git/args_builder.rb +0 -103
- data/lib/git/command_line_result.rb +0 -8
- data/lib/git/deprecation.rb +0 -9
- data/tasks/test.rake +0 -25
- /data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -0
- /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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
# -
|
|
138
|
-
# -
|
|
139
|
-
# -
|
|
140
|
-
# -
|
|
141
|
-
# -
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
-
# @
|
|
1759
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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
|
|
2026
|
+
# Raise if the `keys` array contains duplicate entries
|
|
1977
2027
|
#
|
|
1978
|
-
# Duplicate aliases in a single declaration (e.g.
|
|
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
|
-
#
|
|
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
|
|
1999
|
-
#
|
|
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
|
|
2035
|
-
# (when
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|