2024-01-31 02:02:32 +01:00
# include <termios.h>
# include <unistd.h>
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
2024-02-01 20:18:52 +01:00
# include <linux/limits.h>
2024-01-31 02:02:32 +01:00
# include <time.h>
# include <stdbool.h>
2024-02-01 20:18:52 +01:00
# include <signal.h>
2024-02-09 02:18:14 +01:00
# include <ctype.h>
2024-01-31 02:02:32 +01:00
# include "color.h"
# include "constants.h"
# include "history.h"
2024-02-01 20:02:30 +01:00
# include "commands.h"
2024-01-31 02:02:32 +01:00
2024-02-10 18:29:58 +01:00
void * memalloc ( size_t size ) {
2024-02-10 18:33:54 +01:00
void * ptr = malloc ( size ) ;
2024-02-10 18:29:58 +01:00
if ( ! ptr ) {
fputs ( " rush: Error allocating memory \n " , stderr ) ;
exit ( EXIT_FAILURE ) ;
}
return ptr ;
}
2024-02-01 20:02:30 +01:00
void quit_sig ( int sig ) {
2024-02-01 20:18:52 +01:00
exit ( 0 ) ;
2024-01-31 02:02:32 +01:00
}
void change_terminal_attribute ( int option ) {
static struct termios oldt , newt ;
tcgetattr ( STDIN_FILENO , & oldt ) ;
if ( option ) {
newt = oldt ;
newt . c_lflag & = ~ ( ICANON | ECHO ) ; // allows getchar without pressing enter key and echoing the character twice
tcsetattr ( STDIN_FILENO , TCSANOW , & newt ) ; // set settings to stdin
} else {
tcsetattr ( STDIN_FILENO , TCSANOW , & oldt ) ; // restore to old settings
}
}
char * * setup_path_variable ( ) {
char * envpath = getenv ( " PATH " ) ;
2024-02-06 19:56:52 +01:00
if ( envpath = = NULL ) {
fprintf ( stderr , " rush: PATH environment variable is missing \n " ) ;
exit ( EXIT_FAILURE ) ;
}
2024-02-10 18:29:58 +01:00
char * path_cpy = memalloc ( sizeof ( char ) * ( strlen ( envpath ) + 1 ) ) ;
char * path = memalloc ( sizeof ( char ) * ( strlen ( envpath ) + 1 ) ) ;
2024-01-31 02:02:32 +01:00
strcpy ( path_cpy , envpath ) ;
strcpy ( path , envpath ) ;
int path_count = 0 ;
while ( * path_cpy ! = ' \0 ' ) {
// count number of : to count number of elements
if ( * path_cpy = = ' : ' ) {
path_count + + ;
}
path_cpy + + ;
}
path_count + = 2 ; // adding one to be correct and one for terminator
2024-02-10 18:29:58 +01:00
char * * paths = memalloc ( sizeof ( char * ) * path_count ) ;
2024-01-31 02:02:32 +01:00
char * token = strtok ( path , " : " ) ;
int counter = 0 ;
while ( token ! = NULL ) {
paths [ counter ] = token ; // set element to the pointer of start of path
token = strtok ( NULL , " : " ) ;
counter + + ;
}
paths [ counter ] = NULL ;
return paths ;
}
bool find_command ( char * * paths , char * command ) {
2024-02-08 20:49:11 +01:00
if ( strncmp ( command , " " , 1 ) = = 0 ) {
2024-01-31 02:02:32 +01:00
return false ;
}
while ( * paths ! = NULL ) {
char current_path [ PATH_MAX ] ;
2024-02-08 13:33:42 +01:00
current_path [ 0 ] = ' \0 ' ;
2024-01-31 02:02:32 +01:00
sprintf ( current_path , " %s/%s " , * paths , command ) ;
if ( access ( current_path , X_OK ) = = 0 ) {
// command is executable
return true ;
2024-02-01 20:02:30 +01:00
} else {
if ( is_builtin ( command ) ) {
return true ;
}
2024-01-31 02:02:32 +01:00
}
paths + + ;
}
return false ;
}
2024-02-08 13:33:42 +01:00
void shiftleft ( int chars ) {
printf ( " \033 [%dD " , chars ) ;
}
void shiftright ( int chars ) {
printf ( " \033 [%dC " , chars ) ;
}
2024-02-08 20:49:11 +01:00
void clearline ( ) {
printf ( " \033 [K " ) ; // clear line to the right of cursor
}
void highlight ( char * buffer , char * * paths ) {
char * cmd_part = strchr ( buffer , ' ' ) ;
char * command_without_arg = NULL ;
int cmd_len = 0 ;
bool valid ;
if ( cmd_part ! = NULL ) {
cmd_len = cmd_part - buffer ;
2024-02-10 18:29:58 +01:00
char * cmd = memalloc ( sizeof ( char ) * ( cmd_len + 1 ) ) ;
command_without_arg = memalloc ( sizeof ( char ) * ( cmd_len + 1 ) ) ;
2024-02-08 20:49:11 +01:00
for ( int i = 0 ; i < ( cmd_part - buffer ) ; i + + ) {
cmd [ i ] = buffer [ i ] ;
}
strcpy ( command_without_arg , cmd ) ;
cmd [ cmd_len ] = ' \0 ' ;
command_without_arg [ cmd_len ] = ' \0 ' ;
valid = find_command ( paths , cmd ) ;
free ( cmd ) ;
} else {
valid = find_command ( paths , buffer ) ;
}
if ( valid ) {
if ( command_without_arg ! = NULL ) {
buffer + = cmd_len ;
printf ( " \x1b [38;2;137;180;250m%s \x1b [0m \x1b [38;2;255;255;255m%s \x1b [0m " , command_without_arg , buffer ) ; // print green as valid command, but only color the command, not the arguments
buffer - = cmd_len ;
} else {
printf ( " \x1b [38;2;137;180;250m%s \x1b [0m " , buffer ) ; // print green as valid command
}
} else {
if ( command_without_arg ! = NULL ) {
buffer + = cmd_len ;
printf ( " \x1b [38;2;243;139;168m%s \x1b [0m \x1b [38;2;255;255;255m%s \x1b [0m " , command_without_arg , buffer ) ; // print green as valid command, but only color the command, not the arguments
buffer - = cmd_len ;
} else {
printf ( " \x1b [38;2;243;139;168m%s \x1b [0m " , buffer ) ; // print red as invalid command
}
}
free ( command_without_arg ) ;
}
2024-02-01 20:02:30 +01:00
char * readline ( char * * paths ) {
2024-01-31 02:02:32 +01:00
int bufsize = RL_BUFSIZE ;
int position = 0 ;
2024-02-10 18:29:58 +01:00
char * buffer = memalloc ( sizeof ( char ) * bufsize ) ;
2024-01-31 02:02:32 +01:00
2024-02-08 13:33:42 +01:00
bool moved = false ;
bool backspaced = false ;
bool navigated = false ;
bool insertatmiddle = false ;
2024-02-08 20:49:11 +01:00
bool replaced = false ;
2024-01-31 02:02:32 +01:00
buffer [ 0 ] = ' \0 ' ;
while ( 1 ) {
2024-02-08 13:33:42 +01:00
int c = getchar ( ) ; // read a character
2024-01-31 02:02:32 +01:00
int buf_len = strlen ( buffer ) ;
2024-02-08 13:33:42 +01:00
2024-01-31 02:02:32 +01:00
// check each character user has input
switch ( c ) {
case EOF :
exit ( EXIT_SUCCESS ) ;
2024-02-08 13:33:42 +01:00
case 10 : {
// enter/new line feed
2024-02-02 17:52:21 +01:00
if ( buf_len = = 0 ) {
break ;
}
2024-02-08 20:49:11 +01:00
// check if command includes !!
if ( strstr ( buffer , " !! " ) ! = NULL ) {
char * last_command = read_command ( 1 ) ;
if ( last_command ! = NULL ) {
// replace !! with the last command
char * replace = strstr ( buffer , " !! " ) ;
int replace_len = strlen ( replace ) ;
int last_command_len = strlen ( last_command ) ;
int buffer_len = strlen ( buffer ) ;
if ( last_command_len > replace_len ) {
buffer = realloc ( buffer , buffer_len + last_command_len - replace_len + 1 ) ;
if ( ! buffer ) {
fprintf ( stderr , " rush: Error allocating memory \n " ) ;
exit ( EXIT_FAILURE ) ;
}
}
memmove ( replace + last_command_len , replace + replace_len , buffer_len - ( replace - buffer ) - replace_len + 1 ) ;
memcpy ( replace , last_command , last_command_len ) ;
position + = last_command_len - replace_len ;
shiftright ( last_command_len - replace_len ) ;
replaced = true ;
break ;
}
} else {
buffer [ buf_len ] = ' \0 ' ;
// clear all characters after the command
for ( int start = buf_len + 1 ; buffer [ start ] ! = ' \0 ' ; start + + ) {
buffer [ start ] = ' \0 ' ;
}
printf ( " \n " ) ; // give space for response
position = 0 ;
return buffer ;
2024-01-31 02:02:32 +01:00
}
2024-02-08 13:33:42 +01:00
}
2024-01-31 02:02:32 +01:00
case 127 : // backspace
if ( buf_len > = 1 ) {
2024-02-08 13:33:42 +01:00
position - - ;
for ( int i = position ; i < buf_len ; i + + ) {
// shift the buffer
buffer [ i ] = buffer [ i + 1 ] ;
}
backspaced = true ;
moved = false ;
2024-01-31 02:02:32 +01:00
}
break ;
case 27 : // arrow keys comes at three characters, 27, 91, then 65-68
if ( getchar ( ) = = 91 ) {
int arrow_key = getchar ( ) ;
if ( arrow_key = = 65 ) { // up
// read history file and fill prompt with latest command
char * last_command = read_command ( 1 ) ;
if ( last_command ! = NULL ) {
strcpy ( buffer , last_command ) ;
}
2024-02-08 20:49:11 +01:00
navigated = true ;
2024-02-08 13:33:42 +01:00
moved = false ;
2024-01-31 02:02:32 +01:00
break ;
} else if ( arrow_key = = 66 ) { // down
char * last_command = read_command ( 0 ) ;
if ( last_command ! = NULL ) {
strcpy ( buffer , last_command ) ;
}
2024-02-08 20:49:11 +01:00
navigated = true ;
2024-02-08 13:33:42 +01:00
moved = false ;
2024-01-31 02:02:32 +01:00
break ;
} else if ( arrow_key = = 67 ) { // right
2024-02-06 19:56:52 +01:00
if ( position < buf_len ) {
2024-02-08 13:33:42 +01:00
shiftright ( 1 ) ;
2024-02-06 19:56:52 +01:00
position + + ;
}
2024-02-08 13:33:42 +01:00
moved = true ;
break ;
2024-01-31 02:02:32 +01:00
} else if ( arrow_key = = 68 ) { // left
2024-02-08 13:33:42 +01:00
if ( position > = 1 ) {
shiftleft ( 1 ) ;
2024-02-06 19:56:52 +01:00
position - - ;
}
2024-02-08 13:33:42 +01:00
moved = true ;
break ;
}
}
2024-01-31 02:02:32 +01:00
default :
if ( c > 31 & & c < 127 ) {
2024-02-06 19:56:52 +01:00
if ( position = = buf_len ) {
// Append character to the end of the buffer
buffer [ buf_len ] = c ;
buffer [ buf_len + 1 ] = ' \0 ' ;
2024-02-08 13:33:42 +01:00
moved = false ;
navigated = false ;
2024-02-06 19:56:52 +01:00
} else {
// Insert character at the current position
memmove ( & buffer [ position + 1 ] , & buffer [ position ] , buf_len - position + 1 ) ;
buffer [ position ] = c ;
2024-02-08 13:33:42 +01:00
shiftright ( 1 ) ;
insertatmiddle = true ;
2024-02-06 19:56:52 +01:00
}
position + + ;
2024-01-31 02:02:32 +01:00
}
}
2024-02-08 20:49:11 +01:00
buf_len = strlen ( buffer ) ;
if ( replaced ) {
shiftleft ( buf_len ) ;
clearline ( ) ;
}
2024-02-08 13:33:42 +01:00
if ( navigated & & buf_len > = 1 ) {
2024-02-08 20:49:11 +01:00
if ( position > 0 ) {
shiftleft ( position ) ; // move cursor to the beginning
clearline ( ) ;
2024-02-08 13:33:42 +01:00
}
2024-02-08 20:49:11 +01:00
position = buf_len ;
2024-02-08 13:33:42 +01:00
}
2024-02-08 20:49:11 +01:00
2024-02-08 13:33:42 +01:00
if ( moved ) {
moved = false ;
continue ;
}
2024-02-08 20:49:11 +01:00
if ( ! navigated & & ! replaced ) {
2024-02-08 13:33:42 +01:00
if ( position ! = buf_len ) {
// not at normal place
if ( backspaced ) {
shiftleft ( position + 1 ) ;
} else {
shiftleft ( position ) ; // move cursor to the beginning
}
} else if ( buf_len > 1 ) {
if ( backspaced ) {
shiftleft ( buf_len + 1 ) ; // move cursor to the beginning
} else {
shiftleft ( buf_len - 1 ) ; // move cursor to the beginning
}
} else if ( buf_len = = 1 ) {
if ( backspaced ) {
shiftleft ( 2 ) ;
}
} else if ( buf_len = = 0 ) {
if ( backspaced ) {
shiftleft ( 1 ) ;
}
}
2024-02-08 20:49:11 +01:00
clearline ( ) ;
2024-02-08 13:33:42 +01:00
} else {
navigated = false ;
}
2024-02-01 20:02:30 +01:00
2024-02-08 20:49:11 +01:00
highlight ( buffer , paths ) ;
2024-02-08 13:33:42 +01:00
if ( backspaced ) {
if ( buf_len ! = position ) {
shiftleft ( buf_len - position ) ;
}
backspaced = false ;
}
if ( insertatmiddle ) {
shiftleft ( buf_len - position ) ; // move cursor back to where it was
insertatmiddle = false ;
}
2024-02-08 20:49:11 +01:00
if ( replaced ) {
replaced = false ;
}
2024-01-31 02:02:32 +01:00
// If we have exceeded the buffer, reallocate.
if ( ( buf_len + 1 ) > = bufsize ) {
bufsize + = RL_BUFSIZE ;
buffer = realloc ( buffer , bufsize ) ;
if ( ! buffer ) {
2024-02-01 20:02:30 +01:00
fprintf ( stderr , " rush: Error allocating memory \n " ) ;
2024-01-31 02:02:32 +01:00
exit ( EXIT_FAILURE ) ;
}
}
}
}
// split line into arguments
2024-02-01 20:02:30 +01:00
char * * argsplit ( char * line ) {
2024-01-31 02:02:32 +01:00
int bufsize = TOK_BUFSIZE , position = 0 ;
2024-02-10 18:29:58 +01:00
char * * tokens = memalloc ( bufsize * sizeof ( char * ) ) ;
2024-01-31 02:02:32 +01:00
char * token ;
token = strtok ( line , TOK_DELIM ) ;
while ( token ! = NULL ) {
tokens [ position ] = token ;
position + + ;
if ( position > = bufsize ) {
bufsize + = TOK_BUFSIZE ;
tokens = realloc ( tokens , bufsize * sizeof ( char * ) ) ;
if ( ! tokens ) {
2024-02-06 19:56:52 +01:00
fprintf ( stderr , " rush: Error allocating memory \n " ) ;
2024-01-31 02:02:32 +01:00
exit ( EXIT_FAILURE ) ;
}
}
token = strtok ( NULL , TOK_DELIM ) ;
}
tokens [ position ] = NULL ;
return tokens ;
}
2024-02-08 20:49:11 +01:00
char * * modifyargs ( char * * args ) {
int num_arg = 0 ;
2024-02-10 18:29:58 +01:00
// check if command is ls, diff, or grep, if so, add --color=auto to the arguments
// this is to make ls, diff, and grep have color without user typing it
// this is to make the shell more user friendly
2024-02-09 02:18:14 +01:00
while ( args [ num_arg ] ! = NULL ) {
num_arg + + ;
}
2024-02-10 18:29:58 +01:00
for ( int i = 0 ; i < num_arg ; i + + ) {
// makes ls and diff and grep have color without user typing it
if ( strncmp ( args [ i ] , " ls " , 2 ) = = 0 | | strncmp ( args [ i ] , " diff " , 4 ) = = 0 | | strncmp ( args [ i ] , " grep " , 4 ) = = 0 ) {
for ( int j = num_arg ; j > i ; j - - ) {
args [ j + 1 ] = args [ j ] ;
}
args [ i + 1 ] = " --color=auto " ;
num_arg + + ;
}
2024-02-08 20:49:11 +01:00
}
return args ;
}
2024-02-09 02:18:14 +01:00
char * trimws ( char * str ) {
char * end ;
while ( isspace ( ( unsigned char ) * str ) )
str + + ;
if ( * str = = 0 )
return str ;
end = str + strlen ( str ) - 1 ;
while ( end > str & & isspace ( ( unsigned char ) * end ) )
end - - ;
* ( end + 1 ) = 0 ;
return str ;
}
char * * * pipe_argsplit ( char * line ) {
2024-02-10 18:29:58 +01:00
char * * * cmdv = memalloc ( sizeof ( char * * ) * 128 ) ; // 127 commands, 1 for NULL
char * * cmds = memalloc ( sizeof ( char * ) * 128 ) ; // 127 arguments, 1 for NULL
2024-02-09 02:18:14 +01:00
int num_arg = 0 ;
char * pipe = strtok ( line , " | " ) ;
while ( pipe ! = NULL ) {
pipe = trimws ( pipe ) ;
cmds [ num_arg ] = strdup ( pipe ) ;
pipe = strtok ( NULL , " | " ) ;
num_arg + + ;
}
cmds [ num_arg ] = NULL ;
for ( int i = 0 ; i < num_arg ; i + + ) {
char * * splitted = argsplit ( cmds [ i ] ) ;
cmdv [ i ] = modifyargs ( splitted ) ;
2024-02-10 18:29:58 +01:00
2024-02-09 02:18:14 +01:00
}
cmdv [ num_arg ] = NULL ;
free ( cmds ) ;
return cmdv ;
}
2024-01-31 02:02:32 +01:00
// continously prompt for command and execute it
void command_loop ( char * * paths ) {
char * line ;
char * * args ;
int status = 1 ;
while ( status ) {
time_t t = time ( NULL ) ;
struct tm * current_time = localtime ( & t ) ; // get current time
char timestr [ 256 ] ;
char cwdstr [ PATH_MAX ] ;
if ( strftime ( timestr , sizeof ( timestr ) , " [%H:%M:%S] " , current_time ) = = 0 ) { // format time string
return ;
}
if ( getcwd ( cwdstr , sizeof ( cwdstr ) ) = = NULL ) { // get current working directory
return ;
}
char time [ 256 ] ;
strcpy ( time , timestr ) ;
color_text ( time , lavender ) ; // lavender colored time string
2024-02-10 18:29:58 +01:00
char * cwd = memalloc ( sizeof ( char ) * ( PATH_MAX + 2 ) ) ;
2024-01-31 02:02:32 +01:00
sprintf ( cwd , " [%s] " , cwdstr ) ;
color_text ( cwd , pink ) ; // pink colored current directory
char arrow [ 32 ] = " » " ;
color_text ( arrow , blue ) ;
printf ( " %s %s %s " , time , cwd , arrow ) ;
2024-02-10 18:29:58 +01:00
cmd_count = 0 ; // upward arrow key resets command count
2024-02-01 20:02:30 +01:00
line = readline ( paths ) ;
2024-02-09 02:18:14 +01:00
save_command_history ( line ) ;
bool has_pipe = false ;
for ( int i = 0 ; line [ i ] ! = ' \0 ' ; i + + ) {
if ( line [ i ] = = ' | ' ) {
has_pipe = true ;
break ;
}
}
if ( has_pipe ) {
char * * * pipe_args = pipe_argsplit ( line ) ;
status = execute_pipe ( pipe_args ) ;
while ( * pipe_args ! = NULL ) {
free ( * pipe_args ) ;
pipe_args + + ;
}
} else {
args = argsplit ( line ) ;
args = modifyargs ( args ) ;
2024-02-10 18:29:58 +01:00
status = execute ( args , STDOUT_FILENO , OPT_FGJ ) ;
2024-02-09 02:18:14 +01:00
free ( args ) ;
}
2024-01-31 02:02:32 +01:00
free ( line ) ;
free ( cwd ) ;
} ;
}
int main ( int argc , char * * argv ) {
// setup
2024-02-01 20:02:30 +01:00
signal ( SIGINT , quit_sig ) ;
signal ( SIGTERM , quit_sig ) ;
signal ( SIGQUIT , quit_sig ) ;
2024-01-31 02:02:32 +01:00
check_history_file ( ) ;
char * * paths = setup_path_variable ( ) ;
change_terminal_attribute ( 1 ) ; // turn off echoing and disabling getchar requires pressing enter key to return
command_loop ( paths ) ;
// cleanup
2024-02-08 13:33:42 +01:00
free ( paths ) ;
2024-01-31 02:02:32 +01:00
change_terminal_attribute ( 0 ) ; // change back to default settings
return EXIT_SUCCESS ;
}