@@ -410,6 +410,14 @@ editor directs the user to copy the standalone `scripts/devlog_editor.py` into
|
|||||||
the project checkout, initialize and author the devlog, publish the changes, and
|
the project checkout, initialize and author the devlog, publish the changes, and
|
||||||
recheck. It never edits, commits, or pushes an external project repository.
|
recheck. It never edits, commits, or pushes an external project repository.
|
||||||
|
|
||||||
|
Project Editor requires the Lektor Python package because it uses the site's
|
||||||
|
authoritative importer directly. If the launching Python cannot import Lektor,
|
||||||
|
the editor automatically checks `LABYRICORN_LEKTOR_PYTHON`, a project-local
|
||||||
|
`.venv`, common pipx Lektor environments, and the environment containing a
|
||||||
|
`lektor` command. It relaunches itself with the first runtime that can import
|
||||||
|
both Lektor and Tkinter. Set `LABYRICORN_LEKTOR_PYTHON` to an explicit Python
|
||||||
|
executable when Lektor is installed somewhere else.
|
||||||
|
|
||||||
## Production filesystem and permissions
|
## Production filesystem and permissions
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
_model: entry
|
|
||||||
---
|
|
||||||
title: This is a blog example, delete me later
|
|
||||||
---
|
|
||||||
date: 2026-08-13
|
|
||||||
---
|
|
||||||
author: Christopher Chambers
|
|
||||||
---
|
|
||||||
tags: ai, configuration, html, taggy
|
|
||||||
---
|
|
||||||
kicker: Blog
|
|
||||||
---
|
|
||||||
summary: Summary
|
|
||||||
---
|
|
||||||
published_urls:
|
|
||||||
|
|
||||||
---
|
|
||||||
body:
|
|
||||||
Body (markdown) woohoo!
|
|
||||||
+103
-1
@@ -12,6 +12,7 @@ from pathlib import Path
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -33,6 +34,104 @@ class ProjectEditorError(Exception):
|
|||||||
"""An expected project-editor problem suitable for display to a user."""
|
"""An expected project-editor problem suitable for display to a user."""
|
||||||
|
|
||||||
|
|
||||||
|
def _python_has_lektor(executable: Path) -> bool:
|
||||||
|
if not executable.is_file():
|
||||||
|
return False
|
||||||
|
options: dict[str, Any] = {
|
||||||
|
"capture_output": True,
|
||||||
|
"text": True,
|
||||||
|
"timeout": 10,
|
||||||
|
"check": False,
|
||||||
|
}
|
||||||
|
if os.name == "nt":
|
||||||
|
options["creationflags"] = subprocess.CREATE_NO_WINDOW
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[str(executable), "-c", "import lektor, tkinter"],
|
||||||
|
**options,
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return False
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def _find_lektor_python(site_root: Path) -> Path | None:
|
||||||
|
"""Locate a Python runtime that can import both Lektor and Tkinter."""
|
||||||
|
candidates: list[Path] = []
|
||||||
|
configured = os.environ.get("LABYRICORN_LEKTOR_PYTHON", "").strip()
|
||||||
|
if configured:
|
||||||
|
candidates.append(Path(configured).expanduser())
|
||||||
|
if os.name == "nt":
|
||||||
|
candidates.extend(
|
||||||
|
(
|
||||||
|
site_root / ".venv" / "Scripts" / "python.exe",
|
||||||
|
Path(os.environ.get("LOCALAPPDATA", ""))
|
||||||
|
/ "pipx"
|
||||||
|
/ "pipx"
|
||||||
|
/ "venvs"
|
||||||
|
/ "lektor"
|
||||||
|
/ "Scripts"
|
||||||
|
/ "python.exe",
|
||||||
|
Path.home()
|
||||||
|
/ ".local"
|
||||||
|
/ "pipx"
|
||||||
|
/ "venvs"
|
||||||
|
/ "lektor"
|
||||||
|
/ "Scripts"
|
||||||
|
/ "python.exe",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
candidates.extend(
|
||||||
|
(
|
||||||
|
site_root / ".venv" / "bin" / "python",
|
||||||
|
Path.home() / ".local" / "pipx" / "venvs" / "lektor" / "bin" / "python",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
lektor_command = shutil.which("lektor")
|
||||||
|
if lektor_command:
|
||||||
|
candidates.append(
|
||||||
|
Path(lektor_command).resolve().parent
|
||||||
|
/ ("python.exe" if os.name == "nt" else "python")
|
||||||
|
)
|
||||||
|
|
||||||
|
seen: set[Path] = set()
|
||||||
|
for candidate in candidates:
|
||||||
|
candidate = candidate.resolve()
|
||||||
|
if candidate in seen:
|
||||||
|
continue
|
||||||
|
seen.add(candidate)
|
||||||
|
if _python_has_lektor(candidate):
|
||||||
|
return candidate
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _relaunch_with_lektor(site_root: Path) -> bool:
|
||||||
|
try:
|
||||||
|
import lektor # noqa: F401
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if os.environ.get("LABYRICORN_LEKTOR_RELAUNCHED") == "1":
|
||||||
|
return False
|
||||||
|
executable = _find_lektor_python(site_root)
|
||||||
|
if executable is None:
|
||||||
|
return False
|
||||||
|
gui_executable = executable
|
||||||
|
if os.name == "nt":
|
||||||
|
pythonw = executable.with_name("pythonw.exe")
|
||||||
|
if pythonw.is_file():
|
||||||
|
gui_executable = pythonw
|
||||||
|
child_environment = {**os.environ, "LABYRICORN_LEKTOR_RELAUNCHED": "1"}
|
||||||
|
subprocess.Popen(
|
||||||
|
[str(gui_executable), str(Path(__file__).resolve()), *sys.argv[1:]],
|
||||||
|
cwd=site_root,
|
||||||
|
env=child_environment,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _load_project_importer() -> Any:
|
def _load_project_importer() -> Any:
|
||||||
try:
|
try:
|
||||||
return importlib.import_module("project_sources")
|
return importlib.import_module("project_sources")
|
||||||
@@ -691,8 +790,11 @@ def main() -> int:
|
|||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--site-root", type=Path, default=Path(__file__).resolve().parents[1])
|
parser.add_argument("--site-root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
site_root = args.site_root.resolve()
|
||||||
|
if _relaunch_with_lektor(site_root):
|
||||||
|
return 0
|
||||||
try:
|
try:
|
||||||
run_gui(ProjectSourceRegistry(args.site_root))
|
run_gui(ProjectSourceRegistry(site_root))
|
||||||
except ProjectEditorError as exc:
|
except ProjectEditorError as exc:
|
||||||
parser.exit(1, f"project-editor: ERROR: {exc}\n")
|
parser.exit(1, f"project-editor: ERROR: {exc}\n")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
Reference in New Issue
Block a user