🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

argblock

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

argblock - npm Package Compare versions

Comparing version
0.0.4
to
0.0.5
+4
-1
package.json

@@ -5,5 +5,8 @@ {

"type": "module",
"version": "0.0.4",
"version": "0.0.5",
"main": "dist/argblock.js",
"types": "dist/types/argblock.d.ts",
"files": [
"dist/**"
],
"scripts": {

@@ -10,0 +13,0 @@ "build": "bun build ./src/argblock.ts --outdir ./dist && bun run types",

{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "argblock",
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
"@types/node": ["@types/node@22.15.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-3vMNr4TzNQyjHcRZadojpRaD9Ofr6LsonZAoQ+HMUa/9ORTPoxVIw0e0mpqWpdjj8xybyCM+oKOUH2vwFu/oEw=="],
"bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
}
}
import { parse, globalArg } from "./parse.ts";
import { Param } from "./param.ts";
import { Block } from "./block.ts";
export { parse, Param, Block, globalArg };
import type { Param } from "./param.ts";
type Matcher = (
args: string[],
index: number
) => {
jumpNext: number;
match: boolean;
};
const createDefaultMatcher =
(name: string): Matcher =>
(args: string[], index: number) => {
if (name === args[index]) {
return { jumpNext: 0, match: true };
}
return { jumpNext: 0, match: false };
};
export class Block {
arg: string;
params: Param[];
description: string;
matcher: Matcher;
children: Block[] = [];
constructor({
arg,
params,
description,
matcher,
children = [],
}: {
arg: string;
params: Param[];
description: string;
matcher?: Matcher;
children?: Block[];
}) {
this.arg = arg;
this.params = params;
this.description = description;
this.children = children;
if (matcher) {
this.matcher = matcher;
} else {
this.matcher = createDefaultMatcher(this.arg);
}
}
findParam(name: string) {
for (const param of this.params) {
if (param.name === name) {
return param;
}
}
}
findShortParam(name: string) {
for (const param of this.params) {
if (param.short === name) {
return param;
}
}
}
}
import { checkBoolValue } from "../parse-param/parse-param.ts";
import type { Param } from "../parse.ts";
export const convertParam = (
value: string,
param: Param,
originalParam: string
) => {
if (param.type === "boolean") {
if (!checkBoolValue(value)) {
throw new Error("Param must be boolean: " + originalParam);
}
if (value === "1" || value === "true") {
return true;
}
// 0 false
return false;
}
if (param.type === "number") {
const number = +value;
if (isFinite(number) === false) {
throw new Error("Param must be number: " + originalParam);
}
return number;
}
if (param.type === "string") {
return value;
}
};
import type { Block } from "./block.ts";
import type { Param } from "./param.ts";
export const getNameFromEq = (str: string) => {
const [name, ...values] = str.slice(2).split("=");
return {
name: name as string,
value: values.join("="),
};
};
const throwErrorParam = (arg: string): never => {
throw new Error("Unknown param " + arg);
};
export const getParam = (name: string, block: Block): Param => {
const param = block.findParam(name);
if (!param) {
throwErrorParam(name);
}
return param!;
};
export const getShortParam = (name: string, block: Block): Param => {
const param = block.findShortParam(name);
if (!param) {
throwErrorParam(name);
}
return param!;
};
type TypeMap = {
string: string;
boolean: boolean;
number: number;
};
export class Param<TType extends keyof TypeMap = any> {
name: string;
type: TType;
short?: string;
defaultValue?: TypeMap[TType];
constructor({
type,
short,
name,
defaultValue,
}: {
name: string;
type: TType;
short?: string;
defaultValue?: TypeMap[TType];
}) {
this.type = type;
this.short = short;
this.name = name;
this.defaultValue = defaultValue;
}
}
import { test, expect, describe } from "bun:test";
import { checkFull, parseFull } from "./full.ts"; // укажи путь
import { Block } from "../block.ts";
import { Param } from "../param.ts";
// Утилита для генерации блока
const makeBlock = (params: Param[]) =>
new Block({
arg: "test",
description: "Test block",
params,
});
describe("checkFull", () => {
test("returns true for full param like --name", () => {
expect(checkFull("--name")).toBe(true);
});
test("returns false for short param like -n", () => {
expect(checkFull("-n")).toBe(false);
});
test("returns false for plain string", () => {
expect(checkFull("hello")).toBe(false);
});
});
describe("parseFull", () => {
test("parses --param=value correctly", () => {
const block = makeBlock([new Param({ name: "mode", type: "string" })]);
const result = parseFull("--mode=fast", "", block);
expect(result).toEqual({
values: [{ param: block.findParam("mode")!, value: "fast" }],
jumpNext: 0,
});
});
test("parses --flag true for boolean param", () => {
const block = makeBlock([new Param({ name: "flag", type: "boolean" })]);
const result = parseFull("--flag", "true", block);
expect(result).toEqual({
values: [{ param: block.findParam("flag")!, value: "true" }],
jumpNext: 1,
});
});
test("parses --flag without value as 1 for boolean param", () => {
const block = makeBlock([new Param({ name: "flag", type: "boolean" })]);
const result = parseFull("--flag", "", block);
expect(result).toEqual({
values: [{ param: block.findParam("flag")!, value: "1" }],
jumpNext: 0,
});
});
test("parses --name John for string param", () => {
const block = makeBlock([new Param({ name: "name", type: "string" })]);
const result = parseFull("--name", "John", block);
expect(result).toEqual({
values: [{ param: block.findParam("name")!, value: "John" }],
jumpNext: 1,
});
});
test("throws if param is unknown", () => {
const block = makeBlock([]);
expect(() => parseFull("--unknown", "", block)).toThrow(
"Unknown param --unknown"
);
});
test("throws if param in --param=value is unknown", () => {
const block = makeBlock([]);
expect(() => parseFull("--param=value", "", block)).toThrow(
"Unknown param --param=value"
);
});
});
import type { Block } from "../block.ts";
import { checkBoolValue, getNameFromEq, type ParseReturn } from "./parse-param.ts";
export const checkFull = (arg: string) => {
if (arg.startsWith("--")) {
return true;
}
return false;
};
export const parseFull = (
arg1: string,
arg2: string,
block: Block
): ParseReturn => {
if (arg1.includes("=")) {
const { name, value } = getNameFromEq(arg1.slice(2));
const param = block.findParam(name);
if (!param) {
throw new Error("Unknown param " + arg1);
}
return { values: [{ param, value }], jumpNext: 0 };
}
const name = arg1.slice(2);
const param = block.findParam(name);
if (!param) {
throw new Error("Unknown param " + arg1);
}
if (param.type === "boolean") {
// --hello 1
if (checkBoolValue(arg2)) {
return { values: [{ param, value: arg2 }], jumpNext: 1 };
}
return { values: [{ param, value: "1" }], jumpNext: 0 };
}
//--hello world
return { values: [{ param, value: arg2 }], jumpNext: 1 };
};
import { test, expect, describe } from "bun:test";
import { checkNo, parseNo } from "./no.ts";
import { Param } from "../param.ts";
import { Block } from "../block.ts";
const makeBlock = (params: Param[]) =>
new Block({
arg: "test",
description: "Test block",
params,
});
describe("checkNo", () => {
test("returns true for --no- prefix", () => {
expect(checkNo("--no-cache")).toBe(true);
});
test("returns false if no --no- prefix", () => {
expect(checkNo("--cache")).toBe(false);
expect(checkNo("-c")).toBe(false);
});
});
describe("parseNo", () => {
test("returns value=0 for known param", () => {
const block = makeBlock([new Param({ name: "cache", type: "boolean" })]);
const result = parseNo("--no-cache", block);
expect(result).toEqual({
values: [
{
param: block.findParam("cache")!,
value: "0",
},
],
jumpNext: 0,
});
});
test("throws for unknown param", () => {
const block = makeBlock([]);
expect(() => parseNo("--no-unknown", block)).toThrow(
"Unknown param --no-unknown"
);
});
});
import type { Block } from "../block.ts";
import type { ParseReturn } from "./parse-param.ts";
export const checkNo = (arg1: string) => {
if (arg1.startsWith("--no-")) {
return true;
}
return false;
};
export const parseNo = (arg1: string, block: Block): ParseReturn => {
const param = block.findParam(arg1.slice(5));
if (!param) {
throw new Error("Unknown param " + arg1);
}
return { values: [{ param, value: "0" }], jumpNext: 0 };
};
import type { Block } from "../block.ts";
import type { Param } from "../param.ts";
import { checkFull, parseFull } from "./full.ts";
import { checkNo, parseNo } from "./no.ts";
import { checkShort, parseShort } from "./short.ts";
export type ParseReturn = {
values: { param: Param; value: string }[];
jumpNext: number;
};
export const getNameFromEq = (str: string) => {
const [name, ...values] = str.split("=");
return {
name: name as string,
value: values.join("="),
};
};
export const checkBoolValue = (str: string) => {
if (str === "1" || str === "0" || str === "true" || str === "false") {
return true;
}
return false;
};
export const parseParam = (
arg1: string,
arg2: string,
block: Block
): ParseReturn => {
if (checkNo(arg1)) {
return parseNo(arg1, block);
}
if (checkShort(arg1)) {
return parseShort(arg1, arg2, block);
}
if (checkFull(arg1)) {
return parseFull(arg1, arg2, block);
}
return null as never;
};
import { test, expect, describe } from "bun:test";
import { checkShort, parseShort } from "./short.ts"; // укажи путь
import { Param } from "../param.ts";
import { Block } from "../block.ts";
// Утилита создания блока
const makeBlock = (params: Param[]) =>
new Block({
arg: "test",
description: "Test block",
params,
});
describe("checkShort", () => {
test("returns true for short arg like -a", () => {
expect(checkShort("-a")).toBe(true);
});
test("returns false for long arg like --alpha", () => {
expect(checkShort("--alpha")).toBe(false);
});
test("returns false for regular string", () => {
expect(checkShort("abc")).toBe(false);
});
});
describe("parseShort", () => {
test("parses -abc as multiple boolean params", () => {
const block = makeBlock([
new Param({ name: "a", type: "boolean", short: "a" }),
new Param({ name: "b", type: "boolean", short: "b" }),
new Param({ name: "c", type: "boolean", short: "c" }),
]);
const result = parseShort("-abc", "", block);
expect(result).toEqual({
values: [
{ param: block.findShortParam("a")!, value: "1" },
{ param: block.findShortParam("b")!, value: "1" },
{ param: block.findShortParam("c")!, value: "1" },
],
jumpNext: 0,
});
});
test("parses -v=hello", () => {
const block = makeBlock([
new Param({ name: "verbose", type: "string", short: "v" }),
]);
const result = parseShort("-v=hello", "", block);
expect(result).toEqual({
values: [{ param: block.findShortParam("v")!, value: "hello" }],
jumpNext: 0,
});
});
test("parses -f true for boolean param", () => {
const block = makeBlock([
new Param({ name: "force", type: "boolean", short: "f" }),
]);
const result = parseShort("-f", "true", block);
expect(result).toEqual({
values: [{ param: block.findShortParam("f")!, value: "true" }],
jumpNext: 1,
});
});
test("parses -f without value as true", () => {
const block = makeBlock([
new Param({ name: "force", type: "boolean", short: "f" }),
]);
const result = parseShort("-f", "", block);
expect(result).toEqual({
values: [{ param: block.findShortParam("f")!, value: "1" }],
jumpNext: 0,
});
});
test("parses -n 123 for string or number param", () => {
const block = makeBlock([
new Param({ name: "number", type: "string", short: "n" }),
]);
const result = parseShort("-n", "123", block);
expect(result).toEqual({
values: [{ param: block.findShortParam("n")!, value: "123" }],
jumpNext: 1,
});
});
test("throws error if param not found", () => {
const block = makeBlock([]);
expect(() => parseShort("-x", "", block)).toThrow("Unknown param -x");
});
test("throws error for unknown short param in -abc", () => {
const block = makeBlock([
new Param({ name: "a", type: "boolean", short: "a" }),
]);
expect(() => parseShort("-ab", "", block)).toThrow(
/No param property for shortkey: b/
);
});
});
import type { Block } from "../block.ts";
import { checkBoolValue, getNameFromEq, type ParseReturn } from "./parse-param.ts";
export const checkShort = (arg: string) => {
if (arg.startsWith("-") && !arg.startsWith("--")) {
return true;
}
return false;
};
export const parseShort = (
arg1: string,
arg2: string,
block: Block
): ParseReturn => {
if (arg1.length > 2) {
// -v=hello
if (arg1.includes("=")) {
const { name, value } = getNameFromEq(arg1.slice(1));
const param = block.findShortParam(name);
if (!param) {
throw new Error("Unknown param " + arg1);
}
return { values: [{ param, value }], jumpNext: 0 };
}
// -abc
const values = arg1
.slice(1)
.split("")
.map((name) => {
const param = block.findShortParam(name);
if (!param) {
throw new Error(
"Unknown param: " +
arg1 +
" No param property for shortkey: " +
name
);
}
return { param, value: "1" };
});
return { values, jumpNext: 0 };
}
const name = arg1[1]!;
const param = block.findShortParam(name);
if (!param) {
throw new Error("Unknown param " + arg1);
}
if (param.type === "boolean") {
// -a 1
if (checkBoolValue(arg2)) {
return { values: [{ param, value: arg2 }], jumpNext: 1 };
}
// -a
return { values: [{ param, value: "1" }], jumpNext: 0 };
}
// -a hello
return { values: [{ param, value: arg2 }], jumpNext: 1 };
};
import { expect, it } from "bun:test";
import { Param } from "./param.ts";
import { Block } from "./block.ts";
import { globalArg, parse } from "./parse.ts";
it("long with global", () => {
const globalBlock = new Block({
arg: globalArg,
description: "",
params: [
new Param({ name: "hello", short: "h", type: "string" }),
new Param({ name: "age", short: "a", type: "number" }),
new Param({ name: "enabled", type: "boolean", short: "e" }),
],
children: [
new Block({
arg: "hello",
params: [new Param({ type: "string", name: "hello" })],
description: "",
}),
],
});
const rawArgs = "--hello world -a=25 -e hello --hello=world1";
const result = parse(rawArgs.split(" "), [globalBlock]);
expect(result).toEqual([
{ arg: globalArg, params: { hello: "world", age: "25", enabled: "1" } },
{
arg: "hello",
params: {
hello: "world1",
},
},
]);
});
it("long without global", () => {
const notGlobalBlock = new Block({
arg: "hello",
params: [new Param({ type: "string", name: "hello" })],
description: "",
});
const rawArgs = "hello --hello=world1";
const result = parse(rawArgs.split(" "), [notGlobalBlock]);
expect(result).toEqual([
{
arg: "hello",
params: {
hello: "world1",
},
},
]);
});
it("error arg", () => {
const notGlobalBlock = new Block({
arg: "hello",
params: [new Param({ type: "string", name: "hello" })],
description: "",
});
const rawArgs = "world --hello=world1";
expect(() => {
parse(rawArgs.split(" "), [notGlobalBlock]);
}).toThrow();
});
it("error param", () => {
const notGlobalBlock = new Block({
arg: "hello",
params: [new Param({ type: "string", name: "hello" })],
description: "",
});
const rawArgs = "hello --world=world1";
expect(() => {
parse(rawArgs.split(" "), [notGlobalBlock]);
}).toThrow();
});
import { Block } from "./block.ts";
import { parseParam } from "./parse-param/parse-param.ts";
export const globalArg = "globalArg";
const createDefaultGlobalBlock = (children: Block[]) => {
return new Block({
arg: globalArg,
params: [],
description: `Default global params for app`,
children,
});
};
type ParsedBlock = {
arg: string;
params: Record<string, string>;
};
export const parse = (args: string[], blocks: Block[]) => {
if (blocks.length === 0) {
throw new Error("Empty blocks");
}
const globalArgInit = blocks.length === 1 && blocks[0]!.arg === globalArg;
let currentBlock = globalArgInit
? blocks[0]!
: createDefaultGlobalBlock(blocks);
const parsedBlocks: ParsedBlock[] = [{ arg: currentBlock.arg, params: {} }];
outer: for (let i = 0; i < args.length; i++) {
const arg = args[i]!;
// params
if (arg.startsWith("-")) {
const nextArg = args[i + 1] || "";
const { jumpNext, values } = parseParam(arg, nextArg, currentBlock);
const lastParsedBlock = parsedBlocks.at(-1)!;
for (const { param, value } of values) {
if (param.name in lastParsedBlock.params) {
throw new Error("Param dublicated: " + arg);
}
lastParsedBlock.params[param.name] = value;
}
i += jumpNext;
continue;
}
// args
for (const childBlock of currentBlock.children) {
const { jumpNext, match } = childBlock.matcher(args, i);
if (!match) {
continue;
}
i += jumpNext;
const arg = args.slice(i, i + 1 + jumpNext);
parsedBlocks.push({
arg: arg.join(" "),
params: {},
});
currentBlock = childBlock;
continue outer;
}
throw new Error(`Not param or arg: ${arg}`);
}
return globalArgInit ? parsedBlocks : parsedBlocks.slice(1);
};
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"emitDeclarationOnly": true,
"outDir": "./dist/types",
"declaration": true,
"noEmit": false
},
"include": ["src/argblock.ts"]
}