[UPD] TODO: auto installation

- better script message first execution
- search pycharm-community
- calculate time execution
- integrate crash diagnostic at importation
- show version installed and show installation option into cli
This commit is contained in:
Mathieu Benoit 2025-08-07 06:09:37 -04:00
parent 0ac7728d2f
commit fc73d0b9bd
2 changed files with 366 additions and 83 deletions

View file

@ -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

View file

@ -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")