diff --git a/duniterpy/documents/transaction.py b/duniterpy/documents/transaction.py index 871cc5ac6c0eb7cf351948d46051d29bc8490103..fa950e808bda006e9307855e1d3f70014352d9d5 100644 --- a/duniterpy/documents/transaction.py +++ b/duniterpy/documents/transaction.py @@ -781,7 +781,7 @@ class SimpleTransaction(Transaction): simple = False for o in tx.outputs: # if right condition is not None... - if getattr('right', o.condition, None): + if getattr(o.condition, 'right', None): simple = False # if left is not SIG... elif type(o.condition.left) is not output.SIG: diff --git a/duniterpy/grammars/output.py b/duniterpy/grammars/output.py index ede04ec652a6590efda3d7ddbf39a93a4eb90725..122ef9cd976f35f1230ed447c68712f50f9892ab 100644 --- a/duniterpy/grammars/output.py +++ b/duniterpy/grammars/output.py @@ -1,103 +1,281 @@ +from typing import Optional, TypeVar, Type, Any + from pypeg2 import re, attr, Keyword, Enum, contiguous, maybe_some, whitespace, K from ..constants import PUBKEY_REGEX, HASH_REGEX class Pubkey(str): + """ + Pubkey in transaction output condition + """ regex = re.compile(PUBKEY_REGEX) class Hash(str): + """ + Hash in transaction output condition + """ regex = re.compile(HASH_REGEX) class Int(str): + """ + Integer in transaction output condition + """ regex = re.compile(r"[0-9]+") -class SIG(str): +# required to type hint cls in classmethod +SIGType = TypeVar('SIGType', bound='SIG') + + +class SIG: + """ + Signature function in transaction output condition + """ grammar = "SIG(", attr('pubkey', Pubkey), ")" + def __init__(self, value: str = '') -> None: + """ + Init SIG instance + + :param value: Content of the string + """ + self.value = value + self.pubkey = '' + + def __str__(self) -> str: + return self.value + @classmethod - def token(cls, pubkey): + def token(cls: Type[SIGType], pubkey: str) -> SIGType: + """ + Return SIG instance from pubkey + + :param pubkey: Public key of the signature issuer + :return: + """ sig = cls() sig.pubkey = pubkey return sig - def compose(self, parser, grammar=None, attr_of=None): + def compose(self, parser: Any, grammar: Any = None, attr_of: Any = None) -> str: + """ + Return the SIG(pubkey) expression as string format + + :param parser: Parser instance + :param grammar: Grammar + :param attr_of: Attribute of... + :return: + """ return "SIG({0})".format(self.pubkey) -class CSV(str): +# required to type hint cls in classmethod +CSVType = TypeVar('CSVType', bound='CSV') + + +class CSV: + """ + CSV function in transaction output condition + """ grammar = "CSV(", attr('time', Int), ")" + def __init__(self, value: str = '') -> None: + """ + Init CSV instance + + :param value: Content of the string + """ + self.value = value + self.time = '' + + def __str__(self) -> str: + return self.value + @classmethod - def token(cls, time): + def token(cls: Type[CSVType], time: int) -> CSVType: + """ + Return CSV instance from time + + :param time: Timestamp + :return: + """ csv = cls() csv.time = str(time) return csv - def compose(self, parser, grammar=None, attr_of=None): + def compose(self, parser: Any, grammar: Any = None, attr_of: str = None): + """ + Return the CSV(time) expression as string format + + :param parser: Parser instance + :param grammar: Grammar + :param attr_of: Attribute of... + """ return "CSV({0})".format(self.time) -class CLTV(str): +# required to type hint cls in classmethod +CLTVType = TypeVar('CLTVType', bound='CLTV') + + +class CLTV: + """ + CLTV function in transaction output condition + """ grammar = "CLTV(", attr('timestamp', Int), ")" + def __init__(self, value: str = '') -> None: + """ + Init CLTV instance + + :param value: Content of the string + """ + self.value = value + self.timestamp = '' + + def __str__(self) -> str: + return self.value + @classmethod - def token(cls, timestamp): + def token(cls: Type[CLTVType], timestamp: int) -> CLTVType: + """ + Return CLTV instance from timestamp + + :param timestamp: Timestamp + :return: + """ cltv = cls() cltv.timestamp = str(timestamp) return cltv - def compose(self, parser, grammar=None, attr_of=None): + def compose(self, parser: Any, grammar: Any = None, attr_of: str = None): + """ + Return the CLTV(timestamp) expression as string format + + :param parser: Parser instance + :param grammar: Grammar + :param attr_of: Attribute of... + """ return "CLTV({0})".format(self.timestamp) -class XHX(str): +# required to type hint cls in classmethod +XHXType = TypeVar('XHXType', bound='XHX') + + +class XHX: + """ + XHX function in transaction output condition + """ grammar = "XHX(", attr('sha_hash', Hash), ")" + def __init__(self, value: str = '') -> None: + """ + Init XHX instance + + :param value: Content of the string + """ + self.value = value + self.sha_hash = '' + + def __str__(self) -> str: + return self.value + @classmethod - def token(cls, sha_hash): + def token(cls: Type[XHXType], sha_hash: str) -> XHXType: + """ + Return XHX instance from sha_hash + + :param sha_hash: SHA256 hash + :return: + """ xhx = cls() xhx.sha_hash = sha_hash return xhx - def compose(self, parser, grammar=None, attr_of=None): + def compose(self, parser: Any, grammar: Any = None, attr_of: str = None) -> str: + """ + Return the XHX(sha_hash) expression as string format + + :param parser: Parser instance + :param grammar: Grammar + :param attr_of: Attribute of... + """ return "XHX({0})".format(self.sha_hash) +# required to type hint cls in classmethod +OperatorType = TypeVar('OperatorType', bound='Operator') + + class Operator(Keyword): + """ + Operator in transaction output condition + """ grammar = Enum(K("&&"), K("||"), K("AND"), K("OR")) regex = re.compile(r"[&&|\|\||\w]+") @classmethod - def token(cls, keyword): + def token(cls: Type[OperatorType], keyword: str) -> OperatorType: + """ + Return Operator instance from keyword + + :param keyword: Operator keyword in expression + :return: + """ op = cls(keyword) return op - def compose(self, parser, grammar=None, attr_of=None): + def compose(self, parser: Any, grammar: Any = None, attr_of: str = None) -> str: + """ + Return the Operator keyword as string format + + :param parser: Parser instance + :param grammar: Grammar + :param attr_of: Attribute of... + """ return "{0}".format(self.name) -class Condition(str): +# required to type hint cls in classmethod +ConditionType = TypeVar('ConditionType', bound='Condition') + +class Condition: + """ + Condition expression in transaction output + + """ grammar = None - # value argument mandatory ! dont remove it ! def __init__(self, value: str = '') -> None: """ Init Condition instance + + :param value: Content of the condition as string """ - super().__init__() + self.value = value + self.left = '' + self.right = '' + self.op = '' - # custom fields - self.left = None - self.right = None - self.op = None + def __str__(self) -> str: + return self.value @classmethod - def token(cls, left, op=None, right=None): + def token(cls: Type[ConditionType], left: str, op: Optional[str] = None, + right: Optional[str] = None) -> ConditionType: + """ + Return Condition instance from arguments and Operator + + :param left: Left argument + :param op: Operator + :param right: Right argument + :return: + """ condition = cls() condition.left = left if op: @@ -106,7 +284,14 @@ class Condition(str): condition.right = right return condition - def compose(self, parser, grammar=None, attr_of=None): + 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... + """ if type(self.left) is Condition: left = "({0})".format(parser.compose(self.left, grammar=grammar, attr_of=attr_of)) else: