output.py 9.89 KB
Newer Older
1
from typing import Optional, TypeVar, Type, Any, Union
2

3
from pypeg2 import re, attr, Keyword, Enum, contiguous, maybe_some, whitespace, K
inso's avatar
inso committed
4

5
from ..constants import PUBKEY_REGEX, HASH_REGEX
6

inso's avatar
inso committed
7 8

class Pubkey(str):
9 10 11
    """
    Pubkey in transaction output condition
    """
12

13
    regex = re.compile(PUBKEY_REGEX)
inso's avatar
inso committed
14 15 16


class Hash(str):
17 18 19
    """
    Hash in transaction output condition
    """
20

21
    regex = re.compile(HASH_REGEX)
inso's avatar
inso committed
22 23


inso's avatar
inso committed
24
class Int(str):
25 26 27
    """
    Integer in transaction output condition
    """
28

inso's avatar
inso committed
29 30 31
    regex = re.compile(r"[0-9]+")


32
# required to type hint cls in classmethod
33
SIGType = TypeVar("SIGType", bound="SIG")
34 35 36 37


class SIG:
    """
38
    SIGnature function in transaction output condition
39
    """
inso's avatar
inso committed
40

41 42 43
    grammar = "SIG(", attr("pubkey", Pubkey), ")"

    def __init__(self, value: str = "") -> None:
44 45 46 47 48 49
        """
        Init SIG instance

        :param value: Content of the string
        """
        self.value = value
50
        self.pubkey = ""
51 52 53 54

    def __str__(self) -> str:
        return self.value

55 56 57 58 59 60
    def __eq__(self, other: Any) -> bool:
        """
        Check SIG instances equality
        """
        if not isinstance(other, SIG):
            return NotImplemented
61
        return self.value == other.value and self.pubkey == other.pubkey
62 63 64 65

    def __hash__(self) -> int:
        return hash((self.value, self.pubkey))

inso's avatar
inso committed
66
    @classmethod
67 68 69 70 71 72 73
    def token(cls: Type[SIGType], pubkey: str) -> SIGType:
        """
        Return SIG instance from pubkey

        :param pubkey: Public key of the signature issuer
        :return:
        """
inso's avatar
inso committed
74 75 76 77
        sig = cls()
        sig.pubkey = pubkey
        return sig

78 79 80
    def compose(
        self, parser: Any = None, grammar: Any = None, attr_of: Any = None
    ) -> str:
81 82 83 84 85 86 87 88
        """
        Return the SIG(pubkey) expression as string format

        :param parser: Parser instance
        :param grammar: Grammar
        :param attr_of: Attribute of...
        :return:
        """
inso's avatar
inso committed
89 90 91
        return "SIG({0})".format(self.pubkey)


92
# required to type hint cls in classmethod
93
CSVType = TypeVar("CSVType", bound="CSV")
94 95 96 97 98 99


class CSV:
    """
    CSV function in transaction output condition
    """
inso's avatar
inso committed
100

101 102 103
    grammar = "CSV(", attr("time", Int), ")"

    def __init__(self, value: str = "") -> None:
104 105 106 107 108 109
        """
        Init CSV instance

        :param value: Content of the string
        """
        self.value = value
110
        self.time = ""
111 112 113 114

    def __str__(self) -> str:
        return self.value

115 116 117 118 119 120
    def __eq__(self, other: Any) -> bool:
        """
        Check CSV instances equality
        """
        if not isinstance(other, CSV):
            return NotImplemented
121
        return self.value == other.value and self.time == other.time
122 123 124 125

    def __hash__(self) -> int:
        return hash((self.value, self.time))

inso's avatar
inso committed
126
    @classmethod
127 128 129 130 131 132 133
    def token(cls: Type[CSVType], time: int) -> CSVType:
        """
        Return CSV instance from time

        :param time: Timestamp
        :return:
        """
inso's avatar
inso committed
134 135 136 137
        csv = cls()
        csv.time = str(time)
        return csv

138 139 140
    def compose(
        self, parser: Any = None, grammar: Any = None, attr_of: str = None
    ) -> str:
141 142 143 144 145 146 147
        """
        Return the CSV(time) expression as string format

        :param parser: Parser instance
        :param grammar: Grammar
        :param attr_of: Attribute of...
        """
inso's avatar
inso committed
148 149 150
        return "CSV({0})".format(self.time)


151
# required to type hint cls in classmethod
152
CLTVType = TypeVar("CLTVType", bound="CLTV")
153 154 155 156 157 158


class CLTV:
    """
    CLTV function in transaction output condition
    """
inso's avatar
inso committed
159

160 161 162
    grammar = "CLTV(", attr("timestamp", Int), ")"

    def __init__(self, value: str = "") -> None:
163 164 165 166 167 168
        """
        Init CLTV instance

        :param value: Content of the string
        """
        self.value = value
169
        self.timestamp = ""
170 171 172 173

    def __str__(self) -> str:
        return self.value

174 175 176 177 178 179
    def __eq__(self, other: Any) -> bool:
        """
        Check CLTV instances equality
        """
        if not isinstance(other, CLTV):
            return NotImplemented
180
        return self.value == other.value and self.timestamp == other.timestamp
181 182 183 184

    def __hash__(self) -> int:
        return hash((self.value, self.timestamp))

inso's avatar
inso committed
185
    @classmethod
186 187 188 189 190 191 192
    def token(cls: Type[CLTVType], timestamp: int) -> CLTVType:
        """
        Return CLTV instance from timestamp

        :param timestamp: Timestamp
        :return:
        """
inso's avatar
inso committed
193 194 195 196
        cltv = cls()
        cltv.timestamp = str(timestamp)
        return cltv

197 198 199
    def compose(
        self, parser: Any = None, grammar: Any = None, attr_of: str = None
    ) -> str:
200 201 202 203 204 205 206
        """
        Return the CLTV(timestamp) expression as string format

        :param parser: Parser instance
        :param grammar: Grammar
        :param attr_of: Attribute of...
        """
inso's avatar
inso committed
207 208 209
        return "CLTV({0})".format(self.timestamp)


210
# required to type hint cls in classmethod
211
XHXType = TypeVar("XHXType", bound="XHX")
212 213 214 215 216 217


class XHX:
    """
    XHX function in transaction output condition
    """
inso's avatar
inso committed
218

219 220 221
    grammar = "XHX(", attr("sha_hash", Hash), ")"

    def __init__(self, value: str = "") -> None:
222 223 224 225 226 227
        """
        Init XHX instance

        :param value: Content of the string
        """
        self.value = value
228
        self.sha_hash = ""
229 230 231 232

    def __str__(self) -> str:
        return self.value

233 234 235 236 237 238
    def __eq__(self, other: Any) -> bool:
        """
        Check XHX instances equality
        """
        if not isinstance(other, XHX):
            return NotImplemented
239
        return self.value == other.value and self.sha_hash == other.sha_hash
240 241 242 243

    def __hash__(self) -> int:
        return hash((self.value, self.sha_hash))

inso's avatar
inso committed
244
    @classmethod
245 246 247 248 249 250 251
    def token(cls: Type[XHXType], sha_hash: str) -> XHXType:
        """
        Return XHX instance from sha_hash

        :param sha_hash: SHA256 hash
        :return:
        """
inso's avatar
inso committed
252 253 254 255
        xhx = cls()
        xhx.sha_hash = sha_hash
        return xhx

256 257 258
    def compose(
        self, parser: Any = None, grammar: Any = None, attr_of: str = None
    ) -> str:
259 260 261 262 263 264 265
        """
        Return the XHX(sha_hash) expression as string format

        :param parser: Parser instance
        :param grammar: Grammar
        :param attr_of: Attribute of...
        """
inso's avatar
inso committed
266 267 268
        return "XHX({0})".format(self.sha_hash)


269
# required to type hint cls in classmethod
270
OperatorType = TypeVar("OperatorType", bound="Operator")
271 272


inso's avatar
inso committed
273
class Operator(Keyword):
274 275 276
    """
    Operator in transaction output condition
    """
277

