1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224 | diff --git a/src/build.py b/src/build.py
index 349bf4e..11cb50e 100644
--- a/src/build.py
+++ b/src/build.py
@@ -1,6 +1,8 @@
import re, sys
import importlib.util
import latex2mathml.converter
+
+from src.hash import handle_hash_sync
from . import settings
from jinja2 import Environment, FileSystemLoader
@@ -109,6 +111,9 @@ def compile_md_to_html(md_file:str, html_dest:str ):
"""Convert a Markdown file to HTML, applying filters and Jinja2 processing, and save to dest."""
info(f"Building {GRAY(md_file)}...")
+ hash = handle_hash_sync(md_file)
+ if hash is None:
+ return None
md_content = read_file(md_file)
math_rendered = render_math(md_content)
highlight = "noclasses"
@@ -146,6 +151,7 @@ def compile_md_to_html(md_file:str, html_dest:str ):
html_content = jinja_handler(md_file, filtered_html)
save_html(html_content, html_dest)
info(f"Done: {GRAY(html_dest)}...")
+ return True
def build(building_type:str,project_path:str, file:list[str]):
try:
diff --git a/src/config.py b/src/config.py
index 6bcfc80..5bd7dc3 100644
--- a/src/config.py
+++ b/src/config.py
@@ -3,7 +3,7 @@ from os.path import exists
from tomllib import loads
from .fileops import read_file
from .log import GRAY
-from .modules import Config, Extras, Project, Tree, Webview
+from .modules import Cache, Config, Extras, Project, Tree, Webview
def find_project_from_path(project_path: str):
@@ -59,6 +59,11 @@ def load_extras_config(config) -> Extras:
highlight = config["extras"].get("highlight", "monokai"),
)
+def load_cache_config(config) -> Cache:
+ return Cache(
+ hash = config["cache"].get("hash", ".hash.cache"),
+ )
+
def load_config() -> Config:
config_raw_content = read_file("config.toml")
config = loads(config_raw_content)
@@ -67,5 +72,6 @@ def load_config() -> Config:
tree = load_tree_config(config),
webview = load_webview_config(config),
extras = load_extras_config(config),
+ cache = load_cache_config(config),
)
diff --git a/src/hash.py b/src/hash.py
new file mode 100644
index 0000000..c56a088
--- /dev/null
+++ b/src/hash.py
@@ -0,0 +1,71 @@
+import hashlib
+import json
+from os import path
+
+from src.fileops import read_file, write_file
+from src.log import info, warn
+from . import settings
+
+HASH_FILE_CONTENT: dict | None = None
+
+
+def read_hash_file():
+ global HASH_FILE_CONTENT
+ if HASH_FILE_CONTENT is None:
+ with open(settings.CONFIG.cache.hash, "r") as f:
+ HASH_FILE_CONTENT = json.load(f)
+
+ return
+
+
+def write_hash_file():
+ global HASH_FILE_CONTENT
+ with open(settings.CONFIG.cache.hash, "w") as f:
+ json.dump(HASH_FILE_CONTENT, f)
+
+def md5_handler(md_path):
+ md_content = read_file(md_path)
+ return hashlib.md5(md_content.encode()).hexdigest()
+
+
+def hash_is_modified(md_path, new_hash):
+ global HASH_FILE_CONTENT
+ old_hash = HASH_FILE_CONTENT.get(md_path)
+ if old_hash == new_hash:
+ return None
+ return new_hash
+
+
+def append_md5(key, value):
+ global HASH_FILE_CONTENT
+ HASH_FILE_CONTENT[key] = value
+
+
+def handle_hash_sync(md_path):
+ global HASH_FILE_CONTENT
+ try:
+ hash_dir = settings.CONFIG.cache.hash
+ if settings.CONFIG is None:
+ return
+ if not (path.exists(hash_dir) and path.isfile(hash_dir)):
+ write_file(hash_dir, "{}")
+
+ read_hash_file()
+
+ if HASH_FILE_CONTENT is None:
+ HASH_FILE_CONTENT = {}
+
+ current_hash = md5_handler(md_path)
+ md_rel_path = path.relpath(md_path, settings.CONFIG.tree.markdown)
+ new_hash = hash_is_modified(md_rel_path, current_hash)
+
+ if new_hash is None:
+ return None
+
+ info(f"File hash: {new_hash}")
+ append_md5(md_rel_path, new_hash)
+ write_hash_file()
+ return True
+
+ except Exception as e:
+ warn(f"Hashing failed: {e}")
diff --git a/src/init_project.py b/src/init_project.py
index c4c379c..36c3529 100644
--- a/src/init_project.py
+++ b/src/init_project.py
@@ -1,6 +1,6 @@
from os import path, getcwd, makedirs
-from .modules import Config, Project, Tree, Webview, Extras
+from .modules import Cache, Config, Project, Tree, Webview, Extras
from .log import *
from .errors import fatal
from .templates import *
@@ -30,6 +30,9 @@ def init_config_struct(project_name:str) -> Config:
),
extras = Extras(
highlight = "monokai"
+ ),
+ cache = Cache(
+ hash = ".hash.cache"
)
)
@@ -55,6 +58,9 @@ static_path = "{config.webview.static_path}"
[extras]
highlight = "{config.extras.highlight}"
+
+[cache]
+hash = "{config.cache.hash}"
"""
def check_not_already_initialized(file_path:str) -> None:
diff --git a/src/modules.py b/src/modules.py
index 2e1ecb2..9e8b61c 100644
--- a/src/modules.py
+++ b/src/modules.py
@@ -26,9 +26,14 @@ class Webview:
class Extras:
highlight :str
+@dataclass
+class Cache:
+ hash :str
+
@dataclass()
class Config:
project:Project
tree:Tree
webview:Webview
extras:Extras
+ cache:Cache
diff --git a/src/watcher.py b/src/watcher.py
index 1aef4b4..5301220 100644
--- a/src/watcher.py
+++ b/src/watcher.py
@@ -12,7 +12,7 @@ from .errors import fatal
current_md_file = None
-def reload(changed_path, tree_config, extras_config):
+def reload(changed_path, tree_config):
global current_md_file
try:
if changed_path.endswith(".md"):
@@ -23,7 +23,9 @@ def reload(changed_path, tree_config, extras_config):
md_relpath = path.relpath(current_md_file, tree_config.markdown)
html = path.splitext(md_relpath)[0] + ".html"
dest = path.join(tree_config.dest, html)
- compile_md_to_html(current_md_file, dest)
+ compile_status = compile_md_to_html(current_md_file, dest)
+ if compile_status is None:
+ return None
return html
except Exception as e:
warn(str(e))
@@ -42,7 +44,7 @@ def watch_files(reload_func:Callable | None=None):
if now - last < 0.3:
return
last_reload[reload_path] = now
- file = reload( reload_path, config.tree , config.extras)
+ file = reload( reload_path, config.tree)
if reload_func and file: reload_func(file)
observer = Observer()
handler = ReloadHandler()
|