Esempio n. 1
0
    def parse_target(self, meta, target_options, target_name, tokens):
        tokens = [
            (tokenize.NAME, "dict"),
            *[(toknum, tokval) for toknum, tokval, *_ in tokens],
        ]

        overrides = {}
        untokenized = tokenize.untokenize(tokens)

        try:
            val = ast.parse(untokenized)
        except SyntaxError as e:
            raise BadSpecValue(
                "Target options must be valid dictionary syntax",
                error=e,
                got=untokenized)
        else:
            for kw in val.body[0].value.keywords:
                try:
                    overrides[kw.arg] = ast.literal_eval(kw.value)
                except ValueError:
                    bad = self.determine_bad(kw.value, untokenized)
                    raise BadSpecValue(
                        f"target options can only be python literals: not ({bad})"
                    )

        if "options" not in target_options:
            target_options["options"] = {}

        target_options["options"].update(overrides)
        return self.target_spec.normalise(meta, target_options)
Esempio n. 2
0
    def make_command(self, meta, val, existing):
        v = sb.set_options(path=sb.required(sb.string_spec()),
                           allow_ws_only=sb.defaulted(sb.boolean(),
                                                      False)).normalise(
                                                          meta, val)

        path = v["path"]
        allow_ws_only = v["allow_ws_only"]

        if path not in self.paths:
            raise NoSuchPath(path, sorted(self.paths))

        val = sb.set_options(body=sb.required(
            sb.set_options(args=sb.dictionary_spec(),
                           command=sb.required(sb.string_spec())))).normalise(
                               meta, val)

        args = val["body"]["args"]
        name = val["body"]["command"]

        if existing:
            name = val["body"]["command"] = f"{existing['path']}:{name}"

        extra_context = {}
        if existing:
            extra_context["_parent_command"] = existing["command"]

        everything = meta.everything
        if isinstance(meta.everything, MergedOptions):
            everything = meta.everything.wrapped()
        everything.update(extra_context)
        meta = Meta(everything, []).at("body")

        available_commands = self.paths[path]

        if name not in available_commands:
            raise BadSpecValue(
                "Unknown command",
                wanted=name,
                available=self.available(available_commands,
                                         allow_ws_only=allow_ws_only),
                meta=meta.at("command"),
            )

        command = available_commands[name]["spec"].normalise(
            meta.at("args"), args)

        if not allow_ws_only and command.__whirlwind_ws_only__:
            raise BadSpecValue(
                "Command is for websockets only",
                wanted=name,
                available=self.available(available_commands,
                                         allow_ws_only=allow_ws_only),
                meta=meta.at("command"),
            )

        return command, name
Esempio n. 3
0
    def normalise_filled(self, meta, val):
        val = sb.string_spec().normalise(meta, val)
        if len(val) != 12:
            raise BadSpecValue("serial must be 12 characters long",
                               got=len(val))

        try:
            binascii.unhexlify(val)
        except binascii.Error:
            raise BadSpecValue("serial must be a hex value")

        return val
Esempio n. 4
0
    def normalise_filled(self, meta, val):
        if not val:
            raise BadSpecValue("Animation option must be non empty", meta=meta)

        if isinstance(val, str):
            val = [val]

        if isinstance(val, (list, tuple)) and hasattr(val[0], "resolve"):
            val = val[0]

        if hasattr(val, "resolve"):
            return val

        if isinstance(val, (list, tuple)):
            if len(val) == 1:
                val = [val[0], sb.NotSpecified, sb.NotSpecified]

            if len(val) == 2:
                val = [val[0], sb.NotSpecified, val[1]]

            val = {"name": val[0], "background": val[1], "options": val[2]}

        if not hasattr(val["name"], "resolver"):
            val["name"] = resolve(val["name"])

        return val["name"].resolver(val["options"], val["background"])
Esempio n. 5
0
 def normalise_filled(self, meta, val):
     val = sb.listof(self.spec).normalise(meta, val)
     if len(val) != self.length:
         raise BadSpecValue(
             "Expected certain number of parts", want=self.length, got=len(val), meta=meta
         )
     return val