inso's avatar
inso committed
278 279
    grammar = Enum(K("&&"), K("||"), K("AND"), K("OR"))
    regex = re.compile(r"[&&|\|\||\w]+")
inso's avatar
inso committed
280 281

    @classmethod
282 283 284 285 286 287 288
    def token(cls: Type[OperatorType], keyword: str) -> OperatorType:
        """
        Return Operator instance from keyword

        :param keyword: Operator keyword in expression
        :return:
        """
inso's avatar
inso committed
289 290 291
        op = cls(keyword)
        return op

292 293 294
    def compose(
        self, parser: Any = None, grammar: Any = None, attr_of: str = None
    ) -> str:
295 296 297 298 299 300 301
        """
        Return the Operator keyword as string format

        :param parser: Parser instance
        :param grammar: Grammar
        :param attr_of: Attribute of...
        """
inso's avatar
inso committed
302 303 304
        return "{0}".format(self.name)


305
# required to type hint cls in classmethod
306
ConditionType = TypeVar("ConditionType", bound="Condition")
307

308 309 310 311 312 313

class Condition:
    """
    Condition expression in transaction output

    """
314

315 316
    grammar = None

317
    def __init__(self, value: str = "") -> None:
318 319
        """
        Init Condition instance
320 321

        :param value: Content of the condition as string
322
        """
323
        self.value = value
324 325 326
        self.left = ""  # type: Union[str, Condition]
        self.right = ""  # type: Union[str, Condition]
        self.op = ""  # type: Union[str, Condition]
327 328 329 330 331 332 333

    def __eq__(self, other: Any) -> bool:
        """
        Check Condition instances equality
        """
        if not isinstance(other, Condition):
            return NotImplemented
334 335 336 337 338 339
        return (
            self.value == other.value
            and self.left == other.left
            and self.right == other.right
            and self.op == other.op
        )
340 341 342 343

    def __hash__(self) -> int:
        return hash((self.value, self.left, self.right, self.op))

344 345
    def __str__(self) -> str:
        return self.value
346

inso's avatar
inso committed
347
    @classmethod
348 349 350 351 352 353
    def token(
        cls: Type[ConditionType],
        left: Any,
        op: Optional[Any] = None,
        right: Optional[Any] = None,
    ) -> ConditionType:
354 355 356 357 358 359 360 361
        """
        Return Condition instance from arguments and Operator

        :param left: Left argument
        :param op: Operator
        :param right: Right argument
        :return:
        """
inso's avatar
inso committed
362 363 364 365 366 367 368 369
        condition = cls()
        condition.left = left
        if op:
            condition.op = op
        if right:
            condition.right = right
        return condition

370 371 372 373 374 375 376 377
    def compose(self, parser: Any, grammar: Any = None, attr_of: str = None) -> str:
        """
        Return the Condition as string format

        :param parser: Parser instance
        :param grammar: Grammar
        :param attr_of: Attribute of...
        """
inso's avatar
inso committed
378
        if type(self.left) is Condition:
379 380 381
            left = "({0})".format(
                parser.compose(self.left, grammar=grammar, attr_of=attr_of)
            )
inso's avatar
inso committed
382 383 384
        else:
            left = parser.compose(self.left, grammar=grammar, attr_of=attr_of)

385
        if getattr(self, "op", None):
inso's avatar
inso committed
386 387

            if type(self.right) is Condition:
388 389 390
                right = "({0})".format(
                    parser.compose(self.right, grammar=grammar, attr_of=attr_of)
                )
inso's avatar
inso committed
391 392 393 394 395 396 397 398
            else:
                right = parser.compose(self.right, grammar=grammar, attr_of=attr_of)
            op = parser.compose(self.op, grammar=grammar, attr_of=attr_of)
            result = "{0} {1} {2}".format(left, op, right)
        else:
            result = left
        return result

399

400 401 402 403 404 405 406 407 408
Condition.grammar = contiguous(
    attr("left", [SIG, XHX, CSV, CLTV, ("(", Condition, ")")]),
    maybe_some(
        whitespace,
        attr("op", Operator),
        whitespace,
        attr("right", [SIG, XHX, CSV, CLTV, ("(", Condition, ")")]),
    ),
)