Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions opensiddur/exporter/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
from lxml import etree

from opensiddur.exporter.constants import JLPTEI_NAMESPACE, PROCESSING_NAMESPACE
from opensiddur.exporter.linear import LinearData, get_linear_data
from opensiddur.exporter.linear import LinearData, get_linear_data, reset_linear_data
from opensiddur.exporter.refdb import ReferenceDatabase
from opensiddur.exporter.settings import load_default_settings, load_settings
from opensiddur.exporter.urn import ResolvedUrnRange, UrnResolver
from opensiddur.common.constants import PROJECT_DIRECTORY

class _ProcessingCommand(Enum):
""" Possible ways the compiler can process an element """
Expand Down Expand Up @@ -599,18 +600,37 @@ def process(self, root: Optional[ElementBase] = None):
return copied


def main(): # pragma: no cover
def main(argv: list[str] | None = None): # pragma: no cover
parser = argparse.ArgumentParser(description="Compile a TEI file with external references to a single file.")
parser.add_argument("--project", "-p", type=str, help="The project name.", required=True)
parser.add_argument("--file_name", "-f", type=str, help="The file name (relative to the project).", required=True)
parser.add_argument("--output_file", "-o", type=str, help="The output XML file.")
parser.add_argument("--settings", "-s", type=Path, help="YAML file with compiler settings. See README.md for more details.")
args = parser.parse_args()
parser.add_argument(
"--project-directory",
type=Path,
default=PROJECT_DIRECTORY,
help="Base directory containing project subdirectories (default: <repo>/project).",
)
args = parser.parse_args(argv)

project_directory = args.project_directory.resolve()
reset_linear_data()
linear_data = get_linear_data()

if args.settings:
linear_data = load_settings(args.settings)
linear_data = load_settings(
args.settings,
linear_data=linear_data,
project_directory=project_directory,
)
else:
linear_data = load_default_settings(args.project, args.file_name)
linear_data = load_default_settings(
args.project,
args.file_name,
linear_data=linear_data,
project_directory=project_directory,
)

from opensiddur.exporter.external_compiler import ExternalCompilerProcessor
compiler = ExternalCompilerProcessor(args.project, args.file_name, linear_data=linear_data)
Expand Down
18 changes: 17 additions & 1 deletion opensiddur/exporter/pdf/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@
sys.path.insert(0, str(project_root))

from opensiddur.exporter.tex.latex import transform_xml_to_tex # noqa: E402
from opensiddur.common.constants import PROJECT_DIRECTORY # noqa: E402


def generate_tex(
input_file: Path,
temp_tex_file: Path,
settings_file: Optional[Path] = None,
project_directory: Path = PROJECT_DIRECTORY,
) -> bool:
"""Generate a LuaLaTeX file from compiled JLPTEI XML.

Expand All @@ -55,6 +57,7 @@ def generate_tex(
str(input_file),
output_file=str(temp_tex_file),
settings_file=settings_file,
project_directory=project_directory,
)
print(f"TeX file generated: {temp_tex_file}", file=sys.stderr)
return True
Expand Down Expand Up @@ -275,6 +278,7 @@ def export_to_pdf(
settings_file: Optional[Path] = None,
tex_output: Optional[Path] = None,
build_dir: Optional[Path] = None,
project_directory: Path = PROJECT_DIRECTORY,
) -> bool:
"""Convert a compiled JLPTEI XML file to PDF.

Expand All @@ -296,7 +300,12 @@ def export_to_pdf(
if tex_output is not None:
tex_output.parent.mkdir(parents=True, exist_ok=True)

if not generate_tex(input_file, temp_tex_file, settings_file=settings_file):
if not generate_tex(
input_file,
temp_tex_file,
settings_file=settings_file,
project_directory=project_directory,
):
return False

if not compile_tex_to_pdf(temp_tex_file, output_pdf, build_dir=build_dir):
Expand Down Expand Up @@ -353,6 +362,12 @@ def main(): # pragma: no cover
default=None,
help="Directory to keep LaTeX build artifacts (.log, .aux, etc.) for debugging.",
)
parser.add_argument(
"--project-directory",
type=Path,
default=PROJECT_DIRECTORY,
help="Base directory containing project subdirectories (default: <repo>/project).",
)

args = parser.parse_args()

Expand All @@ -372,6 +387,7 @@ def main(): # pragma: no cover
settings_file=args.settings_file,
tex_output=tex_output,
build_dir=args.build_dir,
project_directory=args.project_directory,
):
sys.exit(1)

Expand Down
34 changes: 27 additions & 7 deletions opensiddur/exporter/refdb.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
""" Reference Database """

import argparse
from pathlib import Path
import re
import sqlite3
Expand Down Expand Up @@ -679,18 +680,37 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.close()


def main(): # pragma: no cover
def main(argv: list[str] | None = None) -> None: # pragma: no cover
"""Synchronize the reference database with the project directory.

Opens the default database and syncs all projects, printing a summary
of the changes made.
"""
print(f"Synchronizing reference database: {INDEX_DB_FILE}")
print(f"Project directory: {PROJECT_DIRECTORY}\n")

