Coverage for gws-app/gws/spec/generator/main.py: 77%

100 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Generator entry points and JSON storage of the specs.""" 

2 

3import os 

4 

5from .. import core 

6from . import base, configref, manifest, normalizer, parser, extractor, strings, typescript, util 

7 

8Error = base.Error 

9 

10 

11def generate_and_write(root_dir='', out_dir='', manifest_path='', debug=False): 

12 """Generate the specs and write them to the output directory. 

13 

14 Writes ``specs.json``, ``gws.generated.ts``, ``configref.en.md`` and 

15 ``configref.de.md``. 

16 

17 Args: 

18 root_dir: Application root directory, defaults to the ``app`` directory of this installation. 

19 out_dir: Output directory. 

20 manifest_path: Path to the application manifest. 

21 debug: If True, log debug messages and dump the generator state after each step. 

22 

23 Raises: 

24 ``GeneratorError``: If the specs cannot be generated. 

25 """ 

26 

27 base.log.set_level('DEBUG' if debug else 'INFO') 

28 

29 gen = _run_generator(root_dir, out_dir, manifest_path, debug) 

30 

31 to_path(f'{gen.outDir}/specs.json', gen.specData) 

32 

33 util.write_file(f'{gen.outDir}/gws.generated.ts', gen.typescript) 

34 

35 util.write_file(f'{gen.outDir}/configref.en.md', gen.configRef['en']) 

36 util.write_file(f'{gen.outDir}/configref.de.md', gen.configRef['de']) 

37 

38 

39def generate(manifest_path='') -> core.SpecData: 

40 """Generate the specs. 

41 

42 Args: 

43 manifest_path: Path to the application manifest. 

44 

45 Returns: 

46 The spec data. 

47 

48 Raises: 

49 ``GeneratorError``: If the specs cannot be generated. 

50 """ 

51 

52 gen = _run_generator(manifest_path=manifest_path) 

53 return gen.specData 

54 

55 

56def to_path(path: str, specs: core.SpecData): 

57 """Write the specs to a JSON file. 

58 

59 Args: 

60 path: File path. 

61 specs: Spec data. 

62 

63 Returns: 

64 The file path. 

65 """ 

66 

67 util.write_json( 

68 path, 

69 { 

70 'meta': specs.meta, 

71 'chunks': specs.chunks, 

72 'serverTypes': specs.serverTypes, 

73 'strings': specs.strings, 

74 }, 

75 ) 

76 return path 

77 

78 

79def from_path(path: str) -> core.SpecData: 

80 """Load the specs from a JSON file written by ``to_path``. 

81 

82 Args: 

83 path: File path. 

84 

85 Returns: 

86 The spec data. 

87 """ 

88 

89 d = util.read_json(path) 

90 s = core.SpecData() 

91 s.meta = d['meta'] 

92 s.chunks = [core.Chunk(**c) for c in d['chunks']] 

93 s.serverTypes = [core.make_type(t) for t in d['serverTypes']] 

94 s.strings = d['strings'] 

95 return s 

96 

97 

98## 

99 

100def _run_generator(root_dir='', out_dir='', manifest_path='', debug=False): 

101 gen = base.Generator() 

102 gen.rootDir = root_dir or base.v.APP_DIR 

103 gen.outDir = out_dir 

104 gen.selfDir = base.v.SELF_DIR 

105 gen.debug = debug 

106 gen.manifestPath = manifest_path 

107 

108 _init_generator(gen) 

109 gen.dump('000_init') 

110 

111 parser.parse(gen) 

112 gen.dump('001_parsed') 

113 

114 normalizer.normalize(gen) 

115 gen.dump('002_normalized') 

116 

117 extractor.extract(gen) 

118 gen.dump('003_extracted') 

119 

120 gen.typescript = typescript.create(gen) 

121 gen.strings = strings.collect(gen) 

122 

123 gen.configRef['en'] = configref.create(gen, 'en') 

124 gen.configRef['de'] = configref.create(gen, 'de') 

125 

126 gen.specData = core.SpecData() 

127 gen.specData.meta = gen.meta 

128 gen.specData.chunks = gen.chunks 

129 gen.specData.serverTypes = gen.serverTypes 

130 gen.specData.strings = gen.strings 

131 

132 return gen 

133 

134 

135def _init_generator(gen: base.Generator): 

136 gen.meta = { 

137 'version': util.read_file(gen.rootDir + '/VERSION').strip(), 

138 'manifestPath': None, 

139 'manifest': None, 

140 } 

141 

142 def _chunk(name, source_dir, bundle_dir): 

143 cc = core.Chunk() 

144 cc.name = name 

145 cc.sourceDir = source_dir 

146 cc.bundleDir = bundle_dir 

147 cc.paths = {kind: [] for _, kind in base.v.FILE_KINDS} 

148 cc.exclude = [] 

149 return cc 

150 

151 gen.chunks = [] 

152 

153 for name, path in base.v.SYSTEM_CHUNKS: 

154 cc = _chunk( 

155 name, 

156 gen.rootDir + path, 

157 base.v.APP_DIR, 

158 ) 

159 gen.chunks.append(cc) 

160 

161 manifest_plugins = None 

162 

163 if gen.manifestPath: 

164 try: 

165 base.log.debug(f'loading manifest {gen.manifestPath!r}') 

166 gen.meta['manifestPath'] = gen.manifestPath 

167 gen.meta['manifest'] = manifest.from_path(gen.manifestPath) 

168 except Exception as exc: 

169 raise base.GeneratorError(f'error loading manifest {gen.manifestPath!r}') from exc 

170 manifest_plugins = gen.meta['manifest'].get('plugins') 

171 

172 plugin_dict = {} 

173 

174 # our plugins 

175 for path in util.find_dirs(gen.rootDir + base.v.PLUGIN_DIR): 

176 name = os.path.basename(path) 

177 cc = _chunk( 

178 base.v.PLUGIN_PREFIX + '.' + name, 

179 path, 

180 path, 

181 ) 

182 plugin_dict[cc.name] = cc 

183 

184 # manifest plugins 

185 for p in manifest_plugins or []: 

186 path = p.get('path') 

187 name = p.get('name') or os.path.basename(path) 

188 if not os.path.isdir(path): 

189 raise base.GeneratorError(f'error loading plugin {name!r}: directory {path!r} not found') 

190 cc = _chunk( 

191 base.v.PLUGIN_PREFIX + '.' + name, 

192 path, 

193 path, 

194 ) 

195 plugin_dict[cc.name] = cc 

196 

197 gen.chunks.extend(plugin_dict.values()) 

198 

199 for chunk in gen.chunks: 

200 if not os.path.isdir(chunk.sourceDir): 

201 continue 

202 

203 excl = base.v.EXCLUDE_PATHS + (chunk.exclude or []) 

204 

205 for path in util.find_files(chunk.sourceDir): 

206 if any(x in path for x in excl): 

207 continue 

208 for pattern, kind in base.v.FILE_KINDS: 

209 if path.endswith(pattern): 

210 chunk.paths[kind].append(path) 

211 break 

212 

213 root_cc = _chunk( 

214 'gws', 

215 gen.rootDir + '/gws', 

216 gen.rootDir + '/gws', 

217 ) 

218 root_cc.paths['python'] = [gen.rootDir + '/gws/__init__.py'] 

219 gen.chunks.insert(0, root_cc)