#! /usr/bin/env python3 import getopt import sys import os import re import fnmatch import fxconv help_string = f""" usage: fxconv [] -o [--fx|--cg] [...] fxconv converts resources such as images and fonts into binary formats for fxSDK applications, using gint and custom conversion formats. When no TYPE is specified (automated mode), fxconv looks for type and parameters in an fxconv-metadata.txt file in the same folder as the input. This is normally the default for add-ins. When TYPE is specified (one-shot conversion), it should be one of: -b, --binary Turn data into an object file without conversion -f, --font Convert to gint's topti font format --bopti-image Convert to gint's bopti image format --libimg-image Convert to the libimg image format --custom Use converters.py; you might want to specify an explicit type by adding a parameter type:your_custom_type (see below) During one-shot conversions, parameters can be specified with a "NAME:VALUE" syntax (names can contain dots). For example: fxconv -f myfont.png -o myfont.o charset:ascii grid.padding:1 height:7 Some formats differ between platforms so you should specify it when possible: --fx, --fx9860G Casio fx-9860G family (black-and-white calculators) --cg, --fxCG50 Casio fx-CG 50 family (16-bit color calculators) """.strip() # Simple error-warnings system FxconvError = fxconv.FxconvError def err(msg): print("\x1b[31;1merror:\x1b[0m", msg, file=sys.stderr) return 1 def warn(msg): print("\x1b[33;1mwarning:\x1b[0m", msg, file=sys.stderr) # "converters" module from the user project... if it exists try: import converters except ImportError: converters = None def parse_parameters(params): """Parse parameters of the form "NAME:VALUE" into a dictionary.""" d = dict() def insert(d, path, value): if len(path) == 1: d[path[0]] = value else: if not path[0] in d: d[path[0]] = dict() insert(d[path[0]], path[1:], value) for decl in params: if ":" not in decl: raise FxconvError(f"invalid parameter {decl}, ignoring") else: name, value = decl.split(":", 1) insert(d, name.split("."), value.strip()) return d def parse_parameters_metadata(contents): """Parse parameters from a metadata file contents.""" RE_COMMENT = re.compile(r'#.*$', re.MULTILINE) contents = re.sub(RE_COMMENT, "", contents) RE_WILDCARD = re.compile(r'^(\S(?:[^:\s]|\\:|\\ )*)\s*:\s*$', re.MULTILINE) lead, *elements = [ s.strip() for s in re.split(RE_WILDCARD, contents) ] if lead: raise FxconvError(f"invalid metadata: {lead} appears before wildcard") # Group elements by pairs (left: wildcard, right: list of properties) elements = list(zip(elements[::2], elements[1::2])) metadata = [] for (wildcard, params) in elements: params = [ s.strip() for s in params.split("\n") if s.strip() ] metadata.append((wildcard, parse_parameters(params))) return metadata def main(): types = "binary image font bopti-image libimg-image custom" mode = "" output = None model = None target = { 'toolchain': None, 'arch': None, 'section': None } use_custom = False # Parse command-line arguments if len(sys.argv) == 1: print(help_string, file=sys.stderr) sys.exit(1) try: models = "fx cg fx9860G fxCG50" longs = f"help output= toolchain= arch= section= {models} {types}" opts, args = getopt.gnu_getopt(sys.argv[1:], "hsbifo:", longs.split()) except getopt.GetoptError as error: return err(error) for name, value in opts: # Print usage if name == "--help": print(help_string, file=sys.stderr) return 0 elif name in [ "-o", "--output" ]: output = value elif name in [ "--fx", "--cg" ]: model = name[2:] elif name == "--fx9860G": model = "fx" elif name == "--fxCG50": model = "cg" elif name == "--toolchain": target['toolchain'] = value elif name == "--arch": target['arch'] = value elif name == "--section": target['section'] = value elif name == "--custom": use_custom = True mode = "custom" # Other names are modes else: mode = name[1] if len(name)==2 else name[2:] # Remaining arguments if args == []: err(f"no input file") sys.exit(1) input = args.pop(0) # In automatic mode, look for information in fxconv-metadata.txt if mode == "": metadata_file = os.path.dirname(input) + "/fxconv-metadata.txt" basename = os.path.basename(input) if not os.path.exists(metadata_file): return err(f"using auto mode but {metadata_file} does not exist") with open(metadata_file, "r") as fp: metadata = parse_parameters_metadata(fp.read()) params = dict() for (wildcard, p) in metadata: if fnmatch.fnmatchcase(basename, wildcard): params.update(**p) # In manual conversion modes, read parameters from the command-line else: params = parse_parameters(args) if "type" in params: pass elif len(mode) == 1: params["type"] = { "b": "binary", "i": "image", "f": "font" }[mode] else: params["type"] = mode # Will be deprecated in the future if params["type"] == "image": warn("type 'image' is deprecated, use 'bopti-image' instead") params["type"] = "bopti-image" # Use the custom module custom = None if use_custom: if converters is None: return err("--custom specified but no [converters] module") custom = converters.convert fxconv.convert(input, params, target, output, model, custom) if __name__ == "__main__": try: sys.exit(main()) except fxconv.FxconvError as e: sys.exit(err(e))