Coverage for src/lcdoc/mkdocs/find_pages/autodocs.py: 25.78%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

163 statements  

1import string lp

2import time lp

3from ast import literal_eval lp

4from contextlib import contextmanager lp

5from functools import partial lp

6from hashlib import md5 lp

7 

8 

9from lcdoc.mkdocs.tools import MDPlugin, app, config_options, find_md_files lp

10from lcdoc.tools import ( lp

11 OD, 

12 dirname, 

13 exists, 

14 flatten, 

15 os, 

16 project, 

17 read_file, 

18 unflatten, 

19 write_file, 

20) 

21 

22 

23def find_pages(find, config, stats): lp

24 """ 

25 find: [{'after': 'index.md', 'dir': 'blueprint'}] or just 'blueprint' 

26 """ 

27 ev = [i.strip() for i in os.environ.get('find_pages', '').split(',')] lp

28 ev = [i for i in ev if i] lp

29 find.extend(ev) lp

30 f = {} lp

31 for k in find: lp

32 if isinstance(k, str): 32 ↛ 34line 32 didn't jump to line 34, because the condition on line 32 was never falselp

33 f[k] = {'dir': k, 'after': None} lp

34 elif isinstance(k, dict): 

35 f[k['dir']] = k 

36 else: 

37 app.die('format problem', element=k) 

38 find = f.values() lp

39 stats['find_terms'] = len(find) lp

40 if not find: 40 ↛ 41line 40 didn't jump to line 41, because the condition on line 40 was never truelp

41 return 

42 for m in find: lp

43 m['found'] = f = find_md_files(match=m['dir'], config=config) lp

44 if not f: 44 ↛ 45line 44 didn't jump to line 45, because the condition on line 44 was never truelp

45 app.info('No pages found', match=m['dir']) 

46 stats['matching'] = sum([len(f['found']) for f in find]) lp

47 return find lp

48 

49 

50class autodocs: lp

51 def do_spec(pconfig, name, spec, config, stats): lp

52 """entry point from mkdocs.yml config""" 

53 d_src = project.root() + '/' + spec.get('src-dir', 'build/autodocs') 

54 if not exists(d_src): 

55 return app.warning('No autodocs source', name=name, **spec) 

56 d_target = project.root() + '/docs/autodocs/' + name 

57 autodocs.link_over(d_src, d_target) 

58 d = 'autodocs/%s:follow' % name 

59 # find pages returns list of dicts where 'found' key is the list of pages found: 

60 f = [k['found'] for k in find_pages([d], config, stats)] 

61 found = [j for i in f for j in i] 

62 if not found: 

63 return 

64 s = spec.get('nav', 'autodocs') 

65 autodocs.insert_nav_plain(config['nav'], found, s) 

66 S.dir = d_target 

67 S.spec = spec 

68 S.plugin_conf = pconfig 

69 S.stats = stats 

70 S.build() 

71 

72 def link_over(d_src, d_target): lp

73 if exists(d_target): 

74 if os.readlink(d_target) == d_src: 

75 return 

76 os.unlink(d_target) 

77 d = os.path.dirname(d_target) 

78 if not exists(d): 

79 os.makedirs(d, exist_ok=True) 

80 os.symlink(d_src, d_target) 

81 app.info('Created symlink', d_src=d_src, d_target=d_target) 

82 

83 def insert_nav_plain(nav, found, ins): lp

84 if isinstance(nav, str): 

85 return 

86 if isinstance(nav, dict): 

87 return [autodocs.insert_nav_plain(v, found, ins) for v in nav.values()] 

88 if ins in nav: 

89 i = nav.index(ins) 

90 nav.pop(i) 

91 return [nav.insert(i, k) for k in reversed(found)] 

92 [autodocs.insert_nav_plain(i, found, ins) for i in nav] 

93 

94 

95hsh = lambda src: md5(src.encode('utf-8')).hexdigest() 95 ↛ exitline 95 didn't run the lambda on line 95lp

96 

97 

98def walk_dir(directory, crit=None): lp

99 crit = (lambda *a: True) if crit is None else crit 

100 files = [] 

101 j = os.path.join 

102 for (dirpath, dirnames, filenames) in os.walk(directory): 

103 files += [j(dirpath, file) for file in filenames if crit(dirpath, file)] 

104 return files 

105 

106 

107def scan_d_autodocs(typ): lp

108 files = walk_dir(S.dir, crit=lambda d, fn: fn.endswith(typ)) 

109 app.info('have %s files' % typ, json=files) 

110 return files 

111 

112 

113now = time.time lp

114 

115from lcdoc.mkdocs.lp.plugs.kroki import run as kroki lp

116 

117 

118class S: lp

119 dir = spec = plugin_conf = stats = None lp

120 

121 def build(): lp

122 S.graph_easy.build() 

123 S.plant.build() 

124 

125 @contextmanager lp

126 def has_changed(fn, ext): 

127 src = read_file(fn) 