with ReferenceDatabase(INDEX_DB_FILE) as refdb:
parser = argparse.ArgumentParser(
description="Synchronize the reference database with JLPTEI project files."
)
parser.add_argument(
"--project-directory",
type=Path,
default=PROJECT_DIRECTORY,
help="Base directory containing project subdirectories (default: <repo>/project).",
)
parser.add_argument(
"--reference-db",
type=Path,
default=INDEX_DB_FILE,
help="Path to reference.db (default: <repo>/database/reference.db).",
)
args = parser.parse_args(argv)
project_directory = args.project_directory.resolve()
reference_db_path = args.reference_db.resolve()

print(f"Synchronizing reference database: {reference_db_path}")
print(f"Project directory: {project_directory}\n")

with ReferenceDatabase(reference_db_path) as refdb:
try:
result = refdb.sync_projects(PROJECT_DIRECTORY)
result = refdb.sync_projects(project_directory)

# Print summary
print("=" * 70)
Expand Down
65 changes: 49 additions & 16 deletions opensiddur/exporter/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,60 @@
from enum import StrEnum
from pathlib import Path
from typing import Optional
from pydantic import BaseModel, Field, field_validator
from pydantic import BaseModel, Field, ValidationInfo, field_validator
import yaml

from opensiddur.exporter.linear import LinearData, ParallelColumnOrder, get_linear_data
from opensiddur.common.constants import PROJECT_DIRECTORY

def _validate_project_list(project_list: list[str],
project_directory: Optional[Path] = None) -> list[str]:
""" Validate a list of projects. """

def _project_directory_from_context(info: ValidationInfo) -> Path:
context = info.context or {}
return Path(context.get("project_directory") or PROJECT_DIRECTORY)


def _validate_project_list(
project_list: list[str],
project_directory: Optional[Path] = None,
) -> list[str]:
"""Validate a list of projects."""
if project_directory is None:
project_directory = PROJECT_DIRECTORY # looked up at call time so tests can patch it
for project in project_list:
if not (project_directory / project).exists():
raise ValueError(f"Project {project} does not exist")
return project_list


def _apply_project_directory(
linear_data: LinearData,
project_directory: Path,
) -> None:
linear_data.xml_cache.base_path = project_directory.resolve()


class Prioritizations(BaseModel):
transclusion: list[str] = Field(default_factory=list)
instructions: list[str] = Field(default_factory=list)

@field_validator("transclusion")
def validate_transclusion(cls, v: list[str]) -> list[str]:
return _validate_project_list(v)
@classmethod
def validate_transclusion(cls, v: list[str], info: ValidationInfo) -> list[str]:
return _validate_project_list(v, _project_directory_from_context(info))

@field_validator("instructions")
def validate_instructions(cls, v: list[str]) -> list[str]:
return _validate_project_list(v)
@classmethod
def validate_instructions(cls, v: list[str], info: ValidationInfo) -> list[str]:
return _validate_project_list(v, _project_directory_from_context(info))

class ParallelConfig(BaseModel):
projects: list[str] = Field(default_factory=list)
column_order: ParallelColumnOrder = ParallelColumnOrder.PRIMARY_FIRST

@field_validator("projects")
def validate_projects(cls, v: list[str]) -> list[str]:
return _validate_project_list(v)
@classmethod
def validate_projects(cls, v: list[str], info: ValidationInfo) -> list[str]:
return _validate_project_list(v, _project_directory_from_context(info))


class ParallelLayout(StrEnum):
Expand Down Expand Up @@ -85,16 +104,26 @@ class SettingsYaml(BaseModel):
typography: TypographyConfig = Field(default_factory=TypographyConfig)

@field_validator("annotations")
def validate_annotations(cls, v: list[str]) -> list[str]:
return _validate_project_list(v)

def load_settings(settings_file: Path, linear_data: Optional[LinearData] = None) -> LinearData:
@classmethod
def validate_annotations(cls, v: list[str], info: ValidationInfo) -> list[str]:
return _validate_project_list(v, _project_directory_from_context(info))

def load_settings(
settings_file: Path,
linear_data: Optional[LinearData] = None,
project_directory: Optional[Path] = None,
) -> LinearData:
""" Load settings into linear data from a YAML file. """
project_directory = Path(project_directory or PROJECT_DIRECTORY).resolve()
with open(settings_file, 'r') as f:
data = yaml.safe_load(f)

settings = SettingsYaml.model_validate(data)
settings = SettingsYaml.model_validate(
data,
context={"project_directory": project_directory},
)
linear_data = linear_data or get_linear_data()
_apply_project_directory(linear_data, project_directory)
linear_data.project_priority = settings.priority.transclusion
linear_data.instruction_priority = settings.priority.instructions
linear_data.annotation_projects = settings.annotations
Expand All @@ -107,9 +136,13 @@ def load_settings(settings_file: Path, linear_data: Optional[LinearData] = None)
def load_default_settings(
project: str,
file_name: str,
linear_data: Optional[LinearData] = None) -> LinearData:
linear_data: Optional[LinearData] = None,
project_directory: Optional[Path] = None,
) -> LinearData:
""" Load default settings into linear data. """
project_directory = Path(project_directory or PROJECT_DIRECTORY).resolve()
linear_data = linear_data or get_linear_data()
_apply_project_directory(linear_data, project_directory)
linear_data.project_priority = [project]
linear_data.instruction_priority = [project]
linear_data.annotation_projects = [project]
Expand Down
Loading