Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
node_modules/
.vendor/
coverage/
test/tmp/
**/test/tmp/
test/*.log
**/test/*.log
lib/
*.tsbuildinfo
84 changes: 83 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/node-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@alchemy.run/node-utils",
"version": "0.0.2",
"type": "module",
"author": "Sam Goodwin (sam@alchemy.run)",
"repository": {
"type": "git",
Expand All @@ -18,6 +19,11 @@
"default": "./lib/index.js"
}
},
"dependencies": {
"@effect/platform-bun": "4.0.0-beta.66",
"@effect/platform-node": "4.0.0-beta.66",
"effect": "4.0.0-beta.66"
},
"scripts": {
"test": "bun test"
},
Expand Down
55 changes: 55 additions & 0 deletions packages/node-utils/src/braces/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { fill } from "../fill-range.ts";
import * as utils from "./utils.ts";

export interface CompileOptions {
escapeInvalid?: boolean;
[k: string]: unknown;
}

export function compile(ast: utils.Node, options: CompileOptions = {}): string {
const walk = (node: utils.Node, parent: utils.Node = {}): string => {
const invalidBlock = utils.isInvalidBrace(parent);
const invalidNode = node.invalid === true && options.escapeInvalid === true;
const invalid = invalidBlock === true || invalidNode === true;
const prefix = options.escapeInvalid === true ? "\\" : "";
let output = "";

if (node.isOpen === true) return prefix + node.value;
if (node.isClose === true) return prefix + node.value;
if (node.type === "open") return invalid ? prefix + node.value : "(";
if (node.type === "close") return invalid ? prefix + node.value : ")";
if (node.type === "comma") {
return node.prev.type === "comma" ? "" : invalid ? node.value : "|";
}

if (node.value) return node.value;

if (node.nodes && node.ranges > 0) {
const args = utils.reduce(node.nodes);
const range = fill(
args[0] as any,
args[1] as any,
args[2] as any,
{ ...options, wrap: false, toRegex: true, strictZeros: true } as any,
) as string;

if ((range as unknown as string).length !== 0) {
return args.length > 1 && (range as unknown as string).length > 1
? `(${range})`
: (range as string);
}
}

if (node.nodes) {
for (const child of node.nodes as utils.Node[]) {
output += walk(child, node);
}
}

return output;
};

return walk(ast);
}

export default compile;
50 changes: 50 additions & 0 deletions packages/node-utils/src/braces/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export const MAX_LENGTH = 10000;

export const CHAR_0 = "0";
export const CHAR_9 = "9";

export const CHAR_UPPERCASE_A = "A";
export const CHAR_LOWERCASE_A = "a";
export const CHAR_UPPERCASE_Z = "Z";
export const CHAR_LOWERCASE_Z = "z";

export const CHAR_LEFT_PARENTHESES = "(";
export const CHAR_RIGHT_PARENTHESES = ")";

export const CHAR_ASTERISK = "*";

export const CHAR_AMPERSAND = "&";
export const CHAR_AT = "@";
export const CHAR_BACKSLASH = "\\";
export const CHAR_BACKTICK = "`";
export const CHAR_CARRIAGE_RETURN = "\r";
export const CHAR_CIRCUMFLEX_ACCENT = "^";
export const CHAR_COLON = ":";
export const CHAR_COMMA = ",";
export const CHAR_DOLLAR = "$";
export const CHAR_DOT = ".";
export const CHAR_DOUBLE_QUOTE = '"';
export const CHAR_EQUAL = "=";
export const CHAR_EXCLAMATION_MARK = "!";
export const CHAR_FORM_FEED = "\f";
export const CHAR_FORWARD_SLASH = "/";
export const CHAR_HASH = "#";
export const CHAR_HYPHEN_MINUS = "-";
export const CHAR_LEFT_ANGLE_BRACKET = "<";
export const CHAR_LEFT_CURLY_BRACE = "{";
export const CHAR_LEFT_SQUARE_BRACKET = "[";
export const CHAR_LINE_FEED = "\n";
export const CHAR_NO_BREAK_SPACE = " ";
export const CHAR_PERCENT = "%";
export const CHAR_PLUS = "+";
export const CHAR_QUESTION_MARK = "?";
export const CHAR_RIGHT_ANGLE_BRACKET = ">";
export const CHAR_RIGHT_CURLY_BRACE = "}";
export const CHAR_RIGHT_SQUARE_BRACKET = "]";
export const CHAR_SEMICOLON = ";";
export const CHAR_SINGLE_QUOTE = "'";
export const CHAR_SPACE = " ";
export const CHAR_TAB = "\t";
export const CHAR_UNDERSCORE = "_";
export const CHAR_VERTICAL_LINE = "|";
export const CHAR_ZERO_WIDTH_NOBREAK_SPACE = "";
135 changes: 135 additions & 0 deletions packages/node-utils/src/braces/expand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { fill } from "../fill-range.ts";
import { stringify } from "./stringify.ts";
import * as utils from "./utils.ts";

export interface ExpandOptions {
rangeLimit?: number | false;
step?: number | string;
[k: string]: unknown;
}

const append = (
queue: any = "",
stash: any = "",
enclose = false,
): any[] => {
const result: any[] = [];
queue = ([] as any[]).concat(queue);
stash = ([] as any[]).concat(stash);

if (!stash.length) return queue;
if (!queue.length) {
return enclose
? utils.flatten(stash).map((ele) => `{${ele}}`)
: stash;
}

for (const item of queue) {
if (Array.isArray(item)) {
for (const value of item) {
result.push(append(value, stash, enclose));
}
} else {
for (let ele of stash) {
if (enclose === true && typeof ele === "string") ele = `{${ele}}`;
result.push(Array.isArray(ele) ? append(item, ele, enclose) : item + ele);
}
}
}
return utils.flatten(result);
};

export function expand(ast: utils.Node, options: ExpandOptions = {}): string[] {
const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit;

const walk = (node: utils.Node, parent: utils.Node = {}): any => {
node.queue = [];

let p = parent;
let q = parent.queue;

while (p.type !== "brace" && p.type !== "root" && p.parent) {
p = p.parent;
q = p.queue;
}

if (node.invalid || node.dollar) {
q.push(append(q.pop(), stringify(node, options)));
return;
}

if (
node.type === "brace" &&
node.invalid !== true &&
node.nodes.length === 2
) {
q.push(append(q.pop(), ["{}"]));
return;
}

if (node.nodes && node.ranges > 0) {
const args = utils.reduce(node.nodes);

if (
utils.exceedsLimit(
args[0],
args[1],
options.step ?? args[2],
rangeLimit as number | false,
)
) {
throw new RangeError(
"expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.",
);
}

let range = fill(args[0] as any, args[1] as any, args[2] as any, options as any);
if ((range as any).length === 0) {
range = stringify(node, options) as any;
}

q.push(append(q.pop(), range));
node.nodes = [];
return;
}

const enclose = utils.encloseBrace(node);
let queue = node.queue;
let block = node;

while (block.type !== "brace" && block.type !== "root" && block.parent) {
block = block.parent;
queue = block.queue;
}

for (let i = 0; i < node.nodes.length; i++) {
const child = node.nodes[i];

if (child.type === "comma" && node.type === "brace") {
if (i === 1) queue.push("");
queue.push("");
continue;
}

if (child.type === "close") {
q.push(append(q.pop(), queue, enclose));
continue;
}

if (child.value && child.type !== "open") {
queue.push(append(queue.pop(), child.value));
continue;
}

if (child.nodes) {
walk(child, node);
}
}

return queue;
};

return utils.flatten(walk(ast));
}

export default expand;
Loading