Skip to content

Commit

Permalink
Consolidated (de)compile options & added a format option instead; CLI…
Browse files Browse the repository at this point in the history
… can detect input file type automatically. Added yaml export type, and fixed a few bugs.
  • Loading branch information
ilonachan authored and ilonachan committed Sep 1, 2024
1 parent 11f4a90 commit 61fbd76
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 37 deletions.
137 changes: 103 additions & 34 deletions formats/gds.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import click
import contextlib
import json
import yaml
import os
import sys

import parse
import ast
Expand All @@ -16,7 +19,9 @@ def cli():
commands_i = {val["id"]: key for key, val in commands.items() if "id" in val} # Inverted version of commands

class GDS:
def __init__(self, cmds = []): #modes: "bin"/"b", "json"/"j", "gda"/"a"
def __init__(self, cmds=None): #modes: "bin"/"b", "json"/"j", "gda"/"a"
if cmds is None:
cmds = []
self.cmds = cmds

@classmethod
Expand Down Expand Up @@ -83,10 +88,16 @@ def from_gds(Self, file):

@classmethod
def from_json (Self, file):
cmds = json.loads(file)
cmds = json.loads(file)["data"]
#TODO: reject non-compatible json files
return Self(cmds)

@classmethod
def from_yaml(Self, file):
cmds = yaml.safe_load(file)["data"]
#TODO: reject non-compatible yaml files
return Self(cmds)

@classmethod
def from_gda (Self, file): #TODO: make this, so gds_old can be completely removed
cmds = []
Expand All @@ -98,8 +109,6 @@ def from_gda (Self, file): #TODO: make this, so gds_old can be completely remove
if line == '':
continue

data = {}

line, strings = parse.remove_strings(line)
line = line.rstrip().split(" ")
cmd = line[0]
Expand Down Expand Up @@ -148,14 +157,17 @@ def __getitem__ (self, index):
index = int(index)
return self.cmds[index]

def to_json (self):
def to_json(self):
return json.dumps({"version": v, "data": self.cmds}, indent=4)

def to_yaml(self):
return yaml.safe_dump({"version": v, "data": self.cmds})

def to_gds (self):
out = b"\x00" * 2
for command in self.cmds:
if type(command["command"]["id"]) == int:
out += command["command"]["id"].to_bytes(2, "little")
if type(command["command"]) == int:
out += command["command"].to_bytes(2, "little")
else:
out += commands[command["command"]["id"]].to_bytes(2, "little")
for param in command["parameters"]:
Expand Down Expand Up @@ -258,52 +270,109 @@ def process(input, output):
foreach_file_pair(pairs, process, quiet=quiet)


@cli.command(
name="compilejson",
help="Converts a JSON document created by 'compilejson' to a GDS script file.",
no_args_is_help = True
)
@click.argument("input")
@click.argument("output")
def create_json(input, output):
input = open(input, encoding="utf-8").read()
output = open(output, "wb")

gds = GDS.from_json(input)
output.write(gds.to_bin())
output.close()

@cli.command(
name="compile",
help="Generates a GDS binary script file from human-readable GDA files.",
no_args_is_help = True
)
@click.argument("input")
@click.argument("output")
def create_from_gda(input, output):
input = open(input, encoding="utf-8").read()
output = open(output, "wb")
@click.argument("output", required=False, default = None)
@click.option("--format", "-f", required=False, default = None, multiple=False, help="The format of the input file. Will be inferred from the file ending or content if unset. Possible values: gda, json, yaml")
def compile(input, output, format):
"""
Generates a GDS binary from a human-readable script file.
"""
inpath = input
if format not in [None, "gda", "json", "yaml", "yml"]:
raise Exception(f"Unsupported input format: '{format}'")

if format is None:
if inpath.lower().endswith(".gda"):
format = "gda"
elif inpath.lower().endswith(".json"):
format = "json"
elif inpath.lower().endswith(".yml") or inpath.lower().endswith(".yaml"):
format = "yaml"


if output is None:
output = inpath
if format == 'gda' and output.lower().endswith(".gda"):
output = output[:-4]
elif format == 'json' and output.lower().endswith(".json"):
output = output[:-5]
elif format in ['yaml', 'yml'] and output.lower().endswith(".yml"):
output = output[:-4]
elif format in ['yaml', 'yml'] and output.lower().endswith(".yaml"):
output = output[:-5]
output += ".gds"

