@slicedearth/whoisleuth-cli 2.2.0 → 2.4.0
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.
- package/NOTICE +6 -0
- package/README.md +20 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/arguments.mjs +454 -1748
- package/cli/artifact-validation/case-response.mjs +66 -35
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +12 -6
- package/cli/artifact-validation/signed-review.mjs +112 -72
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +90 -24
- package/cli/assurance-command-runner.mjs +39 -4
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +34 -3
- package/cli/bulk-command-runner.mjs +2 -0
- package/cli/bulk-output.mjs +29 -3
- package/cli/case-command.mjs +195 -0
- package/cli/case-pack.mjs +66 -111
- package/cli/command-argument-grammar.mjs +215 -0
- package/cli/command-reference.mjs +342 -285
- package/cli/completion.mjs +303 -80
- package/cli/config-profile.mjs +7 -5
- package/cli/ct-event-intake.mjs +12 -12
- package/cli/discover.mjs +4 -3
- package/cli/discovery-observation-snapshot.mjs +59 -18
- package/cli/discovery-scan-command-runner.mjs +21 -0
- package/cli/discovery-scan.mjs +13 -4
- package/cli/discovery-workflow.mjs +1 -1
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +149 -102
- package/cli/errors.mjs +33 -1
- package/cli/evidence-command-runner.mjs +16 -5
- package/cli/evidence-signing.mjs +2 -2
- package/cli/external-observation-mapping.mjs +7 -8
- package/cli/formatters/terminal-lookup.mjs +18 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/inline-command-families.mjs +2 -0
- package/cli/input.mjs +116 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +18 -8
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +32 -19
- package/cli/investigation-run.mjs +188 -33
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/lookalike-calibration.mjs +7 -2
- package/cli/lookup-brief.mjs +4 -2
- package/cli/lookup-browser-view.mjs +1 -1
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +17 -6
- package/cli/lookup-diff.mjs +37 -3
- package/cli/lookup-plan.mjs +11 -5
- package/cli/lookup-reconcile.mjs +10 -8
- package/cli/mail-header-review.mjs +467 -0
- package/cli/mail-review.mjs +66 -14
- package/cli/manual.mjs +1 -0
- package/cli/network-command-runner.mjs +2 -0
- package/cli/output-file.mjs +31 -5
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +2 -2
- package/cli/review-command-runner.mjs +70 -0
- package/cli/risk-calibration.mjs +19 -17
- package/cli/runner.mjs +62 -7
- package/cli/sharing-review.mjs +21 -34
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +74 -83
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-command-runner.mjs +68 -13
- package/docs/cli-reference.md +77 -24
- package/docs/cli.md +300 -25
- package/lib/abort.mjs +4 -1
- package/lib/availability.mjs +161 -112
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +51 -8
- package/lib/browser-library-profile.mjs +42 -51
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-search.mjs +11 -4
- package/lib/dns-convergence-review.mjs +1 -0
- package/lib/dns-delegation-health.mjs +24 -14
- package/lib/dns-inheritance-review.mjs +217 -0
- package/lib/dns-intelligence.mjs +73 -26
- package/lib/dns-mx.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +6 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-change-packet.mjs +49 -16
- package/lib/domain-change-review.mjs +14 -8
- package/lib/domain-control-flight-recorder.mjs +221 -117
- package/lib/domain-control-manifest.mjs +129 -69
- package/lib/domain-posture-analysis.mjs +20 -7
- package/lib/domain-posture-parsers.mjs +201 -8
- package/lib/domain-posture.mjs +147 -29
- package/lib/evidence-export-privacy.mjs +561 -0
- package/lib/evidence-export.mjs +11 -543
- package/lib/evidence-report.mjs +5 -3
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +12 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +2335 -940
- package/lib/hostname.mjs +2 -38
- package/lib/html-signals.mjs +111 -108
- package/lib/http-evidence-bounds.mjs +2 -1
- package/lib/http-intelligence.mjs +39 -26
- package/lib/local-geoip-evidence.mjs +19 -3
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-diagnostics.mjs +24 -1
- package/lib/lookup-progress.mjs +30 -13
- package/lib/lookup-response-contract.mjs +1048 -0
- package/lib/lookup-response.mjs +8 -10
- package/lib/lookup-source-progress.mjs +40 -14
- package/lib/lookup.mjs +48 -27
- package/lib/nameserver-preflight-review.mjs +1 -0
- package/lib/observed-network-context.mjs +43 -27
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +69 -133
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash.mjs +43 -14
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-capabilities.mjs +1 -1
- package/lib/rdap-client.mjs +15 -7
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +21 -1
- package/lib/registrar-standing-contract.mjs +1 -0
- package/lib/registry-capabilities.mjs +5 -1
- package/lib/registry-capability-catalogue.mjs +2 -1
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +85 -34
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +18 -6
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/security-txt.mjs +42 -7
- package/lib/service-binding-dns.mjs +8 -0
- package/lib/smtp-transport-review.mjs +27 -15
- package/lib/sslbl-certificates.generated.mjs +136 -135
- package/lib/static-html-analysis.mjs +178 -151
- package/lib/structured-data-identity.mjs +1 -0
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +39 -0
- package/lib/typosquat-generator.mjs +19 -15
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +153 -45
- package/lib/whois-authority.mjs +14 -2
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-transport.mjs +23 -7
- package/lib/zone-intent-review.mjs +16 -6
- package/package.json +2 -1
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-model.mjs +156 -62
- package/packages/cases/case-export-input.mjs +53 -0
- package/packages/cases/case-migration-model.mjs +127 -44
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +91 -0
- package/packages/cases/case-record-contracts.mjs +5 -20
- package/packages/cases/case-record-core.mjs +12 -22
- package/packages/cases/case-record-decisions.mjs +72 -0
- package/packages/cases/case-record-model.mjs +1 -0
- package/packages/cases/case-record-operations.mjs +95 -31
- package/packages/cases/case-record-projection.mjs +221 -0
- package/packages/cases/case-report.mjs +70 -54
- package/packages/cases/case-response-actions.mjs +654 -0
- package/packages/cases/case-response-model.mjs +64 -1115
- package/packages/cases/case-response-outcomes.mjs +317 -0
- package/packages/cases/case-response-packet-render.mjs +153 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +74 -572
- package/packages/cases/case-response-records.mjs +94 -0
- package/packages/cases/case-response-review-inputs.mjs +371 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +31 -2
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
- package/packages/comparison/page-similarity.mjs +5 -2
- package/packages/comparison/relationship-evidence.mjs +85 -8
- package/packages/comparison/relationship-provenance.mjs +93 -0
- package/packages/contracts/analyst-interchange.mjs +216 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +366 -83
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +94 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
- package/packages/contracts/domain-control-manifest.mjs +127 -73
- package/packages/contracts/domain-control-monitor.mjs +48 -45
- package/packages/contracts/domain-control-review.mjs +179 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +16 -0
- package/packages/contracts/ip-address.mjs +34 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/offline-comparison.mjs +1 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/risk-calibration.mjs +15 -11
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +183 -211
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/domain-control-runtime.mjs +201 -33
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +104 -0
- package/packages/evidence/observation.mjs +16 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/external-findings-import.mjs +92 -51
- package/packages/interchange/web-capture-import.mjs +382 -0
- package/packages/investigation/bulk-mail-exposure.mjs +2 -1
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +193 -165
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +276 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +23 -11
- package/packages/workspace/bulk-session-model.mjs +100 -24
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +70 -22
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-template-model.mjs +19 -4
- package/packages/workspace/page-baseline.mjs +18 -15
- package/packages/workspace/relationship-observation-model.mjs +41 -23
- package/packages/workspace/watchlist-history.mjs +2 -2
- package/packages/workspace/watchlist-store.mjs +8 -2
- package/packages/workspace/website-snapshot-model.mjs +164 -26
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +87 -21
- package/third-party-notices.txt +29 -2
- package/cli/evidence-command-arguments.mjs +0 -156
- package/packages/investigation/bulk-review-export.mjs +0 -63
package/cli/completion.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CLI_COMMAND_REGISTRY, CLI_COMMANDS, COMMAND_DESCRIPTIONS, FILE_POSITIONAL_COMMANDS, commandOptionSpec, commandDefinition, commandPositionalSpecs, metaActionDefinition, } from "./command-reference.mjs";
|
|
2
|
-
|
|
2
|
+
// Emergency bound for one static script; input and execution bounds are independent.
|
|
3
|
+
const MAX_CLI_COMPLETION_BYTES = 1024 * 1024;
|
|
3
4
|
const HELP_META_ACTION = metaActionDefinition('help');
|
|
4
5
|
const VERSION_META_ACTION = metaActionDefinition('version');
|
|
5
6
|
const COMMAND_META_ALIASES = Object.freeze([...HELP_META_ACTION.aliases]);
|
|
@@ -92,10 +93,83 @@ function commandOptionPatterns(kinds) {
|
|
|
92
93
|
return CLI_COMMANDS.flatMap((command) => optionNamesByKind(command, kinds)
|
|
93
94
|
.map((option) => `${command}:${option}`)).join('|');
|
|
94
95
|
}
|
|
96
|
+
function completionExclusions() {
|
|
97
|
+
const exclusions = new Map();
|
|
98
|
+
let changed = false;
|
|
99
|
+
const add = (command, selected, excluded) => {
|
|
100
|
+
const key = `${command}:${selected}`;
|
|
101
|
+
const values = exclusions.get(key) ?? new Set();
|
|
102
|
+
if (!values.has(excluded)) {
|
|
103
|
+
values.add(excluded);
|
|
104
|
+
changed = true;
|
|
105
|
+
}
|
|
106
|
+
exclusions.set(key, values);
|
|
107
|
+
};
|
|
108
|
+
for (const definition of CLI_COMMAND_REGISTRY) {
|
|
109
|
+
for (const constraint of definition.grammar.constraints) {
|
|
110
|
+
if (constraint.kind === 'mutually_exclusive') {
|
|
111
|
+
for (const selected of constraint.options) {
|
|
112
|
+
for (const excluded of constraint.options) {
|
|
113
|
+
if (selected !== excluded)
|
|
114
|
+
add(definition.command, selected, excluded);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (constraint.kind === 'excludes_all' || constraint.kind === 'value_excludes') {
|
|
119
|
+
const selected = constraint.kind === 'value_excludes'
|
|
120
|
+
? `${constraint.option}=${constraint.value}` : constraint.option;
|
|
121
|
+
for (const excluded of constraint.excludedOptions) {
|
|
122
|
+
add(definition.command, selected, excluded);
|
|
123
|
+
add(definition.command, excluded, selected);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
do {
|
|
129
|
+
changed = false;
|
|
130
|
+
for (const definition of CLI_COMMAND_REGISTRY) {
|
|
131
|
+
for (const constraint of definition.grammar.constraints) {
|
|
132
|
+
if (constraint.kind !== 'requires_all' && constraint.kind !== 'requires_any')
|
|
133
|
+
continue;
|
|
134
|
+
const required = constraint.requiredOptions;
|
|
135
|
+
for (const [key, blocked] of exclusions) {
|
|
136
|
+
if (!key.startsWith(`${definition.command}:`))
|
|
137
|
+
continue;
|
|
138
|
+
const unavailable = required.map((option) => blocked.has(option));
|
|
139
|
+
if (constraint.kind === 'requires_all' ? unavailable.some(Boolean) : unavailable.every(Boolean)) {
|
|
140
|
+
add(definition.command, key.slice(definition.command.length + 1), constraint.option);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const requiredExclusions = required.map((option) => exclusions.get(`${definition.command}:${option}`) ?? new Set());
|
|
144
|
+
for (const blocked of new Set(requiredExclusions.flatMap((values) => [...values]))) {
|
|
145
|
+
if (constraint.kind === 'requires_all' || requiredExclusions.every((values) => values.has(blocked))) {
|
|
146
|
+
add(definition.command, constraint.option, blocked);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} while (changed);
|
|
152
|
+
return new Map([...exclusions].map(([key, values]) => [key, [...values]]));
|
|
153
|
+
}
|
|
154
|
+
const COMPLETION_EXCLUSIONS = completionExclusions();
|
|
155
|
+
function groupedCompletionExclusions() {
|
|
156
|
+
const groups = new Map();
|
|
157
|
+
for (const [key, values] of COMPLETION_EXCLUSIONS) {
|
|
158
|
+
const excluded = [...values].sort().join(' ');
|
|
159
|
+
const keys = groups.get(excluded) ?? [];
|
|
160
|
+
keys.push(key);
|
|
161
|
+
groups.set(excluded, keys);
|
|
162
|
+
}
|
|
163
|
+
return [...groups].map(([excluded, keys]) => ({ keys, excluded }));
|
|
164
|
+
}
|
|
165
|
+
function repeatableOptions(command) {
|
|
166
|
+
return commandDefinition(command).grammar.options
|
|
167
|
+
.filter((option) => option.occurrence === 'repeatable').map((option) => option.option);
|
|
168
|
+
}
|
|
95
169
|
function bashCompletion() {
|
|
96
|
-
const cases = CLI_COMMANDS.map((command) => ` ${command}) options="${commandOptions(command)}"; value_options="${optionNamesByKind(command, ['enum', 'file', 'integer', 'policy_list', 'text']).join(' ')}"; file_limit=${maximumFilePositionals(command)} ;;`).join('\n');
|
|
97
|
-
const commandValueCases = COMMAND_VALUE_OPTION_GROUPS.map((group) => ` ${group.commands.map((command) => `${command}:${group.option}`).join('|')})
|
|
98
|
-
const valueCases = Object.entries(VALUE_OPTIONS).map(([option, values]) => ` ${option})
|
|
170
|
+
const cases = CLI_COMMANDS.map((command) => ` ${command}) options="${commandOptions(command)}"; value_options="${optionNamesByKind(command, ['enum', 'file', 'integer', 'policy_list', 'text']).join(' ')}"; repeat_options="${repeatableOptions(command).join(' ')}"; file_limit=${maximumFilePositionals(command)} ;;`).join('\n');
|
|
171
|
+
const commandValueCases = COMMAND_VALUE_OPTION_GROUPS.map((group) => ` ${group.commands.map((command) => `${command}:${group.option}`).join('|')}) _whoisleuth_complete_values "${group.values.join(' ')}"; return ;;`).join('\n');
|
|
172
|
+
const valueCases = Object.entries(VALUE_OPTIONS).map(([option, values]) => ` ${option}) _whoisleuth_complete_values "${values.join(' ')}"; return ;;`).join('\n');
|
|
99
173
|
const integerCases = CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options
|
|
100
174
|
.filter((option) => option.valueKind === 'integer')
|
|
101
175
|
.map((option) => {
|
|
@@ -120,8 +194,28 @@ _whoisleuth_direct_lookup_target() {
|
|
|
120
194
|
case " ${CLI_COMMANDS.join(' ')} " in *" \${candidate} "*) return 1 ;; esac
|
|
121
195
|
"\${COMP_WORDS[0]}" "\${candidate}" --plan --json >/dev/null 2>&1
|
|
122
196
|
}
|
|
197
|
+
_whoisleuth_complete_files() {
|
|
198
|
+
local candidate
|
|
199
|
+
COMPREPLY=()
|
|
200
|
+
# Readline handles quoting and escapes; preserve each path as one candidate.
|
|
201
|
+
while IFS= read -r candidate; do
|
|
202
|
+
COMPREPLY+=("\${candidate}")
|
|
203
|
+
done < <(compgen -o default -- "\${1}")
|
|
204
|
+
}
|
|
205
|
+
_whoisleuth_exclusions() {
|
|
206
|
+
case "\${1}:\${2}" in
|
|
207
|
+
${groupedCompletionExclusions().map((group) => ` ${group.keys.join('|')}) printf '%s' '${group.excluded}' ;;`).join('\n')}
|
|
208
|
+
esac
|
|
209
|
+
}
|
|
210
|
+
_whoisleuth_complete_values() {
|
|
211
|
+
local candidate
|
|
212
|
+
COMPREPLY=()
|
|
213
|
+
for candidate in $(compgen -W "\${1}" -- "\${current}"); do
|
|
214
|
+
[[ " \${blocked_options} " == *" \${previous}=\${candidate} "* ]] || COMPREPLY+=("\${candidate}")
|
|
215
|
+
done
|
|
216
|
+
}
|
|
123
217
|
_whoisleuth_completion() {
|
|
124
|
-
local current previous command options value_options file_limit positional_count expect_value foreign_option word integer_values seen_options i
|
|
218
|
+
local current previous command options value_options repeat_options file_limit positional_count expect_value foreign_option word integer_values seen_options i ended blocked_options value_option candidate available
|
|
125
219
|
current="\${COMP_WORDS[COMP_CWORD]}"
|
|
126
220
|
previous="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
127
221
|
command="\${COMP_WORDS[1]}"
|
|
@@ -140,18 +234,28 @@ ${cases}
|
|
|
140
234
|
expect_value=0
|
|
141
235
|
foreign_option=0
|
|
142
236
|
seen_options=""
|
|
237
|
+
repeat_options="\${repeat_options:-}"
|
|
238
|
+
blocked_options=""
|
|
239
|
+
ended=0
|
|
240
|
+
value_option=""
|
|
143
241
|
for ((i=2; i<COMP_CWORD; i++)); do
|
|
144
242
|
word="\${COMP_WORDS[i]}"
|
|
145
|
-
if (( expect_value )); then
|
|
243
|
+
if (( expect_value )); then
|
|
244
|
+
blocked_options="\${blocked_options} $(_whoisleuth_exclusions "\${command}" "\${value_option}=\${word}")"
|
|
245
|
+
expect_value=0; continue
|
|
246
|
+
fi
|
|
247
|
+
if (( ended )); then ((positional_count+=1)); continue; fi
|
|
248
|
+
if [[ "\${word}" == -- ]]; then ended=1; continue; fi
|
|
146
249
|
if [[ "\${word}" == -* ]]; then
|
|
147
250
|
if [[ " \${options} " != *" \${word} "* ]]; then foreign_option=1; continue; fi
|
|
148
251
|
seen_options="\${seen_options} \${word}"
|
|
149
|
-
|
|
252
|
+
blocked_options="\${blocked_options} $(_whoisleuth_exclusions "\${command}" "\${word}")"
|
|
253
|
+
if [[ " \${value_options} " == *" \${word} "* ]]; then expect_value=1; value_option="\${word}"; fi
|
|
150
254
|
else
|
|
151
255
|
((positional_count+=1))
|
|
152
256
|
fi
|
|
153
257
|
done
|
|
154
|
-
if [[ " \${options} " == *" \${previous} "* ]]; then
|
|
258
|
+
if (( ! ended && expect_value )) && [[ " \${options} " == *" \${previous} "* ]]; then
|
|
155
259
|
case "\${command}:\${previous}" in
|
|
156
260
|
${commandValueCases}
|
|
157
261
|
esac
|
|
@@ -159,7 +263,7 @@ ${commandValueCases}
|
|
|
159
263
|
${valueCases}
|
|
160
264
|
esac
|
|
161
265
|
case "\${command}:\${previous}" in
|
|
162
|
-
${commandOptionPatterns(['file'])})
|
|
266
|
+
${commandOptionPatterns(['file'])}) _whoisleuth_complete_files "\${current}"; return ;;
|
|
163
267
|
${integerCases}
|
|
164
268
|
${commandOptionPatterns(['text'])}) COMPREPLY=(); return ;;
|
|
165
269
|
esac
|
|
@@ -167,20 +271,29 @@ ${integerCases}
|
|
|
167
271
|
case "\${command}:\${positional_count}" in
|
|
168
272
|
${positionalValueCases}
|
|
169
273
|
esac
|
|
170
|
-
if (( file_limit > positional_count && foreign_option == 0 && expect_value == 0 )) && [[ "\${current}" != -* ]]; then
|
|
171
|
-
|
|
274
|
+
if (( file_limit > positional_count && foreign_option == 0 && expect_value == 0 )) && { (( ended )) || [[ "\${current}" != -* ]]; }; then
|
|
275
|
+
_whoisleuth_complete_files "\${current}"
|
|
172
276
|
return
|
|
173
277
|
fi
|
|
174
|
-
COMPREPLY=(
|
|
278
|
+
COMPREPLY=()
|
|
279
|
+
(( ended || foreign_option || expect_value )) && return
|
|
280
|
+
available=""
|
|
281
|
+
for candidate in \${options}; do
|
|
282
|
+
[[ " \${blocked_options} " == *" \${candidate} "* ]] && continue
|
|
283
|
+
if [[ " \${seen_options} " == *" \${candidate} "* && " \${repeat_options} " != *" \${candidate} "* ]]; then continue; fi
|
|
284
|
+
if (( COMP_CWORD > 2 )) && [[ " ${COMMAND_META_ALIASES.join(' ')} " == *" \${candidate} "* ]]; then continue; fi
|
|
285
|
+
available="\${available} \${candidate}"
|
|
286
|
+
done
|
|
287
|
+
COMPREPLY=( $(compgen -W "\${available}" -- "\${current}") )
|
|
175
288
|
}
|
|
176
|
-
complete -F _whoisleuth_completion whoisleuth
|
|
289
|
+
complete -o filenames -F _whoisleuth_completion whoisleuth
|
|
177
290
|
`;
|
|
178
291
|
}
|
|
179
292
|
function zshCompletion() {
|
|
180
293
|
const commandEntries = CLI_COMMANDS.map((command) => `'${command}:${COMMAND_DESCRIPTIONS[command] || command}'`).join(' ');
|
|
181
|
-
const cases = CLI_COMMANDS.map((command) => ` ${command}) options=(${commandOptions(command)}); value_options=(${optionNamesByKind(command, ['enum', 'file', 'integer', 'policy_list', 'text']).join(' ')}); file_limit=${maximumFilePositionals(command)} ;;`).join('\n');
|
|
182
|
-
const commandValueCases = COMMAND_VALUE_OPTION_GROUPS.map((group) => ` ${group.commands.map((command) => `${command}:${group.option}`).join('|')})
|
|
183
|
-
const valueCases = Object.entries(VALUE_OPTIONS).map(([option, values]) => ` ${option})
|
|
294
|
+
const cases = CLI_COMMANDS.map((command) => ` ${command}) options=(${commandOptions(command)}); value_options=(${optionNamesByKind(command, ['enum', 'file', 'integer', 'policy_list', 'text']).join(' ')}); repeat_options=(${repeatableOptions(command).join(' ')}); file_limit=${maximumFilePositionals(command)} ;;`).join('\n');
|
|
295
|
+
const commandValueCases = COMMAND_VALUE_OPTION_GROUPS.map((group) => ` ${group.commands.map((command) => `${command}:${group.option}`).join('|')}) _whoisleuth_values ${group.values.join(' ')}; return ;;`).join('\n');
|
|
296
|
+
const valueCases = Object.entries(VALUE_OPTIONS).map(([option, values]) => ` ${option}) _whoisleuth_values ${values.join(' ')}; return ;;`).join('\n');
|
|
184
297
|
const integerCases = CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options
|
|
185
298
|
.filter((option) => option.valueKind === 'integer')
|
|
186
299
|
.map((option) => {
|
|
@@ -204,10 +317,23 @@ _whoisleuth_direct_lookup_target() {
|
|
|
204
317
|
[[ " ${CLI_COMMANDS.join(' ')} " == *" \${candidate} "* ]] && return 1
|
|
205
318
|
"\${words[1]}" "\${candidate}" --plan --json >/dev/null 2>&1
|
|
206
319
|
}
|
|
320
|
+
_whoisleuth_exclusions() {
|
|
321
|
+
case "\${1}:\${2}" in
|
|
322
|
+
${groupedCompletionExclusions().map((group) => ` ${group.keys.join('|')}) print -r -- '${group.excluded}' ;;`).join('\n')}
|
|
323
|
+
esac
|
|
324
|
+
}
|
|
325
|
+
_whoisleuth_values() {
|
|
326
|
+
local candidate
|
|
327
|
+
local -a values
|
|
328
|
+
for candidate in "$@"; do
|
|
329
|
+
[[ " \${blocked_options} " == *" \${previous}=\${candidate} "* ]] || values+=("\${candidate}")
|
|
330
|
+
done
|
|
331
|
+
(( \${#values} )) && compadd -- \${values[@]}
|
|
332
|
+
}
|
|
207
333
|
_whoisleuth() {
|
|
208
|
-
local command previous word
|
|
209
|
-
local -a options commands value_options seen_options
|
|
210
|
-
integer file_limit=0 positional_count=0 expect_value=0 foreign_option=0 i
|
|
334
|
+
local command previous word blocked_options="" value_option="" candidate
|
|
335
|
+
local -a options commands value_options seen_options repeat_options available
|
|
336
|
+
integer file_limit=0 positional_count=0 expect_value=0 foreign_option=0 ended=0 i
|
|
211
337
|
commands=(${commandEntries} ${[
|
|
212
338
|
...HELP_META_ACTION.aliases.map((alias) => `'${alias}:Show help'`),
|
|
213
339
|
...VERSION_META_ACTION.aliases.map((alias) => `'${alias}:Show version'`),
|
|
@@ -227,16 +353,22 @@ ${cases}
|
|
|
227
353
|
esac
|
|
228
354
|
for ((i=3; i<CURRENT; i++)); do
|
|
229
355
|
word="\${words[i]}"
|
|
230
|
-
if (( expect_value )); then
|
|
356
|
+
if (( expect_value )); then
|
|
357
|
+
blocked_options="\${blocked_options} $(_whoisleuth_exclusions "\${command}" "\${value_option}=\${word}")"
|
|
358
|
+
expect_value=0; continue
|
|
359
|
+
fi
|
|
360
|
+
if (( ended )); then ((positional_count+=1)); continue; fi
|
|
361
|
+
if [[ "\${word}" == -- ]]; then ended=1; continue; fi
|
|
231
362
|
if [[ "\${word}" == -* ]]; then
|
|
232
363
|
if [[ " \${options[*]} " != *" \${word} "* ]]; then foreign_option=1; continue; fi
|
|
233
364
|
seen_options+=("\${word}")
|
|
234
|
-
|
|
365
|
+
blocked_options="\${blocked_options} $(_whoisleuth_exclusions "\${command}" "\${word}")"
|
|
366
|
+
if [[ " \${value_options[*]} " == *" \${word} "* ]]; then expect_value=1; value_option="\${word}"; fi
|
|
235
367
|
else
|
|
236
368
|
((positional_count+=1))
|
|
237
369
|
fi
|
|
238
370
|
done
|
|
239
|
-
if [[ " \${options[*]} " == *" \${previous} "* ]]; then
|
|
371
|
+
if (( ! ended && expect_value )) && [[ " \${options[*]} " == *" \${previous} "* ]]; then
|
|
240
372
|
case "\${command}:\${previous}" in
|
|
241
373
|
${commandValueCases}
|
|
242
374
|
esac
|
|
@@ -252,11 +384,18 @@ ${integerCases}
|
|
|
252
384
|
case "\${command}:\${positional_count}" in
|
|
253
385
|
${positionalValueCases}
|
|
254
386
|
esac
|
|
255
|
-
if (( file_limit > positional_count && foreign_option == 0 && expect_value == 0 )) && [[ "\${words[CURRENT]}" != -* ]]; then
|
|
387
|
+
if (( file_limit > positional_count && foreign_option == 0 && expect_value == 0 )) && { (( ended )) || [[ "\${words[CURRENT]}" != -* ]]; }; then
|
|
256
388
|
_files
|
|
257
389
|
return
|
|
258
390
|
fi
|
|
259
|
-
|
|
391
|
+
(( ended || foreign_option || expect_value )) && return
|
|
392
|
+
for candidate in \${options[@]}; do
|
|
393
|
+
[[ " \${blocked_options} " == *" \${candidate} "* ]] && continue
|
|
394
|
+
if [[ " \${seen_options[*]} " == *" \${candidate} "* && " \${repeat_options[*]} " != *" \${candidate} "* ]]; then continue; fi
|
|
395
|
+
if (( CURRENT > 3 )) && [[ " ${COMMAND_META_ALIASES.join(' ')} " == *" \${candidate} "* ]]; then continue; fi
|
|
396
|
+
available+=("\${candidate}")
|
|
397
|
+
done
|
|
398
|
+
(( \${#available} )) && compadd -- \${available[@]}
|
|
260
399
|
}
|
|
261
400
|
if [[ "\${funcstack[1]}" == "_whoisleuth" ]]; then
|
|
262
401
|
_whoisleuth "\$@"
|
|
@@ -266,10 +405,16 @@ fi
|
|
|
266
405
|
`;
|
|
267
406
|
}
|
|
268
407
|
function fishCompletion() {
|
|
269
|
-
const commandLines = CLI_COMMANDS.map((command) => (`
|
|
408
|
+
const commandLines = CLI_COMMANDS.map((command) => (`__whoisleuth_add_command '${command}' '${COMMAND_DESCRIPTIONS[command]}'`));
|
|
270
409
|
const optionGroups = new Map();
|
|
271
410
|
for (const definition of CLI_COMMAND_REGISTRY) {
|
|
272
|
-
|
|
411
|
+
const options = [
|
|
412
|
+
...definition.grammar.options,
|
|
413
|
+
...definition.grammar.metaActions.map((id) => ({
|
|
414
|
+
option: longMetaAlias(metaActionDefinition(id)), arity: 0, valueKind: 'flag',
|
|
415
|
+
})),
|
|
416
|
+
];
|
|
417
|
+
for (const option of options) {
|
|
273
418
|
const key = `${option.option}\u0000${option.arity}\u0000${option.valueKind === 'file'}`;
|
|
274
419
|
const group = optionGroups.get(key) ?? {
|
|
275
420
|
option: option.option,
|
|
@@ -284,14 +429,14 @@ function fishCompletion() {
|
|
|
284
429
|
const optionLines = [...optionGroups.values()].map((group) => {
|
|
285
430
|
const condition = `__whoisleuth_command_is ${group.commands.join(' ')}`
|
|
286
431
|
+ (group.commands.includes('lookup') ? '; or __whoisleuth_direct_lookup_target' : '');
|
|
287
|
-
return `complete -c whoisleuth -n '${condition}'${group.option === HELP_LONG_ALIAS ? ` -s ${HELP_SHORT_ALIAS.slice(1)}` : ''} -l ${group.option.slice(2)}${group.arity === 1 ? ' -r' : '
|
|
432
|
+
return `complete -c whoisleuth -n 'begin; ${condition}; end; and __whoisleuth_option_available ${group.option}'${group.option === HELP_LONG_ALIAS ? ` -s ${HELP_SHORT_ALIAS.slice(1)}` : ''} -l ${group.option.slice(2)}${group.arity === 1 ? group.file ? ' -r -F' : ' -r -f' : ''}`;
|
|
288
433
|
});
|
|
289
434
|
const commandValueLines = COMMAND_VALUE_OPTION_GROUPS.flatMap((group) => {
|
|
290
435
|
const lines = [
|
|
291
|
-
`complete -c whoisleuth -n '__whoisleuth_command_is ${group.commands.join(' ')}; and __fish_prev_arg_in ${group.option}' -a '${group.values.join(' ')}'`,
|
|
436
|
+
`complete -c whoisleuth -n '__whoisleuth_command_is ${group.commands.join(' ')}; and __fish_prev_arg_in ${group.option}' -l ${group.option.slice(2)} -r -f -a '(__whoisleuth_values ${group.option} ${group.values.join(' ')})'`,
|
|
292
437
|
];
|
|
293
438
|
if (group.commands.includes('lookup')) {
|
|
294
|
-
lines.push(`complete -c whoisleuth -n '__whoisleuth_direct_lookup_target; and __fish_prev_arg_in ${group.option}' -a '${group.values.join(' ')}'`);
|
|
439
|
+
lines.push(`complete -c whoisleuth -n '__whoisleuth_direct_lookup_target; and __fish_prev_arg_in ${group.option}' -l ${group.option.slice(2)} -r -f -a '(__whoisleuth_values ${group.option} ${group.values.join(' ')})'`);
|
|
295
440
|
}
|
|
296
441
|
return lines;
|
|
297
442
|
});
|
|
@@ -301,10 +446,10 @@ function fishCompletion() {
|
|
|
301
446
|
? `__fish_prev_arg_in ${option}`
|
|
302
447
|
: `__whoisleuth_command_is ${commands.join(' ')}; and __fish_prev_arg_in ${option}`;
|
|
303
448
|
const lines = commands.length > 0
|
|
304
|
-
? [`complete -c whoisleuth -n '${namedCondition}' -a '${values.join(' ')}'`]
|
|
449
|
+
? [`complete -c whoisleuth -n '${namedCondition}' -l ${option.slice(2)} -r -f -a '(__whoisleuth_values ${option} ${values.join(' ')})'`]
|
|
305
450
|
: [];
|
|
306
451
|
if (commands.includes('lookup')) {
|
|
307
|
-
lines.push(`complete -c whoisleuth -n '__whoisleuth_direct_lookup_target; and __fish_prev_arg_in ${option}' -a '${values.join(' ')}'`);
|
|
452
|
+
lines.push(`complete -c whoisleuth -n '__whoisleuth_direct_lookup_target; and __fish_prev_arg_in ${option}' -l ${option.slice(2)} -r -f -a '(__whoisleuth_values ${option} ${values.join(' ')})'`);
|
|
308
453
|
}
|
|
309
454
|
return lines;
|
|
310
455
|
});
|
|
@@ -313,6 +458,7 @@ function fishCompletion() {
|
|
|
313
458
|
.flatMap((option) => option.integerRanges.map((range) => {
|
|
314
459
|
const qualifiers = [
|
|
315
460
|
`__fish_prev_arg_in ${option.option}`,
|
|
461
|
+
`and __whoisleuth_value_expected ${option.option}`,
|
|
316
462
|
`and __whoisleuth_command_is ${definition.command}`,
|
|
317
463
|
...(range.whenOptionPresent
|
|
318
464
|
? [`and __whoisleuth_seen ${definition.command} ${range.whenOptionPresent}`]
|
|
@@ -320,24 +466,14 @@ function fishCompletion() {
|
|
|
320
466
|
? [`and not __whoisleuth_seen ${definition.command} ${option.integerRanges.find((candidate) => candidate.whenOptionPresent)?.whenOptionPresent}`]
|
|
321
467
|
: []),
|
|
322
468
|
].join('; ');
|
|
323
|
-
return `complete -c whoisleuth -n '${qualifiers}' -a '(__whoisleuth_integer_values ${range.minimum} ${range.maximum})'`;
|
|
469
|
+
return `complete -c whoisleuth -n '${qualifiers}' -l ${option.option.slice(2)} -r -f -a '(__whoisleuth_integer_values ${range.minimum} ${range.maximum})'`;
|
|
324
470
|
})));
|
|
325
|
-
const positionalCases =
|
|
326
|
-
...FILE_POSITIONAL_COMMANDS,
|
|
327
|
-
...CLI_COMMANDS.filter((command) => positionalEnumValues(command).length > 0),
|
|
328
|
-
...CLI_COMMAND_REGISTRY.filter((definition) => definition.grammar.options
|
|
329
|
-
.some((option) => option.integerRanges.some((range) => range.whenOptionPresent !== null)))
|
|
330
|
-
.map((definition) => definition.command),
|
|
331
|
-
])].map((command) => {
|
|
471
|
+
const positionalCases = CLI_COMMANDS.map((command) => {
|
|
332
472
|
const definition = commandDefinition(command);
|
|
333
473
|
const commandValueOptions = definition.grammar.options
|
|
334
|
-
.filter((option) => option.
|
|
474
|
+
.filter((option) => option.arity === 1)
|
|
335
475
|
.map((option) => option.option);
|
|
336
|
-
|
|
337
|
-
|| positionalEnumValues(command).length > 0;
|
|
338
|
-
return ` case ${command}\n${positioning
|
|
339
|
-
? ` set options $options ${definition.completion.options.join(' ')}\n`
|
|
340
|
-
: ' set strict 0\n'} set value_options $value_options ${commandValueOptions.join(' ')}\n set limit ${maximumFilePositionals(command)}`;
|
|
476
|
+
return ` case ${command}\n set options ${commandOptions(command)}\n set value_options ${commandValueOptions.join(' ')}\n set repeat_options ${repeatableOptions(command).join(' ')}\n set limit ${maximumFilePositionals(command)}`;
|
|
341
477
|
}).join('\n');
|
|
342
478
|
const positionalEnumLines = CLI_COMMANDS.flatMap((command) => {
|
|
343
479
|
const values = positionalEnumValues(command);
|
|
@@ -373,6 +509,9 @@ function __whoisleuth_command_is
|
|
|
373
509
|
test (count \$words) -ge 2; or return 1
|
|
374
510
|
contains -- \$words[2] $argv
|
|
375
511
|
end
|
|
512
|
+
function __whoisleuth_add_command
|
|
513
|
+
complete -c whoisleuth -n '__fish_use_subcommand' -a \$argv[1] -d \$argv[2]
|
|
514
|
+
end
|
|
376
515
|
function __whoisleuth_integer_values
|
|
377
516
|
set -l value $argv[1]
|
|
378
517
|
while test $value -le $argv[2]
|
|
@@ -380,60 +519,118 @@ function __whoisleuth_integer_values
|
|
|
380
519
|
set value (math $value + 1)
|
|
381
520
|
end
|
|
382
521
|
end
|
|
522
|
+
function __whoisleuth_exclusions
|
|
523
|
+
switch "$argv[1]:$argv[2]"
|
|
524
|
+
${groupedCompletionExclusions().map((group) => ` case ${group.keys.map((key) => `'${key}'`).join(' ')}\n printf '%s\\n' ${group.excluded}`).join('\n')}
|
|
525
|
+
end
|
|
526
|
+
end
|
|
383
527
|
function __whoisleuth_position_state
|
|
384
528
|
set -l expected_command $argv[1]
|
|
385
529
|
set -l words (commandline -opc)
|
|
386
530
|
test (count $words) -ge 2; or return 1
|
|
387
531
|
set -l command $words[2]
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
532
|
+
if not contains -- $command ${CLI_COMMANDS.join(' ')}
|
|
533
|
+
__whoisleuth_direct_lookup_target; or return 1
|
|
534
|
+
set command lookup
|
|
535
|
+
end
|
|
536
|
+
if test -n "$expected_command"
|
|
537
|
+
test "$command" = "$expected_command"; or return 1
|
|
538
|
+
end
|
|
539
|
+
set -l options
|
|
540
|
+
set -l value_options
|
|
541
|
+
set -l repeat_options
|
|
391
542
|
set -l limit 0
|
|
392
|
-
set -l strict 1
|
|
393
543
|
switch $command
|
|
394
544
|
${positionalCases}
|
|
395
545
|
case '*'
|
|
396
546
|
return 1
|
|
397
547
|
end
|
|
398
548
|
set -l expect_value 0
|
|
549
|
+
set -l value_option @none
|
|
550
|
+
set -l ended 0
|
|
399
551
|
set -l positional_count 0
|
|
400
552
|
set -l seen
|
|
553
|
+
set -l blocked
|
|
401
554
|
for word in $words[3..-1]
|
|
402
555
|
if test $expect_value -eq 1
|
|
556
|
+
set -a blocked (__whoisleuth_exclusions $command "$value_option=$word")
|
|
403
557
|
set expect_value 0
|
|
558
|
+
set value_option @none
|
|
559
|
+
continue
|
|
560
|
+
end
|
|
561
|
+
if test $ended -eq 1
|
|
562
|
+
set positional_count (math $positional_count + 1)
|
|
563
|
+
continue
|
|
564
|
+
end
|
|
565
|
+
if test "$word" = --
|
|
566
|
+
set ended 1
|
|
404
567
|
continue
|
|
405
568
|
end
|
|
406
569
|
if string match -q -- '-*' $word
|
|
407
|
-
|
|
408
|
-
contains -- $word $options; or return 1
|
|
409
|
-
end
|
|
570
|
+
contains -- $word $options; or return 1
|
|
410
571
|
set -a seen $word
|
|
411
|
-
|
|
572
|
+
set -a blocked (__whoisleuth_exclusions $command $word)
|
|
573
|
+
if contains -- $word $value_options
|
|
574
|
+
set expect_value 1
|
|
575
|
+
set value_option $word
|
|
576
|
+
end
|
|
412
577
|
else
|
|
413
578
|
set positional_count (math $positional_count + 1)
|
|
414
579
|
end
|
|
415
580
|
end
|
|
416
|
-
|
|
417
|
-
|
|
581
|
+
# Rows: position, file limit, pending option, separator, argument count,
|
|
582
|
+
# supplied options, excluded options/values, and repeatable options.
|
|
583
|
+
printf '%s\\n' $positional_count $limit $value_option $ended (math (count $words) - 2) "seen $seen" "blocked $blocked" "repeat $repeat_options"
|
|
418
584
|
end
|
|
419
585
|
function __whoisleuth_position_is
|
|
420
586
|
set -l state (__whoisleuth_position_state $argv[2]); or return 1
|
|
587
|
+
test "$state[3]" = @none; or return 1
|
|
421
588
|
test $state[1] -eq $argv[1]
|
|
422
589
|
end
|
|
423
590
|
function __whoisleuth_file_position
|
|
424
591
|
set -l words (commandline -opc)
|
|
425
592
|
test (count $words) -ge 2; or return 1
|
|
426
|
-
set -l state (__whoisleuth_position_state
|
|
593
|
+
set -l state (__whoisleuth_position_state); or return 1
|
|
427
594
|
test $state[2] -gt $state[1]; or return 1
|
|
595
|
+
test "$state[3]" = @none; or return 1
|
|
596
|
+
test $state[4] -eq 1; and return 0
|
|
428
597
|
not string match -qr -- ^- (commandline -ct)
|
|
429
598
|
end
|
|
430
599
|
function __whoisleuth_seen
|
|
431
600
|
set -l s (__whoisleuth_position_state $argv[1]); or return 1
|
|
432
|
-
contains -- $argv[2] $s[
|
|
601
|
+
contains -- $argv[2] (string split ' ' -- $s[6])
|
|
602
|
+
end
|
|
603
|
+
function __whoisleuth_option_available
|
|
604
|
+
set -l state (__whoisleuth_position_state); or return 1
|
|
605
|
+
test $state[4] -eq 0; or return 1
|
|
606
|
+
if test "$state[3]" != @none
|
|
607
|
+
test "$state[3]" = "$argv[1]"
|
|
608
|
+
return $status
|
|
609
|
+
end
|
|
610
|
+
contains -- $argv[1] (string split ' ' -- $state[7]); and return 1
|
|
611
|
+
if contains -- $argv[1] (string split ' ' -- $state[6])
|
|
612
|
+
contains -- $argv[1] (string split ' ' -- $state[8]); or return 1
|
|
613
|
+
end
|
|
614
|
+
if test $state[5] -gt 0
|
|
615
|
+
contains -- $argv[1] ${COMMAND_META_ALIASES.join(' ')}; and return 1
|
|
616
|
+
end
|
|
617
|
+
return 0
|
|
618
|
+
end
|
|
619
|
+
function __whoisleuth_value_expected
|
|
620
|
+
set -l state (__whoisleuth_position_state); or return 1
|
|
621
|
+
test $state[4] -eq 0; and test "$state[3]" = "$argv[1]"
|
|
622
|
+
end
|
|
623
|
+
function __whoisleuth_values
|
|
624
|
+
set -l state (__whoisleuth_position_state); or return 1
|
|
625
|
+
test $state[4] -eq 0; and test "$state[3]" = "$argv[1]"; or return 1
|
|
626
|
+
for value in $argv[2..-1]
|
|
627
|
+
contains -- "$argv[1]=$value" (string split ' ' -- $state[7]); or printf '%s\\n' $value
|
|
628
|
+
end
|
|
433
629
|
end
|
|
434
630
|
complete -c whoisleuth -f
|
|
435
631
|
${[HELP_META_ACTION, VERSION_META_ACTION].map((action) => (`complete -c whoisleuth -n '__fish_use_subcommand' -s ${shortMetaAlias(action).slice(1)} -l ${longMetaAlias(action).slice(2)}`)).join('\n')}
|
|
436
632
|
${commandLines.join('\n')}
|
|
633
|
+
functions -e __whoisleuth_add_command
|
|
437
634
|
${positionalEnumLines.join('\n')}
|
|
438
635
|
complete -c whoisleuth -n '__whoisleuth_file_position' -F
|
|
439
636
|
${optionLines.join('\n')}
|
|
@@ -475,9 +672,27 @@ function powershellCompletion() {
|
|
|
475
672
|
Register-ArgumentCompleter -Native -CommandName whoisleuth -ScriptBlock {
|
|
476
673
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
477
674
|
$noMatch = { [System.Management.Automation.CompletionResult]::new(' ', 'no matches', 'ParameterValue', 'No completion available') }
|
|
675
|
+
$completeFiles = {
|
|
676
|
+
param($prefix)
|
|
677
|
+
$files = @([System.Management.Automation.CompletionCompleters]::CompleteFilename($prefix))
|
|
678
|
+
foreach ($file in $files) {
|
|
679
|
+
# Native arguments need literal paths, not provider wildcard escapes.
|
|
680
|
+
$path = $file.ToolTip
|
|
681
|
+
if ($file.ResultType -eq 'ProviderContainer') { $path += [IO.Path]::DirectorySeparatorChar }
|
|
682
|
+
$literal = "'" + [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($path) + "'"
|
|
683
|
+
[System.Management.Automation.CompletionResult]::new($literal, $file.ListItemText, $file.ResultType, $file.ToolTip)
|
|
684
|
+
}
|
|
685
|
+
if ($files.Count -eq 0) { & $noMatch }
|
|
686
|
+
}
|
|
478
687
|
$commands = @(${CLI_COMMANDS.map((command) => `'${command}'`).join(', ')})
|
|
479
688
|
$options = @{
|
|
480
689
|
${Object.entries(commandOptions).map(([command, options]) => ` '${command}' = @(${options.map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
690
|
+
}
|
|
691
|
+
$repeatOptions = @{
|
|
692
|
+
${CLI_COMMANDS.filter((command) => repeatableOptions(command).length > 0).map((command) => ` '${command}' = @(${repeatableOptions(command).map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
693
|
+
}
|
|
694
|
+
$exclusions = @{
|
|
695
|
+
${[...COMPLETION_EXCLUSIONS].map(([key, excluded]) => ` '${key}' = @(${excluded.map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
481
696
|
}
|
|
482
697
|
$values = @{
|
|
483
698
|
${[
|
|
@@ -524,36 +739,43 @@ ${Object.entries(conditionalIntegerRanges).map(([key, range]) => ` '${key}' =
|
|
|
524
739
|
if ($directLookup) { $command = 'lookup' }
|
|
525
740
|
$commandKnown = $options.ContainsKey($command)
|
|
526
741
|
$activeOptions = if ($commandKnown) { $options[$command] } else { @(${COMMAND_META_ALIASES.map((alias) => `'${alias}'`).join(', ')}) }
|
|
527
|
-
$previousOwned = $activeOptions -contains $previous
|
|
528
|
-
if ($previousOwned -and $fileOptions.ContainsKey($command) -and $fileOptions[$command] -contains $previous) {
|
|
529
|
-
$files = @(Get-ChildItem -Path "${'$'}wordToComplete*" -File -ErrorAction SilentlyContinue)
|
|
530
|
-
$files | ForEach-Object {
|
|
531
|
-
[System.Management.Automation.CompletionResult]::new($_.FullName, $_.Name, 'ProviderItem', $_.FullName)
|
|
532
|
-
}
|
|
533
|
-
if ($files.Count -eq 0) { & $noMatch }
|
|
534
|
-
return
|
|
535
|
-
}
|
|
536
|
-
if ($previousOwned -and $textOptions.ContainsKey($command) -and $textOptions[$command] -contains $previous) {
|
|
537
|
-
$completionText = if ([string]::IsNullOrEmpty($wordToComplete)) { ' ' } else { [string]$wordToComplete }
|
|
538
|
-
$listItemText = if ([string]::IsNullOrEmpty($wordToComplete)) { 'value' } else { [string]$wordToComplete }
|
|
539
|
-
[System.Management.Automation.CompletionResult]::new($completionText, $listItemText, 'ParameterValue', 'Enter a value')
|
|
540
|
-
return
|
|
541
|
-
}
|
|
542
742
|
$positionCount = 0
|
|
543
743
|
$expectValue = $false
|
|
544
744
|
$foreignOption = $false
|
|
745
|
+
$ended = $false
|
|
746
|
+
$valueOption = ''
|
|
545
747
|
$seenOptions = @()
|
|
748
|
+
$blockedOptions = @()
|
|
546
749
|
for ($index = 2; $index -le $previousIndex; $index += 1) {
|
|
547
750
|
$word = $elements[$index]
|
|
548
|
-
if ($expectValue) {
|
|
751
|
+
if ($expectValue) {
|
|
752
|
+
$key = "${'$'}{command}:${'$'}valueOption=$word"
|
|
753
|
+
if ($exclusions.ContainsKey($key)) { $blockedOptions += $exclusions[$key] }
|
|
754
|
+
$expectValue = $false; continue
|
|
755
|
+
}
|
|
756
|
+
if ($ended) { $positionCount += 1; continue }
|
|
757
|
+
if ($word -eq '--') { $ended = $true; continue }
|
|
549
758
|
if ($word.StartsWith('-')) {
|
|
550
759
|
if ($activeOptions -notcontains $word) { $foreignOption = $true; continue }
|
|
551
760
|
$seenOptions += $word
|
|
552
|
-
|
|
761
|
+
$key = "${'$'}{command}:$word"
|
|
762
|
+
if ($exclusions.ContainsKey($key)) { $blockedOptions += $exclusions[$key] }
|
|
763
|
+
if ($valueOptions.ContainsKey($command) -and $valueOptions[$command] -contains $word) { $expectValue = $true; $valueOption = $word }
|
|
553
764
|
} else {
|
|
554
765
|
$positionCount += 1
|
|
555
766
|
}
|
|
556
767
|
}
|
|
768
|
+
$previousOwned = -not $ended -and $expectValue -and $activeOptions -contains $previous
|
|
769
|
+
if ($previousOwned -and $fileOptions.ContainsKey($command) -and $fileOptions[$command] -contains $previous) {
|
|
770
|
+
& $completeFiles $wordToComplete
|
|
771
|
+
return
|
|
772
|
+
}
|
|
773
|
+
if ($previousOwned -and $textOptions.ContainsKey($command) -and $textOptions[$command] -contains $previous) {
|
|
774
|
+
$completionText = if ([string]::IsNullOrEmpty($wordToComplete)) { ' ' } else { [string]$wordToComplete }
|
|
775
|
+
$listItemText = if ([string]::IsNullOrEmpty($wordToComplete)) { 'value' } else { [string]$wordToComplete }
|
|
776
|
+
[System.Management.Automation.CompletionResult]::new($completionText, $listItemText, 'ParameterValue', 'Enter a value')
|
|
777
|
+
return
|
|
778
|
+
}
|
|
557
779
|
$integerKey = "${'$'}{command}:${'$'}previous"
|
|
558
780
|
if ($previousOwned -and $integerRanges.ContainsKey($integerKey)) {
|
|
559
781
|
$range = $integerRanges[$integerKey]
|
|
@@ -577,6 +799,7 @@ ${Object.entries(conditionalIntegerRanges).map(([key, range]) => ` '${key}' =
|
|
|
577
799
|
if ($previousOwned -and $values.ContainsKey($valueKey)) {
|
|
578
800
|
$matched = $false
|
|
579
801
|
foreach ($candidate in $values[$valueKey]) {
|
|
802
|
+
if ($blockedOptions -contains "$previous=$candidate") { continue }
|
|
580
803
|
if ($candidate.StartsWith($wordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
581
804
|
[System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
|
|
582
805
|
$matched = $true
|
|
@@ -597,14 +820,11 @@ ${Object.entries(conditionalIntegerRanges).map(([key, range]) => ` '${key}' =
|
|
|
597
820
|
if (-not $matched) { & $noMatch }
|
|
598
821
|
return
|
|
599
822
|
}
|
|
600
|
-
if ($fileLimits.ContainsKey($command) -and $fileLimits[$command] -gt $positionCount -and -not $foreignOption -and -not $expectValue -and -not $wordToComplete.StartsWith('-')) {
|
|
601
|
-
$
|
|
602
|
-
$files | ForEach-Object {
|
|
603
|
-
[System.Management.Automation.CompletionResult]::new($_.FullName, $_.Name, 'ProviderItem', $_.FullName)
|
|
604
|
-
}
|
|
605
|
-
if ($files.Count -eq 0) { & $noMatch }
|
|
823
|
+
if ($fileLimits.ContainsKey($command) -and $fileLimits[$command] -gt $positionCount -and -not $foreignOption -and -not $expectValue -and ($ended -or -not $wordToComplete.StartsWith('-'))) {
|
|
824
|
+
& $completeFiles $wordToComplete
|
|
606
825
|
return
|
|
607
826
|
}
|
|
827
|
+
if ($ended -or $foreignOption -or $expectValue) { & $noMatch; return }
|
|
608
828
|
$candidates = if ($rootCompletion -and -not $directLookup) {
|
|
609
829
|
@($commands) + @(${ROOT_META_ALIASES.map((alias) => `'${alias}'`).join(', ')})
|
|
610
830
|
} elseif ($commandKnown) {
|
|
@@ -614,6 +834,9 @@ ${Object.entries(conditionalIntegerRanges).map(([key, range]) => ` '${key}' =
|
|
|
614
834
|
}
|
|
615
835
|
$matched = $false
|
|
616
836
|
foreach ($candidate in $candidates) {
|
|
837
|
+
if ($blockedOptions -contains $candidate) { continue }
|
|
838
|
+
if ($seenOptions -contains $candidate -and (-not $repeatOptions.ContainsKey($command) -or $repeatOptions[$command] -notcontains $candidate)) { continue }
|
|
839
|
+
if ($previousIndex -ge 2 -and @(${COMMAND_META_ALIASES.map((alias) => `'${alias}'`).join(', ')}) -contains $candidate) { continue }
|
|
617
840
|
if ($candidate.StartsWith($wordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
618
841
|
[System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
|
|
619
842
|
$matched = $true
|