Esempio n. 6
0
    def normalise(self, meta, val):
        if type(val) not in (tuple, list):
            raise BadSpecValue("Expected a list", got=type(val), meta=meta)

        if len(val) != len(self.specs):
            raise BadSpecValue(
                "Expected a value with certain number of items",
                wanted=len(self.specs),
                got=len(val),
                meta=meta,
            )

        res = []
        for i, v in enumerate(val):
            res.append(self.specs[i].normalise(meta.indexed_at(i), v))

        return res
Esempio n. 7
0
    def normalise_filled(self, meta, val):
        if val in ("on", "ON", True, 1):
            return "on"
        elif val in ("off", "OFF", False, 0):
            return "off"

        raise BadSpecValue("Power must be on/off, True/False or 0/1",
                           wanted=val,
                           meta=meta)
Esempio n. 8
0
    def normalise_filled(self, meta, val):
        if val == "rainbow":
            return HueRange(0, 360)

        was_list = False
        if type(val) is list:
            was_list = True
            if len(val) not in (1, 2):
                raise BadSpecValue("A hue range must be 2 or 1 items",
                                   got=val,
                                   meta=meta)
            if len(val) == 1:
                val = [val[0], val[0]]

        try:
            val = int(val)
            if val < 0 or val > 360:
                raise BadSpecValue("A hue number must be between 0 and 360",
                                   got=val,
                                   meta=meta)
            val = [val, val]
        except (ValueError, TypeError):
            pass

        if type(val) is str:
            val = val.split("-", 1)

        for part in val:
            if type(part) is str and (not part or not part.isdigit()):
                msg = "Hue range must be the string 'rainbow' or a string of '<min>-<max>'"
                if was_list:
                    msg = f"{msg} or a list of [<min>, <max>]"
                raise BadSpecValue(msg, got=val, meta=meta)

        rnge = [int(p) for p in val]
        for i, number in enumerate(rnge):
            if number < 0 or number > 360:
                raise BadSpecValue("A hue number must be between 0 and 360",
                                   got=number,
                                   meta=meta.indexed_at(i))

        return HueRange(*rnge)
Esempio n. 9
0
 def normalise_filled(self, meta, val):
     val = self.spec.normalise(meta, val)
     if val < self.minimum or val > self.maximum:
         raise BadSpecValue(
             "Value must be between min and max values",
             wanted=val,
             minimum=self.minimum,
             maximum=self.maximum,
             meta=meta,
         )
     return val
Esempio n. 10
0
 def normalise_filled(self, meta, val):
     val = self.spec.normalise(meta, val)
     if val < self.minimum or val > self.maximum:
         raise BadSpecValue(
             "Number must be between min and max",
             minimum=self.minimum,
             maximum=self.maximum,
             got=val,
             meta=meta,
         )
     return val
Esempio n. 11
0
    def normalise(self, meta, val):
        if val is sb.NotSpecified:
            return 0

        if isinstance(val, bool):
            return 0 if val is False else 0xFFFF
        elif isinstance(val, int):
            return 0 if val == 0 else 0xFFFF
        elif val in ("on", "off"):
            return 0 if val == "off" else 0xFFFF
        else:
            raise BadSpecValue("Unknown value for power", got=val, meta=meta)
Esempio n. 12
0
    def normalise_filled(self, meta, val):
        val = sb.string_spec().normalise(meta, val)

        if not val.startswith("d073d5"):
            raise BadSpecValue("serials must start with d073d5",
                               got=val,
                               meta=meta)
        if len(val) != 12:
            raise BadSpecValue(
                "serials must be 12 characters long, like d073d5001337",
                got=val,
                meta=meta)

        try:
            binascii.unhexlify(val)
        except binascii.Error as error:
            raise BadSpecValue("serials must be valid hex",
                               error=error,
                               got=val,
                               meta=meta)

        return val
Esempio n. 13
0
 def normalise_filled(self, meta, val):
     if isinstance(val, int):
         return False if val == 0 else True
     elif isinstance(val, str):
         return False if val.lower() in ("no", "false") else True
     elif isinstance(val, list):
         if len(val) != 1:
             raise BadSpecValue(
                 "Lists can only be turned into a boolean if they have only one item",
                 got=len(val),
                 meta=meta,
             )
         return boolean().normalise(meta.indexed_at(0), val[0])
     return sb.boolean().normalise(meta, val)
