Coverage for src / competitive_verifier / documents / builder.py: 97%
57 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-05 16:00 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-05 16:00 +0000
1import pathlib
2import shutil
3from logging import getLogger
5from pydantic import BaseModel
7import competitive_verifier_resources
8from competitive_verifier import git, github
9from competitive_verifier.models import VerificationInput, VerifyCommandResult
11from .config import ConfigYaml, load_config_yml
12from .front_matter import Markdown
13from .render import RenderJob
15logger = getLogger(__name__)
18class DocumentBuilder(BaseModel):
19 verifications: VerificationInput
20 result: VerifyCommandResult
21 docs_dir: pathlib.Path
22 destination_dir: pathlib.Path
23 include: list[str] | None
24 exclude: list[str] | None
26 def build(self) -> bool:
27 logger.info("Working directory: %s", pathlib.Path.cwd().as_posix())
28 logger.info("Generate documents...")
30 # implementation
31 result = self.impl()
33 logger.info("Generated.")
34 logger.info(
35 competitive_verifier_resources.doc_usage(
36 markdown_dir_path=self.destination_dir,
37 repo_name=github.env.get_repository()
38 or "competitive-verifier/competitive-verifier",
39 )
40 )
42 return result
44 def impl(self) -> bool:
45 self.destination_dir.mkdir(parents=True, exist_ok=True)
47 config_yml = load_config_yml(self.docs_dir)
48 logger.info("_config.yml: %s", config_yml)
50 index_md_path = self.docs_dir / "index.md"
51 index_md = Markdown.load_file(index_md_path) if index_md_path.exists() else None
53 # Write code documents.
54 self.write_code_docs(
55 config_yml=config_yml,
56 index_md=index_md,
57 static_dir=self.docs_dir / "static",
58 )
60 # Write _config.yml
61 (self.destination_dir / "_config.yml").write_bytes(config_yml.model_dump_yml())
63 # Copy static files
64 self.copy_static_files(static_dir=self.docs_dir / "static")
65 return True
67 def copy_static_files(self, *, static_dir: pathlib.Path):
68 logger.info("Copy library static files...")
69 for path, content in competitive_verifier_resources.jekyll_files().items():
70 file_dst = self.destination_dir / path
71 logger.debug("Writing to %s", file_dst.as_posix())
72 file_dst.parent.mkdir(parents=True, exist_ok=True)
73 file_dst.write_bytes(content)
75 logger.info("Copy user static files...")
76 try:
77 if static_dir.is_dir():
78 shutil.copytree(
79 static_dir,
80 self.destination_dir,
81 dirs_exist_ok=True,
82 )
83 except Exception:
84 logger.exception("Failed to copy user static files.")
86 def write_code_docs(
87 self,
88 *,
89 config_yml: ConfigYaml,
90 index_md: Markdown | None,
91 static_dir: pathlib.Path | None,
92 ):
93 logger.info("Write document files...")
95 exclude = (self.exclude or []) + (config_yml.exclude or [])
96 if static_dir and static_dir.is_relative_to("."):
97 exclude.append(self.docs_dir.relative_to(".").as_posix())
99 sources = git.ls_files(*(self.include or []))
100 if exclude:
101 sources -= git.ls_files(*exclude)
103 for job in RenderJob.enumerate_jobs(
104 sources=sources,
105 verifications=self.verifications,
106 result=self.result,
107 config=config_yml,
108 index_md=index_md,
109 ):
110 logger.debug(job)
111 dst = self.destination_dir / job.destination_name
112 dst.parent.mkdir(parents=True, exist_ok=True)
113 logger.info("writing to %s", dst.as_posix())
114 with dst.open("wb") as fp:
115 job.write_to(fp)