Compare commits
4 commits
4dd57e4c50
...
db1e9fa24f
| Author | SHA1 | Date | |
|---|---|---|---|
| db1e9fa24f | |||
| 839ef08ea4 | |||
| a2b507c471 | |||
| b856548d64 |
4 changed files with 268 additions and 39 deletions
|
|
@ -213,20 +213,19 @@ class TODO:
|
|||
|
||||
── {t("Sources & documentation")} ──
|
||||
[7] {t("Git - Git tools")}
|
||||
[8] {t("Update - Update all developed staging source code")}
|
||||
[9] {t("Doc - Documentation search")}
|
||||
[8] {t("Doc - Documentation search")}
|
||||
|
||||
── {t("AI & automation")} ──
|
||||
[10] {t("GPT code - AI assistant tools")}
|
||||
[11] {t("Automation - Demonstration of developed features")}
|
||||
[9] {t("GPT code - AI assistant tools")}
|
||||
[10] {t("Automation - Demonstration of developed features")}
|
||||
|
||||
── {t("Deployment, network & security")} ──
|
||||
[12] {t("Deploy - Deploy ERPLibre locally")}
|
||||
[13] {t("Network - Network tools")}
|
||||
[14] {t("Security - Dependency security audit")}
|
||||
[11] {t("Deploy - Deploy ERPLibre locally")}
|
||||
[12] {t("Network - Network tools")}
|
||||
[13] {t("Security - Dependency security audit")}
|
||||
|
||||
── {t("Preferences")} ──
|
||||
[15] {t("Language - Change language / Changer la langue")}
|
||||
[14] {t("Language - Change language / Changer la langue")}
|
||||
[0] {t("Back")}
|
||||
"""
|
||||
while True:
|
||||
|
|
@ -263,34 +262,30 @@ class TODO:
|
|||
if status is not False:
|
||||
return
|
||||
elif status == "8":
|
||||
status = self.prompt_execute_update()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "9":
|
||||
status = self.prompt_execute_doc()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "10":
|
||||
elif status == "9":
|
||||
status = self.prompt_execute_gpt_code()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "11":
|
||||
elif status == "10":
|
||||
status = self.prompt_execute_function()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "12":
|
||||
elif status == "11":
|
||||
status = self.prompt_execute_deploy()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "13":
|
||||
elif status == "12":
|
||||
status = self.prompt_execute_network()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "14":
|
||||
elif status == "13":
|
||||
status = self.prompt_execute_security()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "15":
|
||||
elif status == "14":
|
||||
status = self._change_language()
|
||||
if status is not False:
|
||||
return
|
||||
|
|
@ -4181,6 +4176,16 @@ class TODO:
|
|||
}
|
||||
)
|
||||
|
||||
# Déplacé depuis le menu Execute : mise à jour de tout le code source
|
||||
# de dev en staging (sous-menu de mise à jour).
|
||||
choices.append(
|
||||
{
|
||||
"prompt_description": t(
|
||||
"Update - Update all developed staging source code"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
help_info = self.fill_help_info(choices)
|
||||
|
||||
while True:
|
||||
|
|
@ -4189,10 +4194,12 @@ class TODO:
|
|||
if status == "0":
|
||||
return False
|
||||
elif status == str(len(choices)):
|
||||
self.debug_ide()
|
||||
self.prompt_execute_update()
|
||||
elif status == str(len(choices) - 1):
|
||||
self.upgrade_module()
|
||||
self.debug_ide()
|
||||
elif status == str(len(choices) - 2):
|
||||
self.upgrade_module()
|
||||
elif status == str(len(choices) - 3):
|
||||
self.open_shell_on_database()
|
||||
else:
|
||||
cmd_no_found = True
|
||||
|
|
|
|||
|
|
@ -580,20 +580,20 @@ TRANSLATIONS = {
|
|||
"en": "Open ERPLibre with TODO 🤖",
|
||||
},
|
||||
"Update all erplibre_base on database test": {
|
||||
"fr": "Mise à jour de tous les erplibre_base sur la base de données test",
|
||||
"en": "Update all erplibre_base on database test",
|
||||
"fr": "🔄 Mise à jour de tous les erplibre_base sur la base de données test",
|
||||
"en": "🔄 Update all erplibre_base on database test",
|
||||
},
|
||||
"Show code status": {
|
||||
"fr": "Afficher le statut du code",
|
||||
"en": "Show code status",
|
||||
"fr": "🔍 Afficher le statut du code",
|
||||
"en": "🔍 Show code status",
|
||||
},
|
||||
"Stash all code": {
|
||||
"fr": "Remiser tout le code",
|
||||
"en": "Stash all code",
|
||||
"fr": "📦 Remiser tout le code",
|
||||
"en": "📦 Stash all code",
|
||||
},
|
||||
"Format modified code": {
|
||||
"fr": "Formater le code modifié",
|
||||
"en": "Format modified code",
|
||||
"fr": "🎨 Formater le code modifié",
|
||||
"en": "🎨 Format modified code",
|
||||
},
|
||||
# todo.py hardcoded prompt_descriptions
|
||||
"Mobile - Compile and run software": {
|
||||
|
|
@ -601,24 +601,24 @@ TRANSLATIONS = {
|
|||
"en": "Mobile - Compile and run software",
|
||||
},
|
||||
"Upgrade Odoo - Migration Database": {
|
||||
"fr": "Mise à jour Odoo - Migration de base de données",
|
||||
"en": "Upgrade Odoo - Migration Database",
|
||||
"fr": "🚚 Mise à jour Odoo - Migration de base de données",
|
||||
"en": "🚚 Upgrade Odoo - Migration Database",
|
||||
},
|
||||
"Upgrade Poetry - Dependency of Odoo": {
|
||||
"fr": "Mise à jour Poetry - Dépendances d'Odoo",
|
||||
"en": "Upgrade Poetry - Dependency of Odoo",
|
||||
"fr": "📦 Mise à jour Poetry - Dépendances d'Odoo",
|
||||
"en": "📦 Upgrade Poetry - Dependency of Odoo",
|
||||
},
|
||||
"Open SHELL": {
|
||||
"fr": "Ouvrir le SHELL",
|
||||
"en": "Open SHELL",
|
||||
"fr": "🐚 Ouvrir le SHELL",
|
||||
"en": "🐚 Open SHELL",
|
||||
},
|
||||
"Upgrade Module": {
|
||||
"fr": "Mise à jour de module",
|
||||
"en": "Upgrade Module",
|
||||
"fr": "🧩 Mise à jour de module",
|
||||
"en": "🧩 Upgrade Module",
|
||||
},
|
||||
"Debug": {
|
||||
"fr": "Débogage",
|
||||
"en": "Debug",
|
||||
"fr": "🐛 Débogage",
|
||||
"en": "🐛 Debug",
|
||||
},
|
||||
"Migration module coverage": {
|
||||
"fr": "Couverture de migration des modules",
|
||||
|
|
|
|||
|
|
@ -210,12 +210,192 @@ def _menu_labels(cls) -> dict:
|
|||
return {}
|
||||
|
||||
|
||||
def _config_list(config_key, todo_dir):
|
||||
"""Entrées d'une liste de config de todo.json (ex. « code_from_makefile »),
|
||||
cherchée à n'importe quel niveau. [] si absente."""
|
||||
try:
|
||||
data = json.loads(
|
||||
(Path(todo_dir) / "todo.json").read_text(encoding="utf-8")
|
||||
)
|
||||
except (OSError, ValueError):
|
||||
return []
|
||||
found = []
|
||||
|
||||
def walk(o):
|
||||
if isinstance(o, dict):
|
||||
for k, v in o.items():
|
||||
if k == config_key and isinstance(v, list):
|
||||
found.extend(v)
|
||||
walk(v)
|
||||
elif isinstance(o, list):
|
||||
for x in o:
|
||||
walk(x)
|
||||
|
||||
walk(data)
|
||||
return found
|
||||
|
||||
|
||||
def _len_choices_offset(comp):
|
||||
"""K pour « str(len(choices)) » -> 0 et « str(len(choices) - N) » -> N ;
|
||||
None si le nœud n'est pas de cette forme."""
|
||||
if not (
|
||||
isinstance(comp, ast.Call)
|
||||
and isinstance(comp.func, ast.Name)
|
||||
and comp.func.id == "str"
|
||||
and comp.args
|
||||
):
|
||||
return None
|
||||
arg = comp.args[0]
|
||||
|
||||
def is_len_choices(x):
|
||||
return (
|
||||
isinstance(x, ast.Call)
|
||||
and isinstance(x.func, ast.Name)
|
||||
and x.func.id == "len"
|
||||
and x.args
|
||||
and isinstance(x.args[0], ast.Name)
|
||||
and x.args[0].id == "choices"
|
||||
)
|
||||
|
||||
if is_len_choices(arg):
|
||||
return 0
|
||||
if (
|
||||
isinstance(arg, ast.BinOp)
|
||||
and isinstance(arg.op, ast.Sub)
|
||||
and is_len_choices(arg.left)
|
||||
and isinstance(arg.right, ast.Constant)
|
||||
):
|
||||
return int(arg.right.value)
|
||||
return None
|
||||
|
||||
|
||||
def _dispatch_len(func) -> dict:
|
||||
"""{K: méthode} pour « status == str(len(choices) - K): self.M() »."""
|
||||
out = {}
|
||||
for node in ast.walk(func):
|
||||
if not isinstance(node, ast.If):
|
||||
continue
|
||||
test = node.test
|
||||
if not (
|
||||
isinstance(test, ast.Compare)
|
||||
and isinstance(test.left, ast.Name)
|
||||
and test.left.id == "status"
|
||||
and len(test.ops) == 1
|
||||
and isinstance(test.ops[0], ast.Eq)
|
||||
):
|
||||
continue
|
||||
k = _len_choices_offset(test.comparators[0])
|
||||
if k is None:
|
||||
continue
|
||||
method = None
|
||||
for stmt in node.body:
|
||||
for n in ast.walk(stmt):
|
||||
if (
|
||||
isinstance(n, ast.Call)
|
||||
and isinstance(n.func, ast.Attribute)
|
||||
and isinstance(n.func.value, ast.Name)
|
||||
and n.func.value.id == "self"
|
||||
):
|
||||
method = n.func.attr
|
||||
break
|
||||
if method:
|
||||
break
|
||||
if method:
|
||||
out[k] = method
|
||||
return out
|
||||
|
||||
|
||||
def _choices_children(func, todo_dir):
|
||||
"""Commandes d'un menu bâti par « choices » (config_file.get_config +
|
||||
choices.append/extend) avec dispatch « str(len(choices)-N) ». Renvoie une
|
||||
liste de (label, méthode, kwargs) dans l'ordre affiché, ou None si le motif
|
||||
ne s'applique pas. Les entrées de CONFIG rejouent via
|
||||
execute_from_configuration ; les entrées APPENDÉES via leur méthode."""
|
||||
def _dict_label(dnode):
|
||||
d = {}
|
||||
for k, v in zip(dnode.keys, dnode.values):
|
||||
if isinstance(k, ast.Constant):
|
||||
d[k.value] = _str_of(v)
|
||||
return d.get("prompt_description") or d.get("prompt_description_key")
|
||||
|
||||
# On collecte affectations et append AVEC leur n° de ligne pour rejouer
|
||||
# dans l'ORDRE (les entrées « menu_entry = {…}; choices.append(menu_entry) »
|
||||
# réutilisent la même variable -> il faut suivre la dernière valeur).
|
||||
config_key = None
|
||||
events = [] # (lineno, kind, payload)
|
||||
for node in ast.walk(func):
|
||||
if (
|
||||
isinstance(node, ast.Assign)
|
||||
and len(node.targets) == 1
|
||||
and isinstance(node.targets[0], ast.Name)
|
||||
):
|
||||
var = node.targets[0].id
|
||||
val = node.value
|
||||
if isinstance(val, ast.Dict):
|
||||
events.append((node.lineno, "assign", (var, val)))
|
||||
elif (
|
||||
var == "choices"
|
||||
and isinstance(val, ast.Call)
|
||||
and isinstance(val.func, ast.Attribute)
|
||||
and val.func.attr == "get_config"
|
||||
and val.args
|
||||
and isinstance(val.args[0], ast.Constant)
|
||||
):
|
||||
config_key = val.args[0].value
|
||||
elif (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "append"
|
||||
and isinstance(node.func.value, ast.Name)
|
||||
and node.func.value.id == "choices"
|
||||
and node.args
|
||||
):
|
||||
events.append((node.lineno, "append", node.args[0]))
|
||||
events.sort(key=lambda e: e[0])
|
||||
var_dict, appended = {}, []
|
||||
for _ln, kind, payload in events:
|
||||
if kind == "assign":
|
||||
var_dict[payload[0]] = payload[1]
|
||||
else: # append : arg = Dict littéral ou Name (variable)
|
||||
arg = payload
|
||||
dnode = (
|
||||
arg
|
||||
if isinstance(arg, ast.Dict)
|
||||
else var_dict.get(arg.id)
|
||||
if isinstance(arg, ast.Name)
|
||||
else None
|
||||
)
|
||||
if dnode is not None:
|
||||
lab = _dict_label(dnode)
|
||||
if lab:
|
||||
appended.append(lab)
|
||||
if config_key is None and not appended:
|
||||
return None
|
||||
children = []
|
||||
for entry in _config_list(config_key, todo_dir) if config_key else []:
|
||||
lab = (
|
||||
entry.get("prompt_description_key")
|
||||
or entry.get("prompt_description")
|
||||
or "?"
|
||||
)
|
||||
children.append((lab, "execute_from_configuration", {"instance": entry}))
|
||||
len_disp = _dispatch_len(func)
|
||||
n_config = len(children) # figé : `children` grossit dans la boucle
|
||||
n_total = n_config + len(appended)
|
||||
for j, lab in enumerate(appended):
|
||||
pos = n_config + j # 0-based dans la liste globale (stable)
|
||||
method = len_disp.get(n_total - 1 - pos) # None si non mappé
|
||||
children.append((lab, method, {}))
|
||||
return children
|
||||
|
||||
|
||||
def build_code_tree(todo_path=None) -> dict | None:
|
||||
"""Construit l'arbre des menus/commandes EN LISANT le code de todo.py
|
||||
(AST). Chaque menu (méthode de _MENU_LABELS) devient un nœud ; ses branches
|
||||
« status == N » deviennent des sous-menus (si la cible est un menu) ou des
|
||||
commandes (feuilles). None si l'analyse échoue."""
|
||||
p = Path(todo_path) if todo_path else (Path(__file__).parent / "todo.py")
|
||||
todo_dir = p.parent # todo.json est à côté de todo.py
|
||||
try:
|
||||
mod = ast.parse(p.read_text(encoding="utf-8"))
|
||||
except (OSError, SyntaxError):
|
||||
|
|
@ -270,6 +450,25 @@ def build_code_tree(todo_path=None) -> dict | None:
|
|||
"section": entry["section"] if entry else None,
|
||||
}
|
||||
)
|
||||
# Menus bâtis par « choices » (get_config + append) sans dispatch
|
||||
# littéral « status == "N" » : Code, Update, Git, Database…
|
||||
if not disp:
|
||||
for lab, tmethod, tkwargs in (
|
||||
_choices_children(func, todo_dir) or []
|
||||
):
|
||||
if tmethod in labels: # une entrée qui ouvre un sous-menu
|
||||
node["children"].append(build(tmethod))
|
||||
else:
|
||||
node["children"].append(
|
||||
{
|
||||
"label": lab,
|
||||
"is_menu": False,
|
||||
"children": [],
|
||||
"method": tmethod,
|
||||
"kwargs": tkwargs,
|
||||
"section": None,
|
||||
}
|
||||
)
|
||||
seen.discard(method)
|
||||
return node
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ import shutil
|
|||
import sys
|
||||
import zipfile
|
||||
from uuid import uuid4
|
||||
from script.todo.version_manager import get_odoo_version
|
||||
|
||||
import click
|
||||
import todo_file_browser
|
||||
|
||||
from script.todo.version_manager import get_odoo_version
|
||||
|
||||
new_path = os.path.normpath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
)
|
||||
|
|
@ -768,6 +769,28 @@ class TodoUpgrade:
|
|||
|
||||
print("✅ -> Restore database")
|
||||
|
||||
if not self.dct_progression.get("state_1_update_all"):
|
||||
print(
|
||||
"[1] Update all addons before neutralize (already neutralize by odoo if supported)"
|
||||
)
|
||||
wait_continue = (
|
||||
input(
|
||||
"💬 Do you need to upgrade before a database neutralization, press to ignore : "
|
||||
)
|
||||
.strip()
|
||||
.lower()
|
||||
)
|
||||
if wait_continue == "1":
|
||||
status, cmd_executed = self.todo_upgrade_execute(
|
||||
f"./script/addons/update_addons_all.sh {database_name}",
|
||||
single_source_odoo=True,
|
||||
)
|
||||
if not status:
|
||||
self.dct_progression["state_1_update_all"] = True
|
||||
self.write_config()
|
||||
|
||||
print("✅ -> Update database before neutralize by module")
|
||||
|
||||
print("✅ -> Neutralize database")
|
||||
if do_neutralize:
|
||||
status, cmd_executed = self.todo_upgrade_execute(
|
||||
|
|
|
|||
Loading…
Reference in a new issue