Esempio n. 14
0
    def normalise_filled(self, meta, value):
        if isinstance(value, str):
            value = value.split("-")
            if len(value) == 1:
                value *= 2
        elif isinstance(value, (int, float)):
            value = (value, value)

        if not isinstance(value, (list, tuple)):
            raise BadSpecValue("Speed option must be 'min-max' or [min, max]",
                               got=value,
                               meta=meta)

        kls = Rate if self.rate else Range
        return kls(value[0], value[1], **self.kwargs)
Esempio n. 15
0
    def normalise(self, meta, val):
        if "HARDCODED_DISCOVERY" in os.environ and self.see_env:
            meta = Meta(meta.everything, []).at("${HARDCODED_DISCOVERY}")
            try:
                val = json.loads(os.environ["HARDCODED_DISCOVERY"])
            except (TypeError, ValueError) as error:
                raise BadSpecValue(
                    "Found HARDCODED_DISCOVERY in environment, but it was not valid json",
                    reason=error,
                    meta=meta,
                )

        if val in (sb.NotSpecified, None):
            return val

        return self.spec.normalise(meta, val)
Esempio n. 16
0
    def normalise(self, meta, val):
        if isinstance(val, Services):
            return val

        val = sb.string_spec().normalise(meta, val)

        available = []
        for name, member in Services.__members__.items():
            if not name.startswith("RESERVED"):
                available.append(name)
                if name == val:
                    return member

        raise BadSpecValue("Unknown service type",
                           want=val,
                           available=sorted(available),
                           meta=meta)
Esempio n. 17
0
    def normalise_filled(self, meta, val):
        if type(val) is str:
            try:
                v = json.loads(val)
            except (TypeError, ValueError) as error:
                raise BadSpecValue("Value was not valid json", error=error, meta=meta)
            else:
                normalised = self.spec.normalise(meta, v)
                if not self.storing:
                    return normalised

                return json.dumps(normalised, default=lambda o: repr(o))
        else:
            v = self.spec.normalise(meta, val)
            if not self.storing:
                return v
            return json.dumps(v)
Esempio n. 18
0
    def initial_parse(self, val):
        lines = [val.encode(), b""]
        readline = lambda size=-1: lines.pop(0)

        try:
            tokens = list(tokenize.tokenize(readline))
        except tokenize.TokenError as e:
            raise BadSpecValue("Failed to parse specifier", error=e)

        if tokens[0].type is tokenize.ENCODING:
            tokens.pop(0)

        if tokens[-1].type is tokenize.ENDMARKER:
            tokens.pop(-1)

        if tokens[-1].type is tokenize.NEWLINE:
            tokens.pop(-1)

        return tokens
Esempio n. 19
0
    def normalise(self, meta, val):
        if val is sb.NotSpecified:
            return hp.Color(0, 0, 1, 3500)

        keys = ("hue", "saturation", "brightness", "kelvin")

        if isinstance(val, (list, tuple)):
            while len(val) < 4:
                val = (*val, 0)
        elif isinstance(val, dict):
            for k in keys:
                if k not in val:
                    val = {**val, k: 0}
            val = tuple(val[k] for k in keys)
        elif any(hasattr(val, k) for k in keys):
            val = tuple(getattr(val, k, 0) for k in keys)
        else:
            raise BadSpecValue("Unknown value for color", got=val, meta=meta)

        return hp.Color(*val[:4])
Esempio n. 20
0
    def parse_overridden_target(self, meta, val, collector, target_register,
                                target_name, tokens):
        if target_name not in target_register.targets:
            raise TargetNotFound(name=target_name,
                                 available=list(
                                     target_register.targets.keys()))

        if tokens[0].string != "(" or tokens[-1].string != ")":
            raise BadSpecValue(
                "target with options should have options in parenthesis",
                got=val)

        parent_target_options = collector.configuration.get(
            ["targets", target_name], ignore_converters=True).as_dict()

        target = self.parse_target(meta, parent_target_options, target_name,
                                   tokens)

        name = f"{target_name}_{uuid.uuid4().hex}"
        target_register.add_target(name, target)

        return name
