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 modification concurrente de type lire-modifier-écrire, utiliser
|
||||
=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
|
||||
|
||||
|
||||
+42
-5
@@ -37,13 +37,24 @@ labels contenant notamment =:= ne sont pas acceptés par Windows.
|
||||
|
||||
*** 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é :
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
python -m venv .venv
|
||||
#+END_SRC
|
||||
|
||||
Sous Linux :
|
||||
Sous Linux et macOS :
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
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
|
||||
=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
|
||||
|
||||
PDF Arranger permet d'ouvrir et de réorganiser plus facilement les
|
||||
PDF. Son absence est signalée comme facultative dans le diagnostic. Il
|
||||
doit fournir la commande =pdf-arranger= ou =pdfarranger= dans =PATH=.
|
||||
Sous macOS, Copienator utilise automatiquement Aperçu comme solution de
|
||||
repli.
|
||||
|
||||
*** 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.
|
||||
|
||||
Le bouton =Diagnostic…= vérifie les modules Python, Poppler, LaTeX,
|
||||
PDF Arranger et la configuration Gemini. Sous Windows, les exécutables
|
||||
externes doivent être accessibles depuis =PATH=. 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.
|
||||
PDF Arranger (ou Aperçu sous macOS) et la configuration Gemini. Sous
|
||||
Windows, les exécutables externes doivent être accessibles depuis
|
||||
=PATH=. Sous macOS, les emplacements standards de Homebrew et MacTeX
|
||||
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
|
||||
=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=
|
||||
|
||||
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.
|
||||
+ Quand un label est manquant, il est possible de cliquer sur
|
||||
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 `…|` :
|
||||
+ `|…` n'est pas arrêté verticalement par son type opposé.
|
||||
+ `…|` est stoppé horizontalement par le `|…` le plus proche.
|
||||
|
||||
+48
-4
@@ -8,6 +8,12 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
MACOS_EXECUTABLE_DIRECTORIES = (
|
||||
Path("/opt/homebrew/bin"),
|
||||
Path("/usr/local/bin"),
|
||||
Path("/Library/TeX/texbin"),
|
||||
)
|
||||
|
||||
WINDOWS_RESERVED_NAMES = {
|
||||
"CON",
|
||||
"PRN",
|
||||
@@ -71,6 +77,36 @@ def safe_filename(value: str, fallback: str = "Unknown") -> str:
|
||||
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:
|
||||
"""Open a file with the desktop's default application."""
|
||||
target = str(Path(path).expanduser().resolve())
|
||||
@@ -78,17 +114,26 @@ def open_path(path: str | Path) -> None:
|
||||
os.startfile(target) # type: ignore[attr-defined]
|
||||
elif sys.platform.startswith("linux"):
|
||||
subprocess.Popen(["xdg-open", target])
|
||||
elif sys.platform == "darwin":
|
||||
opener = find_executable("open") or "/usr/bin/open"
|
||||
subprocess.Popen([opener, target])
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported platform: {sys.platform}")
|
||||
|
||||
|
||||
def launch_pdf_arranger(path: str | Path) -> None:
|
||||
executable = shutil.which("pdf-arranger") or shutil.which("pdfarranger")
|
||||
if not executable:
|
||||
target = str(Path(path).expanduser().resolve())
|
||||
executable = find_executable("pdf-arranger", "pdfarranger")
|
||||
if executable:
|
||||
subprocess.Popen([executable, target])
|
||||
return
|
||||
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."
|
||||
)
|
||||
subprocess.Popen([executable, str(Path(path).expanduser().resolve())])
|
||||
|
||||
|
||||
def replace_with_link_or_copy(
|
||||
@@ -118,4 +163,3 @@ def replace_with_link_or_copy(
|
||||
|
||||
shutil.copy2(source_path, destination_path)
|
||||
return "copy"
|
||||
|
||||
|
||||
@@ -8,7 +8,12 @@ from tkinter import filedialog, messagebox, ttk
|
||||
from typing import Any
|
||||
|
||||
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 .runner import ProcessRunner
|
||||
@@ -95,7 +100,7 @@ def plotting_shortcut_lines() -> list[str]:
|
||||
def build_runner_environment(
|
||||
base: dict[str, str], api_key: str, proxy: str, use_proxy: bool
|
||||
) -> dict[str, str]:
|
||||
environment = dict(base)
|
||||
environment = add_platform_executable_paths(base)
|
||||
environment["PYTHONUNBUFFERED"] = "1"
|
||||
environment["PYTHONIOENCODING"] = "utf-8"
|
||||
if api_key.strip():
|
||||
|
||||
@@ -7,7 +7,7 @@ import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from copienator.platform import windows_filename_problems
|
||||
from copienator.platform import find_executable, windows_filename_problems
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -33,8 +33,10 @@ def collect_diagnostics(
|
||||
checks = [
|
||||
DiagnosticCheck(
|
||||
"Système",
|
||||
os.name == "nt" or sys.platform.startswith("linux"),
|
||||
f"{sys.platform} — Linux et Windows sont pris en charge",
|
||||
os.name == "nt"
|
||||
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),
|
||||
]
|
||||
@@ -65,7 +67,13 @@ def collect_diagnostics(
|
||||
("PDF Arranger", ("pdf-arranger", "pdfarranger"), False),
|
||||
)
|
||||
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"
|
||||
checks.append(DiagnosticCheck(label, executable is not None, detail, required))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user