Loading src/climax/__main__.py +86 −38 Original line number Diff line number Diff line import subprocess from pathlib import Path from typing import Optional, Union, Iterator, List, Any from typing import Optional, Union, Iterator, List from typing_extensions import Annotated import gettext import tempfile Loading @@ -14,7 +14,7 @@ import shutil from functools import cache import socket from .eggs import get_yolk from .model import MaXProjectConfig, db, MaxInstall, DEFAULT_DOT_BASEX_FILE from .model import MaXProjectConfig, db as climax_db, DEFAULT_DOT_BASEX_FILE from .config import ( WELCOME_PAGE, USER_MAX_DIR, Loading @@ -34,7 +34,7 @@ from .config import ( CRASH_MESSAGE, ) locales_dir = Path(__file__).parent / "locales" locales_dir = Path(Path(__file__).parent, "locales") gettext.bindtextdomain("messages", str(locales_dir)) gettext.textdomain("messages") _ = gettext.gettext Loading @@ -46,20 +46,33 @@ from rich.progress import track # noqa: E402 from rich.console import Console # noqa: E402 from rich.table import Table # noqa: E402 climax_db = db() def _ping_projects(max_install_directory: Path, meta: dict = None): if isinstance(max_install_directory, str): max_install_directory = Path(max_install_directory) db = climax_db() max_install = db.fetch_or_create_project_from_path( str(max_install_directory.resolve()) ) if meta is not None: max_install.meta = meta db.save(max_install) db.commit() last_project_dir = None def _track_last_dir(directory: Any) -> Any: def _track_last_dir(directory: Path) -> Path: global last_project_dir last_project_dir = directory _ping_projects(directory) return directory def _port_is_in_use(port: int) -> bool: def _port_is_in_use(port: int, host: str = "localhost") -> bool: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(("localhost", port)) == 0 return s.connect_ex((host, port)) == 0 def _closer_free_port_to(start_port: int) -> int: Loading Loading @@ -279,6 +292,7 @@ def _sync_bundles(root_directory: Path): dir_name, _ = os.path.splitext(os.path.basename(bundle["url"])) Path(bundles_dir, dir_name).rename(bundle_dir) bundle_archive_path.unlink() _ping_projects(root_directory) def _copy_max_tree( Loading Loading @@ -342,14 +356,12 @@ def _install_new_max_instance(root_directory: Path, init_values: dict = None): welcome_page = Path(root_directory, "content_html", "fr", "index.html") with open(welcome_page, "w", encoding="utf-8") as f: f.write(WELCOME_PAGE) climax_db.save(MaxInstall(None, str(root_directory.resolve()))) climax_db.commit() rich_print( "[bold]" + _("La nouvelle instance de MaX est prête dans {}").format(root_directory) + "[/bold]" ) print( rich_print( _( 'Vous pouvez éventuellement installer un projet de démonstration avec "climax demo".' ) Loading Loading @@ -414,7 +426,9 @@ def new( ensure_java() root_directory, is_max = ensure_available_max_directory(directory) if is_max: print(_("Le dossier contient une instance de MaX. Utilisez la commande sync")) rich_print( _("Le dossier contient une instance de MaX. Utilisez la commande sync") ) raise typer.Exit() init_values = None if interactive: Loading Loading @@ -475,27 +489,14 @@ def demo( ) @app.command( help=_( "Démarre l'instance de MaX.\n\nSi les ports choisis ne sont pas disponibles, climax tentera de lancer le serveur sur d'autres ports." ) ) def start( def _start_http_server( http_host: str = WEB_HOST, http_port: int = WEB_PORT, basex_port: int = BASEX_PORT, http_stop_port: int = STOP_PORT, service: Annotated[ bool, typer.Option( help=_( 'Démarrer MaX en tant que service. Utilisez "climax stop" pour arrêter le service.' ) ), ] = False, directory: Annotated[ str, typer.Option(help=_("Dossier du projet MaX")) ] = os.getcwd(), service: bool = False, directory: str = os.getcwd(), ping_projects: bool = True, ): basex_port = _closer_free_port_to(basex_port) http_port = _closer_free_port_to(http_port) Loading Loading @@ -523,7 +524,47 @@ def start( + _("Démarrage de MaX sur http://{}:{}").format(http_host, http_port) + "[/green]" ) if ping_projects: _ping_projects( directory, { "basex_port": basex_port, "http_port": http_port, "http_host": http_host, "http_stop_port": http_stop_port, }, ) try: subprocess.run(process_args) except KeyboardInterrupt: _ping_projects(directory, {}) @app.command( help=_( "Démarre l'instance de MaX.\n\nSi les ports choisis ne sont pas disponibles, climax tentera de lancer le serveur sur d'autres ports." ) ) def start( http_host: str = WEB_HOST, http_port: int = WEB_PORT, basex_port: int = BASEX_PORT, http_stop_port: int = STOP_PORT, service: Annotated[ bool, typer.Option( help=_( 'Démarrer MaX en tant que service. Utilisez "climax stop" pour arrêter le service.' ) ), ] = False, directory: Annotated[ str, typer.Option(help=_("Dossier du projet MaX")) ] = os.getcwd(), ): _start_http_server( http_host, http_port, basex_port, http_stop_port, service, directory, True ) def _choose_between(values: List[str], label: str = "") -> str: Loading Loading @@ -560,6 +601,7 @@ def stop( "stop", ] subprocess.run(process_args) _ping_projects(directory, {}) @app.command(help=_("Affiche la configuration de MaX")) Loading Loading @@ -599,12 +641,13 @@ def freeze( port_number = _free_port() stop_port = port_number + 1 stop_port = _closer_free_port_to(stop_port) start( _start_http_server( WEB_HOST, port_number, http_stop_port=stop_port, service=True, directory=directory, ping_projects=False, ) # copy website try: Loading Loading @@ -811,10 +854,8 @@ def sync( if is_max: _sync_max_instance(root_directory) climax_db.save(MaxInstall(None, str(root_directory.resolve()))) climax_db.commit() else: print( rich_print( _( "Le dossier ne contient pas une instance de MaX. Utiliser la commande new" ) Loading Loading @@ -904,14 +945,21 @@ def static_list( @app.command(help=_("Liste les projets gérés par climax")) def projects(): console = Console() table = Table(_("Projet"), _("Chemin"), show_lines=True) for max_install in climax_db.projects(): table = Table(_("Projet"), _("Chemin"), _("URL"), show_lines=True) db = climax_db() for max_install in db.projects(): if max_install.exists(): config = max_install.config() table.add_row(config.title, max_install.path) url = "" if max_install.meta: if _port_is_in_use( max_install.meta.get("http_port"), max_install.meta.get("http_host") ): url = f"http://{max_install.meta.get('http_host')}:{max_install.meta.get('http_port')}/" table.add_row(config.title, max_install.path, url) else: climax_db.delete("maxinstall", "id = ?", max_install.id) climax_db.commit() db.delete("maxinstall", "id = ?", max_install.id) db.commit() console.print(table) Loading src/climax/model.py +27 −2 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ from bs4 import BeautifulSoup from pathlib import Path from typing import Union, Dict, Iterator import os import json import oora from dataclasses import dataclass from .config import USER_MAX_DIR Loading Loading @@ -260,6 +261,7 @@ class MaXProjectConfig: class MaxInstall: id: int path: str _meta: str = None def exists(self): own_path = Path(self.path) Loading @@ -271,10 +273,32 @@ class MaxInstall: if self.exists(): return MaXProjectConfig(Path(self.path, "config.xml")) @property def meta(self) -> dict: if self._meta is not None: return json.loads(self._meta) return {} @meta.setter def meta(self, data: dict = None): if data is not None: self._meta = json.dumps(data) else: self._meta = None class ClimaxStore(oora.DB): def fetch_or_create_project_from_path(self, path) -> Union[MaxInstall, None]: row = self.execute( "select id, path, _meta from maxinstall where path = ?", [path] ).fetchone() if row: return self.hydrate(MaxInstall, row) else: return MaxInstall(None, path) def projects(self) -> Iterator[MaxInstall]: for row in self.execute("select * from maxinstall"): for row in self.execute("select id, path, _meta from maxinstall"): yield self.hydrate(MaxInstall, row) Loading @@ -285,6 +309,7 @@ def db() -> ClimaxStore: "climax.sqlite3", ), migrations={ "0000": "CREATE TABLE IF NOT EXISTS maxinstall(id INTEGER PRIMARY KEY, path TEXT UNIQUE ON CONFLICT REPLACE NOT NULL);" "0000": "CREATE TABLE IF NOT EXISTS maxinstall(id INTEGER PRIMARY KEY, path TEXT UNIQUE ON CONFLICT REPLACE NOT NULL);", "0001": "ALTER TABLE maxinstall ADD COLUMN _meta TEXT;", }, ).migrate() Loading
src/climax/__main__.py +86 −38 Original line number Diff line number Diff line import subprocess from pathlib import Path from typing import Optional, Union, Iterator, List, Any from typing import Optional, Union, Iterator, List from typing_extensions import Annotated import gettext import tempfile Loading @@ -14,7 +14,7 @@ import shutil from functools import cache import socket from .eggs import get_yolk from .model import MaXProjectConfig, db, MaxInstall, DEFAULT_DOT_BASEX_FILE from .model import MaXProjectConfig, db as climax_db, DEFAULT_DOT_BASEX_FILE from .config import ( WELCOME_PAGE, USER_MAX_DIR, Loading @@ -34,7 +34,7 @@ from .config import ( CRASH_MESSAGE, ) locales_dir = Path(__file__).parent / "locales" locales_dir = Path(Path(__file__).parent, "locales") gettext.bindtextdomain("messages", str(locales_dir)) gettext.textdomain("messages") _ = gettext.gettext Loading @@ -46,20 +46,33 @@ from rich.progress import track # noqa: E402 from rich.console import Console # noqa: E402 from rich.table import Table # noqa: E402 climax_db = db() def _ping_projects(max_install_directory: Path, meta: dict = None): if isinstance(max_install_directory, str): max_install_directory = Path(max_install_directory) db = climax_db() max_install = db.fetch_or_create_project_from_path( str(max_install_directory.resolve()) ) if meta is not None: max_install.meta = meta db.save(max_install) db.commit() last_project_dir = None def _track_last_dir(directory: Any) -> Any: def _track_last_dir(directory: Path) -> Path: global last_project_dir last_project_dir = directory _ping_projects(directory) return directory def _port_is_in_use(port: int) -> bool: def _port_is_in_use(port: int, host: str = "localhost") -> bool: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(("localhost", port)) == 0 return s.connect_ex((host, port)) == 0 def _closer_free_port_to(start_port: int) -> int: Loading Loading @@ -279,6 +292,7 @@ def _sync_bundles(root_directory: Path): dir_name, _ = os.path.splitext(os.path.basename(bundle["url"])) Path(bundles_dir, dir_name).rename(bundle_dir) bundle_archive_path.unlink() _ping_projects(root_directory) def _copy_max_tree( Loading Loading @@ -342,14 +356,12 @@ def _install_new_max_instance(root_directory: Path, init_values: dict = None): welcome_page = Path(root_directory, "content_html", "fr", "index.html") with open(welcome_page, "w", encoding="utf-8") as f: f.write(WELCOME_PAGE) climax_db.save(MaxInstall(None, str(root_directory.resolve()))) climax_db.commit() rich_print( "[bold]" + _("La nouvelle instance de MaX est prête dans {}").format(root_directory) + "[/bold]" ) print( rich_print( _( 'Vous pouvez éventuellement installer un projet de démonstration avec "climax demo".' ) Loading Loading @@ -414,7 +426,9 @@ def new( ensure_java() root_directory, is_max = ensure_available_max_directory(directory) if is_max: print(_("Le dossier contient une instance de MaX. Utilisez la commande sync")) rich_print( _("Le dossier contient une instance de MaX. Utilisez la commande sync") ) raise typer.Exit() init_values = None if interactive: Loading Loading @@ -475,27 +489,14 @@ def demo( ) @app.command( help=_( "Démarre l'instance de MaX.\n\nSi les ports choisis ne sont pas disponibles, climax tentera de lancer le serveur sur d'autres ports." ) ) def start( def _start_http_server( http_host: str = WEB_HOST, http_port: int = WEB_PORT, basex_port: int = BASEX_PORT, http_stop_port: int = STOP_PORT, service: Annotated[ bool, typer.Option( help=_( 'Démarrer MaX en tant que service. Utilisez "climax stop" pour arrêter le service.' ) ), ] = False, directory: Annotated[ str, typer.Option(help=_("Dossier du projet MaX")) ] = os.getcwd(), service: bool = False, directory: str = os.getcwd(), ping_projects: bool = True, ): basex_port = _closer_free_port_to(basex_port) http_port = _closer_free_port_to(http_port) Loading Loading @@ -523,7 +524,47 @@ def start( + _("Démarrage de MaX sur http://{}:{}").format(http_host, http_port) + "[/green]" ) if ping_projects: _ping_projects( directory, { "basex_port": basex_port, "http_port": http_port, "http_host": http_host, "http_stop_port": http_stop_port, }, ) try: subprocess.run(process_args) except KeyboardInterrupt: _ping_projects(directory, {}) @app.command( help=_( "Démarre l'instance de MaX.\n\nSi les ports choisis ne sont pas disponibles, climax tentera de lancer le serveur sur d'autres ports." ) ) def start( http_host: str = WEB_HOST, http_port: int = WEB_PORT, basex_port: int = BASEX_PORT, http_stop_port: int = STOP_PORT, service: Annotated[ bool, typer.Option( help=_( 'Démarrer MaX en tant que service. Utilisez "climax stop" pour arrêter le service.' ) ), ] = False, directory: Annotated[ str, typer.Option(help=_("Dossier du projet MaX")) ] = os.getcwd(), ): _start_http_server( http_host, http_port, basex_port, http_stop_port, service, directory, True ) def _choose_between(values: List[str], label: str = "") -> str: Loading Loading @@ -560,6 +601,7 @@ def stop( "stop", ] subprocess.run(process_args) _ping_projects(directory, {}) @app.command(help=_("Affiche la configuration de MaX")) Loading Loading @@ -599,12 +641,13 @@ def freeze( port_number = _free_port() stop_port = port_number + 1 stop_port = _closer_free_port_to(stop_port) start( _start_http_server( WEB_HOST, port_number, http_stop_port=stop_port, service=True, directory=directory, ping_projects=False, ) # copy website try: Loading Loading @@ -811,10 +854,8 @@ def sync( if is_max: _sync_max_instance(root_directory) climax_db.save(MaxInstall(None, str(root_directory.resolve()))) climax_db.commit() else: print( rich_print( _( "Le dossier ne contient pas une instance de MaX. Utiliser la commande new" ) Loading Loading @@ -904,14 +945,21 @@ def static_list( @app.command(help=_("Liste les projets gérés par climax")) def projects(): console = Console() table = Table(_("Projet"), _("Chemin"), show_lines=True) for max_install in climax_db.projects(): table = Table(_("Projet"), _("Chemin"), _("URL"), show_lines=True) db = climax_db() for max_install in db.projects(): if max_install.exists(): config = max_install.config() table.add_row(config.title, max_install.path) url = "" if max_install.meta: if _port_is_in_use( max_install.meta.get("http_port"), max_install.meta.get("http_host") ): url = f"http://{max_install.meta.get('http_host')}:{max_install.meta.get('http_port')}/" table.add_row(config.title, max_install.path, url) else: climax_db.delete("maxinstall", "id = ?", max_install.id) climax_db.commit() db.delete("maxinstall", "id = ?", max_install.id) db.commit() console.print(table) Loading
src/climax/model.py +27 −2 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ from bs4 import BeautifulSoup from pathlib import Path from typing import Union, Dict, Iterator import os import json import oora from dataclasses import dataclass from .config import USER_MAX_DIR Loading Loading @@ -260,6 +261,7 @@ class MaXProjectConfig: class MaxInstall: id: int path: str _meta: str = None def exists(self): own_path = Path(self.path) Loading @@ -271,10 +273,32 @@ class MaxInstall: if self.exists(): return MaXProjectConfig(Path(self.path, "config.xml")) @property def meta(self) -> dict: if self._meta is not None: return json.loads(self._meta) return {} @meta.setter def meta(self, data: dict = None): if data is not None: self._meta = json.dumps(data) else: self._meta = None class ClimaxStore(oora.DB): def fetch_or_create_project_from_path(self, path) -> Union[MaxInstall, None]: row = self.execute( "select id, path, _meta from maxinstall where path = ?", [path] ).fetchone() if row: return self.hydrate(MaxInstall, row) else: return MaxInstall(None, path) def projects(self) -> Iterator[MaxInstall]: for row in self.execute("select * from maxinstall"): for row in self.execute("select id, path, _meta from maxinstall"): yield self.hydrate(MaxInstall, row) Loading @@ -285,6 +309,7 @@ def db() -> ClimaxStore: "climax.sqlite3", ), migrations={ "0000": "CREATE TABLE IF NOT EXISTS maxinstall(id INTEGER PRIMARY KEY, path TEXT UNIQUE ON CONFLICT REPLACE NOT NULL);" "0000": "CREATE TABLE IF NOT EXISTS maxinstall(id INTEGER PRIMARY KEY, path TEXT UNIQUE ON CONFLICT REPLACE NOT NULL);", "0001": "ALTER TABLE maxinstall ADD COLUMN _meta TEXT;", }, ).migrate()