gds = GDS.from_gda(input)
input = open(inpath, encoding="utf-8").read()
gds = None
with contextlib.suppress(Exception):
if format == 'gda':
gds = GDS.from_gda(input)
elif format == 'json':
gds = GDS.from_json(input)
elif format in ['yaml', 'yml']:
gds = GDS.from_yaml(input)

if gds is None:
if format is not None:
# TODO: should this abort instead?
print(f"WARNING: Input file '{inpath}' did not have expected format '{format}'", file = sys.stderr)
# format not specified and couldn't be inferred, or file turns out not to have the correct format
# => try all the formats & see which one works (only one should be possible)
for f in ["json", "yaml", "gda"]:
with contextlib.suppress(Exception):
if f == 'gda':
gds = GDS.from_gda(input)
elif f == 'json':
gds = GDS.from_json(input)
elif f == 'yaml':
gds = GDS.from_yaml(input)
if gds is None:
raise Exception(f"File '{inpath}' couldn't be read: not a known file format"
+(f" (expected '{format}')" if format is not None else ""))

output = open(output, "wb")
output.write(gds.to_bin())
output.close()

@cli.command(
name="decompile",
help="Converts a GDS file into a human-readable GDA script format.",
no_args_is_help = True
)
@click.argument("input")
@click.argument("output", required=False)
def create_to_gda(input, output = None):
@click.argument("output", required=False, default = None)
@click.option("--format", "-f", default="gda", required=False, multiple=False, help="The format used for output. Possible values: gda (default), json, yaml")
def decompile(input, output, format):
"""
Convert a GDS file into a human-readable GDA script format.
"""
out_ending = ""
if format == 'gda':
out_ending = ".gda"
elif format == 'json':
out_ending = ".json"
elif format in ['yaml', 'yml']:
out_ending = ".yml"
else:
raise Exception(f"Unsupported output format: '{format}'")

if output is None:
output = input
if output.lower().endswith(".gds"):
output = output[:-4]
output = output + ".gda"
output = output + out_ending

input = open(input, "rb").read()
output = open(output, "w", encoding="utf-8")
gds = GDS.from_gds(input)
output.write(gds.to_gda())
output.close()

with open(output, "w", encoding="utf-8") as output:
if format == 'gda':
output.write(gds.to_gda())
elif format == 'json':
output.write(gds.to_json())
elif format in ['yaml', 'yml']:
output.write(gds.to_yaml())
11 changes: 8 additions & 3 deletions utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os

def cli_file_pairs(input = None, output = None, *, in_ending = None, out_ending = None, recursive = False):
def cli_file_pairs(input = None, output = None, *, in_ending = None, out_ending = None, filter_infer=None, recursive = False):
"""
Given the file path inputs to the various CLI commands, determines which input files should be operated on and mapped to which output files.
Expand Down Expand Up @@ -39,7 +39,7 @@ def listfiles(path):
continue
yield os.path.join(path, f)

def filter_infer(input):
def default_filter_infer(input, force_accept=False):
if in_ending is not None and not input.lower().endswith(in_ending):
return None
if out_ending is not None and input.lower().endswith(out_ending):
Expand All @@ -53,15 +53,20 @@ def filter_infer(input):
output += out_ending
return output

if filter_infer is None:
filter_infer = default_filter_infer

input_dir = ""
input_paths = []
rel_pairs = None
if os.path.isfile(input):
input_dir, ip = os.path.split(input)
input_paths = [ip]
rel_pairs = [(ip, filter_infer(ip, force_accept=True)) for ip in input_paths]
else:
input_dir = input
input_paths = [os.path.relpath(f, input_dir) for f in listfiles(input)]
rel_pairs = [(ip, filter_infer(ip)) for ip in input_paths]
rel_pairs = [(ip, filter_infer(ip)) for ip in input_paths]
rel_pairs = [(ip, op) for (ip, op) in rel_pairs if op is not None]

if output is None:
Expand Down

0 comments on commit 61fbd76

Please sign in to comment.