-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BREAKING CHANGE: completely overhaul arg parsing & allow invoking CLI…
… w/o initialBranch if cached ...as long as the initialBranch was already provided in some previous invocation of stacked-rebase, thus cached. sometime later, we could have a config for the default initial branch.. - create generic utils for argv parsing (separate package soon?) - introduce multiple tests, which caught lots of (mostly new) bugs! - tidy up code at start of gitStackedRebase, which now makes great sense & looks nice - same in other places - clear up mental model of "parsing options" - it's "resolve" instead of "parse" - move initialBranch parsing into resolveOptions - don't want out of sync `resolvedOptions.initialBranch` vs `nameOfInitialBranch` - get rid of "intial branch is optional for some args" -- non issue - because e.g. for --continue to have any meaning, you'd have to have already started a stacked rebase & thus would have the initial branch cached.
- Loading branch information
Showing
19 changed files
with
714 additions
and
260 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#!/usr/bin/env ts-node-dev | ||
|
||
import assert from "assert"; | ||
|
||
import { NonPositional, NonPositionalWithValue, eatNonPositionals, eatNonPositionalsWithValues } from "./argparse"; | ||
|
||
export function argparse_TC() { | ||
eatNonPositionals_singleArg(); | ||
eatNonPositionals_multipleArgs(); | ||
|
||
eatNonPositionalsWithValues_singleArg(); | ||
eatNonPositionalsWithValues_multipleArgs(); | ||
} | ||
|
||
function eatNonPositionals_singleArg() { | ||
const argv = ["origin/master", "--autosquash", "foo", "bar"]; | ||
const targetArgs = ["--autosquash", "--no-autosquash"]; | ||
const expected: NonPositional[] = [{ argName: "--autosquash", origIdx: 1 }]; | ||
const expectedLeftoverArgv = ["origin/master", "foo", "bar"]; | ||
|
||
const parsed = eatNonPositionals(targetArgs, argv); | ||
|
||
assert.deepStrictEqual(parsed, expected); | ||
assert.deepStrictEqual(argv, expectedLeftoverArgv); | ||
} | ||
function eatNonPositionals_multipleArgs() { | ||
const argv = ["origin/master", "--autosquash", "foo", "bar", "--no-autosquash", "baz"]; | ||
const targetArgs = ["--autosquash", "--no-autosquash"]; | ||
const expected: NonPositional[] = [ | ||
{ argName: "--autosquash", origIdx: 1 }, | ||
{ argName: "--no-autosquash", origIdx: 4 }, | ||
]; | ||
const expectedLeftoverArgv = ["origin/master", "foo", "bar", "baz"]; | ||
|
||
const parsed = eatNonPositionals(targetArgs, argv); | ||
|
||
assert.deepStrictEqual(parsed, expected); | ||
assert.deepStrictEqual(argv, expectedLeftoverArgv); | ||
} | ||
|
||
function eatNonPositionalsWithValues_singleArg() { | ||
const argv = ["origin/master", "--git-dir", "~/.dotfiles", "foo", "bar"]; | ||
const targetArgs = ["--git-dir", "--gd"]; | ||
const expected: NonPositionalWithValue[] = [{ argName: "--git-dir", origIdx: 1, argVal: "~/.dotfiles" }]; | ||
const expectedLeftoverArgv = ["origin/master", "foo", "bar"]; | ||
|
||
const parsed = eatNonPositionalsWithValues(targetArgs, argv); | ||
|
||
assert.deepStrictEqual(parsed, expected); | ||
assert.deepStrictEqual(argv, expectedLeftoverArgv); | ||
} | ||
function eatNonPositionalsWithValues_multipleArgs() { | ||
const argv = ["origin/master", "--git-dir", "~/.dotfiles", "foo", "bar", "--misc", "miscVal", "unrelatedVal"]; | ||
const targetArgs = ["--git-dir", "--gd", "--misc"]; | ||
const expected: NonPositionalWithValue[] = [ | ||
{ argName: "--git-dir", origIdx: 1, argVal: "~/.dotfiles" }, | ||
{ argName: "--misc", origIdx: 5, argVal: "miscVal" }, | ||
]; | ||
const expectedLeftoverArgv = ["origin/master", "foo", "bar", "unrelatedVal"]; | ||
|
||
const parsed = eatNonPositionalsWithValues(targetArgs, argv); | ||
|
||
assert.deepStrictEqual(parsed, expected); | ||
assert.deepStrictEqual(argv, expectedLeftoverArgv); | ||
} | ||
|
||
if (!module.parent) { | ||
argparse_TC(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import assert from "assert"; | ||
|
||
export type Maybe<T> = T | undefined; | ||
|
||
export type Argv = string[]; | ||
export type MaybeArg = Maybe<string>; | ||
|
||
/** | ||
* parses the argv. | ||
* mutates the `argv` array. | ||
*/ | ||
export function createArgParse(argv: Argv) { | ||
const getArgv = (): Argv => argv; | ||
const peakNextArg = (): MaybeArg => argv[0]; | ||
const eatNextArg = (): MaybeArg => argv.shift(); | ||
const hasMoreArgs = (): boolean => argv.length > 0; | ||
|
||
return { | ||
getArgv, | ||
peakNextArg, | ||
eatNextArg, | ||
hasMoreArgs, | ||
eatNonPositionals: (argNames: string[]) => eatNonPositionals(argNames, argv), | ||
eatNonPositionalsWithValues: (argNames: string[]) => eatNonPositionalsWithValues(argNames, argv), | ||
}; | ||
} | ||
|
||
export type NonPositional = { | ||
origIdx: number; | ||
argName: string; | ||
}; | ||
|
||
export type NonPositionalWithValue = NonPositional & { | ||
argVal: string; | ||
}; | ||
|
||
export function eatNonPositionals( | ||
argNames: string[], | ||
argv: Argv, | ||
{ | ||
howManyItemsToTakeWhenArgMatches = 1, // | ||
} = {} | ||
): NonPositional[] { | ||
const argMatches = (idx: number) => argNames.includes(argv[idx]); | ||
let matchedArgIndexes: NonPositional["origIdx"][] = []; | ||
|
||
for (let i = 0; i < argv.length; i++) { | ||
if (argMatches(i)) { | ||
for (let j = 0; j < howManyItemsToTakeWhenArgMatches; j++) { | ||
matchedArgIndexes.push(i + j); | ||
} | ||
} | ||
} | ||
|
||
if (!matchedArgIndexes.length) { | ||
return []; | ||
} | ||
|
||
const nonPositionalsWithValues: NonPositional[] = []; | ||
for (const idx of matchedArgIndexes) { | ||
nonPositionalsWithValues.push({ | ||
origIdx: idx, | ||
argName: argv[idx], | ||
}); | ||
} | ||
|
||
const shouldRemoveArg = (idx: number) => matchedArgIndexes.includes(idx); | ||
const argvIndexesToRemove: number[] = []; | ||
|
||
for (let i = 0; i < argv.length; i++) { | ||
if (shouldRemoveArg(i)) { | ||
argvIndexesToRemove.push(i); | ||
} | ||
} | ||
|
||
removeArrayValuesAtIndices(argv, argvIndexesToRemove); | ||
|
||
return nonPositionalsWithValues; | ||
} | ||
|
||
export function eatNonPositionalsWithValues(argNames: string[], argv: Argv): NonPositionalWithValue[] { | ||
const argsWithTheirValueAsNextItem: NonPositional[] = eatNonPositionals(argNames, argv, { | ||
howManyItemsToTakeWhenArgMatches: 2, | ||
}); | ||
|
||
assert.deepStrictEqual(argsWithTheirValueAsNextItem.length % 2, 0, `expected all arguments to have a value.`); | ||
|
||
const properArgsWithValues: NonPositionalWithValue[] = []; | ||
for (let i = 0; i < argsWithTheirValueAsNextItem.length; i += 2) { | ||
const arg = argsWithTheirValueAsNextItem[i]; | ||
const val = argsWithTheirValueAsNextItem[i + 1]; | ||
|
||
properArgsWithValues.push({ | ||
origIdx: arg.origIdx, | ||
argName: arg.argName, | ||
argVal: val.argName, | ||
}); | ||
} | ||
|
||
return properArgsWithValues; | ||
} | ||
|
||
/** | ||
* internal utils | ||
*/ | ||
|
||
export function removeArrayValuesAtIndices<T>(arrayRef: T[], indexesToRemove: number[]): void { | ||
/** | ||
* go in reverse. | ||
* | ||
* because if went from 0 to length, | ||
* removing an item from the array would adjust all other indices, | ||
* which creates a mess & needs extra handling. | ||
*/ | ||
const indexesBigToSmall = [...indexesToRemove].sort((A, B) => B - A); | ||
|
||
for (const idxToRemove of indexesBigToSmall) { | ||
arrayRef.splice(idxToRemove, 1); | ||
} | ||
|
||
return; | ||
} | ||
|
||
/** | ||
* common utilities for dealing w/ parsed values: | ||
*/ | ||
|
||
export function maybe<T, S, N>( | ||
x: T, // | ||
Some: (x: T) => S, | ||
None: (x?: never) => N | ||
) { | ||
if (x instanceof Array) { | ||
return x.length ? Some(x) : None(); | ||
} | ||
|
||
return x !== undefined ? Some(x) : None(); | ||
} | ||
|
||
export const last = <T>(xs: T[]): T => xs[xs.length - 1]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.