128 fnb = fn.rsplit(ext, 1)[0] 

129 sfn, fnhsh, skip = '%s.svg' % fnb, '%s%s.md5' % (fnb, ext), False 

130 cur_hash = hsh(src) 

131 if exists(sfn): 

132 if read_file(fnhsh, dflt='') == cur_hash: 

133 app.debug('graph up to date', fn=fn) 

134 sfn, src = (None, None) 

135 yield (sfn, src) 

136 write_file(fnhsh, cur_hash) 

137 

138 class graph_easy: lp

139 cmd = lambda: 'graph-easy' 139 ↛ exitline 139 didn't run the lambda on line 139lp

140 

141 def build(): lp

142 S.stats['graph_easy_count'] = 0 

143 S.stats['graph_easy_built_count'] = 0 

144 S.stats['graph_easy_build_time'] = 0 

145 S.graph_easy.files = fs = scan_d_autodocs('.graph_easy.src') 

146 if not fs: 

147 return app.info('no .graph_easy.src files') 

148 if not S.graph_easy.cmd(): 

149 app.die('have no graph_easy resource') 

150 

151 [S.graph_easy.create(f) for f in fs] 

152 

153 def create(fn): lp

154 """check if the src changed via a hash written at last create""" 

155 S.stats['graph_easy_count'] += 1 

156 with S.has_changed(fn, '.graph_easy.src') as nfos: 

157 if not nfos[0]: 

158 return 

159 fn_svg, src = nfos 

160 app.info('rebuilding', fmt='svg', fn=fn_svg) 

161 cmd = '"%s" --as %s --output "%s" "%s"' 

162 cmd = cmd % (S.graph_easy.cmd(), 'svg', fn_svg, fn,) 

163 t0 = now() 

164 err = os.system(cmd) 

165 if err: 

166 app.die('svg creation failed', fn=fn, cmd=cmd) 

167 S.stats['graph_easy_built_count'] += 1 

168 S.stats['graph_easy_build_time'] += now() - t0 

169 

170 class plant: lp

171 def build(): lp

172 S.stats['plantuml_count'] = 0 

173 S.stats['plantuml_built_count'] = 0 

174 S.stats['plantuml_build_time'] = 0 

175 S.plant.files = fs = scan_d_autodocs('.plantuml') 

176 if not fs: 

177 return app.info('no .plantuml files') 

178 # import base64, zlib, requests 

179 # import httpx as requests 

180 

181 # st = None 

182 # if FLG.plantuml_style != 'default': 

183 # # also in use there for the 'normal' mkdocs plantuml_markdown inline plants: 

184 # fn_st = d_assets() + '/mkdocs/lcd/assets/plantuml/%s' % FLG.plantuml_style 

185 # if not exists(fn_st): 

186 # app.die('Style not found', fn=fn_st) 

187 # st = read_file(fn_st, dflt='') 

188 

189 for f in fs: 

190 with S.has_changed(f, '.plantuml') as nfos: 

191 S.stats['plantuml_built_count'] += 1 

192 if not nfos[0]: 

193 continue 

194 fn_svg, src = nfos 

195 t0 = now() 

196 

197 S.stats['plantuml_count'] += 1 

198 res = kroki( 

199 src, 

200 {'mode': 'kroki:plantuml', 'puml': 'dark_blue', 'get_svg': True,}, 

201 ) 

202 # g = base64.urlsafe_b64encode(zlib.compress(s.encode('utf-8'))) 

203 write_file(fn_svg, res) 

204 app.info('created plant svg', fn=fn_svg) 

205 S.stats['plantuml_build_time'] += now() - t0 

206 

207 # now, insert the svgs INTO the md files, inline. Then mouse overs work: 

208 # the md files got placeholders for the svgs, while pytesting: 

209 all_mds_with_plants = set([fnp.rsplit('/', 2)[0] for fnp in fs]) 

210 dir_autodocs = S.dir + '/' 

211 for md in all_mds_with_plants: 

212 s = read_file(md + '.md') 

213 for f in [i for i in fs if i.startswith(md)]: 

214 fsvg = f.replace('.plantuml', '.svg') 

215 svg = read_file(fsvg).split('>', 1)[1].strip().split('<!--MD5', 1)[0] 

216 if not svg.endswith('</svg>'): 

217 svg += '</g></svg>' 

218 # testname (e.g. test02_foo) for links building in js 

219 n_test = fsvg.rsplit('/', 2)[-2] 

220 id = '<svg pth_rel="%s/" id="%s" class="call_flow_chart" ' 

221 id = id % (n_test, n_test) 

222 svg = svg.replace('<svg ', id) 

223 fsvg = fsvg.rsplit(dir_autodocs, 1)[1] 

224 app.info('Embedding svg into md', md=md, svg=f) 

225 tsvg = '[svg_placeholder:%s]' % fsvg 

226 s = s.replace(tsvg, svg) 

227 write_file(md + '.md', s)