shell-dsl 0.0.37 → 0.0.39
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/README.md +4 -3
- package/dist/cjs/index.cjs +2 -1
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/commands/find/find.cjs +27 -11
- package/dist/cjs/src/commands/find/find.cjs.map +3 -3
- package/dist/cjs/src/commands/index.cjs +5 -1
- package/dist/cjs/src/commands/index.cjs.map +3 -3
- package/dist/cjs/src/commands/printf/printf.cjs +416 -0
- package/dist/cjs/src/commands/printf/printf.cjs.map +10 -0
- package/dist/cjs/src/commands/tree/tree.cjs +15 -8
- package/dist/cjs/src/commands/tree/tree.cjs.map +3 -3
- package/dist/mjs/index.mjs +3 -1
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/src/commands/find/find.mjs +27 -11
- package/dist/mjs/src/commands/find/find.mjs.map +3 -3
- package/dist/mjs/src/commands/index.mjs +5 -1
- package/dist/mjs/src/commands/index.mjs.map +3 -3
- package/dist/mjs/src/commands/printf/printf.mjs +376 -0
- package/dist/mjs/src/commands/printf/printf.mjs.map +10 -0
- package/dist/mjs/src/commands/tree/tree.mjs +15 -8
- package/dist/mjs/src/commands/tree/tree.mjs.map +3 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/src/commands/index.d.ts +1 -0
- package/dist/types/src/commands/printf/printf.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/commands/find/find.ts
|
|
2
2
|
import { matchGlob } from "../../utils/match-glob.mjs";
|
|
3
|
-
async function evalExpr(expr, basename, isFile, isDir, entryPath, ctx) {
|
|
3
|
+
async function evalExpr(expr, basename, isFile, isDir, entryPath, ctx, runActions) {
|
|
4
4
|
switch (expr.type) {
|
|
5
5
|
case "true":
|
|
6
6
|
return true;
|
|
@@ -9,23 +9,32 @@ async function evalExpr(expr, basename, isFile, isDir, entryPath, ctx) {
|
|
|
9
9
|
case "ftype":
|
|
10
10
|
return expr.value === "f" ? isFile : isDir;
|
|
11
11
|
case "and": {
|
|
12
|
-
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);
|
|
12
|
+
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx, runActions);
|
|
13
13
|
if (!leftResult)
|
|
14
14
|
return false;
|
|
15
|
-
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);
|
|
15
|
+
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx, runActions);
|
|
16
16
|
}
|
|
17
17
|
case "or": {
|
|
18
|
-
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);
|
|
18
|
+
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx, runActions);
|
|
19
19
|
if (leftResult)
|
|
20
20
|
return true;
|
|
21
|
-
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);
|
|
21
|
+
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx, runActions);
|
|
22
22
|
}
|
|
23
23
|
case "not":
|
|
24
|
-
return !await evalExpr(expr.expr, basename, isFile, isDir, entryPath, ctx);
|
|
24
|
+
return !await evalExpr(expr.expr, basename, isFile, isDir, entryPath, ctx, runActions);
|
|
25
|
+
case "print":
|
|
26
|
+
if (runActions) {
|
|
27
|
+
await ctx.stdout.writeText(entryPath + `
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
25
31
|
case "exec": {
|
|
26
32
|
if (expr.batchMode) {
|
|
27
33
|
return true;
|
|
28
34
|
}
|
|
35
|
+
if (!runActions) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
29
38
|
if (!ctx.exec) {
|
|
30
39
|
await ctx.stderr.writeText(`find: -exec not supported (no exec capability)
|
|
31
40
|
`);
|
|
@@ -45,6 +54,7 @@ async function evalExpr(expr, basename, isFile, isDir, entryPath, ctx) {
|
|
|
45
54
|
}
|
|
46
55
|
function hasActionExpr(expr) {
|
|
47
56
|
switch (expr.type) {
|
|
57
|
+
case "print":
|
|
48
58
|
case "exec":
|
|
49
59
|
return true;
|
|
50
60
|
case "and":
|
|
@@ -183,6 +193,10 @@ function parseExprArgs(args) {
|
|
|
183
193
|
}
|
|
184
194
|
return { type: "exec", cmdName, cmdArgs, batchMode };
|
|
185
195
|
}
|
|
196
|
+
if (tok === "-print") {
|
|
197
|
+
advance();
|
|
198
|
+
return { type: "print" };
|
|
199
|
+
}
|
|
186
200
|
throw new ParseError(`find: unknown predicate '${tok}'`);
|
|
187
201
|
}
|
|
188
202
|
const expr = parseOr();
|
|
@@ -281,8 +295,9 @@ var find = async (ctx) => {
|
|
|
281
295
|
const isDir = entryStat.isDirectory();
|
|
282
296
|
const isFile = entryStat.isFile();
|
|
283
297
|
const basename = ctx.fs.basename(path);
|
|
284
|
-
const
|
|
285
|
-
|
|
298
|
+
const meetsMinDepth = minDepth === undefined || depth >= minDepth;
|
|
299
|
+
const matches = await evalExpr(expr, basename, isFile, isDir, displayPath, ctx, meetsMinDepth);
|
|
300
|
+
if (matches && meetsMinDepth) {
|
|
286
301
|
if (batchExecNodes.length > 0) {
|
|
287
302
|
batchPaths.push(displayPath);
|
|
288
303
|
} else if (!hasAction) {
|
|
@@ -304,8 +319,9 @@ var find = async (ctx) => {
|
|
|
304
319
|
}
|
|
305
320
|
if (stat.isFile()) {
|
|
306
321
|
const basename = ctx.fs.basename(resolvedStart);
|
|
307
|
-
const
|
|
308
|
-
|
|
322
|
+
const meetsMinDepth = minDepth === undefined || minDepth <= 0;
|
|
323
|
+
const matches = await evalExpr(expr, basename, true, false, normalizedPath, ctx, meetsMinDepth);
|
|
324
|
+
if (maxDepth !== undefined && maxDepth < 0) {} else if (matches && meetsMinDepth) {
|
|
309
325
|
if (batchExecNodes.length > 0) {
|
|
310
326
|
batchPaths.push(normalizedPath);
|
|
311
327
|
} else if (!hasAction) {
|
|
@@ -342,4 +358,4 @@ export {
|
|
|
342
358
|
find
|
|
343
359
|
};
|
|
344
360
|
|
|
345
|
-
//# debugId=
|
|
361
|
+
//# debugId=10955D1C374A7D0364756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/commands/find/find.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { Command, CommandContext, ExecResult } from \"../../types.mjs\";\nimport { matchGlob } from \"../../utils/match-glob.mjs\";\n\n// Expression tree types\ntype FindExpr =\n | { type: \"name\"; pattern: string; ignoreCase: boolean }\n | { type: \"ftype\"; value: \"f\" | \"d\" }\n | { type: \"and\"; left: FindExpr; right: FindExpr }\n | { type: \"or\"; left: FindExpr; right: FindExpr }\n | { type: \"not\"; expr: FindExpr }\n | { type: \"true\" }\n | { type: \"exec\"; cmdName: string; cmdArgs: string[]; batchMode: boolean };\n\nasync function evalExpr(\n expr: FindExpr,\n basename: string,\n isFile: boolean,\n isDir: boolean,\n entryPath: string,\n ctx: CommandContext,\n): Promise<boolean> {\n switch (expr.type) {\n case \"true\":\n return true;\n case \"name\":\n return matchGlob(expr.pattern, basename, expr.ignoreCase);\n case \"ftype\":\n return expr.value === \"f\" ? isFile : isDir;\n case \"and\": {\n const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);\n if (!leftResult) return false;\n return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);\n }\n case \"or\": {\n const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);\n if (leftResult) return true;\n return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);\n }\n case \"not\":\n return !(await evalExpr(expr.expr, basename, isFile, isDir, entryPath, ctx));\n case \"exec\": {\n if (expr.batchMode) {\n // In batch mode, always return true during traversal; paths are collected externally\n return true;\n }\n // Per-file mode: execute command with {} replaced by entryPath\n if (!ctx.exec) {\n await ctx.stderr.writeText(\"find: -exec not supported (no exec capability)\\n\");\n return false;\n }\n const resolvedArgs = expr.cmdArgs.map(a => a === \"{}\" ? entryPath : a);\n const result: ExecResult = await ctx.exec(expr.cmdName, resolvedArgs);\n // Pass stdout/stderr through to find's streams\n if (result.stdout.length > 0) {\n await ctx.stdout.write(result.stdout);\n }\n if (result.stderr.length > 0) {\n await ctx.stderr.write(result.stderr);\n }\n return result.exitCode === 0;\n }\n }\n}\n\n/** Check if expression tree contains any -exec node */\nfunction hasActionExpr(expr: FindExpr): boolean {\n switch (expr.type) {\n case \"exec\":\n return true;\n case \"and\":\n case \"or\":\n return hasActionExpr(expr.left) || hasActionExpr(expr.right);\n case \"not\":\n return hasActionExpr(expr.expr);\n default:\n return false;\n }\n}\n\n/** Collect all batch-mode -exec nodes from the expression tree */\nfunction collectBatchExecNodes(expr: FindExpr): Array<{ type: \"exec\"; cmdName: string; cmdArgs: string[]; batchMode: boolean }> {\n switch (expr.type) {\n case \"exec\":\n return expr.batchMode ? [expr] : [];\n case \"and\":\n case \"or\":\n return [...collectBatchExecNodes(expr.left), ...collectBatchExecNodes(expr.right)];\n case \"not\":\n return collectBatchExecNodes(expr.expr);\n default:\n return [];\n }\n}\n\nclass ParseError extends Error {\n constructor(msg: string) {\n super(msg);\n }\n}\n\nfunction parseExprArgs(args: string[]): FindExpr {\n if (args.length === 0) return { type: \"true\" };\n\n let pos = 0;\n\n function peek(): string | undefined {\n return args[pos];\n }\n\n function advance(): string {\n return args[pos++]!;\n }\n\n function parseOr(): FindExpr {\n let left = parseAnd();\n while (peek() === \"-o\") {\n advance();\n const right = parseAnd();\n left = { type: \"or\", left, right };\n }\n return left;\n }\n\n function parseAnd(): FindExpr {\n let left = parseUnary();\n while (pos < args.length) {\n const next = peek();\n if (next === \"-o\" || next === \")\" || next === undefined) break;\n if (next === \"-a\") {\n advance();\n }\n const right = parseUnary();\n left = { type: \"and\", left, right };\n }\n return left;\n }\n\n function parseUnary(): FindExpr {\n const next = peek();\n if (next === \"!\" || next === \"-not\") {\n advance();\n const expr = parseUnary();\n return { type: \"not\", expr };\n }\n return parsePrimary();\n }\n\n function parsePrimary(): FindExpr {\n const tok = peek();\n if (tok === undefined) {\n throw new ParseError(\"find: expected expression\");\n }\n\n if (tok === \"(\") {\n advance();\n const expr = parseOr();\n if (peek() !== \")\") {\n throw new ParseError(\"find: missing closing ')'\");\n }\n advance();\n return expr;\n }\n\n if (tok === \"-name\" || tok === \"-iname\") {\n advance();\n const pattern = peek();\n if (pattern === undefined) {\n throw new ParseError(`find: missing argument to '${tok}'`);\n }\n advance();\n return { type: \"name\", pattern, ignoreCase: tok === \"-iname\" };\n }\n\n if (tok === \"-type\") {\n advance();\n const val = peek();\n if (val === undefined) {\n throw new ParseError(\"find: missing argument to '-type'\");\n }\n if (val !== \"f\" && val !== \"d\") {\n throw new ParseError(`find: Unknown argument to -type: ${val}`);\n }\n advance();\n return { type: \"ftype\", value: val };\n }\n\n if (tok === \"-exec\") {\n advance();\n const cmdName = peek();\n if (cmdName === undefined || cmdName === \";\" || cmdName === \"+\") {\n throw new ParseError(\"find: -exec: missing command\");\n }\n advance();\n\n const cmdArgs: string[] = [];\n let batchMode = false;\n let foundTerminator = false;\n\n while (pos < args.length) {\n const a = args[pos]!;\n if (a === \";\") {\n advance();\n foundTerminator = true;\n break;\n }\n if (a === \"+\") {\n advance();\n batchMode = true;\n foundTerminator = true;\n break;\n }\n cmdArgs.push(a);\n advance();\n }\n\n if (!foundTerminator) {\n throw new ParseError(\"find: -exec: missing terminator (';' or '+')\");\n }\n\n return { type: \"exec\", cmdName, cmdArgs, batchMode };\n }\n\n throw new ParseError(`find: unknown predicate '${tok}'`);\n }\n\n const expr = parseOr();\n if (pos < args.length) {\n throw new ParseError(`find: unexpected '${args[pos]}'`);\n }\n return expr;\n}\n\nexport const find: Command = async (ctx) => {\n const args = [...ctx.args];\n const paths: string[] = [];\n\n // Parse arguments: paths come before first flag/operator\n let i = 0;\n\n // Collect paths (args before first -, !, or ()\n while (i < args.length && !args[i]!.startsWith(\"-\") && args[i] !== \"!\" && args[i] !== \"(\" && args[i] !== \")\") {\n paths.push(args[i]!);\n i++;\n }\n\n // Default to current directory if no paths\n if (paths.length === 0) {\n paths.push(\".\");\n }\n\n // Extract global options (-maxdepth, -mindepth) from remaining args\n let maxDepth: number | undefined;\n let minDepth: number | undefined;\n const exprArgs: string[] = [];\n\n let j = i;\n while (j < args.length) {\n const arg = args[j]!;\n if (arg === \"-maxdepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-maxdepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -maxdepth\\n`);\n return 1;\n }\n maxDepth = depth;\n } else if (arg === \"-mindepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-mindepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -mindepth\\n`);\n return 1;\n }\n minDepth = depth;\n } else {\n exprArgs.push(arg);\n }\n j++;\n }\n\n // Parse expression tree\n let expr: FindExpr;\n try {\n expr = parseExprArgs(exprArgs);\n } catch (e) {\n if (e instanceof ParseError) {\n await ctx.stderr.writeText(e.message + \"\\n\");\n return 1;\n }\n throw e;\n }\n\n const hasAction = hasActionExpr(expr);\n const batchExecNodes = collectBatchExecNodes(expr);\n const batchPaths: string[] = [];\n\n let hasError = false;\n\n // Process each starting path\n for (const startPath of paths) {\n const normalizedPath = startPath === \"/\" ? \"/\" : startPath.replace(/\\/+$/, '');\n const resolvedStart = ctx.fs.resolve(ctx.cwd, startPath);\n\n // Check if path exists\n let stat;\n try {\n stat = await ctx.fs.stat(resolvedStart);\n } catch {\n await ctx.stderr.writeText(`find: '${startPath}': No such file or directory\\n`);\n hasError = true;\n continue;\n }\n\n // Recursive traversal function\n async function traverse(path: string, displayPath: string, depth: number): Promise<void> {\n // Check maxdepth\n if (maxDepth !== undefined && depth > maxDepth) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await ctx.fs.stat(path);\n } catch {\n return;\n }\n\n const isDir = entryStat.isDirectory();\n const isFile = entryStat.isFile();\n const basename = ctx.fs.basename(path);\n\n // Check if this entry matches the expression\n const matches = await evalExpr(expr, basename, isFile, isDir, displayPath, ctx);\n\n if (matches && (minDepth === undefined || depth >= minDepth)) {\n if (batchExecNodes.length > 0) {\n batchPaths.push(displayPath);\n } else if (!hasAction) {\n // No action expressions: default print behavior\n await ctx.stdout.writeText(displayPath + \"\\n\");\n }\n // If has per-file -exec actions, output was already handled in evalExpr\n }\n\n // Recurse into directories\n if (isDir) {\n try {\n const entries = await ctx.fs.readdir(path);\n entries.sort();\n for (const entry of entries) {\n const childPath = ctx.fs.resolve(path, entry);\n const childDisplayPath = displayPath === \".\" ? entry : `${displayPath}/${entry}`;\n await traverse(childPath, childDisplayPath, depth + 1);\n }\n } catch {\n // Ignore errors reading directory contents\n }\n }\n }\n\n // Start traversal\n if (stat.isFile()) {\n const basename = ctx.fs.basename(resolvedStart);\n const matches = await evalExpr(expr, basename, true, false, normalizedPath, ctx);\n\n if (maxDepth !== undefined && maxDepth < 0) {\n // skip\n } else if (matches && (minDepth === undefined || minDepth <= 0)) {\n if (batchExecNodes.length > 0) {\n batchPaths.push(normalizedPath);\n } else if (!hasAction) {\n await ctx.stdout.writeText(normalizedPath + \"\\n\");\n }\n }\n } else {\n await traverse(resolvedStart, normalizedPath, 0);\n }\n }\n\n // Execute batch -exec nodes with all collected paths\n if (batchExecNodes.length > 0 && batchPaths.length > 0 && ctx.exec) {\n for (const node of batchExecNodes) {\n // Replace {} in cmdArgs with all paths\n const resolvedArgs: string[] = [];\n for (const a of node.cmdArgs) {\n if (a === \"{}\") {\n resolvedArgs.push(...batchPaths);\n } else {\n resolvedArgs.push(a);\n }\n }\n const result = await ctx.exec(node.cmdName, resolvedArgs);\n if (result.stdout.length > 0) {\n await ctx.stdout.write(result.stdout);\n }\n if (result.stderr.length > 0) {\n await ctx.stderr.write(result.stderr);\n }\n }\n }\n\n return hasError ? 1 : 0;\n};\n"
|
|
5
|
+
"import type { Command, CommandContext, ExecResult } from \"../../types.mjs\";\nimport { matchGlob } from \"../../utils/match-glob.mjs\";\n\n// Expression tree types\ntype FindExpr =\n | { type: \"name\"; pattern: string; ignoreCase: boolean }\n | { type: \"ftype\"; value: \"f\" | \"d\" }\n | { type: \"and\"; left: FindExpr; right: FindExpr }\n | { type: \"or\"; left: FindExpr; right: FindExpr }\n | { type: \"not\"; expr: FindExpr }\n | { type: \"true\" }\n | { type: \"print\" }\n | { type: \"exec\"; cmdName: string; cmdArgs: string[]; batchMode: boolean };\n\nasync function evalExpr(\n expr: FindExpr,\n basename: string,\n isFile: boolean,\n isDir: boolean,\n entryPath: string,\n ctx: CommandContext,\n runActions: boolean,\n): Promise<boolean> {\n switch (expr.type) {\n case \"true\":\n return true;\n case \"name\":\n return matchGlob(expr.pattern, basename, expr.ignoreCase);\n case \"ftype\":\n return expr.value === \"f\" ? isFile : isDir;\n case \"and\": {\n const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx, runActions);\n if (!leftResult) return false;\n return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx, runActions);\n }\n case \"or\": {\n const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx, runActions);\n if (leftResult) return true;\n return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx, runActions);\n }\n case \"not\":\n return !(await evalExpr(expr.expr, basename, isFile, isDir, entryPath, ctx, runActions));\n case \"print\":\n if (runActions) {\n await ctx.stdout.writeText(entryPath + \"\\n\");\n }\n return true;\n case \"exec\": {\n if (expr.batchMode) {\n // In batch mode, always return true during traversal; paths are collected externally\n return true;\n }\n if (!runActions) {\n return true;\n }\n // Per-file mode: execute command with {} replaced by entryPath\n if (!ctx.exec) {\n await ctx.stderr.writeText(\"find: -exec not supported (no exec capability)\\n\");\n return false;\n }\n const resolvedArgs = expr.cmdArgs.map(a => a === \"{}\" ? entryPath : a);\n const result: ExecResult = await ctx.exec(expr.cmdName, resolvedArgs);\n // Pass stdout/stderr through to find's streams\n if (result.stdout.length > 0) {\n await ctx.stdout.write(result.stdout);\n }\n if (result.stderr.length > 0) {\n await ctx.stderr.write(result.stderr);\n }\n return result.exitCode === 0;\n }\n }\n}\n\n/** Check if expression tree contains any -exec node */\nfunction hasActionExpr(expr: FindExpr): boolean {\n switch (expr.type) {\n case \"print\":\n case \"exec\":\n return true;\n case \"and\":\n case \"or\":\n return hasActionExpr(expr.left) || hasActionExpr(expr.right);\n case \"not\":\n return hasActionExpr(expr.expr);\n default:\n return false;\n }\n}\n\n/** Collect all batch-mode -exec nodes from the expression tree */\nfunction collectBatchExecNodes(expr: FindExpr): Array<{ type: \"exec\"; cmdName: string; cmdArgs: string[]; batchMode: boolean }> {\n switch (expr.type) {\n case \"exec\":\n return expr.batchMode ? [expr] : [];\n case \"and\":\n case \"or\":\n return [...collectBatchExecNodes(expr.left), ...collectBatchExecNodes(expr.right)];\n case \"not\":\n return collectBatchExecNodes(expr.expr);\n default:\n return [];\n }\n}\n\nclass ParseError extends Error {\n constructor(msg: string) {\n super(msg);\n }\n}\n\nfunction parseExprArgs(args: string[]): FindExpr {\n if (args.length === 0) return { type: \"true\" };\n\n let pos = 0;\n\n function peek(): string | undefined {\n return args[pos];\n }\n\n function advance(): string {\n return args[pos++]!;\n }\n\n function parseOr(): FindExpr {\n let left = parseAnd();\n while (peek() === \"-o\") {\n advance();\n const right = parseAnd();\n left = { type: \"or\", left, right };\n }\n return left;\n }\n\n function parseAnd(): FindExpr {\n let left = parseUnary();\n while (pos < args.length) {\n const next = peek();\n if (next === \"-o\" || next === \")\" || next === undefined) break;\n if (next === \"-a\") {\n advance();\n }\n const right = parseUnary();\n left = { type: \"and\", left, right };\n }\n return left;\n }\n\n function parseUnary(): FindExpr {\n const next = peek();\n if (next === \"!\" || next === \"-not\") {\n advance();\n const expr = parseUnary();\n return { type: \"not\", expr };\n }\n return parsePrimary();\n }\n\n function parsePrimary(): FindExpr {\n const tok = peek();\n if (tok === undefined) {\n throw new ParseError(\"find: expected expression\");\n }\n\n if (tok === \"(\") {\n advance();\n const expr = parseOr();\n if (peek() !== \")\") {\n throw new ParseError(\"find: missing closing ')'\");\n }\n advance();\n return expr;\n }\n\n if (tok === \"-name\" || tok === \"-iname\") {\n advance();\n const pattern = peek();\n if (pattern === undefined) {\n throw new ParseError(`find: missing argument to '${tok}'`);\n }\n advance();\n return { type: \"name\", pattern, ignoreCase: tok === \"-iname\" };\n }\n\n if (tok === \"-type\") {\n advance();\n const val = peek();\n if (val === undefined) {\n throw new ParseError(\"find: missing argument to '-type'\");\n }\n if (val !== \"f\" && val !== \"d\") {\n throw new ParseError(`find: Unknown argument to -type: ${val}`);\n }\n advance();\n return { type: \"ftype\", value: val };\n }\n\n if (tok === \"-exec\") {\n advance();\n const cmdName = peek();\n if (cmdName === undefined || cmdName === \";\" || cmdName === \"+\") {\n throw new ParseError(\"find: -exec: missing command\");\n }\n advance();\n\n const cmdArgs: string[] = [];\n let batchMode = false;\n let foundTerminator = false;\n\n while (pos < args.length) {\n const a = args[pos]!;\n if (a === \";\") {\n advance();\n foundTerminator = true;\n break;\n }\n if (a === \"+\") {\n advance();\n batchMode = true;\n foundTerminator = true;\n break;\n }\n cmdArgs.push(a);\n advance();\n }\n\n if (!foundTerminator) {\n throw new ParseError(\"find: -exec: missing terminator (';' or '+')\");\n }\n\n return { type: \"exec\", cmdName, cmdArgs, batchMode };\n }\n\n if (tok === \"-print\") {\n advance();\n return { type: \"print\" };\n }\n\n throw new ParseError(`find: unknown predicate '${tok}'`);\n }\n\n const expr = parseOr();\n if (pos < args.length) {\n throw new ParseError(`find: unexpected '${args[pos]}'`);\n }\n return expr;\n}\n\nexport const find: Command = async (ctx) => {\n const args = [...ctx.args];\n const paths: string[] = [];\n\n // Parse arguments: paths come before first flag/operator\n let i = 0;\n\n // Collect paths (args before first -, !, or ()\n while (i < args.length && !args[i]!.startsWith(\"-\") && args[i] !== \"!\" && args[i] !== \"(\" && args[i] !== \")\") {\n paths.push(args[i]!);\n i++;\n }\n\n // Default to current directory if no paths\n if (paths.length === 0) {\n paths.push(\".\");\n }\n\n // Extract global options (-maxdepth, -mindepth) from remaining args\n let maxDepth: number | undefined;\n let minDepth: number | undefined;\n const exprArgs: string[] = [];\n\n let j = i;\n while (j < args.length) {\n const arg = args[j]!;\n if (arg === \"-maxdepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-maxdepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -maxdepth\\n`);\n return 1;\n }\n maxDepth = depth;\n } else if (arg === \"-mindepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-mindepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -mindepth\\n`);\n return 1;\n }\n minDepth = depth;\n } else {\n exprArgs.push(arg);\n }\n j++;\n }\n\n // Parse expression tree\n let expr: FindExpr;\n try {\n expr = parseExprArgs(exprArgs);\n } catch (e) {\n if (e instanceof ParseError) {\n await ctx.stderr.writeText(e.message + \"\\n\");\n return 1;\n }\n throw e;\n }\n\n const hasAction = hasActionExpr(expr);\n const batchExecNodes = collectBatchExecNodes(expr);\n const batchPaths: string[] = [];\n\n let hasError = false;\n\n // Process each starting path\n for (const startPath of paths) {\n const normalizedPath = startPath === \"/\" ? \"/\" : startPath.replace(/\\/+$/, '');\n const resolvedStart = ctx.fs.resolve(ctx.cwd, startPath);\n\n // Check if path exists\n let stat;\n try {\n stat = await ctx.fs.stat(resolvedStart);\n } catch {\n await ctx.stderr.writeText(`find: '${startPath}': No such file or directory\\n`);\n hasError = true;\n continue;\n }\n\n // Recursive traversal function\n async function traverse(path: string, displayPath: string, depth: number): Promise<void> {\n // Check maxdepth\n if (maxDepth !== undefined && depth > maxDepth) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await ctx.fs.stat(path);\n } catch {\n return;\n }\n\n const isDir = entryStat.isDirectory();\n const isFile = entryStat.isFile();\n const basename = ctx.fs.basename(path);\n const meetsMinDepth = minDepth === undefined || depth >= minDepth;\n\n // Check if this entry matches the expression\n const matches = await evalExpr(expr, basename, isFile, isDir, displayPath, ctx, meetsMinDepth);\n\n if (matches && meetsMinDepth) {\n if (batchExecNodes.length > 0) {\n batchPaths.push(displayPath);\n } else if (!hasAction) {\n // No action expressions: default print behavior\n await ctx.stdout.writeText(displayPath + \"\\n\");\n }\n // If has per-file -exec actions, output was already handled in evalExpr\n }\n\n // Recurse into directories\n if (isDir) {\n try {\n const entries = await ctx.fs.readdir(path);\n entries.sort();\n for (const entry of entries) {\n const childPath = ctx.fs.resolve(path, entry);\n const childDisplayPath = displayPath === \".\" ? entry : `${displayPath}/${entry}`;\n await traverse(childPath, childDisplayPath, depth + 1);\n }\n } catch {\n // Ignore errors reading directory contents\n }\n }\n }\n\n // Start traversal\n if (stat.isFile()) {\n const basename = ctx.fs.basename(resolvedStart);\n const meetsMinDepth = minDepth === undefined || minDepth <= 0;\n const matches = await evalExpr(expr, basename, true, false, normalizedPath, ctx, meetsMinDepth);\n\n if (maxDepth !== undefined && maxDepth < 0) {\n // skip\n } else if (matches && meetsMinDepth) {\n if (batchExecNodes.length > 0) {\n batchPaths.push(normalizedPath);\n } else if (!hasAction) {\n await ctx.stdout.writeText(normalizedPath + \"\\n\");\n }\n }\n } else {\n await traverse(resolvedStart, normalizedPath, 0);\n }\n }\n\n // Execute batch -exec nodes with all collected paths\n if (batchExecNodes.length > 0 && batchPaths.length > 0 && ctx.exec) {\n for (const node of batchExecNodes) {\n // Replace {} in cmdArgs with all paths\n const resolvedArgs: string[] = [];\n for (const a of node.cmdArgs) {\n if (a === \"{}\") {\n resolvedArgs.push(...batchPaths);\n } else {\n resolvedArgs.push(a);\n }\n }\n const result = await ctx.exec(node.cmdName, resolvedArgs);\n if (result.stdout.length > 0) {\n await ctx.stdout.write(result.stdout);\n }\n if (result.stderr.length > 0) {\n await ctx.stderr.write(result.stderr);\n }\n }\n }\n\n return hasError ? 1 : 0;\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AACA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "10955D1C374A7D0364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/commands/index.ts
|
|
2
2
|
import { echo } from "./echo/echo.mjs";
|
|
3
|
+
import { printf } from "./printf/printf.mjs";
|
|
3
4
|
import { cat } from "./cat/cat.mjs";
|
|
4
5
|
import { grep } from "./grep/grep.mjs";
|
|
5
6
|
import { wc } from "./wc/wc.mjs";
|
|
@@ -28,6 +29,7 @@ import { tr } from "./tr/tr.mjs";
|
|
|
28
29
|
import { cut } from "./cut/cut.mjs";
|
|
29
30
|
import { od } from "./od/od.mjs";
|
|
30
31
|
import { echo as echo2 } from "./echo/echo.mjs";
|
|
32
|
+
import { printf as printf2 } from "./printf/printf.mjs";
|
|
31
33
|
import { cat as cat2 } from "./cat/cat.mjs";
|
|
32
34
|
import { grep as grep2 } from "./grep/grep.mjs";
|
|
33
35
|
import { wc as wc2 } from "./wc/wc.mjs";
|
|
@@ -57,6 +59,7 @@ import { cut as cut2 } from "./cut/cut.mjs";
|
|
|
57
59
|
import { od as od2 } from "./od/od.mjs";
|
|
58
60
|
var builtinCommands = {
|
|
59
61
|
echo: echo2,
|
|
62
|
+
printf: printf2,
|
|
60
63
|
cat: cat2,
|
|
61
64
|
grep: grep2,
|
|
62
65
|
wc: wc2,
|
|
@@ -102,6 +105,7 @@ export {
|
|
|
102
105
|
sed,
|
|
103
106
|
rm,
|
|
104
107
|
pwd,
|
|
108
|
+
printf,
|
|
105
109
|
od,
|
|
106
110
|
mv,
|
|
107
111
|
mkdir,
|
|
@@ -123,4 +127,4 @@ export {
|
|
|
123
127
|
awk
|
|
124
128
|
};
|
|
125
129
|
|
|
126
|
-
//# debugId=
|
|
130
|
+
//# debugId=56B4F99C3CC35C8764756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/commands/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { Command } from \"../types.mjs\";\n\nexport { echo } from \"./echo/echo.mjs\";\nexport { cat } from \"./cat/cat.mjs\";\nexport { grep } from \"./grep/grep.mjs\";\nexport { wc } from \"./wc/wc.mjs\";\nexport { head } from \"./head/head.mjs\";\nexport { tail } from \"./tail/tail.mjs\";\nexport { sort } from \"./sort/sort.mjs\";\nexport { uniq } from \"./uniq/uniq.mjs\";\nexport { pwd } from \"./pwd/pwd.mjs\";\nexport { ls } from \"./ls/ls.mjs\";\nexport { mkdir } from \"./mkdir/mkdir.mjs\";\nexport { rm } from \"./rm/rm.mjs\";\nexport { test, bracket } from \"./test/test.mjs\";\nexport { trueCmd, falseCmd } from \"./true-false/true-false.mjs\";\nexport { touch } from \"./touch/touch.mjs\";\nexport { cp } from \"./cp/cp.mjs\";\nexport { mv } from \"./mv/mv.mjs\";\nexport { tee } from \"./tee/tee.mjs\";\nexport { tree } from \"./tree/tree.mjs\";\nexport { find } from \"./find/find.mjs\";\nexport { sed } from \"./sed/sed.mjs\";\nexport { awk } from \"./awk/awk.mjs\";\nexport { breakCmd, continueCmd } from \"./break-continue/break-continue.mjs\";\nexport { colon } from \"./colon/colon.mjs\";\nexport { cd } from \"./cd/cd.mjs\";\nexport { tr } from \"./tr/tr.mjs\";\nexport { cut } from \"./cut/cut.mjs\";\nexport { od } from \"./od/od.mjs\";\n\n// Re-export all commands as a bundle\nimport { echo } from \"./echo/echo.mjs\";\nimport { cat } from \"./cat/cat.mjs\";\nimport { grep } from \"./grep/grep.mjs\";\nimport { wc } from \"./wc/wc.mjs\";\nimport { head } from \"./head/head.mjs\";\nimport { tail } from \"./tail/tail.mjs\";\nimport { sort } from \"./sort/sort.mjs\";\nimport { uniq } from \"./uniq/uniq.mjs\";\nimport { pwd } from \"./pwd/pwd.mjs\";\nimport { ls } from \"./ls/ls.mjs\";\nimport { mkdir } from \"./mkdir/mkdir.mjs\";\nimport { rm } from \"./rm/rm.mjs\";\nimport { test, bracket } from \"./test/test.mjs\";\nimport { trueCmd, falseCmd } from \"./true-false/true-false.mjs\";\nimport { touch } from \"./touch/touch.mjs\";\nimport { cp } from \"./cp/cp.mjs\";\nimport { mv } from \"./mv/mv.mjs\";\nimport { tee } from \"./tee/tee.mjs\";\nimport { tree } from \"./tree/tree.mjs\";\nimport { find } from \"./find/find.mjs\";\nimport { sed } from \"./sed/sed.mjs\";\nimport { awk } from \"./awk/awk.mjs\";\nimport { breakCmd, continueCmd } from \"./break-continue/break-continue.mjs\";\nimport { colon } from \"./colon/colon.mjs\";\nimport { cd } from \"./cd/cd.mjs\";\nimport { tr } from \"./tr/tr.mjs\";\nimport { cut } from \"./cut/cut.mjs\";\nimport { od } from \"./od/od.mjs\";\n\nexport const builtinCommands: Record<string, Command> = {\n echo,\n cat,\n grep,\n wc,\n head,\n tail,\n sort,\n uniq,\n pwd,\n ls,\n mkdir,\n rm,\n test,\n \"[\": bracket,\n true: trueCmd,\n false: falseCmd,\n touch,\n cp,\n mv,\n tee,\n tree,\n find,\n sed,\n awk,\n break: breakCmd,\n continue: continueCmd,\n \":\": colon,\n cd,\n tr,\n cut,\n od,\n};\n"
|
|
5
|
+
"import type { Command } from \"../types.mjs\";\n\nexport { echo } from \"./echo/echo.mjs\";\nexport { printf } from \"./printf/printf.mjs\";\nexport { cat } from \"./cat/cat.mjs\";\nexport { grep } from \"./grep/grep.mjs\";\nexport { wc } from \"./wc/wc.mjs\";\nexport { head } from \"./head/head.mjs\";\nexport { tail } from \"./tail/tail.mjs\";\nexport { sort } from \"./sort/sort.mjs\";\nexport { uniq } from \"./uniq/uniq.mjs\";\nexport { pwd } from \"./pwd/pwd.mjs\";\nexport { ls } from \"./ls/ls.mjs\";\nexport { mkdir } from \"./mkdir/mkdir.mjs\";\nexport { rm } from \"./rm/rm.mjs\";\nexport { test, bracket } from \"./test/test.mjs\";\nexport { trueCmd, falseCmd } from \"./true-false/true-false.mjs\";\nexport { touch } from \"./touch/touch.mjs\";\nexport { cp } from \"./cp/cp.mjs\";\nexport { mv } from \"./mv/mv.mjs\";\nexport { tee } from \"./tee/tee.mjs\";\nexport { tree } from \"./tree/tree.mjs\";\nexport { find } from \"./find/find.mjs\";\nexport { sed } from \"./sed/sed.mjs\";\nexport { awk } from \"./awk/awk.mjs\";\nexport { breakCmd, continueCmd } from \"./break-continue/break-continue.mjs\";\nexport { colon } from \"./colon/colon.mjs\";\nexport { cd } from \"./cd/cd.mjs\";\nexport { tr } from \"./tr/tr.mjs\";\nexport { cut } from \"./cut/cut.mjs\";\nexport { od } from \"./od/od.mjs\";\n\n// Re-export all commands as a bundle\nimport { echo } from \"./echo/echo.mjs\";\nimport { printf } from \"./printf/printf.mjs\";\nimport { cat } from \"./cat/cat.mjs\";\nimport { grep } from \"./grep/grep.mjs\";\nimport { wc } from \"./wc/wc.mjs\";\nimport { head } from \"./head/head.mjs\";\nimport { tail } from \"./tail/tail.mjs\";\nimport { sort } from \"./sort/sort.mjs\";\nimport { uniq } from \"./uniq/uniq.mjs\";\nimport { pwd } from \"./pwd/pwd.mjs\";\nimport { ls } from \"./ls/ls.mjs\";\nimport { mkdir } from \"./mkdir/mkdir.mjs\";\nimport { rm } from \"./rm/rm.mjs\";\nimport { test, bracket } from \"./test/test.mjs\";\nimport { trueCmd, falseCmd } from \"./true-false/true-false.mjs\";\nimport { touch } from \"./touch/touch.mjs\";\nimport { cp } from \"./cp/cp.mjs\";\nimport { mv } from \"./mv/mv.mjs\";\nimport { tee } from \"./tee/tee.mjs\";\nimport { tree } from \"./tree/tree.mjs\";\nimport { find } from \"./find/find.mjs\";\nimport { sed } from \"./sed/sed.mjs\";\nimport { awk } from \"./awk/awk.mjs\";\nimport { breakCmd, continueCmd } from \"./break-continue/break-continue.mjs\";\nimport { colon } from \"./colon/colon.mjs\";\nimport { cd } from \"./cd/cd.mjs\";\nimport { tr } from \"./tr/tr.mjs\";\nimport { cut } from \"./cut/cut.mjs\";\nimport { od } from \"./od/od.mjs\";\n\nexport const builtinCommands: Record<string, Command> = {\n echo,\n printf,\n cat,\n grep,\n wc,\n head,\n tail,\n sort,\n uniq,\n pwd,\n ls,\n mkdir,\n rm,\n test,\n \"[\": bracket,\n true: trueCmd,\n false: falseCmd,\n touch,\n cp,\n mv,\n tee,\n tree,\n find,\n sed,\n awk,\n break: breakCmd,\n continue: continueCmd,\n \":\": colon,\n cd,\n tr,\n cut,\n od,\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA,iBAAS;AACT,gBAAS;AACT,iBAAS;AACT,eAAS;AACT,iBAAS;AACT,iBAAS;AACT,iBAAS;AACT,iBAAS;AACT,gBAAS;AACT,eAAS;AACT,kBAAS;AACT,eAAS;AACT,iBAAS,kBAAM;AACf,oBAAS,sBAAS;AAClB,kBAAS;AACT,eAAS;AACT,eAAS;AACT,gBAAS;AACT,iBAAS;AACT,iBAAS;AACT,gBAAS;AACT,gBAAS;AACT,qBAAS,0BAAU;AACnB,kBAAS;AACT,eAAS;AACT,eAAS;AACT,gBAAS;AACT,eAAS;AAEF,IAAM,kBAA2C;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV,KAAK;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA,iBAAS;AACT,mBAAS;AACT,gBAAS;AACT,iBAAS;AACT,eAAS;AACT,iBAAS;AACT,iBAAS;AACT,iBAAS;AACT,iBAAS;AACT,gBAAS;AACT,eAAS;AACT,kBAAS;AACT,eAAS;AACT,iBAAS,kBAAM;AACf,oBAAS,sBAAS;AAClB,kBAAS;AACT,eAAS;AACT,eAAS;AACT,gBAAS;AACT,iBAAS;AACT,iBAAS;AACT,gBAAS;AACT,gBAAS;AACT,qBAAS,0BAAU;AACnB,kBAAS;AACT,eAAS;AACT,eAAS;AACT,gBAAS;AACT,eAAS;AAEF,IAAM,kBAA2C;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV,KAAK;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
|
|
8
|
+
"debugId": "56B4F99C3CC35C8764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// src/commands/printf/printf.ts
|
|
2
|
+
var INTEGER_SPECIFIERS = new Set(["d", "i", "u", "o", "x", "X"]);
|
|
3
|
+
var FLOAT_SPECIFIERS = new Set(["f", "F", "e", "E", "g", "G"]);
|
|
4
|
+
var STRING_SPECIFIERS = new Set(["s", "b", "c"]);
|
|
5
|
+
var LENGTH_MODIFIERS = new Set(["h", "l", "L", "j", "z", "t"]);
|
|
6
|
+
function isOctalDigit(char) {
|
|
7
|
+
return char !== undefined && char >= "0" && char <= "7";
|
|
8
|
+
}
|
|
9
|
+
function isDigit(char) {
|
|
10
|
+
return char !== undefined && char >= "0" && char <= "9";
|
|
11
|
+
}
|
|
12
|
+
function readEscape(input, index) {
|
|
13
|
+
if (index + 1 >= input.length) {
|
|
14
|
+
return { text: "\\", nextIndex: index + 1, stop: false };
|
|
15
|
+
}
|
|
16
|
+
const char = input[index + 1];
|
|
17
|
+
if (char === "c") {
|
|
18
|
+
return { text: "", nextIndex: index + 2, stop: true };
|
|
19
|
+
}
|
|
20
|
+
if (isOctalDigit(char)) {
|
|
21
|
+
let digits = "";
|
|
22
|
+
let nextIndex = index + 1;
|
|
23
|
+
if (input[nextIndex] === "0") {
|
|
24
|
+
nextIndex++;
|
|
25
|
+
}
|
|
26
|
+
while (digits.length < 3 && isOctalDigit(input[nextIndex])) {
|
|
27
|
+
digits += input[nextIndex];
|
|
28
|
+
nextIndex++;
|
|
29
|
+
}
|
|
30
|
+
const codePoint = digits === "" ? 0 : Number.parseInt(digits, 8);
|
|
31
|
+
return { text: String.fromCharCode(codePoint), nextIndex, stop: false };
|
|
32
|
+
}
|
|
33
|
+
if (char === "x") {
|
|
34
|
+
let digits = "";
|
|
35
|
+
let nextIndex = index + 2;
|
|
36
|
+
while (digits.length < 2 && nextIndex < input.length && /[0-9a-fA-F]/.test(input[nextIndex])) {
|
|
37
|
+
digits += input[nextIndex];
|
|
38
|
+
nextIndex++;
|
|
39
|
+
}
|
|
40
|
+
if (digits.length > 0) {
|
|
41
|
+
return { text: String.fromCharCode(Number.parseInt(digits, 16)), nextIndex, stop: false };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
switch (char) {
|
|
45
|
+
case "a":
|
|
46
|
+
return { text: "\x07", nextIndex: index + 2, stop: false };
|
|
47
|
+
case "b":
|
|
48
|
+
return { text: "\b", nextIndex: index + 2, stop: false };
|
|
49
|
+
case "f":
|
|
50
|
+
return { text: "\f", nextIndex: index + 2, stop: false };
|
|
51
|
+
case "n":
|
|
52
|
+
return { text: `
|
|
53
|
+
`, nextIndex: index + 2, stop: false };
|
|
54
|
+
case "r":
|
|
55
|
+
return { text: "\r", nextIndex: index + 2, stop: false };
|
|
56
|
+
case "t":
|
|
57
|
+
return { text: "\t", nextIndex: index + 2, stop: false };
|
|
58
|
+
case "v":
|
|
59
|
+
return { text: "\v", nextIndex: index + 2, stop: false };
|
|
60
|
+
case "\\":
|
|
61
|
+
return { text: "\\", nextIndex: index + 2, stop: false };
|
|
62
|
+
default:
|
|
63
|
+
return { text: `\\${char}`, nextIndex: index + 2, stop: false };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function expandPrintfEscapes(input) {
|
|
67
|
+
let text = "";
|
|
68
|
+
for (let i = 0;i < input.length; ) {
|
|
69
|
+
if (input[i] !== "\\") {
|
|
70
|
+
text += input[i];
|
|
71
|
+
i++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const escape = readEscape(input, i);
|
|
75
|
+
text += escape.text;
|
|
76
|
+
i = escape.nextIndex;
|
|
77
|
+
if (escape.stop) {
|
|
78
|
+
return { text, stop: true };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { text, stop: false };
|
|
82
|
+
}
|
|
83
|
+
function readNumber(input, index) {
|
|
84
|
+
let digits = "";
|
|
85
|
+
let nextIndex = index;
|
|
86
|
+
while (isDigit(input[nextIndex])) {
|
|
87
|
+
digits += input[nextIndex];
|
|
88
|
+
nextIndex++;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
value: digits === "" ? undefined : Number.parseInt(digits, 10),
|
|
92
|
+
nextIndex
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function parseConversion(format, index) {
|
|
96
|
+
let nextIndex = index + 1;
|
|
97
|
+
let flags = "";
|
|
98
|
+
while (true) {
|
|
99
|
+
const flag = format[nextIndex];
|
|
100
|
+
if (flag === undefined || !"-+ #0".includes(flag)) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
flags += flag;
|
|
104
|
+
nextIndex++;
|
|
105
|
+
}
|
|
106
|
+
const widthResult = readNumber(format, nextIndex);
|
|
107
|
+
const width = widthResult.value;
|
|
108
|
+
nextIndex = widthResult.nextIndex;
|
|
109
|
+
let precision;
|
|
110
|
+
if (format[nextIndex] === ".") {
|
|
111
|
+
const precisionResult = readNumber(format, nextIndex + 1);
|
|
112
|
+
precision = precisionResult.value ?? 0;
|
|
113
|
+
nextIndex = precisionResult.nextIndex;
|
|
114
|
+
}
|
|
115
|
+
if (LENGTH_MODIFIERS.has(format[nextIndex] ?? "")) {
|
|
116
|
+
const modifier = format[nextIndex];
|
|
117
|
+
nextIndex++;
|
|
118
|
+
if (modifier === "h" && format[nextIndex] === "h" || modifier === "l" && format[nextIndex] === "l") {
|
|
119
|
+
nextIndex++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const specifier = format[nextIndex];
|
|
123
|
+
if (specifier === undefined) {
|
|
124
|
+
return { nextIndex, error: "missing format character" };
|
|
125
|
+
}
|
|
126
|
+
if (!INTEGER_SPECIFIERS.has(specifier) && !FLOAT_SPECIFIERS.has(specifier) && !STRING_SPECIFIERS.has(specifier)) {
|
|
127
|
+
return { nextIndex: nextIndex + 1, error: `invalid format character '${specifier}'` };
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
spec: { flags, width, precision, specifier },
|
|
131
|
+
nextIndex: nextIndex + 1
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function pad(value, spec, numeric = false) {
|
|
135
|
+
const width = spec.width ?? 0;
|
|
136
|
+
if (value.length >= width) {
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
const leftAlign = spec.flags.includes("-");
|
|
140
|
+
const useZeroPad = numeric && spec.flags.includes("0") && !leftAlign && spec.precision === undefined;
|
|
141
|
+
const padChar = useZeroPad ? "0" : " ";
|
|
142
|
+
const padding = padChar.repeat(width - value.length);
|
|
143
|
+
if (leftAlign) {
|
|
144
|
+
return value + padding;
|
|
145
|
+
}
|
|
146
|
+
if (useZeroPad && (value.startsWith("-") || value.startsWith("+") || value.startsWith(" "))) {
|
|
147
|
+
return value[0] + padding + value.slice(1);
|
|
148
|
+
}
|
|
149
|
+
if (useZeroPad && (value.startsWith("0x") || value.startsWith("0X"))) {
|
|
150
|
+
return value.slice(0, 2) + padding + value.slice(2);
|
|
151
|
+
}
|
|
152
|
+
return padding + value;
|
|
153
|
+
}
|
|
154
|
+
function integerFromArg(arg) {
|
|
155
|
+
const trimmed = arg.trim();
|
|
156
|
+
if (/^[+-]?0[xX][0-9a-fA-F]+$/.test(trimmed)) {
|
|
157
|
+
const sign = trimmed.startsWith("-") ? -1 : 1;
|
|
158
|
+
return sign * Number.parseInt(trimmed.replace(/^[+-]?0[xX]/, ""), 16);
|
|
159
|
+
}
|
|
160
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
161
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
162
|
+
}
|
|
163
|
+
function floatFromArg(arg) {
|
|
164
|
+
const parsed = Number.parseFloat(arg.trim());
|
|
165
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
166
|
+
}
|
|
167
|
+
function formatInteger(arg, spec) {
|
|
168
|
+
const originalValue = Math.trunc(integerFromArg(arg));
|
|
169
|
+
const unsignedValue = originalValue < 0 ? originalValue >>> 0 : originalValue;
|
|
170
|
+
let value = spec.specifier === "u" || spec.specifier === "o" || spec.specifier === "x" || spec.specifier === "X" ? unsignedValue : originalValue;
|
|
171
|
+
let sign = "";
|
|
172
|
+
if ((spec.specifier === "d" || spec.specifier === "i") && value < 0) {
|
|
173
|
+
sign = "-";
|
|
174
|
+
value = Math.abs(value);
|
|
175
|
+
} else if ((spec.specifier === "d" || spec.specifier === "i") && spec.flags.includes("+")) {
|
|
176
|
+
sign = "+";
|
|
177
|
+
} else if ((spec.specifier === "d" || spec.specifier === "i") && spec.flags.includes(" ")) {
|
|
178
|
+
sign = " ";
|
|
179
|
+
}
|
|
180
|
+
let digits;
|
|
181
|
+
if (spec.specifier === "o") {
|
|
182
|
+
digits = value.toString(8);
|
|
183
|
+
} else if (spec.specifier === "x" || spec.specifier === "X") {
|
|
184
|
+
digits = value.toString(16);
|
|
185
|
+
if (spec.specifier === "X") {
|
|
186
|
+
digits = digits.toUpperCase();
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
digits = value.toString(10);
|
|
190
|
+
}
|
|
191
|
+
if (spec.precision !== undefined) {
|
|
192
|
+
if (spec.precision === 0 && value === 0) {
|
|
193
|
+
digits = "";
|
|
194
|
+
} else {
|
|
195
|
+
digits = digits.padStart(spec.precision, "0");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
let prefix = "";
|
|
199
|
+
if (spec.flags.includes("#")) {
|
|
200
|
+
if (spec.specifier === "o" && !digits.startsWith("0")) {
|
|
201
|
+
prefix = "0";
|
|
202
|
+
} else if (spec.specifier === "x" && value !== 0) {
|
|
203
|
+
prefix = "0x";
|
|
204
|
+
} else if (spec.specifier === "X" && value !== 0) {
|
|
205
|
+
prefix = "0X";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return pad(sign + prefix + digits, spec, true);
|
|
209
|
+
}
|
|
210
|
+
function formatFloat(arg, spec) {
|
|
211
|
+
const value = floatFromArg(arg);
|
|
212
|
+
const precision = spec.precision ?? 6;
|
|
213
|
+
let formatted;
|
|
214
|
+
switch (spec.specifier) {
|
|
215
|
+
case "e":
|
|
216
|
+
case "E":
|
|
217
|
+
formatted = value.toExponential(precision);
|
|
218
|
+
break;
|
|
219
|
+
case "g":
|
|
220
|
+
case "G":
|
|
221
|
+
formatted = value.toPrecision(precision === 0 ? 1 : precision);
|
|
222
|
+
break;
|
|
223
|
+
default:
|
|
224
|
+
formatted = value.toFixed(precision);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
if (spec.specifier === "E" || spec.specifier === "G" || spec.specifier === "F") {
|
|
228
|
+
formatted = formatted.toUpperCase();
|
|
229
|
+
}
|
|
230
|
+
if (value >= 0 && spec.flags.includes("+")) {
|
|
231
|
+
formatted = `+${formatted}`;
|
|
232
|
+
} else if (value >= 0 && spec.flags.includes(" ")) {
|
|
233
|
+
formatted = ` ${formatted}`;
|
|
234
|
+
}
|
|
235
|
+
return pad(formatted, spec, true);
|
|
236
|
+
}
|
|
237
|
+
function formatString(value, spec) {
|
|
238
|
+
const truncated = spec.precision === undefined ? value : value.slice(0, spec.precision);
|
|
239
|
+
return pad(truncated, spec);
|
|
240
|
+
}
|
|
241
|
+
function renderConversion(spec, args, argIndex) {
|
|
242
|
+
const hasArg = argIndex < args.length;
|
|
243
|
+
const arg = hasArg ? args[argIndex] : "";
|
|
244
|
+
const nextArgIndex = hasArg ? argIndex + 1 : argIndex;
|
|
245
|
+
if (spec.specifier === "s") {
|
|
246
|
+
return {
|
|
247
|
+
output: formatString(arg, spec),
|
|
248
|
+
nextArgIndex,
|
|
249
|
+
consumedArg: hasArg,
|
|
250
|
+
stop: false
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (spec.specifier === "b") {
|
|
254
|
+
const expanded = expandPrintfEscapes(arg);
|
|
255
|
+
return {
|
|
256
|
+
output: formatString(expanded.text, spec),
|
|
257
|
+
nextArgIndex,
|
|
258
|
+
consumedArg: hasArg,
|
|
259
|
+
stop: expanded.stop
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (spec.specifier === "c") {
|
|
263
|
+
return {
|
|
264
|
+
output: formatString(arg.slice(0, 1), spec),
|
|
265
|
+
nextArgIndex,
|
|
266
|
+
consumedArg: hasArg,
|
|
267
|
+
stop: false
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (INTEGER_SPECIFIERS.has(spec.specifier)) {
|
|
271
|
+
return {
|
|
272
|
+
output: formatInteger(hasArg ? arg : "0", spec),
|
|
273
|
+
nextArgIndex,
|
|
274
|
+
consumedArg: hasArg,
|
|
275
|
+
stop: false
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
output: formatFloat(hasArg ? arg : "0", spec),
|
|
280
|
+
nextArgIndex,
|
|
281
|
+
consumedArg: hasArg,
|
|
282
|
+
stop: false
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function renderPass(format, args, startArgIndex) {
|
|
286
|
+
let output = "";
|
|
287
|
+
let argIndex = startArgIndex;
|
|
288
|
+
let consumedArgs = 0;
|
|
289
|
+
for (let i = 0;i < format.length; ) {
|
|
290
|
+
const char = format[i];
|
|
291
|
+
if (char === "\\") {
|
|
292
|
+
const escape = readEscape(format, i);
|
|
293
|
+
output += escape.text;
|
|
294
|
+
i = escape.nextIndex;
|
|
295
|
+
if (escape.stop) {
|
|
296
|
+
return { output, nextArgIndex: argIndex, consumedArgs, stop: true };
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (char !== "%") {
|
|
301
|
+
output += char;
|
|
302
|
+
i++;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (format[i + 1] === "%") {
|
|
306
|
+
output += "%";
|
|
307
|
+
i += 2;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const parsed = parseConversion(format, i);
|
|
311
|
+
if (parsed.error || !parsed.spec) {
|
|
312
|
+
return {
|
|
313
|
+
output,
|
|
314
|
+
nextArgIndex: argIndex,
|
|
315
|
+
consumedArgs,
|
|
316
|
+
stop: false,
|
|
317
|
+
error: parsed.error ?? "invalid format"
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const rendered = renderConversion(parsed.spec, args, argIndex);
|
|
321
|
+
output += rendered.output;
|
|
322
|
+
argIndex = rendered.nextArgIndex;
|
|
323
|
+
if (rendered.consumedArg) {
|
|
324
|
+
consumedArgs++;
|
|
325
|
+
}
|
|
326
|
+
i = parsed.nextIndex;
|
|
327
|
+
if (rendered.stop) {
|
|
328
|
+
return { output, nextArgIndex: argIndex, consumedArgs, stop: true };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { output, nextArgIndex: argIndex, consumedArgs, stop: false };
|
|
332
|
+
}
|
|
333
|
+
function formatPrintf(format, args) {
|
|
334
|
+
let output = "";
|
|
335
|
+
let argIndex = 0;
|
|
336
|
+
let renderedAtLeastOnce = false;
|
|
337
|
+
while (!renderedAtLeastOnce || argIndex < args.length) {
|
|
338
|
+
const pass = renderPass(format, args, argIndex);
|
|
339
|
+
renderedAtLeastOnce = true;
|
|
340
|
+
output += pass.output;
|
|
341
|
+
argIndex = pass.nextArgIndex;
|
|
342
|
+
if (pass.error) {
|
|
343
|
+
return { output, error: pass.error };
|
|
344
|
+
}
|
|
345
|
+
if (pass.stop) {
|
|
346
|
+
return { output };
|
|
347
|
+
}
|
|
348
|
+
if (pass.consumedArgs === 0) {
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return { output };
|
|
353
|
+
}
|
|
354
|
+
var printf = async (ctx) => {
|
|
355
|
+
if (ctx.args.length === 0) {
|
|
356
|
+
await ctx.stderr.writeText(`printf: missing format operand
|
|
357
|
+
`);
|
|
358
|
+
return 1;
|
|
359
|
+
}
|
|
360
|
+
const [format, ...args] = ctx.args;
|
|
361
|
+
const result = formatPrintf(format, args);
|
|
362
|
+
if (result.output.length > 0) {
|
|
363
|
+
await ctx.stdout.writeText(result.output);
|
|
364
|
+
}
|
|
365
|
+
if (result.error) {
|
|
366
|
+
await ctx.stderr.writeText(`printf: ${result.error}
|
|
367
|
+
`);
|
|
368
|
+
return 1;
|
|
369
|
+
}
|
|
370
|
+
return 0;
|
|
371
|
+
};
|
|
372
|
+
export {
|
|
373
|
+
printf
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
//# debugId=E3692C39E2D017E164756E2164756E21
|