uEdition-Editor 2.0.0b2__tar.gz → 2.0.0b4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of uEdition-Editor might be problematic. Click here for more details.
- uedition_editor-2.0.0b4/Containerfile +10 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/PKG-INFO +13 -9
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/README.md +3 -2
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/pyproject.toml +9 -6
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/__about__.py +1 -1
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/api/branches.py +40 -2
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/api/configs.py +14 -3
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/api/files.py +158 -85
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/api/util.py +34 -10
- uedition_editor-2.0.0b2/uedition_editor/frontend/dist/assets/CodeMirrorEditor-BFeD7k_m.js → uedition_editor-2.0.0b4/uedition_editor/frontend/dist/assets/CodeMirrorEditor-CscxspV6.js +1 -1
- uedition_editor-2.0.0b2/uedition_editor/frontend/dist/assets/FolderEditor-DIEGEh84.js → uedition_editor-2.0.0b4/uedition_editor/frontend/dist/assets/FolderEditor-Bj2w6MKI.js +1 -1
- uedition_editor-2.0.0b4/uedition_editor/frontend/dist/assets/ImageEditor-BCtwmSpD.js +1 -0
- uedition_editor-2.0.0b4/uedition_editor/frontend/dist/assets/TeiEditor-FqXheBay.js +192 -0
- uedition_editor-2.0.0b4/uedition_editor/frontend/dist/assets/index-CD2zWAAU.js +17 -0
- uedition_editor-2.0.0b2/uedition_editor/frontend/dist/assets/index-C9d-OSiG.css → uedition_editor-2.0.0b4/uedition_editor/frontend/dist/assets/index-HmBQiVS_.css +1 -1
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/frontend/dist/index.html +2 -2
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/settings.py +11 -6
- uedition_editor-2.0.0b2/uedition_editor/frontend/dist/assets/ImageEditor-BRTKiGoH.js +0 -1
- uedition_editor-2.0.0b2/uedition_editor/frontend/dist/assets/TeiEditor-BeHkuEAu.js +0 -192
- uedition_editor-2.0.0b2/uedition_editor/frontend/dist/assets/index-BtIoSLIQ.js +0 -16
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/.gitignore +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/.pre-commit-config.yaml +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/LICENSE.txt +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/__init__.py +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/__main__.py +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/api/__init__.py +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/api/auth.py +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/api/tests.py +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/cli/__init__.py +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/frontend/dist/assets/CodeMirrorEditor-ddcHt3UE.css +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/frontend/dist/assets/index-Vcq4gwWv.js +0 -0
- {uedition_editor-2.0.0b2 → uedition_editor-2.0.0b4}/uedition_editor/frontend/dist/ueditor.svg +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
FROM python:3.13-bookworm
|
|
2
|
+
|
|
3
|
+
RUN apt-get update && \
|
|
4
|
+
apt-get dist-upgrade -y
|
|
5
|
+
|
|
6
|
+
COPY dist/*.whl /tmp/uedition-editor-source/
|
|
7
|
+
|
|
8
|
+
RUN pip install /tmp/uedition-editor-source/*.whl
|
|
9
|
+
|
|
10
|
+
CMD [ "uvicorn", "--port", "8000", "--host", "0.0.0.0", "uedition_editor:app" ]
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uEdition-Editor
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Project-URL: Documentation, https://github.com/
|
|
5
|
-
Project-URL: Issues, https://github.com/
|
|
6
|
-
Project-URL: Source, https://github.com/
|
|
3
|
+
Version: 2.0.0b4
|
|
4
|
+
Project-URL: Documentation, https://github.com/uEdition/uEditor#readme
|
|
5
|
+
Project-URL: Issues, https://github.com/uEdition/uEditor/issues
|
|
6
|
+
Project-URL: Source, https://github.com/uEdition/uEditor
|
|
7
7
|
Author-email: Mark Hall <mark.hall@work.room3b.eu>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE.txt
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
15
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
17
16
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
18
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.11
|
|
19
18
|
Requires-Dist: fastapi<1
|
|
19
|
+
Requires-Dist: httptools<1
|
|
20
20
|
Requires-Dist: httpx<1,>=0.28.1
|
|
21
21
|
Requires-Dist: lxml<6,>=5
|
|
22
22
|
Requires-Dist: pydantic-settings<3,>=2
|
|
@@ -25,16 +25,19 @@ Requires-Dist: pygit2<2,>=1.17.0
|
|
|
25
25
|
Requires-Dist: pyjwt[crypto]<3,>=2.10.1
|
|
26
26
|
Requires-Dist: python-multipart<1,>=0.0.9
|
|
27
27
|
Requires-Dist: python-slugify<9,>=8.0.4
|
|
28
|
+
Requires-Dist: pyyaml-include<3,>=2
|
|
28
29
|
Requires-Dist: pyyaml<7,>=6
|
|
29
30
|
Requires-Dist: typer<1,>=0.15.1
|
|
30
|
-
Requires-Dist: uedition<2,>=
|
|
31
|
+
Requires-Dist: uedition<2.1,>=2.0.0a2
|
|
31
32
|
Requires-Dist: uvicorn<1
|
|
33
|
+
Requires-Dist: uvloop<1
|
|
34
|
+
Requires-Dist: websockets<16,>=15
|
|
32
35
|
Description-Content-Type: text/markdown
|
|
33
36
|
|
|
34
37
|
# μEditor
|
|
35
38
|
|
|
36
|
-
[](https://pypi.org/project/uedition-editor)
|
|
40
|
+
[](https://pypi.org/project/uedition-editor)
|
|
38
41
|
[](https://github.com/uEdition/uEditor/actions/workflows/tests.yml)
|
|
39
42
|
[](https://github.com/uEdition/uEditor/actions/workflows/tests.yml)
|
|
40
43
|
|
|
@@ -43,6 +46,7 @@ Description-Content-Type: text/markdown
|
|
|
43
46
|
**Table of Contents**
|
|
44
47
|
|
|
45
48
|
- [Installation](#installation)
|
|
49
|
+
- [Documentation](#documentation)
|
|
46
50
|
- [License](#license)
|
|
47
51
|
|
|
48
52
|
## Installation
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# μEditor
|
|
2
2
|
|
|
3
|
-
[](https://pypi.org/project/uedition-editor)
|
|
4
|
+
[](https://pypi.org/project/uedition-editor)
|
|
5
5
|
[](https://github.com/uEdition/uEditor/actions/workflows/tests.yml)
|
|
6
6
|
[](https://github.com/uEdition/uEditor/actions/workflows/tests.yml)
|
|
7
7
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
**Table of Contents**
|
|
11
11
|
|
|
12
12
|
- [Installation](#installation)
|
|
13
|
+
- [Documentation](#documentation)
|
|
13
14
|
- [License](#license)
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
@@ -7,14 +7,13 @@ name = "uEdition-Editor"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = ''
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
keywords = []
|
|
13
13
|
authors = [{ name = "Mark Hall", email = "mark.hall@work.room3b.eu" }]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 4 - Beta",
|
|
16
16
|
"Programming Language :: Python",
|
|
17
|
-
"Programming Language :: Python :: 3.10",
|
|
18
17
|
"Programming Language :: Python :: 3.11",
|
|
19
18
|
"Programming Language :: Python :: 3.12",
|
|
20
19
|
"Programming Language :: Python :: 3.13",
|
|
@@ -23,6 +22,7 @@ classifiers = [
|
|
|
23
22
|
]
|
|
24
23
|
dependencies = [
|
|
25
24
|
"fastapi<1",
|
|
25
|
+
"httptools<1",
|
|
26
26
|
"httpx>=0.28.1,<1",
|
|
27
27
|
"lxml>=5,<6",
|
|
28
28
|
"pydantic[email]>=2,<3",
|
|
@@ -32,15 +32,18 @@ dependencies = [
|
|
|
32
32
|
"python-slugify>=8.0.4,<9",
|
|
33
33
|
"pyjwt[crypto]>=2.10.1,<3",
|
|
34
34
|
"PyYaml>=6,<7",
|
|
35
|
-
"
|
|
35
|
+
"pyyaml_include>=2,<3",
|
|
36
|
+
"uEdition>=2.0.0a2,<2.1",
|
|
36
37
|
"typer>=0.15.1,<1",
|
|
37
38
|
"uvicorn<1",
|
|
39
|
+
"uvloop<1",
|
|
40
|
+
"websockets>=15,<16",
|
|
38
41
|
]
|
|
39
42
|
|
|
40
43
|
[project.urls]
|
|
41
|
-
Documentation = "https://github.com/
|
|
42
|
-
Issues = "https://github.com/
|
|
43
|
-
Source = "https://github.com/
|
|
44
|
+
Documentation = "https://github.com/uEdition/uEditor#readme"
|
|
45
|
+
Issues = "https://github.com/uEdition/uEditor/issues"
|
|
46
|
+
Source = "https://github.com/uEdition/uEditor"
|
|
44
47
|
|
|
45
48
|
[project.scripts]
|
|
46
49
|
ueditor = "uedition_editor.cli:app"
|
|
@@ -9,7 +9,7 @@ from typing import Annotated
|
|
|
9
9
|
from fastapi import APIRouter, Depends, Header
|
|
10
10
|
from fastapi.exceptions import HTTPException
|
|
11
11
|
from pydantic import BaseModel, Field
|
|
12
|
-
from pygit2 import GitError, Repository
|
|
12
|
+
from pygit2 import GitError, Repository, Signature
|
|
13
13
|
from pygit2.enums import FetchPrune, RepositoryOpenFlag
|
|
14
14
|
|
|
15
15
|
from uedition_editor.api.auth import get_current_user
|
|
@@ -18,6 +18,7 @@ from uedition_editor.api.files import router as files_router
|
|
|
18
18
|
from uedition_editor.api.util import (
|
|
19
19
|
BranchContextManager,
|
|
20
20
|
RemoteRepositoryCallbacks,
|
|
21
|
+
commit_and_push,
|
|
21
22
|
fetch_and_pull_branch,
|
|
22
23
|
fetch_repo,
|
|
23
24
|
pull_branch,
|
|
@@ -37,6 +38,7 @@ class BranchModel(BaseModel):
|
|
|
37
38
|
id: str
|
|
38
39
|
title: str
|
|
39
40
|
nogit: bool = False
|
|
41
|
+
update_from_default: bool = False
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
def slugify(slug: str) -> str:
|
|
@@ -63,7 +65,17 @@ async def branches(
|
|
|
63
65
|
branches = []
|
|
64
66
|
if category == "local":
|
|
65
67
|
for branch_name in repo.branches.local:
|
|
66
|
-
|
|
68
|
+
repo.checkout(repo.branches[branch_name])
|
|
69
|
+
diff = repo.diff(
|
|
70
|
+
repo.revparse_single(init_settings.git.default_branch),
|
|
71
|
+
)
|
|
72
|
+
branches.append(
|
|
73
|
+
{
|
|
74
|
+
"id": branch_name,
|
|
75
|
+
"title": de_slugify(branch_name),
|
|
76
|
+
"update_from_default": diff.stats.files_changed > 0,
|
|
77
|
+
}
|
|
78
|
+
)
|
|
67
79
|
elif category == "remote":
|
|
68
80
|
if init_settings.git.remote_name in list(repo.remotes.names()):
|
|
69
81
|
repo.remotes[init_settings.git.remote_name].fetch(
|
|
@@ -158,6 +170,32 @@ async def fetch_branches(
|
|
|
158
170
|
raise HTTPException(500, "Git error") from ge
|
|
159
171
|
|
|
160
172
|
|
|
173
|
+
@router.post("/{branch_id}/merge-from-default", status_code=204)
|
|
174
|
+
async def merge_from_default(
|
|
175
|
+
branch_id: str,
|
|
176
|
+
current_user: Annotated[dict, Depends(get_current_user)],
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Merge all changes from the default branch."""
|
|
179
|
+
async with BranchContextManager(branch_id) as repo:
|
|
180
|
+
repo.checkout(repo.branches[init_settings.git.default_branch])
|
|
181
|
+
if init_settings.git.remote_name in list(repo.remotes.names()):
|
|
182
|
+
fetch_repo(repo, init_settings.git.remote_name)
|
|
183
|
+
pull_branch(repo, init_settings.git.default_branch)
|
|
184
|
+
repo.checkout(repo.branches[branch_id])
|
|
185
|
+
default_branch_head = repo.revparse_single(init_settings.git.default_branch)
|
|
186
|
+
diff = repo.diff(default_branch_head)
|
|
187
|
+
if diff.stats.files_changed > 0:
|
|
188
|
+
repo.merge(default_branch_head.id)
|
|
189
|
+
commit_and_push(
|
|
190
|
+
repo,
|
|
191
|
+
init_settings.git.remote_name,
|
|
192
|
+
branch_id,
|
|
193
|
+
f"Merged {init_settings.git.default_branch}",
|
|
194
|
+
Signature(current_user["name"], current_user["sub"]),
|
|
195
|
+
extra_parents=[default_branch_head.id],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
161
199
|
@router.delete("/{branch_id}", status_code=204)
|
|
162
200
|
async def delete_branch(
|
|
163
201
|
branch_id: str,
|
|
@@ -13,7 +13,8 @@ from fastapi.responses import Response
|
|
|
13
13
|
from uedition_editor.api.auth import get_current_user
|
|
14
14
|
from uedition_editor.api.util import BranchContextManager, BranchNotFoundError
|
|
15
15
|
from uedition_editor.settings import (
|
|
16
|
-
|
|
16
|
+
TEINode,
|
|
17
|
+
UEditionSettings,
|
|
17
18
|
UEditorSettings,
|
|
18
19
|
get_uedition_settings,
|
|
19
20
|
get_ueditor_settings,
|
|
@@ -23,7 +24,7 @@ from uedition_editor.settings import (
|
|
|
23
24
|
router = APIRouter(prefix="/configs")
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
@router.get("/uedition", response_model=
|
|
27
|
+
@router.get("/uedition", response_model=UEditionSettings)
|
|
27
28
|
async def uedition_config(
|
|
28
29
|
branch_id: str,
|
|
29
30
|
current_user: Annotated[dict, Depends(get_current_user)], # noqa:ARG001
|
|
@@ -31,7 +32,17 @@ async def uedition_config(
|
|
|
31
32
|
"""Fetch the uEdition configuration."""
|
|
32
33
|
try:
|
|
33
34
|
async with BranchContextManager(branch_id):
|
|
34
|
-
|
|
35
|
+
settings = get_uedition_settings()
|
|
36
|
+
if "tei" in settings.sphinx_config:
|
|
37
|
+
if "blocks" in settings.sphinx_config["tei"]:
|
|
38
|
+
settings.sphinx_config["tei"]["blocks"] = [
|
|
39
|
+
TEINode(**block).model_dump() for block in settings.sphinx_config["tei"]["blocks"]
|
|
40
|
+
]
|
|
41
|
+
if "marks" in settings.sphinx_config["tei"]:
|
|
42
|
+
settings.sphinx_config["tei"]["marks"] = [
|
|
43
|
+
TEINode(**mark).model_dump() for mark in settings.sphinx_config["tei"]["marks"]
|
|
44
|
+
]
|
|
45
|
+
return settings.model_dump()
|
|
35
46
|
except BranchNotFoundError as bnfe:
|
|
36
47
|
raise HTTPException(404) from bnfe
|
|
37
48
|
|
|
@@ -12,19 +12,26 @@ import shutil
|
|
|
12
12
|
from typing import Annotated
|
|
13
13
|
|
|
14
14
|
import pygit2
|
|
15
|
-
from fastapi import APIRouter, Depends, Header, UploadFile
|
|
15
|
+
from fastapi import APIRouter, Depends, Header, Response, UploadFile
|
|
16
16
|
from fastapi.exceptions import HTTPException
|
|
17
17
|
from fastapi.responses import FileResponse
|
|
18
18
|
from lxml import etree
|
|
19
19
|
|
|
20
20
|
from uedition_editor.api.auth import get_current_user
|
|
21
|
-
from uedition_editor.api.util import
|
|
21
|
+
from uedition_editor.api.util import (
|
|
22
|
+
BranchContextManager,
|
|
23
|
+
BranchNotFoundError,
|
|
24
|
+
commit_and_push,
|
|
25
|
+
)
|
|
22
26
|
from uedition_editor.settings import (
|
|
23
27
|
TEIMetadataSection,
|
|
28
|
+
TEINode,
|
|
24
29
|
TEINodeAttribute,
|
|
25
30
|
TEISettings,
|
|
26
31
|
TEITextSection,
|
|
32
|
+
UEditionSettings,
|
|
27
33
|
UEditorSettings,
|
|
34
|
+
get_uedition_settings,
|
|
28
35
|
get_ueditor_settings,
|
|
29
36
|
init_settings,
|
|
30
37
|
)
|
|
@@ -60,21 +67,25 @@ def guess_type(url: str) -> tuple[str | None, str | None]:
|
|
|
60
67
|
return ("application/unknown", None)
|
|
61
68
|
|
|
62
69
|
|
|
63
|
-
def build_file_tree(path: str, strip_len) -> list[dict]:
|
|
70
|
+
def build_file_tree(path: str, strip_len: int, uedition_settings: UEditionSettings) -> list[dict]:
|
|
64
71
|
"""Recursively build a tree of directories and files."""
|
|
65
72
|
files = []
|
|
66
73
|
for filename in os.listdir(path):
|
|
67
74
|
full_filename = os.path.join(path, filename)
|
|
68
75
|
if os.path.isdir(full_filename):
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
if filename != ".git" and full_filename[strip_len:] not in (
|
|
77
|
+
uedition_settings.output.path,
|
|
78
|
+
"_build",
|
|
79
|
+
):
|
|
80
|
+
files.append(
|
|
81
|
+
{
|
|
82
|
+
"name": filename,
|
|
83
|
+
"fullpath": full_filename[strip_len:],
|
|
84
|
+
"type": "folder",
|
|
85
|
+
"content": build_file_tree(full_filename, strip_len, uedition_settings),
|
|
86
|
+
"mimetype": "application/folder",
|
|
87
|
+
}
|
|
88
|
+
)
|
|
78
89
|
elif os.path.isfile(full_filename):
|
|
79
90
|
mimetype = guess_type(filename)
|
|
80
91
|
files.append(
|
|
@@ -104,7 +115,7 @@ async def get_files(
|
|
|
104
115
|
"fullpath": "",
|
|
105
116
|
"type": "folder",
|
|
106
117
|
"mimetype": "application/folder",
|
|
107
|
-
"content": build_file_tree(full_path, len(full_path) + 1),
|
|
118
|
+
"content": build_file_tree(full_path, len(full_path) + 1, get_uedition_settings()),
|
|
108
119
|
}
|
|
109
120
|
]
|
|
110
121
|
except BranchNotFoundError as bnfe:
|
|
@@ -220,91 +231,142 @@ def clean_tei_subdoc(node: dict) -> dict:
|
|
|
220
231
|
|
|
221
232
|
def parse_tei_file(path: str, settings: UEditorSettings) -> list[dict]:
|
|
222
233
|
"""Parse a TEI file into its constituent parts."""
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
"name": section.name,
|
|
232
|
-
"title": section.title,
|
|
233
|
-
"type": section.type,
|
|
234
|
-
"content": [],
|
|
235
|
-
}
|
|
236
|
-
)
|
|
237
|
-
elif section.type == "text":
|
|
238
|
-
result.append(
|
|
239
|
-
{
|
|
240
|
-
"name": section.name,
|
|
241
|
-
"title": section.title,
|
|
242
|
-
"type": section.type,
|
|
243
|
-
"content": {},
|
|
244
|
-
}
|
|
245
|
-
)
|
|
246
|
-
elif section.type == "textlist":
|
|
247
|
-
result.append(
|
|
248
|
-
{
|
|
249
|
-
"name": section.name,
|
|
250
|
-
"title": section.title,
|
|
251
|
-
"type": section.type,
|
|
252
|
-
"content": [],
|
|
253
|
-
}
|
|
254
|
-
)
|
|
255
|
-
else: # noqa: PLR5501
|
|
256
|
-
if section.type == "metadata":
|
|
257
|
-
result.append(
|
|
258
|
-
{
|
|
259
|
-
"name": section.name,
|
|
260
|
-
"title": section.title,
|
|
261
|
-
"type": section.type,
|
|
262
|
-
"content": [parse_metadata_node(node) for node in section_root[0]],
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
elif section.type == "text":
|
|
266
|
-
result.append(
|
|
267
|
-
{
|
|
268
|
-
"name": section.name,
|
|
269
|
-
"title": section.title,
|
|
270
|
-
"type": section.type,
|
|
271
|
-
"content": clean_tei_subdoc(parse_tei_subdoc(section_root[0], settings.tei)),
|
|
272
|
-
}
|
|
273
|
-
)
|
|
274
|
-
elif section.type == "textlist":
|
|
275
|
-
content = []
|
|
276
|
-
for node in section_root:
|
|
277
|
-
content.append(
|
|
234
|
+
try:
|
|
235
|
+
doc = etree.parse(path) # noqa: S320
|
|
236
|
+
result = []
|
|
237
|
+
for section in settings.tei.sections:
|
|
238
|
+
section_root = doc.xpath(section.selector, namespaces=namespaces)
|
|
239
|
+
if len(section_root) == 0:
|
|
240
|
+
if section.type == "metadata":
|
|
241
|
+
result.append(
|
|
278
242
|
{
|
|
279
|
-
"
|
|
280
|
-
"
|
|
243
|
+
"name": section.name,
|
|
244
|
+
"title": section.title,
|
|
245
|
+
"type": section.type,
|
|
246
|
+
"content": [],
|
|
281
247
|
}
|
|
282
248
|
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
249
|
+
elif section.type == "text":
|
|
250
|
+
result.append(
|
|
251
|
+
{
|
|
252
|
+
"name": section.name,
|
|
253
|
+
"title": section.title,
|
|
254
|
+
"type": section.type,
|
|
255
|
+
"content": {},
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
elif section.type == "textlist":
|
|
259
|
+
result.append(
|
|
260
|
+
{
|
|
261
|
+
"name": section.name,
|
|
262
|
+
"title": section.title,
|
|
263
|
+
"type": section.type,
|
|
264
|
+
"content": [],
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
else: # noqa: PLR5501
|
|
268
|
+
if section.type == "metadata":
|
|
269
|
+
result.append(
|
|
270
|
+
{
|
|
271
|
+
"name": section.name,
|
|
272
|
+
"title": section.title,
|
|
273
|
+
"type": section.type,
|
|
274
|
+
"content": [parse_metadata_node(node) for node in section_root[0]],
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
elif section.type == "text":
|
|
278
|
+
result.append(
|
|
279
|
+
{
|
|
280
|
+
"name": section.name,
|
|
281
|
+
"title": section.title,
|
|
282
|
+
"type": section.type,
|
|
283
|
+
"content": clean_tei_subdoc(parse_tei_subdoc(section_root[0], settings.tei)),
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
elif section.type == "textlist":
|
|
287
|
+
content = []
|
|
288
|
+
for node in section_root:
|
|
289
|
+
content.append(
|
|
290
|
+
{
|
|
291
|
+
"attrs": {"id": node.attrib["{http://www.w3.org/XML/1998/namespace}id"]},
|
|
292
|
+
"content": clean_tei_subdoc(parse_tei_subdoc(node, settings.tei)),
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
result.append(
|
|
296
|
+
{
|
|
297
|
+
"name": section.name,
|
|
298
|
+
"title": section.title,
|
|
299
|
+
"type": section.type,
|
|
300
|
+
"content": content,
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
return result
|
|
304
|
+
except etree.XMLSyntaxError as e:
|
|
305
|
+
is_empty = False
|
|
306
|
+
with open(path) as in_f:
|
|
307
|
+
if in_f.read() == "":
|
|
308
|
+
is_empty = True
|
|
309
|
+
if is_empty:
|
|
310
|
+
result = []
|
|
311
|
+
for section in settings.tei.sections:
|
|
312
|
+
if section.type == "metadata":
|
|
313
|
+
result.append(
|
|
314
|
+
{
|
|
315
|
+
"name": section.name,
|
|
316
|
+
"title": section.title,
|
|
317
|
+
"type": section.type,
|
|
318
|
+
"content": [],
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
elif section.type == "text":
|
|
322
|
+
result.append(
|
|
323
|
+
{
|
|
324
|
+
"name": section.name,
|
|
325
|
+
"title": section.title,
|
|
326
|
+
"type": section.type,
|
|
327
|
+
"content": {},
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
elif section.type == "textlist":
|
|
331
|
+
result.append(
|
|
332
|
+
{
|
|
333
|
+
"name": section.name,
|
|
334
|
+
"title": section.title,
|
|
335
|
+
"type": section.type,
|
|
336
|
+
"content": [],
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
return result
|
|
340
|
+
else:
|
|
341
|
+
raise e
|
|
292
342
|
|
|
293
343
|
|
|
294
344
|
@router.get("/{path:path}", response_model=None)
|
|
295
345
|
async def get_file(
|
|
296
346
|
branch_id: str,
|
|
297
347
|
path: str,
|
|
298
|
-
settings: Annotated[UEditorSettings, Depends(get_ueditor_settings)],
|
|
299
348
|
current_user: Annotated[dict, Depends(get_current_user)], # noqa:ARG001
|
|
349
|
+
response: Response,
|
|
300
350
|
) -> dict | FileResponse:
|
|
301
351
|
"""Fetch a single file from the repo."""
|
|
302
352
|
try:
|
|
303
353
|
async with BranchContextManager(branch_id):
|
|
354
|
+
ueditor_settings = get_ueditor_settings()
|
|
355
|
+
uedition_settings = get_uedition_settings()
|
|
304
356
|
full_path = os.path.abspath(os.path.join(init_settings.base_path, *path.split("/")))
|
|
305
357
|
if full_path.startswith(os.path.abspath(init_settings.base_path)) and os.path.isfile(full_path):
|
|
306
358
|
if full_path.endswith(".tei"):
|
|
307
|
-
|
|
359
|
+
if "tei" in uedition_settings.sphinx_config:
|
|
360
|
+
if "blocks" in uedition_settings.sphinx_config["tei"]:
|
|
361
|
+
ueditor_settings.tei.blocks.extend(
|
|
362
|
+
[TEINode(**block) for block in uedition_settings.sphinx_config["tei"]["blocks"]]
|
|
363
|
+
)
|
|
364
|
+
if "marks" in uedition_settings.sphinx_config["tei"]:
|
|
365
|
+
ueditor_settings.tei.marks.extend(
|
|
366
|
+
[TEINode(**mark) for mark in uedition_settings.sphinx_config["tei"]["marks"]]
|
|
367
|
+
)
|
|
368
|
+
response.headers["Content-Type"] = "application/json+tei"
|
|
369
|
+
return parse_tei_file(full_path, ueditor_settings)
|
|
308
370
|
else:
|
|
309
371
|
return FileResponse(full_path, media_type=guess_type(full_path)[0])
|
|
310
372
|
raise HTTPException(404)
|
|
@@ -585,8 +647,9 @@ def serialise_tei_text(root: dict, data: dict, settings: TEITextSection, tei_set
|
|
|
585
647
|
parent["children"] = []
|
|
586
648
|
doc = data["content"]
|
|
587
649
|
# TODO: Add docu attributes to the parent node
|
|
588
|
-
|
|
589
|
-
|
|
650
|
+
if "content" in doc:
|
|
651
|
+
for element in doc["content"]:
|
|
652
|
+
parent["children"].append(serialise_tei_text_block(element, tei_settings))
|
|
590
653
|
|
|
591
654
|
|
|
592
655
|
def serialise_tei_textlist(root: dict, data: dict, settings: TEITextSection, tei_settings: TEISettings) -> None:
|
|
@@ -662,11 +725,21 @@ async def update_file(
|
|
|
662
725
|
"""Update the file in the repo."""
|
|
663
726
|
try:
|
|
664
727
|
async with BranchContextManager(branch_id) as repo:
|
|
665
|
-
|
|
728
|
+
ueditor_settings = get_ueditor_settings()
|
|
666
729
|
full_path = os.path.abspath(os.path.join(init_settings.base_path, *path.split("/")))
|
|
667
730
|
if full_path.startswith(os.path.abspath(init_settings.base_path)) and os.path.isfile(full_path):
|
|
668
731
|
if full_path.endswith(".tei"):
|
|
669
|
-
|
|
732
|
+
uedition_settings = get_uedition_settings()
|
|
733
|
+
if "tei" in uedition_settings.sphinx_config:
|
|
734
|
+
if "blocks" in uedition_settings.sphinx_config["tei"]:
|
|
735
|
+
ueditor_settings.tei.blocks.extend(
|
|
736
|
+
[TEINode(**block) for block in uedition_settings.sphinx_config["tei"]["blocks"]]
|
|
737
|
+
)
|
|
738
|
+
if "marks" in uedition_settings.sphinx_config["tei"]:
|
|
739
|
+
ueditor_settings.tei.marks.extend(
|
|
740
|
+
[TEINode(**mark) for mark in uedition_settings.sphinx_config["tei"]["marks"]]
|
|
741
|
+
)
|
|
742
|
+
root = serialise_tei_file(full_path, json.load(content.file), ueditor_settings)
|
|
670
743
|
with open(full_path, "wb") as out_f:
|
|
671
744
|
out_f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
672
745
|
out_f.write(
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from asyncio import Lock
|
|
5
5
|
|
|
6
|
-
from pygit2 import
|
|
6
|
+
from pygit2 import (
|
|
7
|
+
CredentialType,
|
|
8
|
+
GitError,
|
|
9
|
+
KeypairFromAgent,
|
|
10
|
+
RemoteCallbacks,
|
|
11
|
+
Repository,
|
|
12
|
+
Signature,
|
|
13
|
+
)
|
|
7
14
|
from pygit2.enums import FetchPrune, MergeAnalysis, RepositoryOpenFlag
|
|
8
15
|
|
|
9
16
|
from uedition_editor.settings import init_settings
|
|
@@ -49,7 +56,12 @@ class BranchContextManager:
|
|
|
49
56
|
class RemoteRepositoryCallbacks(RemoteCallbacks):
|
|
50
57
|
"""Callback handler for connecting to remote repositories."""
|
|
51
58
|
|
|
52
|
-
def credentials(
|
|
59
|
+
def credentials(
|
|
60
|
+
self,
|
|
61
|
+
url: str, # noqa: ARG002
|
|
62
|
+
username_from_url: str | None,
|
|
63
|
+
allowed_types: CredentialType,
|
|
64
|
+
):
|
|
53
65
|
"""Return the credentials for the remote connection."""
|
|
54
66
|
if allowed_types & CredentialType.SSH_KEY == CredentialType.SSH_KEY:
|
|
55
67
|
return KeypairFromAgent(username_from_url)
|
|
@@ -64,13 +76,16 @@ def fetch_repo(repo: Repository, remote: str) -> None:
|
|
|
64
76
|
|
|
65
77
|
def pull_branch(repo: Repository, branch: str) -> None:
|
|
66
78
|
"""Pull and update the branch from the remote repository."""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
try:
|
|
80
|
+
remote_default_head = repo.lookup_reference(repo.branches[branch].upstream_name)
|
|
81
|
+
result, _ = repo.merge_analysis(remote_default_head.target)
|
|
82
|
+
if result & MergeAnalysis.FASTFORWARD == MergeAnalysis.FASTFORWARD:
|
|
83
|
+
repo.checkout_tree(repo.get(remote_default_head.target))
|
|
84
|
+
local_default_head = repo.lookup_reference(f"refs/heads/{branch}")
|
|
85
|
+
local_default_head.set_target(remote_default_head.target)
|
|
86
|
+
repo.head.set_target(remote_default_head.target)
|
|
87
|
+
except KeyError as e:
|
|
88
|
+
logger.error(e)
|
|
74
89
|
|
|
75
90
|
|
|
76
91
|
def fetch_and_pull_branch(repo: Repository, remote: str, branch: str) -> None:
|
|
@@ -80,12 +95,21 @@ def fetch_and_pull_branch(repo: Repository, remote: str, branch: str) -> None:
|
|
|
80
95
|
pull_branch(repo, branch)
|
|
81
96
|
|
|
82
97
|
|
|
83
|
-
def commit_and_push(
|
|
98
|
+
def commit_and_push(
|
|
99
|
+
repo: Repository,
|
|
100
|
+
remote: str,
|
|
101
|
+
branch: str,
|
|
102
|
+
commit_msg: str,
|
|
103
|
+
author: Signature,
|
|
104
|
+
extra_parents: list[str] | None = None,
|
|
105
|
+
) -> None:
|
|
84
106
|
"""Commit changes to the repository and push."""
|
|
85
107
|
if len(repo.status()) > 0:
|
|
86
108
|
logger.debug(f"Committing changes to {', '.join(repo.status().keys())}")
|
|
87
109
|
ref = repo.head.name
|
|
88
110
|
parents = [repo.head.target]
|
|
111
|
+
if extra_parents is not None:
|
|
112
|
+
parents.extend(extra_parents)
|
|
89
113
|
index = repo.index
|
|
90
114
|
index.add_all()
|
|
91
115
|
index.write()
|