Esempio n. 21
0
    def expand(self, meta, val):
        if isinstance(val, str):
            return {"UDP": {"host": val, "port": 56700}}

        if isinstance(val, list):
            val = {"UDP": val}

        v = {}

        for service, options in val.items():
            if isinstance(options, str):
                options = {"host": options, "port": 56700}
            elif isinstance(options, list):
                if len(options) not in (1, 2):
                    raise BadSpecValue("A list must be [host] or [host, port]",
                                       got=options,
                                       meta=meta.at(service))
                if len(options) == 1:
                    options = {"host": options[0], "port": 56700}
                else:
                    options = {"host": options[0], "port": options[1]}
            v[service] = options

        return v
Esempio n. 22
0
 def normalise_empty(self, meta):
     raise BadSpecValue("Preset must have a non empty list of animations",
                        meta=meta)
Esempio n. 23
0
            "d073d500133": "192.168.0.14",
            "e073d5001338": "192.168.0.15",
            "d073d5zz1339": "192.168.0.16",
            True: "192.168.0.17",
        }

        class S:
            def __init__(s, expected):
                s.expected = expected

            def __eq__(s, other):
                return s.expected == str(other)

        e1 = BadSpecValue(
            "serials must be 12 characters long, like d073d5001337",
            got="d073d500133",
            meta=meta.at("d073d500133"),
        )
        e2 = BadSpecValue(
            "serials must start with d073d5", got="e073d5001338", meta=meta.at("e073d5001338")
        )
        e3 = BadSpecValue(
            "serials must be valid hex",
            error=S("Non-hexadecimal digit found"),
            got="d073d5zz1339",
            meta=meta.at("d073d5zz1339"),
        )
        e4 = BadSpecValue("Expected a string", got=bool, meta=meta.at(True))

        errors = [e1, e2, e3, e4]
        with assertRaises(BadSpecValue, _errors=errors):
Esempio n. 24
0
    def interpret(self, meta, val):
        if not isinstance(val, (tuple, list, str)):
            raise BadSpecValue("Each color specifier must be a list or string",
                               got=val,
                               meta=meta)

        if isinstance(val, str):
            val = val.split(",")

        if len(val) == 0:
            return
        elif len(val) == 1:
            val = (*val, (1, 1), (1, 1), (3500, 3500))
        elif len(val) == 2:
            val = (*val, (1, 1), (3500, 3500))
            if val[0] == "rainbow":
                val[2] = (1, 1)
        elif len(val) == 3:
            val = (*val, (3500, 3500))
        elif len(val) > 4:
            raise BadSpecValue("Each color must be 4 or less specifiers",
                               got=val,
                               meta=meta)

        result = []
        for i, v in enumerate(val):
            m = meta.indexed_at(i)

            if not isinstance(v, (tuple, list, str)):
                raise BadSpecValue(
                    "Each color specifier must be a list or string",
                    got=val,
                    meta=m)

            if i != 0 and v == "rainbow":
                raise BadSpecValue("Only hue may be given as 'rainbow'",
                                   meta=m)

            if v == "rainbow":
                result.append((0, 360))
                continue

            if isinstance(v, str):
                v = v.split("-")

            if isinstance(v, (int, float)):
                v = [v]

            if len(v) > 2:
                raise BadSpecValue("A specifier must be two values",
                                   got=v,
                                   meta=m)

            if len(v) == 0:
                continue

            if len(v) == 1:
                v = v * 2

            if i in (1, 2):
                result.append((float(v[0]) * 1000, float(v[1]) * 1000))
            else:
                result.append((float(v[0]), float(v[1])))

        return OneColorRange(*result)
Esempio n. 25
0
 def normalise_filled(self, meta, val):
     val = sb.listof(self.spec).normalise(meta, val)
     if not val:
         raise BadSpecValue(
             "Preset must have a non empty list of animations", meta=meta)
     return val