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

1import pathlib 

2import shutil 

3from logging import getLogger 

4 

5from pydantic import BaseModel 

6 

7import competitive_verifier_resources 

8from competitive_verifier import git, github 

9from competitive_verifier.models import VerificationInput, VerifyCommandResult 

10 

11from .config import ConfigYaml, load_config_yml 

12from .front_matter import Markdown 

13from .render import RenderJob 

14 

15logger = getLogger(__name__) 

16 

17 

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 

25 

26 def build(self) -> bool: 

27 logger.info("Working directory: %s", pathlib.Path.cwd().as_posix()) 

28 logger.info("Generate documents...") 

29 

30 # implementation 

31 result = self.impl() 

32 

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 ) 

41 

42 return result 

43 

44 def impl(self) -> bool: 

45 self.destination_dir.mkdir(parents=True, exist_ok=True) 

46 

47 config_yml = load_config_yml(self.docs_dir) 

48 logger.info("_config.yml: %s", config_yml) 

49 

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 

52 

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 ) 

59 

60 # Write _config.yml 

61 (self.destination_dir / "_config.yml").write_bytes(config_yml.model_dump_yml()) 

62 

63 # Copy static files 

64 self.copy_static_files(static_dir=self.docs_dir / "static") 

65 return True 

66 

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) 

74 

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.") 

85 

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...") 

94 

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()) 

98 

99 sources = git.ls_files(*(self.include or [])) 

100 if exclude: 

101 sources -= git.ls_files(*exclude) 

102 

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)