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
« 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."""
3import os
5from .. import core
6from . import base, configref, manifest, normalizer, parser, extractor, strings, typescript, util
8Error = base.Error
11def generate_and_write(root_dir='', out_dir='', manifest_path='', debug=False):
12 """Generate the specs and write them to the output directory.
14 Writes ``specs.json``, ``gws.generated.ts``, ``configref.en.md`` and
15 ``configref.de.md``.
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.
23 Raises:
24 ``GeneratorError``: If the specs cannot be generated.
25 """
27 base.log.set_level('DEBUG' if debug else 'INFO')
29 gen = _run_generator(root_dir, out_dir, manifest_path, debug)
31 to_path(f'{gen.outDir}/specs.json', gen.specData)
33 util.write_file(f'{gen.outDir}/gws.generated.ts', gen.typescript)
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'])
39def generate(manifest_path='') -> core.SpecData:
40 """Generate the specs.
42 Args:
43 manifest_path: Path to the application manifest.
45 Returns:
46 The spec data.
48 Raises:
49 ``GeneratorError``: If the specs cannot be generated.
50 """
52 gen = _run_generator(manifest_path=manifest_path)
53 return gen.specData
56def to_path(path: str, specs: core.SpecData):
57 """Write the specs to a JSON file.
59 Args:
60 path: File path.
61 specs: Spec data.
63 Returns:
64 The file path.
65 """
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
79def from_path(path: str) -> core.SpecData:
80 """Load the specs from a JSON file written by ``to_path``.
82 Args:
83 path: File path.
85 Returns:
86 The spec data.
87 """
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
98##
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
108 _init_generator(gen)
109 gen.dump('000_init')
111 parser.parse(gen)
112 gen.dump('001_parsed')
114 normalizer.normalize(gen)
115 gen.dump('002_normalized')
117 extractor.extract(gen)
118 gen.dump('003_extracted')
120 gen.typescript = typescript.create(gen)
121 gen.strings = strings.collect(gen)
123 gen.configRef['en'] = configref.create(gen, 'en')
124 gen.configRef['de'] = configref.create(gen, 'de')
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
132 return gen
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 }
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
151 gen.chunks = []
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)
161 manifest_plugins = None
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')
172 plugin_dict = {}
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
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
197 gen.chunks.extend(plugin_dict.values())
199 for chunk in gen.chunks:
200 if not os.path.isdir(chunk.sourceDir):
201 continue
203 excl = base.v.EXCLUDE_PATHS + (chunk.exclude or [])
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
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)