Skip to content

Commit 893bc80

Browse files
committed
MaD: update generator scripts to support writing JSON data extensions
The `generate_mad.py` script accepts a flag that lets you choose between YAML or JSON, but YAML is still the default. We can change the default later, when CLI support for JSON is widely available. `convert_extensions.py` also still hard-codes `.yml` as the output extension/format.
1 parent 96ec1ed commit 893bc80

5 files changed

Lines changed: 49 additions & 8 deletions

File tree

‎misc/scripts/models-as-data/bulk_generate_mad.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,11 @@ def download_and_decompress(analyzed_database: dict) -> str:
430430
def clean_up_mad_destination_for_project(config, name: str):
431431
target = pathlib.Path(config["destination"], name)
432432
if config.get("single-file", False):
433-
target = target.with_suffix(".model.yml")
434-
if target.exists():
435-
print(f"Deleting existing MaD file at {target}")
436-
target.unlink()
433+
for extension in (".model.json", ".model.yml"):
434+
model_target = target.with_suffix(extension)
435+
if model_target.exists():
436+
print(f"Deleting existing MaD file at {model_target}")
437+
model_target.unlink()
437438
elif target.exists():
438439
print(f"Deleting existing MaD directory at {target}")
439440
shutil.rmtree(target, ignore_errors=True)

‎misc/scripts/models-as-data/convert_extensions.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def __init__(self, language, dbDir):
1919
)
2020
self.extDir = os.path.join(self.codeQlRoot, f"{self.language}/ql/lib/ext/")
2121
self.dirname = "modelconverter"
22+
# N.B. change this to .model.json to change the output format:
2223
self.modelFileExtension = ".model.yml"
2324
self.workDir = tempfile.mkdtemp()
2425

‎misc/scripts/models-as-data/data_extension_file.py‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
class DataExtensionFile:
55
"""Simple class for representing a data extension file and writing it to disk in a
6-
pretty-printed YAML format."""
6+
pretty-printed YAML or JSON format."""
77

88
def __init__(self, pack):
99
self.pack = pack
@@ -46,3 +46,31 @@ def write_yaml(self, f):
4646
f.write("extensions:\n")
4747
for predicate in sorted(self.predicates.keys()):
4848
f.write(self.yaml_for_predicate(predicate))
49+
50+
def json_for_row(self, row):
51+
return "[" + ", ".join(json.dumps(value) for value in row) + "]"
52+
53+
def json_for_predicate(self, predicate):
54+
return f""" {{
55+
"addsTo": {{
56+
"pack": "{self.pack}",
57+
"extensible": "{predicate}"
58+
}},
59+
"data": [
60+
{',\n '.join(self.json_for_row(row) for row in self.predicates[predicate].keys())}
61+
]
62+
}}"""
63+
64+
def write_json(self, f):
65+
# It would be preferable to use a serializer like Python's json.dumps, but it can't
66+
# pretty-print with one line per tuple.
67+
f.write("// THIS FILE IS AN AUTO-GENERATED MODELS AS DATA FILE. DO NOT EDIT.\n")
68+
f.write("{\n")
69+
f.write(' "extensions": [\n')
70+
for i, predicate in enumerate(self.predicates):
71+
if i > 0:
72+
f.write(",\n")
73+
f.write(self.json_for_predicate(predicate))
74+
f.write("\n")
75+
f.write(" ]\n")
76+
f.write("}")

‎misc/scripts/models-as-data/generate_mad.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ def make():
119119
"--single-file",
120120
help="Generate a single file with all models instead of separate files for each namespace, using provided argument as the base filename.",
121121
)
122+
p.add_argument(
123+
"--extension-format",
124+
choices=["json", "yaml"],
125+
default="yaml",
126+
help="Format for the generated data extension files (default %(default)s)",
127+
)
122128
generator = p.parse_args(namespace=Generator())
123129

124130
if (
@@ -225,17 +231,19 @@ def run(self):
225231
print("Models as data extensions generated, but not written to file.")
226232
sys.exit(0)
227233

234+
file_extension = ".json" if self.extension_format == "json" else ".yml"
235+
228236
if (
229237
self.with_sinks
230238
or self.with_sources
231239
or self.with_summaries
232240
or self.with_neutrals
233241
):
234-
merger.save(self.generated_frameworks, ".model.yml")
242+
merger.save(self.generated_frameworks, f".model{file_extension}")
235243

236244
if self.with_typebased_summaries:
237245
type_based_extensions.save(
238-
self.generated_frameworks, ".typebased.model.yml"
246+
self.generated_frameworks, f".typebased.model{file_extension}"
239247
)
240248

241249

‎misc/scripts/models-as-data/helpers.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ def save(self, dir, file_extension):
4747
)
4848
target = os.path.join(dir, f"{sanitized_namespace}{file_extension}")
4949
with open(target, "w") as f:
50-
extension.write_yaml(f)
50+
if file_extension.endswith(".json"):
51+
extension.write_json(f)
52+
else:
53+
extension.write_yaml(f)
5154
print("Models as data extensions written to " + target)
5255

5356

0 commit comments

Comments
 (0)