StrictDoc Documentation
strictdoc/core/file_dependency_manager.py
Source file coverage
Path:
strictdoc/core/file_dependency_manager.py
Lines:
113
Non-empty lines:
93
Non-empty lines covered with requirements:
93 / 93 (100.0%)
Functions:
6
Functions covered by requirements:
6 / 6 (100.0%)
1
"""
2
A simple container class for tracking dependencies between artifacts.
3
 
4
It provides answers to whether a given artifact has to be re-generated or not.
5
 
6
@relation(SDOC-SRS-2, scope=file)
7
"""
8
 
9
import datetime
10
import os.path
11
from copy import deepcopy
12
from dataclasses import dataclass
13
from pathlib import Path
14
from typing import Dict, Optional, Set
15
 
16
from strictdoc.core.project_config import ProjectConfig
17
from strictdoc.helpers.file_modification_time import (
18
    get_file_modification_time,
19
)
20
from strictdoc.helpers.pickle import pickle_dump, pickle_load
21
 
22
 
23
@dataclass
24
class FileDependencyManager:
25
    path_to_cache_dir: str
26
    path_to_cache: str
27
    dependencies_now: Dict[str, Set[str]]
28
    dependencies_prev: Dict[str, Set[str]]
29
    dependencies_must_renegerate: Set[str]
30
 
31
    @staticmethod
32
    def create_from_cache(
33
        project_config: ProjectConfig,
34
    ) -> "FileDependencyManager":
35
        path_to_cache_dir = project_config.get_path_to_cache_dir()
36
        path_to_cache = os.path.join(path_to_cache_dir, "dependencies")
37
 
38
        if not os.path.isfile(path_to_cache):
39
            return FileDependencyManager(
40
                path_to_cache_dir, path_to_cache, {}, {}, set()
41
            )
42
 
43
        with open(path_to_cache, "rb") as cache_file:
44
            cache_file_bytes = cache_file.read()
45
 
46
        unpickled_content: Optional[FileDependencyManager] = pickle_load(
47
            cache_file_bytes
48
        )
49
 
50
        if unpickled_content is not None:
51
            assert isinstance(unpickled_content, FileDependencyManager), (
52
                unpickled_content,
53
            )
54
            unpickled_content.dependencies_now.clear()
55
            return unpickled_content
56
 
57
        raise AssertionError(  # pragma: no cover
58
            f"Problem reading the file dependency cache file: {path_to_cache}"
59
        )
60
 
61
    def must_generate(self, path_to_input_file: str) -> bool:
62
        must_regenerate = (
63
            path_to_input_file in self.dependencies_must_renegerate
64
        )
65
        return must_regenerate
66
 
67
    def add_dependency(
68
        self,
69
        path_to_file: str,
70
        path_to_dependent_file: str,
71
    ) -> None:
72
        dependencies = self.dependencies_now.setdefault(path_to_file, set())
73
        dependencies.add(path_to_dependent_file)
74
 
75
    def save_to_cache(self) -> None:
76
        self.dependencies_prev = deepcopy(self.dependencies_now)
77
 
78
        pickled_content = pickle_dump(self)
79
 
80
        Path(self.path_to_cache_dir).mkdir(exist_ok=True, parents=True)
81
        with open(self.path_to_cache, "wb") as cache_file:
82
            cache_file.write(pickled_content)
83
 
84
    def resolve_modification_dates(
85
        self, strictdoc_last_update: datetime.datetime
86
    ) -> None:
87
        self.dependencies_must_renegerate.clear()
88
 
89
        items_now, items_before = (
90
            self.dependencies_now.items(),
91
            self.dependencies_prev.items(),
92
        )
93
        for items_ in [items_now, items_before]:
94
            for path_to_input_file_, dependencies_ in items_:
95
                for path_to_dependency_ in dependencies_:
96
                    if (
97
                        # The file has not been generated yet.
98
                        not os.path.isfile(path_to_dependency_)
99
                        # The file used to exist but not anymore.
100
                        or not os.path.isfile(path_to_input_file_)
101
                        # The file is outdated compared to its HTML output artifact.
102
                        or get_file_modification_time(path_to_input_file_)
103
                        > get_file_modification_time(path_to_dependency_)
104
                        # The file is outdated compared to StrictDoc's own code (this
105
                        # branch is development-only).
106
                        or strictdoc_last_update
107
                        > get_file_modification_time(path_to_dependency_)
108
                    ):
109
                        self.dependencies_must_renegerate.add(
110
                            path_to_dependency_
111
                        )
112
 
113
        self.save_to_cache()