I’m stuck on Stage #NI6.
I’ve tried adding debuggers and simulating the test scenario. I’m pretty sure my parser is working fine. the problem seems to be the cat output, maybe my code to run external programs is not correct but then again, it works fine locally.
Here are my logs:
[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
[tester::#NI6] Running tests for Stage #NI6 (Quoting - Single quotes)
[tester::#NI6] [setup] export PATH=/tmp/strawberry/pear/pear:$PATH
[tester::#NI6] Running ./your_program.sh
[tester::#NI6] [setup] echo -n "blueberry banana." > "/tmp/ant/f 15"
[tester::#NI6] [setup] echo -n "pineapple raspberry." > "/tmp/ant/f 40"
[tester::#NI6] [setup] echo "grape strawberry." > "/tmp/ant/f 83"
[your-program] $ echo 'hello shell'
[your-program] hello shell
[tester::#NI6] ✓ Received expected response
[your-program] $ echo shell script
[your-program] shell script
[tester::#NI6] ✓ Received expected response
[your-program] $ echo 'world example' 'script''hello' shell''test
[your-program] world example scripthello shelltest
[tester::#NI6] ✓ Received expected response
[your-program] $ cat '/tmp/ant/f 15' '/tmp/ant/f 40' '/tmp/ant/f 83'
[your-program]
[tester::#NI6] ^ Line does not match expected value.
[tester::#NI6] Expected: "blueberry banana.pineapple raspberry.grape strawberry."
[tester::#NI6] Received: "" (empty line)
[your-program] $
[tester::#NI6] Assertion failed.
[tester::#NI6] Test failed
And here’s a snippet of my code:
import fs from "fs";
import path from "path";
import { Interface as RLInterface, createInterface } from "readline";
import { spawnSync } from "child_process";
export const canExecute = (filePath: string) => {
try {
fs.accessSync(filePath, fs.constants.X_OK);
return true;
} catch (e) {
return false;
}
};
export const findExec = (exeName: string) => {
const PATH = process.env.PATH?.split(path.delimiter) || [];
for (const basePath of PATH) {
const filePath = path.join(basePath, exeName);
if (fs.existsSync(filePath) && canExecute(filePath)) {
return filePath;
}
}
return null;
};
type CommandHandler = (
args: string[],
rl: RLInterface,
sd: SessionData
) => void;
const builtins = new Set();
const builtinHandlers: Record<string, CommandHandler> = {
exit: (_, rl: RLInterface) => {
rl.close();
},
echo: (args) => {
console.log(args.join(" "));
},
type: (args) => {
if (builtins.has(args[0])) {
console.log(`${args[0]} is a shell builtin`);
} else {
const execPath = findExec(args[0]);
if (execPath) {
console.log(`${args[0]} is ${execPath}`);
} else {
console.log(`${args[0]}: not found`);
}
}
},
pwd: (_, __, sd: SessionData) => {
console.log(sd.currentDirectory);
},
cd: (args, __, sd: SessionData) => {
const homeDir = process.env.HOME || "/";
const newPath =
args[0] === "/"
? "/"
: args[0].startsWith("~")
? path.resolve(sd.currentDirectory, args[0].replace("~", homeDir))
: path.resolve(sd.currentDirectory, args[0]);
if (fs.existsSync(newPath) && fs.statSync(newPath).isDirectory()) {
sd.currentDirectory = newPath;
} else {
console.log(`cd: ${newPath}: No such file or directory`);
}
},
};
Object.keys(builtinHandlers).forEach((name) => {
builtins.add(name);
});
export { builtins, builtinHandlers };
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
async function read() {
const input = await new Promise<string>((resolve) => {
rl.question("$ ", (command) => {
resolve(command.trim());
});
});
return input;
}
const runProgram = (command: string, args: string[]) => {
debugger;
const prog = spawnSync(command, args, { shell: true });
if (prog.stdout) console.log(prog.stdout.toString());
else if (prog.stderr) console.log(prog.stderr.toString());
};
export interface SessionData {
currentDirectory: string;
}
const sessionData = {
currentDirectory: process.cwd(),
};
const parseArgs = (input: string) => {
const args: string[] = [];
let current = "";
let i = 0;
let inSingle = false;
while (i < input.length) {
const ch = input[i];
if (inSingle) {
if (ch === "'") {
// end of single-quoted section
inSingle = false;
} else {
current += ch; // literal
}
i++;
continue;
}
// Normal mode
if (ch === "'") {
inSingle = true;
i++;
continue;
}
if (/\s/.test(ch)) {
if (current.length > 0) {
args.push(current);
current = "";
}
i++;
continue;
}
current += ch;
i++;
}
if (current.length > 0) {
args.push(current);
}
return args;
};
async function evaluate(command: string) {
const parts = parseArgs(command);
const [name, ...args] = parts;
if (!name) {
return;
}
if (builtins.has(name)) {
const handler = builtinHandlers[name];
return handler(args, rl, sessionData);
} else {
const execPath = findExec(name);
if (execPath) {
runProgram(name, args);
} else {
console.log(`${command}: command not found`);
}
}
}
while (true) {
const command = await read();
if (command === "exit") {
rl.close();
break;
}
await evaluate(command);
}
