2# +==== BEGIN tty_ov =================+
4# ..@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
5# .@...........................#@
6# @############################.@
7# @...........................@.@
8# @..#######################..@.@
9# @.#########################.@.@
10# @.##>_#####################.@.@
11# @.#########################.@.@
12# @.#########################.@.@
13# @.#########################.@.@
14# @.#########################.@.@
15# @..#######################..@.@
16# @...........................@.@
17# @..+----+______________.....@.@
18# @..+....+______________+....@.@
19# @..+----+...................@.@
20# @...........................@.#
21# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@#.
25# CREATION DATE: 11-02-2026
26# LAST Modified: 19:38:51 11-02-2026
28# A module that emulates a few core functionalities of a tty (see the inner help for a list of functions).
30# COPYRIGHT: (c) Henry Letellier
31# PURPOSE: This is the main implementation of the terminal and it's interraction, was coded in a week so the structure differs from my other python modules or more recent code.
33# +==== END tty_ov =================+
38from typing
import List, Dict
40from prompt_toolkit.key_binding
import KeyBindings
41from prompt_toolkit.history
import InMemoryHistory
42from ask_question
import AskQuestion
43from colourise_output
import ColouriseOutput
44from .hl_ls
import HLLs
48 """ The class in charge of simulating a tty """
50 def __init__(self, err: int, error: int, success: int, colour_lib: ColouriseOutput, ask_question: AskQuestion, colours: Dict, colourise_output: bool =
True) ->
None:
90 "help",
"man",
".help",
92 "--h",
"-help",
"--help"
130 """ The function in charge of displaying a string on the tty """
134 print(string, end=
"")
137 """ The function in charge of executing command on the host system in a contained manner """
139 return os.system(command)
143 def list_to_str(self, hl_list: List[any], join: str =
" ") -> str:
144 """ Convert a list to a string """
146 list_length = len(hl_list)-1
147 for index, item
in enumerate(hl_list):
149 if index < list_length:
154 """ Display the version of the program """
155 func_name =
"version"
157 help_description = f
"""
158Display the version of the program.
163 {self.program_version}
174 """ Display the history of the commands """
175 func_name =
"history"
177 help_description = f
"""
178Display the history of the commands.
183 The history of the commands
190 "The history of the commands:\n"
193 for index, command
in enumerate(self.
history):
204 """ Change the name of the current session """
205 func_name =
"session_name"
207 help_description = f
"""
208Change the name of the current session.
209If no arguments are passed, the name of the session is displayed
210If one argument is passed, the name of the session is changed to the passed in argument
215 The name of the session is: {self.session_name}
219 The name of the session is changed to: 'test'
224 if len(args) == 0
or args[0] ==
'':
227 "The name of the session is: "
235 "The name of the session is changed to: "
242 """ Display the author of the program """
245 help_description = f
"""
246Display the author of the program.
251 {self.program_author}
262 """ Display the client of the program """
265 help_description = f
"""
266Display the client of the program.
271 This program was created for: {self.client_name}
278 "This program was created for: "
284 def env(self, args: List) -> int:
285 """ Display the environement variables """
288 help_description = f
"""
289Display the environement variables.
294 The environement variables
299 for key, value
in os.environ.items():
305 """ Display the level of the program """
308 "This program has admin privileges: "
314 """ Display the environement variables """
317 help_description = f
"""
318Display the environement variables.
323 The environement variables
324 # terms in one colour and definitions in another
329 for key, value
in os.environ.items():
338 """ Function in charge of setting a variable in the environement of the shell """
341 help_description = f
"""
342Set an environement variable.
343If no arguments are passed, the name and value of the variable will be asked
344If one argument is passed, an empty variable is set
345If two or more argument are passed, the first argument becomes the name of the variable, the rest is compiled into a string seperated by spaces and become the value of that variable
350 Please enter the name of the variable: a
351 Please enter the value of the variable: b
368 arg_length = len(args)
370 os.environ[args[0]] =
""
378 "Please enter the name of the variable: ",
381 var_value = input(
"Please enter the value of the variable: ")
382 os.environ[var_name] = var_value
388 """ Unset a single variable """
389 if argument
in os.environ:
390 del os.environ[argument]
395 """ Ask for the variable that needs to be removed from the environement """
397 "Please enter the name of the variable: ",
400 if var_name
in os.environ:
404 f
"Variable '{var_name}' does not exist\n"
416 f
"Variable '{var_name}' does not exist\n"
422 """ Function in charge of unsetting a variable in the environement of the shell """
423 func_name =
"unsetenv"
425 help_description = f
"""
426Unset an environement variable.
427If no arguments are passed, the name of the variable will be asked
428If one or more argument are passed, the variable with that name is unset
429If '*' is passed, the environement will be flushed
434 Please enter the name of the variable: a
436Input (a does not exist):
439 Variable 'a' does not exist
443 Variables ['a','b'] unset
444Input (a does not exist):
447 Variable 'a' does not exist
448 Variables ['b'] unset
458 arg_length = len(args)
466 "Environement flushed\n"
480 f
"Variable '{args[0]}' does not exist\n"
489 global_status = status
492 f
"Variable '{arg}' does not exist\n"
495 unset_variables.append(arg)
498 f
"Variables {unset_variables} unset\n"
506 """ Display the status code of the last function """
509 help_description = f
"""
510Display the return code of the last function that was called.
514Output (if return code is successefull):
516 This status corresponds to a success
517Output (if the return code is a failure):
519 This status corresponds to and error
520Output (if the return code is unknown [here: 1]):
522 This status is not referenced by this terminal
529 f
"The status is: {self.current_tty_status}\n"
534 "This status corresponds to and error.\n"
539 "This status corresponds to a success.\n"
544 "This status is not referenced by this terminal.\n"
549 "This status generally means that an error has occurred during the execution of a program\n"
555 """ The function in charge of displaying the help for a specific function """
562 """ Display the help on the help function """
564 help_description = f
"""
565Display the help section.
566If no arguments are passed, the available commands are displayed
567If a command is passed in 'help' the help about that command is displayed
568If 'help' is passed in 'help' this section is displayed
571 {func_name} hello_world
573 The {func_name} section of the hello_world function
578 The help section of the prompt function (the line asking for your command)
584 """ Display the help on the prompt function """
585 prompt_description =
"""
586Displays the return code of the previous command.
587Here are the different status colours:
598 "\nUnreferenced status: "
604 "\nYou can display the status code by entering: '?'\n"
609 """ Process the inputs for the help calls """
610 usr_input = args[0].lower()
615 if usr_input ==
"prompt":
620 if usr_input == list(item)[0]:
622 item[usr_input](args[1:])
627 f
"Invalid option: {str(args[0])}\n"
632 def help(self, args: List) -> int:
633 """ The help function in charge of displaying the available options to the user """
635 if argsc > 0
and args[0] !=
'':
638 for i
in enumerate(args):
641 global_status = self.
error
665 def pwd(self, args: List) -> int:
666 """ The function in charge of displaying the current working directory """
669 help_description = f
"""
670Display the current working directory.
675 The current working directory
685 """ Replace characters that could break the creation by """
686 dir_name = dir_name.replace(
"\\",
"/")
687 illegal_character = [
688 "\t",
"\n",
"\r",
"\v",
"\f",
"\b",
"\a",
"\0",
"\'",
"\"",
689 "?",
"*",
"<",
">",
"|",
":",
";",
"!",
"@",
"#",
"$",
"%",
"^",
690 "&",
"(",
")",
"[",
"]",
"{",
"}",
"`",
"~",
"=",
"+"
692 for i
in illegal_character:
694 dir_name = dir_name.replace(i,
" ")
698 """ Create the required directories """
700 if os.path.isfile(path):
703 f
"Directory '{path}' is a file\n"
707 if os.path.isdir(path):
710 f
"Directory '{path}' already exists\n"
715 os.makedirs(path, exist_ok=
True)
716 except OSError
as err:
719 f
"Directory '{path}' could not be created\n{err}"
726 f
"Directory '{path}' created\n"
732 """ Create a directory """
735 help_description = f
"""
737If no arguments are passed, the name of the directory will be asked
738If one or more argument are passed, the directory with that name is created
743 Please enter the name of the directory: a
752 Directories ['a','b'] created
760 Directories ['a/b','c/d'] created
761Input (a already exists):
764 Directory 'a' already exists
768 Directory 'a/b' already exists
774 arg_length = len(args)
777 "Please enter the name of the directory: ",
783 created_directories = []
787 global_status = status
789 created_directories.append(arg)
792 f
"Directories {created_directories} created\n"
800 """ Check the path leading to the file to make sure the path exists """
801 if os.path.exists(file_path):
806 """ Create a file based on the name """
807 filename = filename.replace(
"\"",
" ")
808 filename = filename.replace(
"\\",
"/")
809 filename_display = filename.split(
"/")
810 file_path = filename_display[:-1]
812 filename_display = filename_display[-1]
816 f
"Path '{file_path}' does not exist, file '{filename_display}' creation failed."
820 if os.path.isdir(filename):
823 f
"File '{filename}' is a directory\n"
827 if os.path.isfile(filename):
830 f
"File '{filename}' already exists\n"
835 with open(filename,
"w", encoding=
"utf-8", newline=
"\n")
as file:
838 except IOError
as err:
841 f
"File '{filename}' could not be created\n{err}"
849 """ Create a file in the present path """
852 help_description = f
"""
854If no arguments are passed, the name of the file will be asked
855If one or more argument are passed, the file with that name is created
860 Please enter the name of the file: a
869 Files ['a','b'] created
877 Files ['a/b','c/d'] created
878Input (a already exists):
881 File 'a' already exists
882Input (b already exists):
885 File 'a/b' already exists
886Input (the path to b does not exist):
887 {func_name} not/a/path/b c/d
889 Path 'not/a/path/' does not exist, file 'b' creation failed.
890 Files ['c/d'] created
896 arg_length = len(arg)
899 "Please enter the name of the file: ",
909 global_status = status
911 created_files.append(file)
914 f
"Files {created_files} created\n"
922 """ Remove a directory (child function)"""
923 directory_path = directory_path.replace(
"\\",
"/")
924 if os.path.isdir(directory_path):
925 shutil.rmtree(directory_path)
930 """ Remove a directory """
933 help_description = f
"""
935If no arguments are passed, the name of the directory will be asked
936If one or more argument are passed, the directory with that name is removed
941 Please enter the name of the directory: a
950 Directories ['a','b'] removed
958 Directories ['a/b','c/d'] removed
959Input (a does not exist):
962 Directory 'a' does not exist
963Input (b does not exist):
966 Directory 'a/b' does not exist
967 Directories ['c/d'] removed
973 arg_length = len(args)
976 "Please enter the name of the directory: ",
982 removed_directories = []
983 for directory
in args:
986 global_status = status
988 removed_directories.append(directory)
991 f
"Directories {removed_directories} removed\n"
999 """ Remove an item """
1001 if os.path.isfile(path):
1005 f
"Are you sure you wish to remove folder {path} and all it's content? [(Y)es/(N)o]",
1015 except IOError
as err:
1018 f
"File or directory '{path}' does not exist\n{err}\n"
1024 """ Remove a file or a directory """
1027 help_description = f
"""
1028Remove a file or a directory.
1029If no arguments are passed, the name of the file or directory will be asked
1030If one or more argument are passed, the file or directory with that name is removed
1035 Please enter the name of the file or directory: a
1036 File or directory removed
1040 File or directory removed
1044 Files or directories ['a','b'] removed
1048 File or directory removed
1052 Files or directories ['a/b','c/d'] removed
1053Input (a does not exist):
1056 File or directory 'a' does not exist
1057Input (b does not exist):
1060 File or directory 'a/b' does not exist
1061 Files or directories ['c/d'] removed
1067 arg_length = len(args)
1070 "Please enter the name of the file or directory: ",
1080 global_status = status
1082 removed_files.append(file)
1085 f
"Files or directories {removed_files} removed\n"
1088 return global_status
1093 """ Access a directory based on the provided path """
1105 """ The function in charge of changing the current working directory to a directory further than the home directory """
1110 if path !=
"" and path[0]
in (
"/",
"\\"):
1118 """ The function in charge of rolling back the current working directory to the previous one """
1129 """ The function in charge of changing the current working directory """
1132 help_description = f
"""
1133Change the current working directory.
1134If no arguments are passed, the current working directory is changed to the home directory
1135If one argument is passed, the current working directory is changed to the passed in argument
1140 # The current working directory is changed to the home directory
1144 # The current working directory is changed to /home
1147 {func_name} /home/does_not_exist
1151 {func_name} /a/path /another/path
1153 Invalid number of arguments
1161 if args[0][0] ==
"-":
1163 if args[0][0] ==
"~":
1173 """ The function in charge of exiting the layer in which the user is located """
1176 help_description = f
"""
1177Close this menu and return to the parent session
1182 The prompt from the parent
1195 """ The function in charge of abruptly stopping the program (not recommended) """
1198 help_description = f
"""
1199Exit the program instantly and abruptly.
1200This will close all child processes and will not free the allocated ressources.
1205 Whatever was used to launch this program.
1214 """ Display the return code of the previous function """
1224 """ Set up the functions in charge of changing the prompt with the content of the history command """
1225 bindings = KeyBindings()
1233 @bindings.add('down')
1243 complete_while_typing=
True,
1244 validate_while_typing=
True,
1245 enable_history_search=
True,
1246 key_bindings=bindings,
1251 """ act depending on the special keys pressed or if entered is pressed """
1254 except KeyboardInterrupt:
1259 """ The function in charge of displaying a basic prompt to ask the user to enter an option """
1267 """ Return the current folder """
1269 path = path.replace(
"\\",
"/")
1272 """ Bind the ls function to the ls command """
1275 help_description = f
"""
1276Display the content of the current working directory.
1279 {self.help_function_child_name}
1281 The content of the current working directory
1287 status = self.
ls.
ls(args[0])
1290 status = self.
ls.
ls(
".")
1295 """ This is a function in charge of displaying a Hello World and the passed arguments """
1296 func_name =
"hello_world"
1298 help_description = f
"""
1299Display a 'Hello World !'.
1300If no arguments are passed, a 'Hello World !' is displayed
1301If a command is passed, 'Hello World !' is displayed as well as the passed in arguments
1313 for index, arg
in enumerate(args):
1319 """ Run a command in the host's shell environement """
1320 help_command =
"run"
1322 help_description = f
"""
1323This is a command that allows you to run a command on the parent shell.
1325 {help_command} <your command>
1327 The result of the command you ran.
1330 {help_command} echo "Hello World!"
1340 "You need to specify a command to run\n"
1344 command =
" ".join(args)
1347 f
"Running command: {command}\n"
1353 "Error while running command\n"
1361 """ Check if the current windows user has admin rights """
1363:: Check if the script is running with administrative privileges
1364net session >nul 2>&1
1365if %errorLevel% == 0 (
1371 status = os.system(command)
1377 """ Check if the user has admin rights """
1384 return os.geteuid() == 0
1385 except AttributeError:
1389 """ Run a powershell script as an administrator """
1392 "!! As of date, due to the complexity of running a command with elevated privileges, the execution status cannot be properly tracked !!\n"
1397 "-ExecutionPolicy Bypass",
1408 """ Check if the program has admin rights """
1409 func_name =
"check_admin"
1411 help_description = f
"""
1412Check if the program has admin rights.
1424 f
"{self.is_admin()}\n"
1430 """ Save data to a file """
1433 f
"Saving {data} to file '{filepath}'\n"
1436 with open(filepath,
"w", encoding=
"utf-8", newline=
"\n")
as file:
1441 f
"{data} saved to file '{filepath}'\n"
1443 except IOError
as err:
1446 f
"File '{filepath}' could not be created\n{err}"
1453 def _set_file_content(self, file_path: str, content: str, mode: str =
"w", encoding: str =
"utf-8", newline: str =
"\n") -> int:
1454 with open(file_path, mode, encoding=encoding, newline=newline)
as file:
1459 """ Run a command as an administrator """
1460 func_name =
"run_as_admin"
1462 help_description = f
"""
1463Run a command as an administrator.
1466 {func_name} <your command>
1468 The result of the command you ran.
1471 {func_name} echo "Hello World!"
1481 "You need to specify a command to run\n"
1485 command =
" ".join(args)
1487 commands = f
"{os.getcwd()}\\your_code.bat"
1494 "Error while running command\n"
1500 file_name =
"/tmp/your_code.sh"
1501 user_input =
" ".join(args)
1503 file_path=file_name,
1522 """ Check if string1 is exactly in string2 """
1523 string1_length = string1
1524 string2_length = string2
1525 if string1_length != string2_length:
1527 for index, item
in enumerate(string1):
1528 if item != string2[index]:
1533 """ The function in charge of processing the user input """
1541 command = command[0].lower()
1548 if was_found
is False:
1551 f
"Invalid option: {str(command)}\n"
1556 """ assing the colours to the variables in charge of managing the displays"""
1564 "help_title_colour":
"0E",
1565 "help_command_colour":
"0A",
1566 "help_description_colour":
"0F",
1567 "env_term_colour":
"09",
1568 "env_shell_colour":
"03",
1569 "env_definition_colour":
"0B",
1570 "session_name_colour":
"0D"
1573 if i
not in colours:
1590 """ Display/Change the token in charge of indicating the beginning of a new command when many are put together """
1591 func_name =
"command_seperator"
1593 help_description = f
"""
1594Display the token in charge of indicating the beginning of a new command when many are put together.
1599 {self.command_seperator_token}
1604 The command seperator has be changed from '{self.command_seperator_token}' to '-'.
1608 The command seperator has be changed from '{self.command_seperator_token}' to '-c'.
1617 "The command seperator is: "
1621 f
"{self.command_seperator_token}\n"
1630 "Error: The seperator cannot be the same as the comment token"
1638 "Error: The seperator cannot be empty or contain only blanks/tabs"
1645 f
"The command seperator has be changed from '{prev_seperator}' to '{self.command_seperator_token}'.\n"
1651 """ Display/Change the token in charge of indicating the beginning of a new comment """
1652 func_name =
"comment_token"
1654 help_description = f
"""
1655Display the token in charge of indicating the beginning of a comment.
1660 {self.comment_token}
1665 The comment token has be changed from '{self.comment_token}' to '-'.
1669 The comment token has be changed from '{self.comment_token}' to '-c'.
1678 "The comment token is: "
1682 f
"{self.comment_token}\n"
1691 "Error: The seperator cannot be empty or contain only blanks/tabs"
1698 f
"The comment token has be changed from '{prev_token}' to '{self.comment_token}'.\n"
1704 """ The boot tile """
1709 "Welcome to the DevOps deployer\n"
1722 """ Get the path of the HOME variable based on the system """
1723 if "HOME" in os.environ:
1724 self.
home = os.environ[
"HOME"]
1725 elif "HOMEDRIVE" in os.environ
and "HOMEPATH" in os.environ:
1726 self.
home = f
"{os.environ['HOMEDRIVE']}{os.environ['HOMEPATH']}"
1727 elif "HOMEPATH" in os.environ:
1728 self.
home = os.environ[
"HOMEPATH"]
1729 elif "HOMEDRIVE" in os.environ:
1730 self.
home = os.environ[
"HOMEDRIVE"]
1732 self.
home = os.getcwd()
1735 """ Convert the available commands to a list so that it can be used for command auto-completion """
1741 """ set the values for the variables that can be configured by the user """
1771 "--help": self.
help,
1809 "Display the path to the directory in wich we are located"
1833 "List all files in the current folder"
1837 "List all files in the current folder"
1844 "touch": self.
touch,
1879 """ Free the ressources that were previously allocated """
1901 """ Import functions into the shell """
1902 for function
in functions:
1903 if function
is None or isinstance(function, Dict) !=
True:
1905 if "desc" not in function:
1906 function[
"desc"] =
"No description provided\n"
1908 item = list(function)[0]
1912 f
"Added function {item}\n"
1918 """ Remove a function from the options """
1920 for index, item
in enumerate(function_item):
1921 if item == function:
1929 f
"Failed to remove function {item}"
1935 """ Remove functions from the shell """
1937 for function
in functions:
1938 for item
in function:
1941 global_status = status
1946 """ Display a goodbye message on the exit of the main terminal """
1947 goodbye_message =
"Goodbye, see you next time !\n"
1956 """ Run a complex input """
1957 for item
in complex_input:
1962 """ process multiple command input if provided """
1966 for item
in usr_input:
1968 command_list.append(buffer)
1977 buffer += f
" {item}"
1980 command_list.append(buffer)
1984 """ Check if the argv contains arguments input """
1985 if len(sys.argv) > 1:
1998 """ remove enclosing string from the run string """
1999 if len(input_string) > 0:
2000 if input_string[0] ==
'"':
2001 input_string = input_string[1:]
2002 if input_string[-1] ==
'"':
2003 input_string = input_string[:-1]
2004 if input_string[-2] ==
'"':
2005 input_string = input_string[:-2]+input_string[-1:]
2006 if input_string[-3] ==
'"':
2007 input_string = input_string[:-3]+input_string[-2:]
2011 """ Check if the user input is a pipe input """
2012 if not sys.stdin.isatty():
2013 user_input = sys.stdin.read()
2021 """ The mainloop allowing the terminal to run like any other terminals """
2037if __name__ ==
"__main__":
2050 "help_title_colour":
"0E",
2051 "help_command_colour":
"0A",
2052 "help_description_colour":
"0F",
2053 "env_term_colour":
"09",
2054 "env_shell_colour":
"03",
2055 "env_definition_colour":
"0B",
2056 "session_name_colour":
"0D"
2058 COLOURISE_OUTPUT =
True
2069 TTYI.mainloop(
"Test session")
2070 TTYI.unload_basics()
None __init__(self, int err, int error, int success, ColouriseOutput colour_lib, AskQuestion ask_question, Dict colours, bool colourise_output=True)
None goodbye_message(self)
int check_admin(self, List args)
None print_on_tty(self, str colour, str string)
str list_to_str(self, List[any] hl_list, str join=" ")
int unsetenv(self, List args)
None get_the_home_path(self)
str get_current_folder(self)
int _set_file_content(self, str file_path, str content, str mode="w", str encoding="utf-8", str newline="\n")
None commands_to_auto_complete(self)
str process_key_inputs(self)
None display_status_code(self, List args)
int version(self, List args)
int process_session_name(self, List args)
int run_command(self, List args)
int setenv(self, List args)
int update_comment_token(self, List args)
bool enable_multi_command_help
int client(self, List args)
str command_description_token_inner
int create_directories(self, str path, bool show_if_created=True)
str auto_complete_usr_input
str sanitize_directory_path(self, str dir_name)
None create_key_prompt_bindings(self)
int kill(self, List args)
int import_functions_into_shell(self, List[Dict[str, any]] functions)
int unset_single_variable(self, str argument)
bool is_exactly_in_string(self, str string1, str string2)
int touch(self, List arg)
int check_file_path(self, str file_path)
int ask_for_env_to_unset(self)
None assing_colours(self)
int remove_function_from_options(self, Dict[str, any] function)
int show_history(self, List args)
int author(self, List args)
int create_a_file(self, str filename)
int remove_file(self, List args)
None process_if_pipe_input(self)
int process_help_call(self, List args)
bool check_if_admin_for_windows(self)
None process_if_arg_input(self)
int cd_access_directory(self, str path)
str auto_complete_default_usr_input
None process_complex_input(self, List usr_input)
int mainloop(self, session_name="main")
int remove_functions_from_shell(self, List[Dict[str, any]] functions)
int exit(self, List args)
int make_directory(self, List args)
int remove_an_item(self, str path)
str clean_string(self, str input_string)
str command_seperator_token
int run_as_admin(self, List args)
int command_seperator(self, List args)
int hello_world(self, List args)
int run_external_command(self, str command)
int help(self, List args)
int run_as_windows_admin(self, str file)
None display_prompt(self)
None function_help(self, str function_name, str description)
str help_function_child_name
int bind_ls(self, List args)
int cd_rollback(self, str path)
int env_plus_plus(self, List args)
int save_to_file(self, str data, str filepath)
int remove_directory(self, List args)
None run_complex_input(self, List[str] complex_input)
int change_directory(self, List args)
int remove_a_directory(self, str directory_path)
int cd_go_further_than_home(self, str path)
None display_status_in_prompt(self)