diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2da4143..fd52c74 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,9 +43,6 @@ jobs: - name: Install Nix uses: DeterminateSystems/nix-installer-action@v4 - - name: Run the Magic Nix Cache - uses: DeterminateSystems/magic-nix-cache-action@v2 - - name: Run coding style checker run: | nix run github:Sigmapitech/cs \ @@ -55,6 +52,8 @@ jobs: - name: Build project run: make + - name: Run tester + run: ./validator.py sync_repository: needs: [ check_the_repository_state ] diff --git a/.gitignore b/.gitignore index ee30ab9..7ab607e 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ unit_tests # AFL afl/generated + +out.txt diff --git a/Makefile b/Makefile index 1025b62..9b70271 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ CFLAGS += -Wduplicated-cond -Wformat=2 -Wshadow -fno-builtin CFLAGS += -Wstrict-aliasing=0 -Wstrict-prototypes -Wunreachable-code CFLAGS += -Wwrite-strings -Werror=declaration-after-statement CFLAGS += -Werror=format-nonliteral -Werror=int-conversion -Werror=return-type -CFLAGS += -Wno-discarded-qualifiers +CFLAGS += -Wno-discarded-qualifiers --std=gnu2x LDFLAGS += -L . LDLIBS := -lu diff --git a/README.md b/README.md index 363f326..107eb2a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ - [ ] `time` - [ ] `trap` - [ ] `wc` -- [ ] `which`/`where` +- [x] `which`/`where` - [x] `yes` - [x] pipes @@ -41,8 +41,7 @@ - [ ] `-n` (dry run mode) - [ ] `-h` help (open man?) - [ ] autocompletion of commands -- [x] globbing (*) -- [ ] globbing +- [x] globbing - [ ] var interpreter - [ ] inhibitor - [ ] magic quotes diff --git a/ecsls.toml b/ecsls.toml index 8d1c8b6..04134d1 100644 --- a/ecsls.toml +++ b/ecsls.toml @@ -1 +1,2 @@ - +[reports] +merge = "multiplier" diff --git a/iftest.sh b/iftest.sh new file mode 100644 index 0000000..e3ffc45 --- /dev/null +++ b/iftest.sh @@ -0,0 +1,5 @@ +if ls + echo YES +else + echo NO +endif diff --git a/out.txt b/out.txt new file mode 100644 index 0000000..45f0b6a --- /dev/null +++ b/out.txt @@ -0,0 +1,3 @@ +plop +again +again diff --git a/src/parse_alias.c b/src/alias.c similarity index 82% rename from src/parse_alias.c rename to src/alias.c index 6396c1a..e8dae17 100644 --- a/src/parse_alias.c +++ b/src/alias.c @@ -4,29 +4,27 @@ ** File description: ** parse_alias */ + #include -#include #include #include -#include #include +#include -#include "utils.h" -#include "common.h" -#include "env.h" -#include "exec.h" -#include "u_mem.h" -#include "u_str.h" -#include "history.h" #include "alias.h" +#include "common.h" +#include "history.h" +#include "utils.h" -static int skip_blank(char *buffer, int i) +static +int skip_blank(char *buffer, int i) { for (; buffer[i] != 0 && isblank(buffer[i]); i++); return i; } -static int skip_to_next_token(char *buffer, int i) +static +int skip_to_next_token(char *buffer, int i) { for (; buffer[i] != 0 && is_a_token(buffer, i) == false; i++); return i; @@ -97,3 +95,18 @@ int parse_alias(char **buffer, size_t *buffer_len, alias_t *alias) need_to_replace = replace_alias(buffer, alias); return RETURN_SUCCESS; } + +alias_t init_alias(void) +{ + alias_t alias; + + alias.size = 1; + alias.alias_array = malloc(sizeof(char *) * alias.size); + alias.alias_to_replace = malloc(sizeof(char *) * alias.size); + if (!alias.alias_array || !alias.alias_to_replace) + return alias; + alias.alias_array[0] = NULL; + alias.alias_to_replace[0] = NULL; + alias.size = 0; + return alias; +} diff --git a/src/alias.h b/src/alias.h index 94348d4..bd19a51 100644 --- a/src/alias.h +++ b/src/alias.h @@ -7,9 +7,7 @@ #ifndef ALIAS_H #define ALIAS_H - #include "env.h" - #include "history.h" - #include "shell.h" + #include typedef struct alias_s { size_t size; @@ -19,5 +17,5 @@ typedef struct alias_s { void free_alias(alias_t *alias); int parse_alias(char **buffer, size_t *buffer_len, alias_t *alias); - +alias_t init_alias(void); #endif /* ALIAS*/ diff --git a/src/ast.h b/src/ast.h index 4187be7..7662f8d 100644 --- a/src/ast.h +++ b/src/ast.h @@ -19,7 +19,7 @@ #define IF_PROMPT "if? " #define T_ALL 0xff -typedef enum { +typedef enum : size_t { T_SEMICOLON = 1 << 0, // ; T_LEFT_QUOTE = 1 << 1, // " T_RIGHT_QUOTE = 1 << 2, // " @@ -37,13 +37,16 @@ typedef enum { T_IN_REDIRECT = 1 << 14, // < T_AT = 1 << 15, // < T_WHILE = 1 << 16, // while - T_IF = 1 << 17, // if - T_THEN = 1 << 18, // then - T_ELSE = 1 << 19, // else - T_ENDIF = 1 << 20, // endif - T_EOF = 1 << 21, // \0 - T_ARG = 1 << 22, - T_INVALID = 1 << 23 + T_FOREACH = 1 << 17, // foreach + T_IF = 1 << 18, // if + T_THEN = 1 << 19, // then + T_ELSE = 1 << 20, // else + T_ENDIF = 1 << 21, // endif + T_STAR = 1 << 22, // * + T_NEWLINE = 1 << 23, // \n + T_EOF = 1 << 24, // \0 + T_ARG = 1 << 25, + T_INVALID = 1 << 26 } token_type_t; typedef enum { diff --git a/src/ast/ast.c b/src/ast/ast.c index a6c62ba..39569a0 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -20,8 +20,12 @@ ast_t *parse_arg(ast_ctx_t *ctx, ast_t *node) ctx->act_tok = get_next_token(ctx); if (ctx->act_tok.type == T_SEMICOLON) return node; + if (*ctx->act_tok.str == '\\') { + ctx->act_tok = get_next_token(ctx); + ctx->act_tok.type = T_ARG; + } if (ctx->act_tok.type & (T_ARG | T_REDIRECT | T_APPEND | - T_IN_REDIRECT | T_HEREDOC | T_VAR)) { + T_IN_REDIRECT | T_HEREDOC | T_VAR | T_STAR)) { if (!ensure_node_cap(node)) return NULL; node->vector.tokens[node->vector.sz] = ctx->act_tok; @@ -159,15 +163,15 @@ ast_t *create_semi_node(ast_ctx_t *ctx, ast_t *l_node) static ast_t *fill_semi_node(ast_ctx_t *ctx, ast_t *node) { - while (ctx->act_tok.type == T_SEMICOLON) { + while (ctx->act_tok.type & (T_SEMICOLON | T_NEWLINE)) { ctx->act_tok = get_next_token(ctx); - if (ctx->act_tok.type == T_SEMICOLON) + if (ctx->act_tok.type & (T_SEMICOLON | T_NEWLINE)) continue; if (!ensure_list_cap(node)) - return false; + return NULL; node->list.nodes[node->list.sz] = parse_semi(ctx); if (node->list.nodes[node->list.sz] == NULL) - return false; + return NULL; node->list.sz++; } return node; @@ -187,7 +191,7 @@ ast_t *parse_expression(ast_ctx_t *ctx) l_node = parse_semi(ctx); if (l_node == NULL) return ctx->ast; - if (ctx->act_tok.type == T_SEMICOLON) { + if (ctx->act_tok.type & (T_SEMICOLON | T_NEWLINE)) { node = create_semi_node(ctx, l_node); if (node == NULL) return NULL; diff --git a/src/ast/tokeniser.c b/src/ast/tokeniser.c index ee99479..b06a990 100644 --- a/src/ast/tokeniser.c +++ b/src/ast/tokeniser.c @@ -32,6 +32,8 @@ const tokens_list_t TOKENS_LIST[] = { { T_THEN, "then", 4, "T_THEN"}, { T_ELSE, "else", 4, "T_ELSE"}, { T_ENDIF, "endif", 5, "T_ENDIF"}, + { T_NEWLINE, "\n", 1, "T_NEWLINE"}, + { T_STAR, "*", 1, "T_STAR"}, { T_EOF, "\0", 1, "T_EOF" } }; diff --git a/src/builtins/local.c b/src/builtins/local.c index 18be358..29b7d1c 100644 --- a/src/builtins/local.c +++ b/src/builtins/local.c @@ -20,10 +20,10 @@ bool check_local_var(char *var, char *func_name) { if (!isalpha(var[0])) - return (fprintf(stdout, "%s: Variable name must begin" + return (fprintf(stderr, "%s: Variable name must begin" " with a letter.\n", func_name), RETURN_FAILURE); if (!u_str_is_only_alnum(var)) - return (fprintf(stdout, "%s: Variable name must contain" + return (fprintf(stderr, "%s: Variable name must contain" " alphanumeric characters.\n", func_name), RETURN_FAILURE); return RETURN_SUCCESS; } diff --git a/src/builtins_handler.h b/src/builtins_handler.h index b0793c3..ac137a9 100644 --- a/src/builtins_handler.h +++ b/src/builtins_handler.h @@ -8,17 +8,21 @@ #ifndef BUILTINS_HANDLER_H #define BUILTINS_HANDLER_H + #include + + #include "alias.h" #include "env.h" #include "history.h" - #include "shell.h" - #include "alias.h" #include "local.h" + #include "shell.h" typedef struct { env_t *env; history_t *history; his_command_t *history_command; alias_t *alias; + bool is_running; + struct termios saved_term_settings; local_t *local; } exec_ctx_t; diff --git a/src/common.h b/src/common.h index b404f57..d85c86f 100644 --- a/src/common.h +++ b/src/common.h @@ -7,8 +7,12 @@ #ifndef COMMON_H #define COMMON_H + #include "exec.h" + enum { RETURN_SUCCESS = 0, RETURN_FAILURE = 1 }; + +void free_everything(exec_ctx_t *exec_ctx); #endif /* COMMON_H */ diff --git a/src/exec.c b/src/exec.c index b6174e5..432a2b0 100644 --- a/src/exec.c +++ b/src/exec.c @@ -23,7 +23,6 @@ #include "path.h" #include "u_mem.h" #include "u_str.h" -#include "alias.h" const builtins_funcs_t BUILTINS[] = { { "builtins", &builtins_builtins }, diff --git a/src/globbing.c b/src/globbing.c index 40be699..6eac797 100644 --- a/src/globbing.c +++ b/src/globbing.c @@ -29,15 +29,17 @@ bool process_globbing(char *pattern, args_t *args) { glob_t globs; int glob_result; + char *vl; glob_result = glob(pattern, GLOB_ERR, NULL, &globs); if (!check_glob_result(glob_result, args->args[0])) return false; for (size_t i = 0; i < globs.gl_pathc; i++) { ensure_args_capacity(args); - args->args[args->sz] = strdup(globs.gl_pathv[i]); - if (args->args[args->sz] == NULL) + vl = strdup(globs.gl_pathv[i]); + if (vl == NULL) return globfree(&globs), false; + args->args[args->sz] = vl; args->sz++; } globfree(&globs); @@ -48,7 +50,11 @@ bool process_args(ast_t *node, args_t *args, size_t *toks_i, ef_t *ef) { token_t tok = node->vector.tokens[*toks_i]; - if (strchr(tok.str, '*') != NULL) + if (strchr(tok.str, '\\') != NULL) { + args->args[args->sz] = tok.str; + return true; + } + if (tok.type == T_STAR || strcspn(tok.str, "[]?") != strlen(tok.str)) return (process_globbing(tok.str, args)); if (!ensure_args_capacity(args)) return false; diff --git a/src/handle_vars.c b/src/handle_vars.c index f6230be..3499ad0 100644 --- a/src/handle_vars.c +++ b/src/handle_vars.c @@ -6,6 +6,7 @@ */ #include +#include #include "ast.h" #include "env.h" @@ -22,7 +23,7 @@ char *handle_var_case(ast_t *node, exec_ctx_t *ctx, size_t *i) if (r_char == NULL) r_char = get_local_value(ctx->local, node->vector.tokens[*i].str); if (r_char == NULL) { - printf("%s: Undefined variable.\n", + dprintf(STDERR_FILENO, "%s: Undefined variable.\n", node->vector.tokens[*i].str); return NULL; } diff --git a/src/readline.c b/src/readline.c new file mode 100644 index 0000000..2eaff86 --- /dev/null +++ b/src/readline.c @@ -0,0 +1,118 @@ +/* +** EPITECH PROJECT, 2025 +** __ +** File description: +** _ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "readline.h" +#include "u_str.h" + +static +bool str_printable(char const *str, size_t size) +{ + for (size_t i = 0; i < size; i++) + if (!isprint(str[i])) + return false; + return true; +} + +static +bool ensure_buff_av_capacity(buff_t *buff, size_t requested) +{ + char *new_str; + size_t endsize = BUFF_INIT_SZ; + + if ((buff->sz + requested) < buff->cap) + return true; + for (; endsize < buff->sz + requested; endsize <<= 1); + if (endsize > buff->cap) { + new_str = realloc(buff->str, (sizeof *buff->str) * endsize); + if (new_str == NULL) + return false; + buff->str = new_str; + buff->cap = endsize; + } + return true; +} + +static +bool ensure_buff_capacity(buff_t *buff) +{ + char *new_str; + + if (buff->str == NULL) { + new_str = malloc((sizeof *buff->str) * BUFF_INIT_SZ); + if (new_str == NULL) + return false; + buff->str = new_str; + buff->cap = BUFF_INIT_SZ; + } + if (buff->sz == buff->cap) { + new_str = realloc(buff->str, (sizeof *buff->str) * buff->cap << 1); + if (new_str == NULL) + return false; + buff->str = new_str; + buff->cap <<= 1; + } + return true; +} + +static +bool append_null_terminator(buff_t *buff) +{ + if (!ensure_buff_av_capacity(buff, 1)) + return false; + buff->str[buff->sz - 1] = '\0'; + buff->sz++; + if (isatty(STDIN_FILENO)) + WRITE_CONST(STDOUT_FILENO, "\n"); + return true; +} + +static +int8_t handle_line_buff(buff_t *buff, char *read_buff, ssize_t read_size) +{ + if (*read_buff == CTRL('d')) { + buff->sz = 0; + return RETURN_SUCCESS; + } + if (isatty(STDIN_FILENO) && str_printable(read_buff, read_size)) + write(STDOUT_FILENO, read_buff, read_size); + if (!ensure_buff_av_capacity(buff, read_size)) + return RETURN_FAILURE; + strncpy(buff->str + buff->sz, + read_buff, read_size); + buff->sz += read_size; + return -1; +} + +bool readline(buff_t *buff) +{ + char read_buff[2] = ""; + ssize_t read_size = 0; + + if (!ensure_buff_capacity(buff)) + return false; + while (strchr(read_buff, '\n') == NULL && *read_buff != '\r') { + memset(read_buff, '\0', sizeof read_buff); + read_size = read(STDIN_FILENO, &read_buff, sizeof read_buff - 1); + if (read_size < 0) + return false; + if (read_size == 0) + return true; + if (handle_line_buff(buff, read_buff, read_size) > -1) + return true; + } + return append_null_terminator(buff); +} diff --git a/src/readline.h b/src/readline.h new file mode 100644 index 0000000..67d6510 --- /dev/null +++ b/src/readline.h @@ -0,0 +1,16 @@ +/* +** EPITECH PROJECT, 2025 +** __ +** File description: +** _ +*/ + +#ifndef READLINE + #define READLINE + #define BUFF_INIT_SZ 16 + #include + + #include "u_str.h" + +bool readline(buff_t *buff); +#endif /* READLINE */ diff --git a/src/shell.c b/src/shell.c index 5f25136..8fcb501 100644 --- a/src/shell.c +++ b/src/shell.c @@ -11,15 +11,16 @@ #include #include -#include "ast.h" +#include "builtins_handler.h" #include "common.h" #include "debug.h" #include "env.h" #include "history.h" +#include "local.h" +#include "readline.h" #include "shell.h" #include "u_str.h" -#include "local.h" -#include "loop.h" +#include "visitor.h" __attribute__((unused)) static @@ -58,44 +59,60 @@ void write_prompt(int is_a_tty) } static -bool change_shell_command(char **buffer, exec_ctx_t *exec_ctx, - size_t buffer_sz) +bool change_shell_command(buff_t *buff, exec_ctx_t *exec_ctx) { - size_t buffer_len = 0; char *tmp_buff = NULL; + size_t buffer_len; - if (getline(buffer, &buffer_sz, stdin) == -1) - return true; - tmp_buff = (*buffer); - buffer_len = update_command(&tmp_buff, &buffer_sz, exec_ctx); + buff->sz = 0; + if (!readline(buff)) + return false; + if (!buff->sz) + return false; + tmp_buff = buff->str; + buffer_len = update_command(&tmp_buff, &buff->sz, exec_ctx); if (buffer_len == 0) - return false; - if (buffer_len < 1 || !u_str_is_alnum(tmp_buff)) { - check_basic_error(tmp_buff); - free(tmp_buff); - return false; + return true; + if (buffer_len < 1 || !u_str_is_alnum(tmp_buff)) + return check_basic_error(tmp_buff), true; + U_DEBUG("Buffer [%lu] [%s]\n", buffer_len, tmp_buff); + if (visitor(tmp_buff, exec_ctx) == RETURN_FAILURE + && !exec_ctx->history->last_exit_code) + exec_ctx->history->last_exit_code = RETURN_FAILURE; + return true; +} + +static +void init_shell_repl(exec_ctx_t *exec_ctx) +{ + struct termios repl_settings; + + exec_ctx->is_running = true; + if (isatty(STDIN_FILENO)) { + tcgetattr(STDIN_FILENO, &repl_settings); + exec_ctx->saved_term_settings = repl_settings; + repl_settings.c_iflag = IXON; + repl_settings.c_lflag = ~(ECHO | ICANON); + tcsetattr(STDIN_FILENO, TCSANOW, &repl_settings); } - U_DEBUG("Buffer [%lu] [%s]\n", buffer_len, buffer); - visitor(tmp_buff, exec_ctx); - free(tmp_buff); - return false; } static int shell_loop(int is_a_tty, exec_ctx_t *exec_ctx) { - char *buffer = NULL; - size_t buffer_sz = 0; + buff_t buff = { .str = NULL, 0, .cap = BUFF_INIT_SZ }; + init_shell_repl(exec_ctx); while (true) { write_prompt(is_a_tty); - if (change_shell_command(&buffer, exec_ctx, buffer_sz) == true) + if (!change_shell_command(&buff, exec_ctx)) return exec_ctx->history->last_exit_code; } free(exec_ctx->history_command); - return (free(buffer), exec_ctx->history->last_exit_code); + return free(buff.str), exec_ctx->history->last_exit_code; } +static his_command_t *init_cmd_history(void) { his_command_t *cmd_history = malloc(sizeof(his_command_t) * 100); @@ -126,41 +143,26 @@ bool error_in_init(exec_ctx_t *exec_ctx) return false; } -alias_t init_alias(void) -{ - alias_t alias; - - alias.size = 1; - alias.alias_array = malloc(sizeof(char *) * alias.size); - alias.alias_to_replace = malloc(sizeof(char *) * alias.size); - if (!alias.alias_array || !alias.alias_to_replace) - return alias; - alias.alias_array[0] = NULL; - alias.alias_to_replace[0] = NULL; - alias.size = 0; - return alias; -} - int shell(char **env_ptr) { alias_t alias = init_alias(); env_t env = parse_env(env_ptr); - history_t history = { .cmd_history = NULL, 0, .last_chdir = NULL}; + history_t history = { .cmd_history = NULL, .last_exit_code = 0, + .last_chdir = NULL}; his_command_t *cmd_history = init_cmd_history(); local_t local = create_local(); exec_ctx_t exec_ctx = {.env = &env, .local = &local, .history = &history, .history_command = cmd_history, .alias = &alias}; int shell_result; - if (error_in_init(&exec_ctx) == true){ + if (error_in_init(&exec_ctx) == true) return RETURN_FAILURE; - } U_DEBUG_CALL(debug_env_entries, &env); signal(SIGINT, ignore_sigint); shell_result = shell_loop(isatty(STDIN_FILENO), &exec_ctx); - if (isatty(STDIN_FILENO)) + if (isatty(STDIN_FILENO)) { WRITE_CONST(STDOUT_FILENO, "exit\n"); - free_env(exec_ctx.env); - free_alias(exec_ctx.alias); - return shell_result; + tcsetattr(STDIN_FILENO, TCSANOW, &exec_ctx.saved_term_settings); + } + return free_everything(&exec_ctx), shell_result; } diff --git a/src/shell.h b/src/shell.h index 237ad1c..ec0f497 100644 --- a/src/shell.h +++ b/src/shell.h @@ -8,6 +8,7 @@ #ifndef SHELL_H #define SHELL_H #include "vt100_esc_codes.h" + #define SHELL_PROMPT RED "|> " RESET typedef struct { @@ -15,5 +16,6 @@ typedef struct { int last_exit_code; char *last_chdir; } history_t; + int shell(char **env); #endif /* SHELL_H */ diff --git a/src/update_command.c b/src/update_command.c index 9a4d565..d2ed10d 100644 --- a/src/update_command.c +++ b/src/update_command.c @@ -41,7 +41,6 @@ size_t update_command(char **buffer, buffer_len = u_strlen(*buffer); if (buffer_len < 2) return RETURN_FAILURE; - (*buffer)[buffer_len - 1] = '\0'; if (parse_history(buffer, &buffer_len, buffer_sz, &exec_ctx->history_command) == 84) return RETURN_SUCCESS; diff --git a/src/utils/free.c b/src/utils/free.c new file mode 100644 index 0000000..d448768 --- /dev/null +++ b/src/utils/free.c @@ -0,0 +1,17 @@ +/* +** EPITECH PROJECT, 2025 +** __ +** File description: +** _ +*/ + +#include + +#include "exec.h" + +void free_everything(exec_ctx_t *exec_ctx) +{ + free_env(exec_ctx->env); + free_alias(exec_ctx->alias); + free(exec_ctx->history_command); +} diff --git a/src/visitor.c b/src/visitor.c index d9f492e..c22c0e0 100644 --- a/src/visitor.c +++ b/src/visitor.c @@ -146,7 +146,7 @@ int visit_expression(ef_t *ef, ast_t *node) { int result = RETURN_FAILURE; - if (node->tok.type == T_SEMICOLON) + if (node->tok.type & (T_SEMICOLON | T_NEWLINE)) result = visit_semi(ef, node); if (node->tok.type & (T_IF | T_AND | T_OR)) result = visit_condition(ef, node); @@ -178,6 +178,8 @@ void remove_trailing_semi(char *str) break; if (str[len] == ';') str[len] = '\0'; + if (str[len] == '\n') + str[len] = '\0'; } } diff --git a/validation_tests.py b/validation_tests.py index 660fd9e..d388660 100644 --- a/validation_tests.py +++ b/validation_tests.py @@ -37,9 +37,9 @@ TESTS = [ key="PATH", name="path handing", cmds=[ - "/bin/sh --version\n", - "/../bin/sh --version\n", - "~/../../bin/sh --version\n", + "/bin/ls\n", + "/../bin/ls\n", + # "~/../../bin/sh --version\n", "fixtures/exec.sh\n", ], depends_on=("ARGS",) @@ -101,4 +101,75 @@ TESTS = [ ], depends_on=("ARGS",) ), + + Test( + key="REDIR", + name="I/O redirections", + cmds=[ + "echo plop > out.txt\n", + "cat < out.txt\n", + "echo again >> out.txt\n", + "cat < out.txt\n", + "cat nofile.txt\n", + "echo test > /no/perm/file\n", + ], + depends_on=("ARGS",) + ), + + Test( + key="PIPE", + name="pipes", + cmds=[ + "echo plop | cat\n", + "echo kek | grep kek\n", + "ls | grep Makefile\n", + "who | wc -l\n", + "ls | | cat\n", # Syntax error + ], + depends_on=("ARGS",) + ), + + Test( + key="ENV_EXPANSION", + name="environment variable expansion", + cmds=[ + "echo $HOME\n", + "echo $PATH\n", + "setenv TESTVAR bonjour;echo $TESTVAR;unsetenv TESTVAR;echo $TESTVAR\n", + ], + depends_on=("ENV","SEMICOLON",) + ), + + Test( + key="EXIT", + name="exit command", + cmds=[ + "exit\n", + ], + depends_on=("ARGS",) + ), + + Test( + key="MULTICMD", + name="multiple commands per line", + cmds=[ + "echo one ; echo two ; echo three\n", + "ls ; pwd ; whoami\n", + "cd src/ ; pwd ; cd - ; pwd\n", + ], + depends_on=("SEMICOLON",) + ), + + Test( + key="PARSING_ERR", + name="bad parsing cases", + cmds=[ + "ls |\n", + "ls >\n", + "; ls\n", + ";; ls\n", + "ls ;; ls\n", + ], + depends_on=("PIPE", "REDIR", "SEMICOLON",) + ), ] diff --git a/validator.py b/validator.py old mode 100644 new mode 100755 index fb754c8..c4cb129 --- a/validator.py +++ b/validator.py @@ -1,8 +1,12 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i python3 -p python3 tcsh + from __future__ import annotations from dataclasses import dataclass import difflib import subprocess +import sys @dataclass @@ -84,21 +88,25 @@ class Test: print("\033[31m.\033[0m", end='') # ]] return cmd, result_42sh, result_tcsh - def run(self, test_map): + def run(self, test_map) -> bool: if self.has_run: - return + return True self.has_run = True + + success = True for dep_name in self.depends_on: dep = test_map.get(dep_name) if dep is None: - print("\033[33mOKWarning\033[0m:" # ]] + print("\033[33mWarning\033[0m:" # ]] "Missing dependency:", dep_name) continue if not dep.has_run: - dep.run(test_map) + success &= dep.run(test_map) + if not success: + return False print(self.name, end=" ") failures = [] @@ -108,19 +116,23 @@ class Test: failures.append(failure) if not failures: print(" \033[32mOK\033[0m") # ]] + return True else: print() for fail in failures: print_diff(*fail) + return False def main(): from validation_tests import TESTS test_map = {test.key: test for test in TESTS} + success = True for test in TESTS: - test.run(test_map=test_map) + success &= test.run(test_map=test_map) + return not success if __name__ == "__main__": - main() + sys.exit(main())