import argparse import logging import time from abc import ABC, abstractmethod from enum import IntEnum from pyparsing import ( Combine, Group, OneOrMore, Optional, Suppress, Word, alphanums, nums, ) import voicemeeterlib logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) argparser = argparse.ArgumentParser(description="creates a basic dsl") argparser.add_argument("-i", action="store_true") args = argparser.parse_args() ParamKinds = IntEnum( "ParamKinds", "bool float string", ) class Strategy(ABC): def __init__(self, target, param, val): self.target = target self.param = param self.val = val @abstractmethod def run(self): pass class BoolStrategy(Strategy): def run(self): setattr(self.target, self.param, self.strtobool(self.val)) def strtobool(self, val): """Convert a string representation of truth to it's numeric form.""" val = val.lower() if val in ("y", "yes", "t", "true", "on", "1"): return 1 elif val in ("n", "no", "f", "false", "off", "0"): return 0 else: raise ValueError("invalid truth value %r" % (val,)) class FloatStrategy(Strategy): def run(self): setattr(self.target, self.param, float(self.val)) class StringStrategy(Strategy): def run(self): setattr(self.target, self.param, " ".join(self.val)) class Context: def __init__(self, strategy: Strategy) -> None: self._strategy = strategy @property def strategy(self) -> Strategy: return self._strategy @strategy.setter def strategy(self, strategy: Strategy) -> None: self._strategy = strategy def run(self): self.strategy.run() class Parser: IS_STRING = ("label",) def __init__(self, vm): self.logger = logger.getChild(self.__class__.__name__) self.vm = vm self.kls = Group(OneOrMore(Word(alphanums))) self.token = Suppress("->") self.param = Group(OneOrMore(Word(alphanums))) self.value = Combine( Optional("-") + Word(nums) + Optional(".") + Optional(Word(nums)) ) | Group(OneOrMore(Word(alphanums))) self.event = ( self.kls + self.token + self.param + Optional(self.token) + Optional(self.value) ) def converter(self, cmds): """determines the kind of parameter from the parsed string""" res = list() for cmd in cmds: self.logger.debug(f"running command: {cmd}") match cmd_parsed := self.event.parseString(cmd): case [[kls, index], [param]]: target = getattr(self.vm, kls)[int(index)] res.append(getattr(target, param)) case [[kls, index], [param], val] if param in self.IS_STRING: target = getattr(self.vm, kls)[int(index)] context = self._get_context(ParamKinds.string, target, param, val) context.run() case [[kls, index], [param], [val] | val]: target = getattr(self.vm, kls)[int(index)] try: context = self._get_context(ParamKinds.bool, target, param, val) context.run() except ValueError as e: self.logger.error(f"{e}... switching to float strategy") context.strategy = FloatStrategy(target, param, val) context.run() case [ [kls, index], [secondary, param], [val] | val, ]: primary = getattr(self.vm, kls)[int(index)] target = getattr(primary, secondary) try: context = self._get_context(ParamKinds.bool, target, param, val) context.run() except ValueError as e: self.logger.error(f"{e}... switching to float strategy") context.strategy = FloatStrategy(target, param, val) context.run() case _: self.logger.error( f"unable to determine the kind of parameter from {cmd_parsed}" ) time.sleep(0.05) return res def _get_context(self, kind, *args): """ determines a strategy for a kind of parameter and passes it to the context. """ match kind: case ParamKinds.bool: context = Context(BoolStrategy(*args)) case ParamKinds.float: context = Context(FloatStrategy(*args)) case ParamKinds.string: context = Context(StringStrategy(*args)) return context def interactive_mode(parser): while cmd := input("Please enter command (Press to exit)\n"): if res := parser.parse((cmd,)): print(res) def main(): # fmt: off cmds = ( "strip 0 -> mute -> true", "strip 0 -> mute", "bus 0 -> mute -> true", "strip 0 -> mute -> false", "bus 0 -> mute -> true", "strip 3 -> solo -> true", "strip 3 -> solo -> false", "strip 1 -> A1 -> true", "strip 1 -> A1", "strip 1 -> A1 -> false", "strip 1 -> A1", "strip 3 -> eq on -> true", "bus 3 -> eq on -> false", "strip 4 -> gain -> 1.2", "strip 0 -> gain -> -8.2", "strip 0 -> gain", "strip 1 -> label -> rode podmic", "strip 2 -> limit -> -28", "strip 2 -> limit", "strip 3 -> comp knob -> 3.8" ) # fmt: on with voicemeeterlib.api("potato") as vm: parser = Parser(vm) if args.i: interactive_mode(parser) return if res := parser.converter(cmds): print(res) if __name__ == "__main__": main()