mac support
This commit is contained in:
+1
-1
@@ -35,7 +35,7 @@ le même dossier, synchronise son contenu, puis remplace la destination.
|
|||||||
Une interruption ne laisse donc pas un JSON partiellement écrit. Pour
|
Une interruption ne laisse donc pas un JSON partiellement écrit. Pour
|
||||||
une modification concurrente de type lire-modifier-écrire, utiliser
|
une modification concurrente de type lire-modifier-écrire, utiliser
|
||||||
=atomic_update_json= : cet utilitaire protège l'opération complète avec
|
=atomic_update_json= : cet utilitaire protège l'opération complète avec
|
||||||
un verrou inter-processus Linux/Windows.
|
un verrou inter-processus Linux, Windows et macOS.
|
||||||
|
|
||||||
* Convention des scripts standardisés
|
* Convention des scripts standardisés
|
||||||
|
|
||||||
|
|||||||
+42
-5
@@ -37,13 +37,24 @@ labels contenant notamment =:= ne sont pas acceptés par Windows.
|
|||||||
|
|
||||||
*** Python 3.11 ou plus récent
|
*** Python 3.11 ou plus récent
|
||||||
|
|
||||||
|
Sous macOS avec Homebrew, installer Python et la version correspondante
|
||||||
|
de Tkinter avant de créer l'environnement virtuel. Par exemple :
|
||||||
|
|
||||||
|
#+BEGIN_SRC bash
|
||||||
|
brew install python@3.13 python-tk@3.13
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Utiliser alors =python3.13= à la place de =python= dans les commandes
|
||||||
|
de création de l'environnement si la commande non versionnée n'est pas
|
||||||
|
disponible.
|
||||||
|
|
||||||
Créer et activer un environnement virtuel est recommandé :
|
Créer et activer un environnement virtuel est recommandé :
|
||||||
|
|
||||||
#+BEGIN_SRC bash
|
#+BEGIN_SRC bash
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Sous Linux :
|
Sous Linux et macOS :
|
||||||
|
|
||||||
#+BEGIN_SRC bash
|
#+BEGIN_SRC bash
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
@@ -99,11 +110,35 @@ les installations Python qui ne l'incluent pas d'origine.
|
|||||||
Fermer puis rouvrir le terminal et le GUI après une modification de
|
Fermer puis rouvrir le terminal et le GUI après une modification de
|
||||||
=PATH=.
|
=PATH=.
|
||||||
|
|
||||||
|
**** macOS
|
||||||
|
|
||||||
|
Avec Homebrew, installer Poppler et MacTeX :
|
||||||
|
|
||||||
|
#+BEGIN_SRC bash
|
||||||
|
brew install poppler
|
||||||
|
brew install --cask mactex
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
MacTeX fournit la distribution TeX Live complète utilisée par les
|
||||||
|
modèles de Copienator. Après son installation, rouvrir le terminal ou
|
||||||
|
exécuter :
|
||||||
|
|
||||||
|
#+BEGIN_SRC bash
|
||||||
|
eval "$(/usr/libexec/path_helper)"
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Copienator recherche aussi les exécutables dans =/opt/homebrew/bin=,
|
||||||
|
=/usr/local/bin= et =/Library/TeX/texbin=, notamment lorsque le GUI ne
|
||||||
|
récupère pas le =PATH= du terminal. Ces instructions conviennent aux
|
||||||
|
Mac Intel et Apple Silicon sous macOS 11 ou plus récent.
|
||||||
|
|
||||||
*** Programme externe facultatif
|
*** Programme externe facultatif
|
||||||
|
|
||||||
PDF Arranger permet d'ouvrir et de réorganiser plus facilement les
|
PDF Arranger permet d'ouvrir et de réorganiser plus facilement les
|
||||||
PDF. Son absence est signalée comme facultative dans le diagnostic. Il
|
PDF. Son absence est signalée comme facultative dans le diagnostic. Il
|
||||||
doit fournir la commande =pdf-arranger= ou =pdfarranger= dans =PATH=.
|
doit fournir la commande =pdf-arranger= ou =pdfarranger= dans =PATH=.
|
||||||
|
Sous macOS, Copienator utilise automatiquement Aperçu comme solution de
|
||||||
|
repli.
|
||||||
|
|
||||||
*** Accès à Gemini
|
*** Accès à Gemini
|
||||||
|
|
||||||
@@ -155,10 +190,12 @@ absent, vide ou ne contient que des commentaires. Ces automatismes ne
|
|||||||
se répètent pas lors d'un retour en arrière.
|
se répètent pas lors d'un retour en arrière.
|
||||||
|
|
||||||
Le bouton =Diagnostic…= vérifie les modules Python, Poppler, LaTeX,
|
Le bouton =Diagnostic…= vérifie les modules Python, Poppler, LaTeX,
|
||||||
PDF Arranger et la configuration Gemini. Sous Windows, les exécutables
|
PDF Arranger (ou Aperçu sous macOS) et la configuration Gemini. Sous
|
||||||
externes doivent être accessibles depuis =PATH=. Quand la création de
|
Windows, les exécutables externes doivent être accessibles depuis
|
||||||
liens symboliques ou physiques n'est pas autorisée, l'export et la
|
=PATH=. Sous macOS, les emplacements standards de Homebrew et MacTeX
|
||||||
préparation de =A Rendre= utilisent automatiquement une copie normale.
|
sont également inspectés. Quand la création de liens symboliques ou
|
||||||
|
physiques n'est pas autorisée, l'export et la préparation de =A Rendre=
|
||||||
|
utilisent automatiquement une copie normale.
|
||||||
|
|
||||||
Les chemins des étapes personnelles peuvent être adaptés avec
|
Les chemins des étapes personnelles peuvent être adaptés avec
|
||||||
=CURRENT_SCORE_ODS_PATH=, =FINAL_SCORE_ODS_PATH=,
|
=CURRENT_SCORE_ODS_PATH=, =FINAL_SCORE_ODS_PATH=,
|
||||||
|
|||||||
+2
-2
@@ -119,11 +119,11 @@ Optional : Set proxy with ~export HTTPS_PROXY="http://10.0.0.1:3128"~
|
|||||||
2. =python -m copienator review-labels Interro=
|
2. =python -m copienator review-labels Interro=
|
||||||
|
|
||||||
Permet de vérifier visuellement les labels trouvés.
|
Permet de vérifier visuellement les labels trouvés.
|
||||||
+ Sous linux, on peut faire =e= pour ouvrir le fichier .json et
|
+ Sous Linux et macOS, on peut faire =e= pour ouvrir le fichier .json et
|
||||||
l'éditer a la main.
|
l'éditer a la main.
|
||||||
+ Quand un label est manquant, il est possible de cliquer sur
|
+ Quand un label est manquant, il est possible de cliquer sur
|
||||||
l'image, ce qui copie les coordonnées dans le presse papier
|
l'image, ce qui copie les coordonnées dans le presse papier
|
||||||
(sous linux…), puis on peut l'ajouter à la main.
|
puis on peut l'ajouter à la main.
|
||||||
+ Utilisation de `_`, `|…` et `…|` :
|
+ Utilisation de `_`, `|…` et `…|` :
|
||||||
+ `|…` n'est pas arrêté verticalement par son type opposé.
|
+ `|…` n'est pas arrêté verticalement par son type opposé.
|
||||||
+ `…|` est stoppé horizontalement par le `|…` le plus proche.
|
+ `…|` est stoppé horizontalement par le `|…` le plus proche.
|
||||||
|
|||||||
+51
-7
@@ -8,6 +8,12 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
|
MACOS_EXECUTABLE_DIRECTORIES = (
|
||||||
|
Path("/opt/homebrew/bin"),
|
||||||
|
Path("/usr/local/bin"),
|
||||||
|
Path("/Library/TeX/texbin"),
|
||||||
|
)
|
||||||
|
|
||||||
WINDOWS_RESERVED_NAMES = {
|
WINDOWS_RESERVED_NAMES = {
|
||||||
"CON",
|
"CON",
|
||||||
"PRN",
|
"PRN",
|
||||||
@@ -71,6 +77,36 @@ def safe_filename(value: str, fallback: str = "Unknown") -> str:
|
|||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
|
def add_platform_executable_paths(
|
||||||
|
environment: dict[str, str], platform_name: str | None = None
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Expose common desktop-installed executables to child processes."""
|
||||||
|
result = dict(environment)
|
||||||
|
if (platform_name or sys.platform) != "darwin":
|
||||||
|
return result
|
||||||
|
existing = result.get("PATH", "").split(os.pathsep)
|
||||||
|
additions = [str(path) for path in MACOS_EXECUTABLE_DIRECTORIES if path.is_dir()]
|
||||||
|
result["PATH"] = os.pathsep.join(dict.fromkeys([*additions, *existing]))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def find_executable(
|
||||||
|
*candidates: str, platform_name: str | None = None
|
||||||
|
) -> str | None:
|
||||||
|
"""Find a command in PATH or in standard macOS package locations."""
|
||||||
|
for candidate in candidates:
|
||||||
|
executable = shutil.which(candidate)
|
||||||
|
if executable:
|
||||||
|
return executable
|
||||||
|
if (platform_name or sys.platform) == "darwin":
|
||||||
|
for directory in MACOS_EXECUTABLE_DIRECTORIES:
|
||||||
|
for candidate in candidates:
|
||||||
|
executable = directory / candidate
|
||||||
|
if executable.is_file() and os.access(executable, os.X_OK):
|
||||||
|
return str(executable)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def open_path(path: str | Path) -> None:
|
def open_path(path: str | Path) -> None:
|
||||||
"""Open a file with the desktop's default application."""
|
"""Open a file with the desktop's default application."""
|
||||||
target = str(Path(path).expanduser().resolve())
|
target = str(Path(path).expanduser().resolve())
|
||||||
@@ -78,17 +114,26 @@ def open_path(path: str | Path) -> None:
|
|||||||
os.startfile(target) # type: ignore[attr-defined]
|
os.startfile(target) # type: ignore[attr-defined]
|
||||||
elif sys.platform.startswith("linux"):
|
elif sys.platform.startswith("linux"):
|
||||||
subprocess.Popen(["xdg-open", target])
|
subprocess.Popen(["xdg-open", target])
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
opener = find_executable("open") or "/usr/bin/open"
|
||||||
|
subprocess.Popen([opener, target])
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Unsupported platform: {sys.platform}")
|
raise RuntimeError(f"Unsupported platform: {sys.platform}")
|
||||||
|
|
||||||
|
|
||||||
def launch_pdf_arranger(path: str | Path) -> None:
|
def launch_pdf_arranger(path: str | Path) -> None:
|
||||||
executable = shutil.which("pdf-arranger") or shutil.which("pdfarranger")
|
target = str(Path(path).expanduser().resolve())
|
||||||
if not executable:
|
executable = find_executable("pdf-arranger", "pdfarranger")
|
||||||
raise FileNotFoundError(
|
if executable:
|
||||||
"PDF Arranger is not installed or is not available in PATH."
|
subprocess.Popen([executable, target])
|
||||||
)
|
return
|
||||||
subprocess.Popen([executable, str(Path(path).expanduser().resolve())])
|
if sys.platform == "darwin":
|
||||||
|
opener = find_executable("open") or "/usr/bin/open"
|
||||||
|
subprocess.Popen([opener, "-b", "com.apple.Preview", target])
|
||||||
|
return
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"PDF Arranger is not installed or is not available in PATH."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def replace_with_link_or_copy(
|
def replace_with_link_or_copy(
|
||||||
@@ -118,4 +163,3 @@ def replace_with_link_or_copy(
|
|||||||
|
|
||||||
shutil.copy2(source_path, destination_path)
|
shutil.copy2(source_path, destination_path)
|
||||||
return "copy"
|
return "copy"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ from tkinter import filedialog, messagebox, ttk
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from copienator import ExitCode
|
from copienator import ExitCode
|
||||||
from copienator.platform import WindowsLabelError, open_path, validate_windows_labels
|
from copienator.platform import (
|
||||||
|
WindowsLabelError,
|
||||||
|
add_platform_executable_paths,
|
||||||
|
open_path,
|
||||||
|
validate_windows_labels,
|
||||||
|
)
|
||||||
|
|
||||||
from .diagnostics import collect_diagnostics
|
from .diagnostics import collect_diagnostics
|
||||||
from .runner import ProcessRunner
|
from .runner import ProcessRunner
|
||||||
@@ -95,7 +100,7 @@ def plotting_shortcut_lines() -> list[str]:
|
|||||||
def build_runner_environment(
|
def build_runner_environment(
|
||||||
base: dict[str, str], api_key: str, proxy: str, use_proxy: bool
|
base: dict[str, str], api_key: str, proxy: str, use_proxy: bool
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
environment = dict(base)
|
environment = add_platform_executable_paths(base)
|
||||||
environment["PYTHONUNBUFFERED"] = "1"
|
environment["PYTHONUNBUFFERED"] = "1"
|
||||||
environment["PYTHONIOENCODING"] = "utf-8"
|
environment["PYTHONIOENCODING"] = "utf-8"
|
||||||
if api_key.strip():
|
if api_key.strip():
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import sys
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from copienator.platform import windows_filename_problems
|
from copienator.platform import find_executable, windows_filename_problems
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -33,8 +33,10 @@ def collect_diagnostics(
|
|||||||
checks = [
|
checks = [
|
||||||
DiagnosticCheck(
|
DiagnosticCheck(
|
||||||
"Système",
|
"Système",
|
||||||
os.name == "nt" or sys.platform.startswith("linux"),
|
os.name == "nt"
|
||||||
f"{sys.platform} — Linux et Windows sont pris en charge",
|
or sys.platform.startswith("linux")
|
||||||
|
or sys.platform == "darwin",
|
||||||
|
f"{sys.platform} — Linux, Windows et macOS sont pris en charge",
|
||||||
),
|
),
|
||||||
DiagnosticCheck("Python", True, sys.executable),
|
DiagnosticCheck("Python", True, sys.executable),
|
||||||
]
|
]
|
||||||
@@ -65,7 +67,13 @@ def collect_diagnostics(
|
|||||||
("PDF Arranger", ("pdf-arranger", "pdfarranger"), False),
|
("PDF Arranger", ("pdf-arranger", "pdfarranger"), False),
|
||||||
)
|
)
|
||||||
for label, candidates, required in programs:
|
for label, candidates, required in programs:
|
||||||
executable = next((shutil.which(candidate) for candidate in candidates if shutil.which(candidate)), None)
|
executable = find_executable(*candidates)
|
||||||
|
if label == "PDF Arranger" and not executable and sys.platform == "darwin":
|
||||||
|
system_open = Path("/usr/bin/open")
|
||||||
|
opener = find_executable("open") or (
|
||||||
|
str(system_open) if system_open.is_file() else None
|
||||||
|
)
|
||||||
|
executable = f"{opener} (Aperçu)" if opener else None
|
||||||
detail = executable or "Introuvable dans PATH"
|
detail = executable or "Introuvable dans PATH"
|
||||||
checks.append(DiagnosticCheck(label, executable is not None, detail, required))
|
checks.append(DiagnosticCheck(label, executable is not None, detail, required))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user