Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/giant-buckets-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vortexjs/common": patch
---

Denamespace SKL
5 changes: 5 additions & 0 deletions .changeset/lovely-teeth-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vortexjs/core": minor
---

Add support for intrinsic components
5 changes: 5 additions & 0 deletions .changeset/mean-beds-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vortexjs/core": minor
---

Add optional context API
317 changes: 127 additions & 190 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"bun-plugin-tailwind": "^0.0.15",
"chalk": "^5.4.1",
"@types/bun": "latest",
"tsup": "^8.5.0",
"tsdown": "^0.14.1",
"typescript": "^5",
"@napi-rs/cli": "^3.0.0-alpha.89",
"@vortexjs/discovery-win32-x64-msvc": "0.0.0",
Expand All @@ -31,7 +31,7 @@
"change": "changeset",
"release": "changeset publish",
"build": "turbo build",
"wdev": "turbo @vortexjs/example-wormhole#dev",
"wdev": "cd packages/example-wormhole && turbo build && bun dev",
"version": "changeset version",
"dev": "turbo dev",
"test": "turbo test",
Expand Down
34 changes: 34 additions & 0 deletions packages/args/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
33 changes: 33 additions & 0 deletions packages/args/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# dependencies (bun install)
node_modules

# output
out
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
3 changes: 3 additions & 0 deletions packages/args/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Vortex Args

A lightweight, beautiful args parser.
29 changes: 29 additions & 0 deletions packages/args/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@vortexjs/args",
"type": "module",
"license": "MIT-0",
"repository": {
"url": "https://github.com/andylovescode/vortex"
},
"devDependencies": {
"@types/bun": "catalog:",
"tsdown": "catalog:"
},
"dependencies": {
"@vortexjs/common": "workspace:*"
},
"peerDependencies": {
"typescript": "catalog:"
},
"scripts": {
"build": "tsdown ./src/index.ts --format esm --dts --out-dir dist"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"version": "0.0.1"
}
42 changes: 42 additions & 0 deletions packages/args/src/flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { unwrap } from "@vortexjs/common";

export interface Flags {
positionals: string[];
options: Record<string, string | boolean>;
}

export function parseFlags(args: string[]): Flags {
let p = 0;

const flags: Flags = {
positionals: [],
options: {},
};

while (p < args.length) {
const arg = unwrap(args[p]);
if (arg.startsWith("--")) {
if (arg.includes("=")) {
const [key, value] = arg.slice(2).split("=");
flags.options[unwrap(key)] = unwrap(value);
} else {
const key = arg.slice(2);
const value = args[p + 1];
if (value && !value.startsWith("-")) {
flags.options[key] = value;
p++;
} else {
flags.options[key] = true;
}
}
} else if (arg.startsWith("-")) {
const key = arg.slice(1);
flags.options[key] = true;
} else {
flags.positionals.push(arg);
}
p++;
}

return flags;
}
210 changes: 210 additions & 0 deletions packages/args/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { parseFlags } from "./flags";

export async function parseArgs({
commands,
args,
showHelp,
}: {
commands: Command<any>[];
args: string[];
showHelp(): void;
}): Promise<void> {
const flags = parseFlags(args);
const debugLogging = false;

if (
"help" in flags.options ||
"h" in flags.options ||
flags.positionals[0] === "help"
) {
showHelp();
return;
}

outer: for (const command of commands) {
let ptr = 0;

const args: Record<string, any> = {};

debugLogging &&
console.log(`Checking command: ${command.path.join(" ")}`);

for (const combinator of command.combinators) {
if (typeof combinator === "string") {
if (ptr >= flags.positionals.length) {
debugLogging &&
console.warn(
`command dq'ed due to lack of positionals`,
);
continue outer;
}
if (flags.positionals[ptr] !== combinator) {
debugLogging &&
console.warn(
`command dq'ed due to positional mismatch: expected "${combinator}", got "${flags.positionals[ptr]}"`,
);
continue outer;
}

ptr++;

continue;
}

if (combinator.type === "positional") {
if (ptr >= flags.positionals.length) {
debugLogging &&
console.warn(
`command dq'ed due to lack of positionals for combinator ${combinator.id}`,
);
continue outer;
}
args[combinator.id] = flags.positionals[ptr++];
} else if (combinator.type === "stringOption") {
if (
!(combinator.id in flags.options) &&
!("optional" in combinator)
) {
debugLogging &&
console.warn(
`command dq'ed due to lack of option for combinator ${combinator.id}`,
);
continue outer;
}
args[combinator.id] = flags.options[combinator.id] as
| string
| undefined;
} else if (combinator.type === "booleanOption") {
if (
!(combinator.id in flags.options) &&
!("optional" in combinator)
) {
debugLogging &&
console.warn(
`command dq'ed due to lack of option for combinator ${combinator.id}`,
);
continue outer;
}
args[combinator.id] = flags.options[combinator.id] === true;
}
}

if (ptr < flags.positionals.length) {
debugLogging &&
console.warn(
`command dq'ed due to excess positionals: ${flags.positionals.slice(ptr).join(", ")}`,
);
continue;
}

await command.impl(args);
return;
}

showHelp();
}

export interface Command<T> {
impl(args: T): Promise<void> | void;
combinators: Combinator<string>[];
path: string[];
}

export interface PositionalCombinator<ID extends string> {
type: "positional";
id: ID;
}

export interface StringOptionCombinator<ID extends string> {
type: "stringOption";
id: ID;
}

export interface BooleanOptionCombinator<ID extends string> {
type: "booleanOption";
id: ID;
}

export type OptionalCombinator<BaseCombinator extends Combinator<string>> =
BaseCombinator & {
optional: true;
};

type InternalCombinator<ID extends string> =
| PositionalCombinator<ID>
| StringOptionCombinator<ID>
| BooleanOptionCombinator<ID>;

export type Combinator<ID extends string> =
| InternalCombinator<ID>
| OptionalCombinator<InternalCombinator<ID>>
| string;

export type InferArgEach<Item> = Item extends OptionalCombinator<infer Base>
? Partial<InferArgEach<Base>>
: Item extends PositionalCombinator<infer ID>
? { [K in ID]: string }
: Item extends StringOptionCombinator<infer ID>
? { [K in ID]: string }
: Item extends BooleanOptionCombinator<infer ID>
? { [K in ID]: boolean }
: never;

export type InferArgs<Combinators extends Combinator<string>[]> = {
[K in keyof Combinators]: InferArgEach<Combinators[K]>;
}[number] extends infer U
? (U extends any ? (x: U) => void : never) extends (x: infer I) => void
? I
: never
: never;

export function command<Combinators extends Combinator<string>[]>(
impl: (args: InferArgs<Combinators>) => Promise<void> | void,
...combinators: Combinators
): Command<InferArgs<Combinators>> {
return {
impl,
combinators,
path: combinators.filter((c) => typeof c === "string"),
};
}

export function positional<ID extends string>(
id: ID,
): PositionalCombinator<ID> {
return {
type: "positional",
id,
};
}

export function stringOption<ID extends string>(
id: ID,
): StringOptionCombinator<ID> {
return {
type: "stringOption",
id,
};
}

export function booleanOption<ID extends string>(
id: ID,
): BooleanOptionCombinator<ID> {
return {
type: "booleanOption",
id,
};
}

export function optional<BaseCombinator extends Combinator<string>>(
combinator: BaseCombinator,
): OptionalCombinator<BaseCombinator> {
if (typeof combinator !== "object") {
throw new Error("Cannot make a string combinator optional");
}

return {
...combinator,
optional: true,
};
}
Loading