diff --git a/.gitignore b/.gitignore index 5d381cc..d018309 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +config.json +out/ \ No newline at end of file diff --git a/XFTGenerator.py b/XFTGenerator.py new file mode 100644 index 0000000..9536f4a --- /dev/null +++ b/XFTGenerator.py @@ -0,0 +1,203 @@ +# XF Template Generator +__author__ = "Louis Heredero" +__copyright__ = "Copyright 2023, Louis Heredero" +__license__ = "GPL3.0" +__version__ = "1.0.0" + +import json +import locale +import os +import re +import time + +def get_input(title, default=None, non_null=False): + """Prompts user for an input + + Args: + title (str): Name of value + default (Any, optional): Default value. Defaults to None. + non_null (bool, optional): Whether the can be null. Defaults to False. + + Returns: + str|None: The value + """ + + while True: + txt = title + if default is not None: + txt += f" [{default}]" + txt += ": " + value = input(txt).strip() + if value: + return value + + elif default: + return default + + elif not non_null: + return "" + +def fill_template(dir_, variables, filename): + """Replaces values in a template file + + Args: + dir_ (str): Path of src directory + variables (dict): Dictionary of values to replace + filename (str): Template file name + + Returns: + str: Template content with replaced values + """ + + with open(os.path.join(dir_, "templates", filename), "r") as f: + content = f.read() + + def replace(m): + indent = m.group(1) + key = m.group(2) + txt = variables[key] + + if indent: + lines = txt.split("\n") + lines = [indent+line for line in lines] + txt = "\n".join(lines) + + return txt + + return re.sub("([ \t]*)\$\{(.+?)\}", replace, content) + +def output_file(dir_, filename, content): + """Writes content to a given file. Checks before overwriting + + Args: + dir_ (str): Path of src directory + filename (str): Output file name + content (str): Content to write + """ + + outdir = os.path.join(dir_, "out") + if not os.path.isdir(outdir): + os.mkdir(outdir) + + outpath = os.path.join(outdir, filename) + if os.path.exists(outpath): + replace = input(f"The file {filename} already exists. Overwrite it ? y/N: ") + if replace.lower() != "y": + return + + with open(outpath, "w") as f: + f.write(content) + +def main(): + locale.setlocale(locale.LC_TIME, "en_GB.utf8") + dir_ = os.path.dirname(__file__) + + conf_path = os.path.join(dir_, "config.json") + if os.path.exists(conf_path): + with open(conf_path, "r") as f: + variables = json.load(f) + + else: + variables = {} + + variables["date"] = time.strftime("%B %Y") + + variables["author"] = get_input("Author", variables.get("author", ""), True) + variables["filename"] = get_input("Filename", None, True).upper() + variables["filename_lc"] = variables["filename"].lower() + variables["fn"] = get_input("Filename (short)", None, True).upper() + + ########## + # States # + ########## + + states = [] + + print("States (leave empty to end):") + print("> INIT") + while True: + state = input("> ").upper() + if state: + states.append(state) + else: + break + + states = ["ST"+variables["fn"]+"_"+s for s in states] + variables["STATES_ENUM"] = ",\n".join(states) + + states_cases = [] + + for i, state in enumerate(states): + case_ = f"case {state}:\n" + case_ += " break;" + states_cases.append(case_) + + variables["STATES_CASES"] = "\n\n".join(states_cases) + + ########## + # Events # + ########## + + events = ["init = 100"] + + print("Events (leave empty to end):") + print("> init") + while True: + event = input("> ").lower() + if event: + events.append(event) + + else: + break + + events_enum = ["ev"+variables["fn"]+e for e in events] + events_enum = ",\n".join(events_enum).split("\n") + events_enum[0] += " // TODO change this number (< 256)" + variables["EVENTS_ENUM"] = "\n".join(events_enum) + + events_emits = [] + + emit_def = "" + emit_def += "void {filename}_emit{Event}({filename}* me, uint16_t t) {\n" + emit_def += " POST(me, &{filename}_processEvent, ev{fn}{event}, t, 0);\n" + emit_def += "}" + emit_def = emit_def.replace("{filename}", variables["filename"]).replace("{fn}", variables["fn"]) + + for event in events[1:]: + events_emits.append( + emit_def.replace("{event}", event).replace("{Event}", event.capitalize()) + ) + + variables["EVENTS_EMITS_DEF"] = "\n\n".join(events_emits) + + events_emits = [] + emit_dec = "" + emit_dec += "/**\n" + emit_dec += " * Emit the {event} event\n" + emit_dec += " * @param me the {filename} itself\n" + emit_dec += " * @param t time to wait in ms before triggering event\n" + emit_dec += " */" + emit_dec += "void {filename}_emit{Event}({filename}* me, uint16_t t);" + emit_dec = emit_dec.replace("{filename}", variables["filename"]) + + for event in events[1:]: + events_emits.append( + emit_dec.replace("{event}", event).replace("{Event}", event.capitalize()) + ) + + variables["EVENTS_EMITS_DEC"] = "\n\n".join(events_emits) + + file_c = fill_template(dir_, variables, "file.c") + file_h = fill_template(dir_, variables, "file.h") + + output_file(dir_, variables["filename_lc"]+".c", file_c) + output_file(dir_, variables["filename_lc"]+".h", file_h) + + with open(conf_path, "w") as f: + conf = { + "author": variables["author"] + } + json.dump(conf, f) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/templates/file.c b/templates/file.c new file mode 100644 index 0000000..abe1b07 --- /dev/null +++ b/templates/file.c @@ -0,0 +1,55 @@ +/** + * @author ${author} + * @version 1.0.0 + * @date ${date} + * @file ${filename_lc}.c + */ + +#include "${filename_lc}.h" + +void ${filename}_init(${filename}* me){ + me->state = ST${fn}_INIT; +} + +void ${filename}_startBehaviour(${filename}* me){ + POST(me, &${filename}_processEvent, ev${fn}init, 0, 0); +} + +bool ${filename}_processEvent(Event* ev) { + bool processed = false; + ${filename}* me = (${filename}*)Event_getTarget(ev); + switch (me->state) { // onState + case ST${fn}_INIT: + if (ev->id == ev${fn}init) { + + } + break; + + ${STATES_CASES} + } + + if(oldState != me->state){ + switch (oldState) { // onExit + case ST${fn}_INIT: + break; + + ${STATES_CASES} + } + + switch (me->state) { // onEntry + case ST${fn}_INIT: + break; + + ${STATES_CASES} + } + + processed = true; + } + return processed; +} + +/************ + * EMITTERS * + ************/ + +${EVENTS_EMITS_DEF} \ No newline at end of file diff --git a/templates/file.h b/templates/file.h new file mode 100644 index 0000000..2d6326a --- /dev/null +++ b/templates/file.h @@ -0,0 +1,49 @@ +/** + * @author ${author} + * @version 1.0.0 + * @date ${date} + * @file ${filename_lc}.h + */ +#ifndef ${filename}_H +#define ${filename}_H + +#include "../xf/xf.h" + +typedef enum { + ${STATES_ENUM} +} ${filename}_STATES; + +typedef enum { + ${EVENTS_ENUM} +} ${filename}_EVENTS; + +typedef struct { + ${filename}_STATES state; +} ${filename}; + +/** + * Initialize the ${filename} + * @param me the ${filename} itself + */ +void ${filename}_init(${filename}* me); + +/** + * Start the ${filename} state machine + * @param me the ${filename} itself + */ +void ${filename}_startBehaviour(${filename}* me); + +/** + * Process the event + * @param ev the event to process + * @return true if the event is processed + */ +bool ${filename}_processEvent(Event* ev); + +/************ + * EMITTERS * + ************/ + +${EVENTS_EMITS_DEC} + +#endif