mirror of
https://github.com/tridactyl/tridactyl.git
synced 2026-09-10 07:16:33 -04:00
Most of the changed lines in this are from random breaking changes in typedoc meaning that lots of docstrings needed to be rewritten
206 lines
7.5 KiB
Python
Executable file
206 lines
7.5 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Force reflection upon an unwilling world.
|
|
|
|
Processes a single excmds.ts to produce a background and content version.
|
|
|
|
Objectives:
|
|
Remove duplication of everything in excmds_content.ts
|
|
Be kinder to the type checker
|
|
Capture function signature and types for exmode.parser
|
|
|
|
Caveats:
|
|
Function statements must match existing style:
|
|
signature on a single line, ending with {
|
|
no other statement on the same line as the end brace
|
|
|
|
"""
|
|
|
|
from collections import OrderedDict
|
|
import re
|
|
import textwrap
|
|
|
|
class Signature:
|
|
"""Extract name, parameters and types from a function signature."""
|
|
def __init__(self, raw):
|
|
self.raw = raw
|
|
|
|
namematch = re.match(r".*function\s+([^(]+)", raw)
|
|
self.name = namematch.groups()[0].strip()
|
|
|
|
# Assume the final ) on the line ends the parameter list.
|
|
params_list = raw[namematch.end()+1:raw.rindex(')')].split(',')
|
|
# If func takes no args, params_list is [''], but that's not a real parameter, is it?
|
|
if params_list == ['']: params_list = []
|
|
|
|
# Dictionary { name: type }
|
|
self.params = OrderedDict()
|
|
|
|
for param in params_list:
|
|
# Type declaration
|
|
if ':' in param:
|
|
name, typ = map(str.strip, param.split(':'))
|
|
if (typ not in ('number', 'boolean', 'string', 'string[]', 'ModeName')
|
|
and '|' not in typ
|
|
and typ[0] not in ['"',"'"]
|
|
):
|
|
raise Exception("Edit me! " + typ + " is not a supported type!")
|
|
# Default argument
|
|
elif '=' in param:
|
|
name, default = map(str.strip, param.split('='))
|
|
try:
|
|
float(default)
|
|
typ = "number"
|
|
except ValueError:
|
|
if default in ("true", "false"):
|
|
typ = "boolean"
|
|
elif default[0] == default[-1] and default[0] in ('"', "'"):
|
|
typ = "string"
|
|
else:
|
|
raise Exception("Edit me! Only number, string and boolean defaults supported at the mo!\n\t{raw}".format(**locals()))
|
|
else:
|
|
raise Exception("All parameters must be typed!\n\t{raw}".format(**locals()))
|
|
|
|
if name.endswith('?'): name = name[:-1]
|
|
self.params[name] = typ
|
|
|
|
|
|
def get_block(lines):
|
|
"""next(lines) contains an open brace: return all the lines up to close brace.
|
|
|
|
Moves the lines iterator, so useful for consuming a block.
|
|
|
|
"""
|
|
brace_balance = 0
|
|
block = ""
|
|
while True:
|
|
current_line = next(lines)
|
|
brace_balance += current_line.count('{')
|
|
brace_balance -= current_line.count('}')
|
|
block += current_line
|
|
if brace_balance == 0:
|
|
return block
|
|
|
|
|
|
def dict_to_js(d):
|
|
"Py dict to string that when eval'd will produce equivalent js Map"
|
|
return "new Map(" + str(list(d.items())).replace('(','[').replace(')',']') + ")"
|
|
|
|
def content(lines, context):
|
|
"Extract signature and, if context==background, replace function with a shim."
|
|
|
|
block = get_block(lines)
|
|
sig = Signature(block.split('\n')[0])
|
|
cmd_params = "cmd_params.set('{sig.name}', ".format(**locals()) + dict_to_js(sig.params) + ")"
|
|
message_params = ", ".join(sig.params.keys())
|
|
if context == "background":
|
|
# Consume and replace this block. We emit the line to add the
|
|
# function's signature to cmd_params, the function's signature
|
|
# line unchanged, then a command to message the browser's
|
|
# active tab forwarding all parameters.
|
|
return textwrap.dedent("""\
|
|
{cmd_params}
|
|
{sig.raw}
|
|
logger.debug("shimming excmd {sig.name} from background to content")
|
|
return Messaging.messageActiveTab(
|
|
"excmd_content",
|
|
"{sig.name}",
|
|
[{message_params}],
|
|
)
|
|
}}\n""".format(**locals()))
|
|
else:
|
|
# Emit the line to add the function to cmd_params, then
|
|
# re-emit the original block (because we consumed the block so
|
|
# we could compute the cmd params)
|
|
return "{cmd_params}\n{block}".format(**locals())
|
|
|
|
|
|
def background(lines, context):
|
|
"Extract signature and, if context==content, replace function with a shim."
|
|
|
|
block = get_block(lines)
|
|
sig = Signature(block.split('\n')[0])
|
|
cmd_params = "cmd_params.set('{sig.name}', ".format(**locals()) + dict_to_js(sig.params) + ")"
|
|
message_params = ", ".join(sig.params.keys())
|
|
|
|
if context == "background":
|
|
# Emit the line to add the function to cmd_params, then
|
|
# re-emit the original block (because we consumed the block so
|
|
# we could compute the cmd params)
|
|
return "{cmd_params}\n{block}".format(**locals())
|
|
else:
|
|
# Consume and replace this block. We emit the line to add the
|
|
# function's signature to cmd_params, the function's signature
|
|
# line unchanged, then a command to message the browser's
|
|
# active tab forwarding all parameters.
|
|
return textwrap.dedent("""\
|
|
{cmd_params}
|
|
{sig.raw}
|
|
logger.debug("shimming excmd {sig.name} from content to background")
|
|
return Messaging.message(
|
|
"excmd_background",
|
|
"{sig.name}",
|
|
{message_params}
|
|
)
|
|
}}\n""".format(**locals()))
|
|
|
|
def both(lines, context):
|
|
"Just extract the signature of the command."
|
|
|
|
sig = Signature(next(lines))
|
|
return "cmd_params.set('{sig.name}', ".format(**locals()) + dict_to_js(sig.params) + """)\n{sig.raw}""".format(**locals())
|
|
|
|
|
|
def omit_helper_func_factory(desired_context):
|
|
"Consume this block if context isn't what we want"
|
|
def inner(lines, context):
|
|
if context != desired_context:
|
|
# Consume this block
|
|
get_block(lines)
|
|
return ""
|
|
|
|
return inner
|
|
|
|
|
|
def omit_line_factory(desired_context):
|
|
def inner(lines, context):
|
|
if context != desired_context:
|
|
next(lines)
|
|
return ""
|
|
|
|
return inner
|
|
|
|
|
|
def main():
|
|
"""Iterate over the file, dispatching to appropriate macro handlers."""
|
|
|
|
macros = {
|
|
"content": content,
|
|
"background": background,
|
|
"both": both,
|
|
"content_helper": omit_helper_func_factory("content"),
|
|
"background_helper": omit_helper_func_factory("background"),
|
|
"content_omit_line": omit_line_factory("content"),
|
|
"background_omit_line": omit_line_factory("background"),
|
|
}
|
|
|
|
for context in ("background", "content"):
|
|
with open("src/excmds.ts", encoding="utf-8") as source:
|
|
output = PRELUDE
|
|
lines = iter(source)
|
|
for line in lines:
|
|
if line.startswith("//#"):
|
|
macrocmd = line[3:].strip()
|
|
if macrocmd in macros:
|
|
output += macros[macrocmd](lines, context)
|
|
else:
|
|
raise Exception("Unknown macrocmd! {macrocmd}".format(**locals()))
|
|
else:
|
|
output += line
|
|
# print(output.rstrip())
|
|
with open("src/.excmds_{context}.generated.ts".format(**locals()), "w", encoding="utf-8") as sink:
|
|
print(output.rstrip(), file=sink)
|
|
|
|
|
|
PRELUDE = "/* Generated from excmds.ts. Don't edit this file! */"
|
|
main()
|