PS1 variable support, ~ expansion and pretty pwd

- if arsh has a PS1 env var, it will use it to display prompt. It
supports esc sequences, \u, \w
- if '~' is used, it will be expanded to $HOME env var value
- if PS1 env var uses \w, and $PWD is somewhere inside $HOME, then it
will show '~' in prompt instead of a long path
- arsh sets $SHELL env var on startup
This commit is contained in:
Aleksandr Lebedev 2026-04-21 01:36:40 +02:00
parent 3e1f2a2340
commit db5aef12c2

182
main.c
View file

@ -9,13 +9,21 @@
#include <ctype.h> #include <ctype.h>
constexpr size_t max_length = 1024; constexpr size_t max_length = 1024;
constexpr size_t max_env_var_lenght = 256; constexpr size_t max_env_var_length = 256;
const char* token_separators = " \t\n\r"; const char* token_separators = " \t\n\r";
char* esc = "\x1B";
char* default_prompt = "arsh> ";
static sigjmp_buf restart_jmp_buf; static sigjmp_buf restart_jmp_buf;
static constexpr int restart_value = 69; static constexpr int restart_value = 69;
static volatile sig_atomic_t jump_active = 0; static volatile sig_atomic_t jump_active = 0;
size_t min_size_t(size_t a, size_t b)
{
return a > b ? b : a;
}
void sigint_handler(int signo) void sigint_handler(int signo)
{ {
if(!jump_active) if(!jump_active)
@ -168,6 +176,7 @@ static void append_str(char** buf, size_t* len, size_t* cap, const char* s)
struct pipeline* parse_pipeline(char *str); struct pipeline* parse_pipeline(char *str);
int execute_pipeline(struct pipeline* pl, int capture, char** out); int execute_pipeline(struct pipeline* pl, int capture, char** out);
char* get_home();
static char* run_subcommand(const char* cmd) static char* run_subcommand(const char* cmd)
{ {
@ -223,6 +232,13 @@ char* parse_token(char** input)
continue; continue;
} }
if (!in_single && *p == '~')
{
p++;
append_str(&buf, &len, &cap, get_home());
continue;
}
if (!in_single && *p == '\\') if (!in_single && *p == '\\')
{ {
p++; p++;
@ -288,7 +304,7 @@ char* parse_token(char** input)
if (*p == '{') if (*p == '{')
{ {
p++; p++;
char name[max_env_var_lenght]; char name[max_env_var_length];
int i = 0; int i = 0;
while (*p && *p != '}') while (*p && *p != '}')
name[i++] = *p++; name[i++] = *p++;
@ -299,7 +315,7 @@ char* parse_token(char** input)
continue; continue;
} }
char name[max_env_var_lenght]; char name[max_env_var_length];
int i = 0; int i = 0;
while (*p && (isalnum(*p) || *p == '_')) while (*p && (isalnum(*p) || *p == '_'))
name[i++] = *p++; name[i++] = *p++;
@ -519,7 +535,13 @@ pid_t run_with_redir(struct command* command, int n_pipes, int (*pipes)[2])
int cd(char* path) int cd(char* path)
{ {
return chdir(path); int result = chdir(path);
char cwd[max_length];
if (getcwd(cwd, sizeof(cwd)) != NULL)
{
setenv("PWD", cwd, 1);
}
return result;
} }
int execute_pipeline(struct pipeline* pl, int capture, char** out) int execute_pipeline(struct pipeline* pl, int capture, char** out)
@ -618,10 +640,155 @@ int execute_pipeline(struct pipeline* pl, int capture, char** out)
return WEXITSTATUS(status); return WEXITSTATUS(status);
} }
char username_buf[max_env_var_length] = {0};
char* get_user()
{
char* result = getenv("USER");
if(!result)
{
return "UNKNOWN";
}
size_t length = min_size_t(sizeof(username_buf), strlen(result) + 1);
memcpy(username_buf, result, length);
return username_buf;
}
char home_buf[max_length] = {0};
char* get_home()
{
char* result = getenv("HOME");
if(!result)
{
return "UNKNOWN_HOME_PATH";
}
size_t length = min_size_t(sizeof(home_buf), strlen(result) + 1);
memcpy(home_buf, result, length);
return home_buf;
}
char pwd_buf[max_length] = {0};
char* get_pwd()
{
char* result = getenv("PWD");
if(!result)
{
return "UNKNOWN";
}
size_t length = min_size_t(sizeof(pwd_buf), strlen(result) + 1);
memcpy(pwd_buf, result, length);
return pwd_buf;
}
char* prettify_pwd(char* pwd)
{
if (!pwd)
{
fprintf(stderr, "Internal Error: pwd can't be null");
exit(1);
}
char* home = get_home();
size_t home_len = strlen(home);
size_t pwd_len = strlen(pwd);
if(pwd_len < home_len)
{
return pwd;
}
if(memcmp(home, pwd, home_len) == 0)
{
char* ret = ccalloc(sizeof(char), pwd_len - home_len + 1 + 1);
parser_allocated(ret);
ret[0] = '~';
memcpy(ret + 1, pwd + home_len, pwd_len - home_len);
ret[pwd_len - home_len + 1] = '\0';
return ret;
}
return pwd;
}
char prompt_buf[max_length] = {0};
char* generate_ps1_prompt()
{
char env_buf[max_env_var_length] = {0};
char* ps1 = getenv("PS1");
if(!ps1)
{
return default_prompt;
}
size_t ps1_len = min_size_t(sizeof(env_buf), strlen(ps1) + 1) - 1;
if(ps1_len == 0)
{
return default_prompt;
}
memcpy(env_buf, ps1, ps1_len);
env_buf[ps1_len + 1] = '\0';
if(ps1_len < 2)
{
memcpy(prompt_buf, env_buf, ps1_len + 1);
return prompt_buf;
}
size_t i = 1, j = 0;
size_t start = 0;
while(env_buf[i] != '\0' && j < sizeof(prompt_buf))
{
if(env_buf[i - 1] != '\\')
{
i++;
continue;
}
char* data = 0;
switch (env_buf[i])
{
case 'u':
data = get_user();
break;
case 'w':
data = prettify_pwd(get_pwd());
break;
case 'e':
data = esc;
break;
case ']':
case '[':
data = "";
break;
default:
i++;
continue;
}
if(!data)
{
i++;
continue;
}
size_t len = strlen(data);
if(j + i - 1 - start + len >= sizeof(prompt_buf))
{
fprintf(stderr, "Out of memory(1) for prompt: %d >= %d", j + i - start + len, sizeof(prompt_buf));
break;
}
size_t start_len = min_size_t(sizeof(prompt_buf) - j, i - start - 1);
memcpy(prompt_buf + j, env_buf + start, start_len);
j += start_len;
if(j + len >= sizeof(prompt_buf))
{
fprintf(stderr, "Out of memory(2) for prompt: %d >= %d", j + len, sizeof(prompt_buf));
break;
}
memcpy(prompt_buf + j, data, len);
i += 1; start = i; j += len;
}
size_t start_len = min_size_t(sizeof(prompt_buf) - j, i - start);
memcpy(prompt_buf + j, env_buf + start, start_len);
j += start_len;
prompt_buf[j] = '\0';
return prompt_buf;
}
int main(int argc, const char* argv[]) int main(int argc, const char* argv[])
{ {
setenv("SHELL", argv[0], 1);
FILE* stream = stdin; FILE* stream = stdin;
char* prompt = "arsh> "; char* prompt = generate_ps1_prompt();
const bool spawn_command = argc >= 2 && strcmp(argv[1], "-c") == 0; const bool spawn_command = argc >= 2 && strcmp(argv[1], "-c") == 0;
if(spawn_command) if(spawn_command)
{ {
@ -674,5 +841,10 @@ int main(int argc, const char* argv[])
execute_pipeline(pipeline, 0, NULL); execute_pipeline(pipeline, 0, NULL);
parser_free(); parser_free();
if(prompt)
{
prompt = generate_ps1_prompt();
}
} }
} }