Coverage for src / competitive_verifier / documents / config.py: 84%
76 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 os
2import pathlib
3from logging import getLogger
4from typing import Any
6import yaml
7from pydantic import BaseModel, ConfigDict, Field
9from competitive_verifier import git, github
10from competitive_verifier.log import GitHubMessageParams
11from competitive_verifier.models import RelativeDirectoryPath, SortedPathSet
13_CONFIG_YML_PATH = "_config.yml"
15COMPETITIVE_VERIFY_DOCS_CONFIG_YML = "COMPETITIVE_VERIFY_DOCS_CONFIG_YML"
17logger = getLogger(__name__)
20class ConfigIcons(BaseModel):
21 model_config = ConfigDict(extra="allow")
23 LIBRARY_ALL_AC: str = ":heavy_check_mark:"
24 LIBRARY_PARTIAL_AC: str = ":heavy_check_mark:"
25 LIBRARY_SOME_WA: str = ":question:"
26 LIBRARY_ALL_WA: str = ":x:"
27 LIBRARY_NO_TESTS: str = ":warning:"
28 TEST_ACCEPTED: str = ":heavy_check_mark:"
29 TEST_WRONG_ANSWER: str = ":x:"
30 TEST_WAITING_JUDGE: str = ":warning:"
33class ConfigYaml(BaseModel):
34 model_config = ConfigDict(extra="allow")
36 def merge(self, other: "ConfigYaml"):
37 return ConfigYaml.model_validate(
38 self.model_dump() | other.model_dump(),
39 )
41 def model_dump_yml(self):
42 d = self.model_dump(
43 mode="json",
44 by_alias=True,
45 exclude_none=True,
46 )
47 return yaml.safe_dump(d, encoding="utf-8", line_break="\n")
49 basedir: RelativeDirectoryPath | None = None
50 action_name: str | None = None
51 branch_name: str | None = None
52 workflow_name: str | None = None
54 exclude: list[str] | None = None
55 description: str = (
56 "<small>This documentation is automatically generated by "
57 '<a href="https://github.com/competitive-verifier/competitive-verifier">competitive-verifier/competitive-verifier</a>'
58 "</small>"
59 )
61 plugins: list[str] = Field(
62 default_factory=lambda: [
63 "jemoji",
64 "jekyll-redirect-from",
65 "jekyll-remote-theme",
66 ]
67 )
69 theme: str = "jekyll-theme-minimal"
70 mathjax: str | int = 3
71 highlightjs_style: str = Field(
72 default="default",
73 serialization_alias="highlightjs-style",
74 )
75 filename_index: bool = Field(
76 default=False,
77 serialization_alias="filename-index",
78 )
79 sass: dict[str, Any] = Field(default_factory=lambda: {"style": "compressed"})
80 icons: ConfigIcons = ConfigIcons()
81 consolidate: SortedPathSet | None = None
84def _load_user_render_config_yml(docs_dir: pathlib.Path) -> ConfigYaml | None:
85 env_config_yml = os.getenv(COMPETITIVE_VERIFY_DOCS_CONFIG_YML)
86 if env_config_yml: 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 logger.info("Parse $COMPETITIVE_VERIFY_DOCS_CONFIG_YML: %s", env_config_yml)
88 try:
89 user_config_yml = yaml.safe_load(env_config_yml)
90 return ConfigYaml.model_validate(user_config_yml)
91 except Exception:
92 logger.exception("Failed to parse $COMPETITIVE_VERIFY_DOCS_CONFIG_YML")
94 user_config_yml_path = docs_dir / _CONFIG_YML_PATH
95 if user_config_yml_path.exists():
96 logger.info("Parse user _config.yml: %s", user_config_yml_path)
97 try:
98 user_config_yml = yaml.safe_load(user_config_yml_path.read_bytes())
99 return ConfigYaml.model_validate(user_config_yml)
100 except Exception:
101 logger.exception(
102 "Failed to parse _config.yml: %s",
103 user_config_yml_path,
104 extra={"github": GitHubMessageParams(file=user_config_yml_path)},
105 )
106 return None
109def load_config_yml(docs_dir: pathlib.Path) -> ConfigYaml:
110 logger.info("docs_dir=%s", docs_dir)
112 config_yml = _load_user_render_config_yml(docs_dir)
113 if not config_yml:
114 config_yml = ConfigYaml()
116 if not config_yml.action_name: 116 ↛ 118line 116 didn't jump to line 118 because the condition on line 116 was always true
117 config_yml.action_name = github.env.get_workflow_name()
118 if not config_yml.branch_name: 118 ↛ 120line 118 didn't jump to line 120 because the condition on line 118 was always true
119 config_yml.branch_name = github.env.get_ref_name()
120 if not config_yml.workflow_name: 120 ↛ 123line 120 didn't jump to line 123 because the condition on line 120 was always true
121 config_yml.workflow_name = github.env.get_workflow_filename()
123 if not config_yml.basedir: 123 ↛ 126line 123 didn't jump to line 126 because the condition on line 123 was always true
124 git_root = git.get_root_directory().resolve()
125 config_yml.basedir = pathlib.Path.cwd().resolve().relative_to(git_root)
126 return config_yml