I’m stuck on Stage #GP4
I’ve tried capturing OS-specific home env vars in addition to user.home (which I expect should be all that is needed) and the home directory seems correct but the tests are failing. I think either the test is bugged or I’m fundamentally misunderstanding the challenge. From what I see, it is switching to /root which is in fact the correct home directory when running as root user on linux, but it is expecting /tmp/mango/apple/banana.
Here are my logs:
Submitting changes (commit: 7d05aac)...
⚡ This is a turbo test run. https://codecrafters.io/turbo
Running tests on your code. Logs should appear shortly...
[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
[tester::#GP4] Running tests for Stage #GP4 (Navigation - The cd builtin: Home directory)
[tester::#GP4] Running ./your_program.sh
[your-program] $ cd /tmp/pineapple/apple/blueberry
[your-program] $ pwd
[your-program] /tmp/pineapple/apple/blueberry
[tester::#GP4] Received current working directory response
[your-program] $ cd ~
[your-program] $ pwd
[your-program] /root
[tester::#GP4] ^ Line does not match expected value.
[tester::#GP4] Expected: "/tmp/mango/apple/banana"
[tester::#GP4] Received: "/root"
[your-program] $
[tester::#GP4] Test failed
And here’s a snippet of my code:
private final Map<String, Consumer<String[]>> BUILT_INS = new HashMap<>();
private Path currentPath = Path.of("").toAbsolutePath();
void main() {
// register built-ins
registerBuiltIns();
// cli processing loop
while (true) {
var input = IO.readln("$ ");
if (input == null || input.isBlank()) {
continue;
} else {
input = input.trim();
}
var hasArgs = input.contains(" ");
var command = hasArgs
? input.substring(0, input.indexOf(" "))
: input;
var args = hasArgs
? parseArgs(input.substring(command.length()).trim())
: new String[0];
if ("exit".equals(command)) {
System.exit(0);
} else if (BUILT_INS.containsKey(command)) {
BUILT_INS.get(command).accept(args);
} else {
execute(command, args);
}
}
}
private void cd(String[] args) {
if (args.length == 0) {
System.out.println("usage: cd <path>");
return;
}
Path path;
if (args[0].equals("~")) {
path = getHomePath();
} else if (args[0].startsWith("~" + File.separator)) {
path = getHomePath()
.resolve(args[0].substring(1 + File.separator.length()))
.normalize()
.toAbsolutePath();
} else {
path = currentPath
.resolve(args[0])
.normalize()
.toAbsolutePath();
}
if (Files.exists(path)) {
currentPath = path;
} else {
System.out.printf("cd: %s: No such file or directory%n", args[0]);
}
}
private void echo(String[] args) {
System.out.println(String.join(" ", args));
}
private void execute(String command, String[] args) {
var path = getExecutablePath(command);
if (path == null) {
System.out.printf("%s: command not found%n", command);
return;
}
var parts = new String[args.length + 1];
parts[0] = command;
System.arraycopy(args, 0, parts, 1, args.length);
var processBuilder = new ProcessBuilder(parts);
processBuilder.directory(currentPath.toFile());
processBuilder.redirectErrorStream(true);
try (
var process = processBuilder.start();
var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))
) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
System.out.printf("%s: command not found%n", command);
}
}
private void pwd(String[] args) {
System.out.println(currentPath);
}
private void type(String[] args) {
if (args.length == 0) {
System.out.println("usage: type <command>");
return;
}
var command = args[0];
if (BUILT_INS.containsKey(command)) {
System.out.printf("%s is a shell builtin%n", command);
return;
}
var path = getExecutablePath(command);
if (path == null) {
System.out.printf("%s: not found%n", command);
return;
}
System.out.printf("%s is %s%n", command, path);
}
private Path getExecutablePath(String command) {
var systemPath = System.getenv("PATH");
if (systemPath == null) {
return null;
}
for (var path : systemPath.split(File.pathSeparator)) {
var executablePath = Path.of(path)
.resolve(command)
.toAbsolutePath();
if (Files.exists(executablePath) && Files.isExecutable(executablePath)) {
return executablePath;
}
}
return null;
}
private Path getHomePath() {
var home = System.getProperty("user.home");
if (home == null) {
// try linux specific
home = System.getenv("HOME");
if (home == null) {
// try windows specific
home = System.getenv("USERPROFILE");
if (home == null) {
return currentPath;
}
}
}
return Path.of(home)
.normalize()
.toAbsolutePath();
}
private String[] parseArgs(String input) {
if (input.isEmpty()) {
return new String[0];
}
var args = new ArrayList<String>();
var s = new StringBuilder();
var i = 0;
// parse arguments allowing for quoted strings
// TODO impl escaping
while (i < input.length()) {
var c = input.charAt(i);
if (c == '\'') {
// treat contents within single quotes as a single argument
i++;
while (i < input.length() && input.charAt(i) != '\'') {
s.append(input.charAt(i));
i++;
}
i++;
} else if (c == '"') {
// treat contents within double quotes as a single argument
i++;
while (i < input.length() && input.charAt(i) != '"') {
s.append(input.charAt(i));
i++;
}
i++;
} else if (Character.isWhitespace(c)) {
// treat whitespace between args as delimiter
if (!s.isEmpty()) {
args.add(s.toString());
s.setLength(0);
}
i++;
} else {
s.append(c);
i++;
}
}
if (!s.isEmpty()) {
args.add(s.toString());
}
return args.toArray(String[]::new);
}
private void registerBuiltIns() {
BUILT_INS.put("cd", this::cd);
BUILT_INS.put("echo", this::echo);
BUILT_INS.put("pwd", this::pwd);
BUILT_INS.put("type", this::type);
}