diff --git a/cffi/cdefs.h b/cffi/cdefs.h index 8a62118b..53e91695 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -792,6 +792,21 @@ struct lysc_must { struct lysc_ext_instance *exts; }; +struct pcre2_real_code; +typedef struct pcre2_real_code pcre2_code; + +struct lysc_pattern { + const char *expr; + pcre2_code *code; + const char *dsc; + const char *ref; + const char *emsg; + const char *eapptag; + struct lysc_ext_instance *exts; + uint32_t inverted : 1; + uint32_t refcount : 31; +}; + #define LYSP_RESTR_PATTERN_ACK ... #define LYSP_RESTR_PATTERN_NACK ... diff --git a/libyang/__init__.py b/libyang/__init__.py index aa9dcca9..63dee9c9 100644 --- a/libyang/__init__.py +++ b/libyang/__init__.py @@ -74,6 +74,7 @@ IfNotFeature, IfOrFeatures, Module, + Pattern, Revision, SContainer, SLeaf, @@ -144,6 +145,7 @@ "NodeTypeRemoved", "OrderedByUserAdded", "OrderedByUserRemoved", + "Pattern", "PatternAdded", "PatternRemoved", "PresenceAdded", diff --git a/libyang/schema.py b/libyang/schema.py index 30d4d744..dfe31b0f 100644 --- a/libyang/schema.py +++ b/libyang/schema.py @@ -448,6 +448,24 @@ class Bit(_EnumBit): pass +# ------------------------------------------------------------------------------------- +class Pattern: + __slots__ = ("context", "cdata") + + def __init__(self, context: "libyang.Context", cdata): + self.context = context + self.cdata = cdata # C type: "struct lysc_pattern *" + + def expression(self) -> str: + return c2str(self.cdata.expr) + + def inverted(self) -> bool: + return self.cdata.inverted + + def error_message(self) -> Optional[str]: + return c2str(self.cdata.emsg) if self.cdata.emsg != ffi.NULL else None + + # ------------------------------------------------------------------------------------- class Type: __slots__ = ("context", "cdata", "cdata_parsed", "__dict__") @@ -681,6 +699,24 @@ def all_patterns(self) -> Iterator[Tuple[str, bool]]: else: yield from self.patterns() + def pattern_details(self) -> Iterator[Pattern]: + if self.cdata.basetype != self.STRING: + return + t = ffi.cast("struct lysc_type_str *", self.cdata) + if t.patterns == ffi.NULL: + return + for p in ly_array_iter(t.patterns): + if not p: + continue + yield Pattern(self.context, p) + + def all_pattern_details(self) -> Iterator[Pattern]: + if self.cdata.basetype == lib.LY_TYPE_UNION: + for t in self.union_types(): + yield from t.all_pattern_details() + else: + yield from self.pattern_details() + def require_instance(self) -> Optional[bool]: if self.cdata.basetype != self.LEAFREF: return None diff --git a/tests/test_schema.py b/tests/test_schema.py index a7a68f54..43bf1eb2 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -12,6 +12,7 @@ IOType, LibyangError, Module, + Pattern, Revision, SContainer, SLeaf, @@ -438,6 +439,15 @@ def test_leaf_type_pattern(self): t = leaf.type() self.assertIsInstance(t, Type) self.assertEqual(list(t.patterns()), [("[a-z.]+", False), ("1", True)]) + patterns = list(t.all_pattern_details()) + self.assertEqual(len(patterns), 2) + self.assertIsInstance(patterns[0], Pattern) + self.assertEqual(patterns[0].expression(), "[a-z.]+") + self.assertFalse(patterns[0].inverted()) + self.assertEqual(patterns[0].error_message(), "ERROR1") + self.assertEqual(patterns[1].expression(), "1") + self.assertTrue(patterns[1].inverted()) + self.assertIsNone(patterns[1].error_message()) def test_leaf_type_union(self): leaf = next(self.ctx.find_path("/yolo-system:conf/yolo-system:number")) diff --git a/tests/yang/yolo/yolo-system.yang b/tests/yang/yolo/yolo-system.yang index 78a0e20f..5aa633ad 100644 --- a/tests/yang/yolo/yolo-system.yang +++ b/tests/yang/yolo/yolo-system.yang @@ -85,7 +85,9 @@ module yolo-system { } leaf host { type string { - pattern "[a-z.]+"; + pattern "[a-z.]+" { + error-message "ERROR1"; + } pattern "1" { modifier "invert-match"; }