diff --git a/script/todo/source_todo.sh b/script/todo/source_todo.sh index 6b6ab3b..84082d2 100755 --- a/script/todo/source_todo.sh +++ b/script/todo/source_todo.sh @@ -1,5 +1,23 @@ #!/bin/bash +DIR_VENV_ERPLIBRE_EXIST=1 +DIR_VENV_ERPLIBRE=".venv.erplibre" +# If not exist, create it and do installation +# Can be in conflict with ./script/install_locally.sh -source .venv/bin/activate +if [[ ! -d "$DIR_VENV_ERPLIBRE" ]]; then + DIR_VENV_ERPLIBRE_EXIST=0 + echo "$DIR_VENV_ERPLIBRE does not exist." + if [[ ! -d "~/.pyenv/versions/$(< ./conf/python-erplibre-version)/bin/python" ]]; then + "~/.pyenv/versions/$(< ./conf/python-erplibre-version)/bin/python" -m venv $DIR_VENV_ERPLIBRE + else + python -m venv $DIR_VENV_ERPLIBRE + fi +fi + +# If exist, source it and start installation +source ./.venv.erplibre/bin/activate +if [[ $DIR_VENV_ERPLIBRE_EXIST -eq 0 ]]; then + pip install -r requirement/erplibre_require-ments.txt +fi ./script/todo/todo.py diff --git a/script/todo/todo.py b/script/todo/todo.py index b78b87a..8c0077e 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -1,89 +1,46 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +import datetime import getpass import json import logging import os +import shutil +import subprocess import sys import time -import subprocess + +file_error_path = ".erplibre.error.txt" +cst_venv_erplibre = ".venv.erplibre" +VERSION_DATA_FILE = os.path.join("conf", "supported_version_erplibre.json") +INSTALLED_ODOO_VERSION_FILE = os.path.join( + ".repo", "installed_odoo_version.txt" +) +ODOO_VERSION_FILE = os.path.join(".odoo-version") +ENABLE_CRASH = False +CRASH_E = None try: import tkinter as tk from tkinter import filedialog import click + import humanize import openai from pykeepass import PyKeePass + + # TODO implement urwid to improve text user interface + # import urwid + # TODO implement rich for beautiful print and table + # import rich except ModuleNotFoundError as e: - # TODO auto-detect gnome-terminal, or choose another. - if os.path.exists(".venv"): - print("You forgot to activate source \nsource ./.venv/bin/activate") - time.sleep(0.5) - subprocess.run( - "gnome-terminal -- bash -c './script/todo/source_todo.sh'", - shell=True, - executable="/bin/bash", - ) - else: - first_installation_input = ( - input( - "First installation? This will process system installation" - " before (Y/N): " - ) - .strip() - .upper() - ) - if first_installation_input == "Y": - subprocess.run( - "gnome-terminal -- bash -c" - " './script/version/update_env_version.py --install;bash'", - shell=True, - executable="/bin/bash", - ) - print("Wait after OS installation before continue.") + humanize = None + ENABLE_CRASH = True + CRASH_E = e - # First detect pycharm, need to be open before installation and close to increase speed - result = subprocess.run( - ["which", "pycharm"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - if result.returncode == 0 and not os.path.exists(".idea"): - pycharm_configuration_input = ( - input("Open Pycharm? (Y/N): ").strip().upper() - ) - if pycharm_configuration_input == "Y": - subprocess.run( - "gnome-terminal -- bash -c 'pycharm .'", - shell=True, - executable="/bin/bash", - ) - print( - "Close Pycharm when processing is done before continue" - " this guide." - ) - # Propose Odoo installation - # TODO detect last version supported - odoo_installation_input = ( - input("Install Odoo 16? (Y/N): ").strip().upper() - ) - if odoo_installation_input == "Y": - subprocess.run( - "gnome-terminal -- bash -c 'make install_odoo_16;bash'", - shell=True, - executable="/bin/bash", - ) - print("Wait after installation and open projects by terminal.") - print("make open_terminal") - else: - print("Nothing to do, you need a fresh installation to continue.") - sys.exit(0) - sys.exit(1) +if not ENABLE_CRASH: + print("Importation success!") -# TODO implement urwid to improve text user interface -# import urwid _logger = logging.getLogger(__name__) CONFIG_FILE = "./script/todo/todo.json" @@ -94,6 +51,30 @@ LOGO_ASCII_FILE = "./script/todo/logo_ascii.txt" class TODO: def __init__(self): self.kdbx = None + self.init() + + def init(self): + # Get command + self.cmd_source_erplibre = "" + exec_path_gnome_terminal = shutil.which("gnome-terminal") + if exec_path_gnome_terminal: + self.cmd_source_erplibre = ( + f"gnome-terminal -- bash -c 'source" + f" ./{cst_venv_erplibre}/bin/activate;%s'" + ) + else: + exec_path_tell = shutil.which("osascript") + if exec_path_tell: + self.cmd_source_erplibre = ( + "osascript -e 'tell application \"Terminal\"'" + ) + self.cmd_source_erplibre += " -e 'tell application \"System Events\" to keystroke \"PATH\" using {command down}' -e 'delay 0.1' -e 'do script \"" + self.cmd_source_erplibre += f"./{cst_venv_erplibre}/bin/activate; %s\" in front window'" + self.cmd_source_erplibre += " -e 'end tell'" + else: + self.cmd_source_erplibre = ( + f"source ./{cst_venv_erplibre}/bin/activate;%s" + ) def run(self): with open(LOGO_ASCII_FILE) as my_file: @@ -102,26 +83,38 @@ class TODO: print("🤖 => Entre tes directives par son chiffre et fait Entrée!") help_info = """Commande : [1] Execute -[2] Question -[3] Fork - Ouvre TODO 🤖 dans une nouvelle tabulation +[2] Install +[3] Question +[4] Fork - Ouvre TODO 🤖 dans une nouvelle tabulation [0] Quitter """ - # [3] install while True: - status = click.prompt(help_info) + try: + status = click.prompt(help_info) + except NameError: + print("Do") + print("source .venv.erplibre/bin/activate && make") + sys.exit(1) + except ImportError: + print("Do") + print("source .venv.erplibre/bin/activate && make") + sys.exit(1) print() if status == "0": break elif status == "1": self.prompt_execute() elif status == "2": - self.execute_prompt_ia() + self.prompt_install() elif status == "3": - cmd = ( - f"gnome-terminal --tab -- bash -c 'source" - f" ./.venv/bin/activate;make todo'" - ) - self.executer_commande_live(cmd) + self.execute_prompt_ia() + elif status == "4": + # cmd = ( + # f"gnome-terminal --tab -- bash -c 'source" + # f" ./{cst_venv_erplibre}/bin/activate;make todo'" + # ) + cmd = "make todo" + self.executer_commande_live(cmd, source_erplibre=True) # elif status == "3" or status == "install": # print("install") else: @@ -236,6 +229,164 @@ class TODO: else: print("Commande non trouvée 🤖!") + def prompt_install(self): + print("Detect first installation from code source.") + + first_installation_input = ( + input( + "First system installation? This will process system installation" + " before (Y/N): " + ) + .strip() + .upper() + ) + if first_installation_input == "Y": + cmd = "./script/version/update_env_version.py --install" + self.executer_commande_live(cmd, source_erplibre=True) + print("Wait after OS installation before continue.") + + # First detect pycharm, need to be open before installation and close to increase speed + has_pycharm = False + has_pycharm_community = False + result = subprocess.run( + ["which", "pycharm"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + if result.returncode == 0: + has_pycharm = True + else: + result = subprocess.run( + ["which", "pycharm-community"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + has_pycharm_community = result.returncode == 0 + if (has_pycharm or has_pycharm_community) and not os.path.exists( + ".idea" + ): + pycharm_configuration_input = ( + input("Open Pycharm? (Y/N): ").strip().upper() + ) + if pycharm_configuration_input == "Y": + pycharm_bin = "pycharm" if has_pycharm else "pycharm-community" + self.executer_commande_live(pycharm_bin, source_erplibre=True) + print( + "Close Pycharm when processing is done before continue" + " this guide." + ) + # Propose Odoo installation + # TODO detect last version supported + odoo_installation_input = ( + input("Install virtual environment? (Y/N): ").strip().upper() + ) + if odoo_installation_input == "Y": + # cmd_intern = "./script/install/install_erplibre.sh" + # TODO maybe update q to only install erplibre from install_locally + # TODO problem installing with q, the script depend on odoo + key_i = 0 + dct_cmd_intern_begin = { + "q": ( + "q", + "q: ERPLibre only with system python without Odoo", + "./script/install/install_erplibre.sh", + ), + "w": ( + "w", + "w: Install all Odoo version with ERPLibre", + "make install_odoo_all_version", + ), + } + dct_final_cmd_intern = {} + with open(VERSION_DATA_FILE) as txt: + data_version = json.load(txt) + + if not data_version: + raise Exception( + f"Internal error, no Odoo version is supported, please valide file '{VERSION_DATA_FILE}'" + ) + + lst_version_transform = [] + for key, value in data_version.items(): + lst_version_transform.append(value) + value["erplibre_version"] = key + + lst_version_installed = [] + if os.path.exists(INSTALLED_ODOO_VERSION_FILE): + with open(INSTALLED_ODOO_VERSION_FILE) as txt: + lst_version_installed = sorted(txt.read().splitlines()) + + odoo_installed_version = None + if os.path.exists(ODOO_VERSION_FILE): + with open(ODOO_VERSION_FILE) as txt: + odoo_installed_version = f"odoo{txt.read().strip()}" + + # Add odoo version installation on command + lst_version = sorted( + lst_version_transform, key=lambda k: k.get("erplibre_version") + ) + for dct_version in lst_version[::-1]: + key_i += 1 + key_s = str(key_i) + label = f"{key_s}: Odoo {dct_version.get('odoo_version')}" + + odoo_version = f"odoo{dct_version.get('odoo_version')}" + if odoo_version in lst_version_installed: + label += " - Installed" + if odoo_version == odoo_installed_version: + label += " - Actual" + if dct_version.get("default"): + label += " - Default" + if dct_version.get("is_deprecated"): + label += " - Deprecated" + erplibre_version = dct_version.get("erplibre_version") + dct_cmd_intern_begin[key_s] = ( + key_s, + label, + f"./script/version/update_env_version.py --erplibre_version {erplibre_version} --install_dev", + ) + + # Add final command + dct_cmd_intern = {**dct_cmd_intern_begin, **dct_final_cmd_intern} + + # Show command + odoo_version_input = "" + while odoo_version_input not in dct_cmd_intern.keys(): + if odoo_version_input: + print( + f"Error, cannot understand value '{odoo_version_input}'" + ) + str_input_dyn_odoo_version = ( + "Choose a version:\n\t" + + "\n\t".join([a[1] for a in dct_cmd_intern.values()]) + + "\nSelect : " + ) + odoo_version_input = ( + input(str_input_dyn_odoo_version).strip().lower() + ) + + cmd_intern = dct_cmd_intern.get(odoo_version_input)[2] + print(f"Will execute :\n{cmd_intern}") + + # TODO use external script to detect terminal to use on system + # TODO check script open_terminal_code_generator.sh + # cmd_extern = f"gnome-terminal -- bash -c '{cmd_intern};bash'" + try: + subprocess.run( + cmd_intern, shell=True, executable="/bin/bash", check=True + ) + except subprocess.CalledProcessError as e: + print( + f"Le script Bash «{cmd_intern}» a échoué avec le code de retour {e.returncode}." + ) + print("Wait after installation and open projects by terminal.") + print("make open_terminal") + self.restart_script(str(e)) + else: + print("Nothing to do, you need a fresh installation to continue.") + def execute_from_configuration( self, dct_instance, exec_run_db=False, ignore_makefile=False ): @@ -274,7 +425,6 @@ class TODO: help_info = "Commande :\n" help_end = "[0] Retour\n" for i, dct_instance in enumerate(lst_instance): - help_info += ( f"[{i + 1}] " + dct_instance["prompt_description"] + "\n" ) @@ -459,17 +609,28 @@ class TODO: new_cmd += f' "sleep {1 * i};{cmd}"' self.executer_commande_live(new_cmd) - def executer_commande_live(self, commande): + def executer_commande_live(self, commande, source_erplibre=True): """ Exécute une commande et affiche la sortie en direct. Args: commande (str): La commande à exécuter (sous forme de chaîne de caractères). """ + + if source_erplibre: + # commande = f"source ./{cst_venv_erplibre}/bin/activate && " + commande + # cmd = ( + # f"gnome-terminal --tab -- bash -c 'source" + # f" ./{cst_venv_erplibre}/bin/activate;{commande}'" + # ) + commande = self.cmd_source_erplibre % commande + print(f"Execute : {commande}") + # os.system(f"./script/terminal/open_terminal.sh {commande}") try: process = subprocess.Popen( commande, shell=True, + executable="/bin/bash", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, @@ -504,7 +665,111 @@ class TODO: except Exception as e: print(f"Une erreur s'est produite : {e}") + def crash_diagnostic(self, e): + # TODO show message at start if os.path.exists(file_error_path) + if os.path.exists(file_error_path) and not os.path.exists( + cst_venv_erplibre + ): + print("Got error : ") + print(e) + print("Got error at first execution.", file_error_path) + try: + file = open(file_error_path, "r") + content = file.read() + # TODO si vide, ajouter notre erreur + print(content) + except FileNotFoundError: + print("Error: File not found.") + finally: + if "file" in locals() and file: + file.close() + # Force auto installation + print("Auto installation") + time.sleep(0.5) + cmd = "./script/todo/source_todo.sh" + # self.restart_script(e) + self.executer_commande_live(cmd, source_erplibre=True) + sys.exit(1) + if os.path.exists(cst_venv_erplibre): + print("Import error : ") + print(e) + # TODO auto-detect gnome-terminal, or choose another. Is it done already? + self.restart_script(e) + # self.prompt_install() + + # print( + # f"You forgot to activate source \nsource ./{cst_venv_erplibre}/bin/activate" + # ) + # time.sleep(0.5) + # cmd = "./script/todo/source_todo.sh" + print("Re-execute TODO 🤖 or execute :") + print() + print(f"source {cst_venv_erplibre}/bin/activate;make") + print() + cmd = "./script/todo/todo.py" + # # self.restart_script(e) + try: + # TODO duplicate + import tkinter as tk + from tkinter import filedialog + + import click + import humanize + import openai + from pykeepass import PyKeePass + except ImportError: + print("Rerun and exit") + self.executer_commande_live(cmd, source_erplibre=True) + sys.exit(1) + print("No error") + else: + self.prompt_install() + + def restart_script(self, last_error): + print("Reboot TODO 🤖...") + # os.execv(sys.executable, ['python'] + sys.argv) + # TODO mettre check que le répertoire est créé, s'il existe, auto-loop à corriger + if os.path.exists(cst_venv_erplibre) and not os.path.exists( + file_error_path + ): + # TODO mettre check import suivant ne vont pas planter + try: + with open(file_error_path, "w") as f_file: + f_file.write(str(last_error)) + pass # The file is created and closed here, no content is written + print( + f"Try to reopen process with before :\nsource ./{cst_venv_erplibre}/bin/activate && exec python " + + " ".join(sys.argv) + ) + os.execv( + "/bin/bash", + [ + "/bin/bash", + "-c", + f"source ./{cst_venv_erplibre}/bin/activate && exec python " + + " ".join(sys.argv), + ], + ) + except Exception as e: + print("Error detect at first execution.") + print(e) + if __name__ == "__main__": - todo = TODO() - todo.run() + start_time = time.time() + try: + todo = TODO() + if ENABLE_CRASH: + todo.crash_diagnostic(CRASH_E) + todo.run() + except KeyboardInterrupt: + print("Keyboard interrupt") + finally: + end_time = time.time() + duration_sec = end_time - start_time + if humanize: + duration_delta = datetime.timedelta(seconds=duration_sec) + humain_time = humanize.precisedelta(duration_delta) + print(f"\nTODO execution time {humain_time}\n") + else: + print(f"\nTODO execution time {duration_sec:.2f} sec.\n")