11import os
2+ from pathlib import Path
23import re
3- from typing import Optional
4+ from typing import Optional , Tuple
45
56from mkdocs .exceptions import PluginError
67from mkdocs .plugins import BasePlugin
7- from mkdocs .structure .files import File
8+ from mkdocs .structure .files import File , Files
89from mkdocs .structure .pages import Page
10+ from mkdocs .config .defaults import MkDocsConfig
911
1012
1113BASE_PATH = os .path .dirname (os .path .realpath (__file__ ))
1214
13- LIBS_PATH = BASE_PATH + "/libs/"
14-
1515CSS_PATH = BASE_PATH + "/css/"
1616
1717JS_PATH = BASE_PATH + "/js/"
1818
19+ ARG_PATTERN_TEMPLATE = "{}\\ s*=\\ s*[\" ']?([^\\ s\" ']*)"
20+
1921
2022# "{workerinit}" was the first line of the template: removed in version > 2.0.0.
2123SKELETON = """
3941
4042
4143
44+
45+
46+
47+
4248class Counter :
49+
4350 def __init__ (self , config ):
51+ self .ROOT_URI = Path (config ['docs_dir' ]).as_posix ()
4452 self .count = 0
45- self .config = config
53+ self .config : MkDocsConfig = config
4654 self .spaces = {}
4755 self .macros_contents = {}
4856 self .worker_inits = []
4957
50- def insert_ide (self , macro ):
51- regex_titre = r".*?titre=\"(.*?)\""
52- regex_init = r".*?init=[\"\']?\b([^\s]*)\b"
53- regex_base = r".*?base=[\"\']?\b([^\s]*)\b"
54- regex_sql = r".*?sql=[\"\']?\b([^\s]*)\b"
55- regex_space = r".*?espace=[\"\']?\b([^\s]*)\b"
58+ def insert_ide (self , page :Page ):
59+ def wrapped (matches ):
5660
57- params = str (macro .groups (0 )[0 ])
61+ regex_titre = r".*?titre\s*=\s*\"(.*?)\""
62+ regex_init = ARG_PATTERN_TEMPLATE .format ('init' )
63+ regex_base = ARG_PATTERN_TEMPLATE .format ('base' )
64+ regex_sql = ARG_PATTERN_TEMPLATE .format ('sql' )
65+ regex_space = ARG_PATTERN_TEMPLATE .format ('espace' )
5866
59- titre = "" .join (re .findall (regex_titre , params ))
60- autoexec = "autoexec" in params
61- hide = "hide" in params
62- init = "" .join (re .findall (regex_init , params ))
63- base = "" .join (re .findall (regex_base , params ))
64- sql = "" .join (re .findall (regex_sql , params ))
65- space = "" .join (re .findall (regex_space , params ))
67+ params = str (matches .groups (0 )[0 ])
6668
67- return self .build_sql (titre , autoexec , hide , init , base , sql , space )
69+ titre = "" .join (re .findall (regex_titre , params ))
70+ autoexec = "autoexec" in params
71+ hide = "hide" in params
72+ init = "" .join (re .findall (regex_init , params ))
73+ base = "" .join (re .findall (regex_base , params ))
74+ sql = "" .join (re .findall (regex_sql , params ))
75+ space = "" .join (re .findall (regex_space , params ))
6876
69- def build_sql (self , titre , autoexec , hide , init , base , sql , space ):
77+ return self .build_sql (titre , autoexec , hide , init , base , sql , space , page = page )
78+ return wrapped
79+
80+ def build_sql (self , titre , autoexec , hide , init , base , sql , space , * , page :Page = None ):
7081 self .count += 1
82+ worker = ""
7183
7284 # handle defaults (centralizing the logic in one single place):
7385 autoexec = True if autoexec else ""
@@ -78,7 +90,6 @@ def build_sql(self, titre, autoexec, hide, init, base, sql, space):
7890 sql = sql or ""
7991 space = space or None
8092
81- worker = ""
8293 if space :
8394 if space not in self .spaces :
8495 self .spaces [space ] = 0
@@ -90,37 +101,28 @@ def build_sql(self, titre, autoexec, hide, init, base, sql, space):
90101 else :
91102 self .spaces [space ] += 1
92103 worker = space
104+
93105 if sql != "" :
94- try :
95- with open (os .path .abspath (self .config ["docs_dir" ]) + "/" + sql ) as f :
96- sql = f .readlines ()
97- sql = "" .join (sql )
98- if autoexec :
99- autoexec = sql
100- autoexec = autoexec .replace ("\n " , "\\ n" )
101- autoexec = autoexec .replace ("'" , "\\ '" )
102- except OSError :
103- sql = "Fichier '" + sql + "' introuvable"
106+ ok , sql , _ = self .get_relative_file (page , sql , "Fichier" )
107+ if ok and autoexec :
108+ autoexec = sql .replace ("\n " , "\\ n" ).replace ("'" , "\\ '" )
109+
104110 if init != "" :
105- try :
106- with open (os .path .abspath (self .config ["docs_dir" ]) + "/" + init ) as f :
107- init = f .readlines ()
108- init = "" .join (init )
109- init = init .replace ("\n " , "\\ n" )
110- init = init .replace ("'" , "\\ '" )
111- except OSError :
112- sql = "-- Fichier d'initialisation '" + init + "' introuvable"
113- init = ""
111+ ok , init , _ = self .get_relative_file (page , init , "-- Fichier d'initialisation" )
112+ init = init .replace ("\n " , "\\ n" ).replace ("'" , "\\ '" )
113+ if not ok :
114+ sql , init = init , ""
115+
114116 if base != "/" :
115- base_url = self .config ["site_url" ]
116- if os .path .isfile (os .path .abspath (self .config ["docs_dir" ]) + "/" + base ):
117- base = base_url + "/" + base
118- base = base .replace ("//" , "/" )
117+ ok , nope , resolved_path = self .get_relative_file (page , base , "-- Fichier de base" )
118+ if ok :
119+ base = Path (resolved_path ).as_posix ().replace (self .ROOT_URI , self .config ['site_url' ])
119120 else :
120- sql = "-- Fichier de base '" + base + "' introuvable"
121+ sql = nope
121122 init = ""
122123 base = "/"
123- html = SKELETON .format (
124+
125+ ide_html = SKELETON .format (
124126 numide = self .count ,
125127 title = titre ,
126128 hide = hide ,
@@ -131,7 +133,35 @@ def build_sql(self, titre, autoexec, hide, init, base, sql, space):
131133 worker = worker ,
132134 #workerinit=workerinit, # Not inserted here anymore (version > 2.0.0)
133135 )
134- return html
136+ return ide_html
137+
138+ def get_relative_file (self , page :Page , rel_path :str , header :str ) -> Tuple [bool , str , str ]:
139+ """
140+ Extract a file relative to the current markdown file or to the docs dir, or
141+ return a default message.
142+ """
143+ docs = self .config ["docs_dir" ]
144+
145+ candidates_uris_srcs = [
146+ f"{ docs } /{ Path (page .file .src_uri ).parent .as_posix () } " , # Relative to current page
147+ docs , # Relative to docs_dir
148+ ]
149+ for cnd in candidates_uris_srcs :
150+ path_uri = f"{ cnd } /{ rel_path } "
151+ path = os .path .normpath (os .path .abspath (path_uri ))
152+
153+ if not os .path .isfile (path ):
154+ continue
155+
156+ if path .endswith ('.db' ):
157+ # .db are not readable with utf-8, but no need of their content so no problem.
158+ content = '_dummy'
159+ else :
160+ with open (path , 'r' , encoding = 'utf-8' ) as f :
161+ content = f .read ()
162+ return True , content , path
163+
164+ return False , f"{ header } { rel_path } introuvable." , rel_path
135165
136166 def register_macro_content (self , html_code ):
137167 """
@@ -188,15 +218,12 @@ def on_config(self, config, **kwargs):
188218
189219 return config
190220
191- def on_files (self , files , config ):
192- files .append (
193- File ("sqlite_ide.css" , CSS_PATH , config ["site_dir" ] + "/css/" , False )
194- )
195- files .append (File ("sqlite_ide.js" , JS_PATH , config ["site_dir" ] + "/js/" , False ))
196- files .append (
197- File ("worker.sql-wasm.js" , JS_PATH , config ["site_dir" ] + "/js/" , False )
198- )
199- files .append (File ("sql-wasm.wasm" , JS_PATH , config ["site_dir" ] + "/js/" , False ))
221+ def on_files (self , files :Files , config ):
222+ for folder in "css js" .split ():
223+ for file in (Path (BASE_PATH ) / folder ).iterdir ():
224+ files .append (
225+ File (file .name , file .parent , config ["site_dir" ] + f"/{ folder } /" , False )
226+ )
200227 return files
201228
202229 def on_pre_page (self , page , config , files ):
@@ -220,7 +247,7 @@ def on_page_content(self, html, page, config, files):
220247 regex = r"(?:^|\n)\s*?<p>\s*?{{\s*?sqlide.*?\s+?(.*?)\s*?}}</p>(?:\n|$)"
221248
222249 if c :
223- html = re .sub (regex , c .insert_ide , html , flags = re .MULTILINE | re .DOTALL )
250+ html = re .sub (regex , c .insert_ide ( page ) , html , flags = re .MULTILINE | re .DOTALL )
224251
225252 return html
226253
@@ -262,7 +289,11 @@ def on_page_context(self, ctx, page:Page, config, **kwargs):
262289
263290 # Mutate the page content with all the required logistic if any:
264291 if sql_scripts :
265- page .content = sql_scripts + page .content
292+ page .content = f"""\
293+ { sql_scripts }
294+ { page .content }
295+ <script src="{ base_url } /js/material-tabbed-fix.js"></script>
296+ """
266297
267298 def counter_for (self , page , * , set_counter : Counter = None ) -> Optional [Counter ]:
268299 key = page and page .url
@@ -287,6 +318,7 @@ def sqlide(
287318 when using Pyodide-MkDocs-Theme).
288319 """
289320 # (actual default values are handled in Counter.build_sql)
290- c = self .counter_for (self .macros .page )
291- html_code = c .build_sql (titre , autoexec , hide , init , base , sql , espace )
321+ page = self .macros .page
322+ c = self .counter_for (page )
323+ html_code = c .build_sql (titre , autoexec , hide , init , base , sql , espace , page = page )
292324 return c .register_macro_content (html_code )
0 commit comments