summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--python-awesome-pattern-matching.spec2916
-rw-r--r--sources1
3 files changed, 2918 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..e6d2b1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/awesome-pattern-matching-0.24.4.tar.gz
diff --git a/python-awesome-pattern-matching.spec b/python-awesome-pattern-matching.spec
new file mode 100644
index 0000000..1589f13
--- /dev/null
+++ b/python-awesome-pattern-matching.spec
@@ -0,0 +1,2916 @@
+%global _empty_manifest_terminate_build 0
+Name: python-awesome-pattern-matching
+Version: 0.24.4
+Release: 1
+Summary: Awesome Pattern Matching
+License: MIT License
+URL: https://github.com/scravy/awesome-pattern-matching
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/e6/b5/aedee8533346618f2293632023f2eb62c106902534c2daddfc30531b2891/awesome-pattern-matching-0.24.4.tar.gz
+BuildArch: noarch
+
+
+%description
+# Awesome Pattern Matching (_apm_) for Python
+
+[![Github Actions](https://github.com/scravy/awesome-pattern-matching/workflows/Python%20application/badge.svg)](https://github.com/scravy/awesome-pattern-matching/actions)
+[![Downloads](https://pepy.tech/badge/awesome-pattern-matching)](https://pepy.tech/project/awesome-pattern-matching)
+[![PyPI version](https://badge.fury.io/py/awesome-pattern-matching.svg)](https://pypi.org/project/awesome-pattern-matching/)
+
+```bash
+pip install awesome-pattern-matching
+```
+
+- Simple
+- Powerful
+- Extensible
+- Composable
+- Functional
+- Python 3.7+, PyPy3.7+
+- Typed (IDE friendly)
+- Offers different styles (expression, declarative, statement, ...)
+
+There's a ton of pattern matching libraries available for python, all with varying degrees of maintenance and usability;
+also [since Python 3.10 there is the PEP-634 `match` statement](https://www.python.org/dev/peps/pep-0634/). However,
+this library still offers functionality that PEP-634 doesn't offer, as well as pattern matching for python versions
+before 3.10. [A detailed comparison of PEP-634 and _`apm`_ is available](https://github.com/scravy/awesome-pattern-matching/blob/main/docs/apm_vs_pep634.md).
+
+_`apm`_ defines patterns as objects which are _composable_ and _reusable_. Pieces can be matched and captured into
+variables, much like pattern matching in Haskell or Scala (a feature which most libraries actually lack, but which also
+makes pattern matching useful in the first place - the capability to easily extract data). Here is an example:
+
+```python
+from apm import *
+
+if result := match([1, 2, 3, 4, 5], [1, '2nd' @ _, '3rd' @ _, 'tail' @ Remaining(...)]):
+ print(result['2nd']) # 2
+ print(result['3rd']) # 3
+ print(result['tail']) # [4, 5]
+
+# If you find it more readable, '>>' can be used instead of '@' to capture a variable
+match([1, 2, 3, 4, 5], [1, _ >> '2nd', _ >> '3rd', Remaining(...) >> 'tail'])
+```
+
+Patterns can be composed using `&`, `|`, and `^`, or via their more explicit counterparts `AllOf`, `OneOf`, and `Either`
+. Since patterns are objects, they can be stored in variables and be reused.
+
+```python
+positive_integer = InstanceOf(int) & Check(lambda x: x >= 0)
+```
+
+Some fancy matching patterns are available out of the box:
+
+```python
+from apm import *
+
+def f(x: int, y: float) -> int:
+ pass
+
+if match(f, Arguments(int, float) & Returns(int)):
+ print("Function satisfies required signature")
+```
+
+
+
+
+## Multiple Styles
+
+For matching and selecting from multiple cases, choose your style:
+
+```python
+from apm import *
+
+value = 7
+
+# The simple style
+if match(value, Between(1, 10)):
+ print("It's between 1 and 10")
+elif match(value, Between(11, 20)):
+ print("It's between 11 and 20")
+else:
+ print("It's not between 1 and 20")
+
+# The expression style
+case(value) \
+ .of(Between(1, 10), lambda: print("It's between 1 and 10")) \
+ .of(Between(11, 20), lambda: print("It's between 11 and 20")) \
+ .otherwise(lambda: print("It's not between 1 and 20"))
+
+# The statement style
+try:
+ match(value)
+except Case(Between(1, 10)):
+ print("It's between 1 and 10")
+except Case(Between(11, 20)):
+ print("It's between 11 and 20")
+except Default:
+ print("It's not between 1 and 20")
+
+# The declarative style
+@case_distinction
+def f(n: Match(Between(1, 10))):
+ print("It's between 1 and 10")
+
+@case_distinction
+def f(n: Match(Between(11, 20))):
+ print("It's between 11 and 20")
+
+@case_distinction
+def f(n):
+ print("It's not between 1 and 20")
+
+f(value)
+
+# The terse (pampy) style
+match(value,
+ Between( 1, 10), lambda: print("It's between 1 and 10"),
+ Between(11, 20), lambda: print("It's between 11 and 20"),
+ _, lambda: print("It's not between 1 and 20"))
+```
+
+
+## Nested pattern matches
+
+Patterns are applied recursively, such that nested structures can be matched arbitrarily deep.
+This is super useful for extracting data from complicated structures:
+
+```python
+from apm import *
+
+sample_k8s_response = {
+ "containers": [
+ {
+ "args": [
+ "--cert-dir=/tmp",
+ "--secure-port=4443",
+ "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
+ "--kubelet-use-node-status-port"
+ ],
+ "image": "k8s.gcr.io/metrics-server/metrics-server:v0.4.1",
+ "imagePullPolicy": "IfNotPresent",
+ "name": "metrics-server",
+ "ports": [
+ {
+ "containerPort": 4443,
+ "name": "https",
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+}
+
+if result := match(sample_k8s_response, {
+ "containers": Each({
+ "image": 'image' @ _,
+ "name": 'name' @ _,
+ "ports": Each({
+ "containerPort": 'port' @ _
+ }),
+ })
+ }):
+ print(f"Image: {result['image']}, Name: {result['name']}, Port: {result['port']}")
+```
+
+The above will print
+
+```
+Image: k8s.gcr.io/metrics-server/metrics-server:v0.4.1, Name: metrics-server, Port: 4443
+```
+
+
+## Multimatch
+
+By default `match` records only the last match for captures. If for example `'item' @ InstanceOf(int)` matches multiple times,
+the last match will be recorded in `result['item']`. `match` can record all captures using the `multimatch=True` flag:
+
+```python
+if result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...)), multimatch=True):
+ print(result['item']) # [5, 7]
+
+# The default since v0.15.0 is multimatch=False
+if result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...))):
+ print(result['item']) # 7
+```
+
+
+## Strict vs non-strict matches
+
+Any value which occurs verbatim in a pattern is matched verbatim (`int`, `str`, `list`, ...), except Dictionaries (
+anything which has an `items()` actually).
+
+Thus:
+
+```python
+some_very_complex_object = {
+ "A": 1,
+ "B": 2,
+ "C": 3,
+}
+match(some_very_complex_object, {"C": 3}) # matches!
+```
+
+If you do not want unknown keys to be ignored, wrap the pattern in a `Strict`:
+
+```python
+# does not match, only matches exactly `{"C": 3}`
+match(some_very_complex_object, Strict({"C": 3}))
+```
+
+Lists (anything iterable which does not have an `items()` actually) are also compared as they are, i.e.:
+
+```python
+ls = [1, 2, 3]
+match(ls, [1, 2, 3]) # matches
+match(ls, [1, 2]) # does not match
+```
+
+
+## Match head and tail of a list
+
+It is possible to match the remainder of a list though:
+
+```python
+match(ls, [1, 2, Remaining(InstanceOf(int))])
+```
+
+And each item:
+
+```python
+match(ls, Each(InstanceOf(int)))
+```
+
+Patterns can be joined using `&`, `|`, and `^`:
+
+```python
+match(ls, Each(InstanceOf(int) & Between(1, 3)))
+```
+
+Wild-card matches are supported using Ellipsis (`...`):
+
+```python
+match(ls, [1, Remaining(..., at_least=2)])
+```
+
+The above example also showcases how `Remaining` can be made to match
+`at_least` _n_ number of items (`Each` also has an `at_least` keyword argument).
+
+
+## Wildcard matches anything using `_`
+
+A wildcard pattern can be expressed using `_`. `_` is a `Pattern` and thus `>>` and `@` can be used with it.
+
+```python
+match([1, 2, 3, 4], [1, _, 3, _])
+```
+
+
+## Wildcard matches anything using `...`
+
+The `Ellipsis` can be used as a wildcard match, too. It is however not a `Pattern` (so `|`, `&`, `@`, etc. can not
+be used on it). If you actually want to match `Ellipsis`, wrap it using `Value(...)`.
+
+Otherwise `...` is equivalent for most intents and purposes to `_`:
+
+```python
+match([1, 2, 3, 4], [1, ..., 3, ...])
+```
+
+
+## Support for dataclasses
+
+```python
+@dataclass
+class User:
+ first_name: str
+ last_name: str
+
+value = User("Jane", "Doe")
+
+if match(value, User(_, "Doe")):
+ print("Welcome, member of the Doe family!")
+elif match(value, User(_, _)):
+ print("Welcome, anyone!")
+```
+
+
+## The different styles in detail
+
+### Simple style
+
+- 💚 has access to result captures
+- 💚 vanilla python
+- 💔 no case guards
+- 💔 can not return values (since it's a statement, not an expression)
+- 🖤 a bit repetetive
+- 💚 simplest and most easy to understand style
+- 🖤 fastest of them all
+
+```python
+from apm import *
+
+value = {"a": 7, "b": "foo", "c": "bar"}
+
+if result := match(value, EachItem(_, 'value' @ InstanceOf(str) | ...), multimatch=True):
+ print(result['value']) # ["foo", "bar"]
+```
+
+#### pre `:=` version (Python 3.7)
+
+`bind()` can be used on a `MatchResult` to bind the matched items to an existing dictionary.
+
+```python
+from apm import *
+
+value = {"a": 7, "b": "foo", "c": "bar"}
+
+result = {}
+if match(value, EachItem(_, 'value' @ InstanceOf(str) | ...)).bind(result):
+ print(result['value']) # ["foo", "bar"]
+elif match(value, {"quux": _ >> 'quux'}).bind(result):
+ print(result['quux'])
+```
+
+### Expression style
+
+- 💚 has access to result captures
+- 💚 vanilla python
+- 💚 can return values directly as it is an expression
+- 💚 can use case guards via `when=` or `guarded`
+- 🖤 so terse that it is sometimes hard to read
+
+The expression style is summarized:
+
+```python
+case(value).of(pattern, action) ... .otherwise(default_action)
+```
+
+...where action is either a value or a callable. The captures from the matching result are bound to the named
+parameters of the given callable, i.e. `result['foo']` and `result['bar']` from `'foo' @ _` and `'bar' @ _` will be
+bound to `foo` and `bar` respectively in `lambda foo, bar: ...`.
+
+```python
+from apm import *
+
+display_name = case({'user': 'some-user-id', 'first_name': "Jane", 'last_name': "Doe"}) \
+ .of({'first_name': 'first' @ _, 'last_name': 'last' @ _}, lambda first, last: f"{first}, {last}") \
+ .of({'user': 'user_id' @ _}, lambda user_id: f"#{user_id}") \
+ .otherwise("anonymous")
+```
+
+_Note: To return a value an `.otherwise(...)` case must always be present._
+
+
+### Statement style
+
+This is arguable the most hacky style in _`apm`_, as it re-uses the `try .. except`
+mechanism. It is nevertheless quite readable.
+
+- 💚 has access to result captures
+- 💚 very readable
+- 💔 can not return values (since it's a statement, not an expression)
+- 💚 can use case guards via `when=`
+- 🖤 misuse of the `try .. except` statement
+
+```python
+from apm import *
+
+try:
+ match({'user': 'some-user-id', 'first_name': "Jane", 'last_name': "Doe"})
+except Case({'first_name': 'first' @ _, 'last_name': 'last' @ _}) as result:
+ user = f"{result['first']} {result['last']}"
+except Case({'user': 'user_id' @ _}) as result:
+ user = f"#{result['user_id']}"
+except Default:
+ user = "anonymous"
+
+print(user) # "Jane Doe"
+```
+
+
+### Declarative style
+
+- 💔 does not have access to result captures
+- 💚 very readable
+- 💚 can use case guards via `when=`
+- 💚 can return values
+- 🖤 the most bloated version of all styles
+
+```python
+from apm import *
+
+@case_distinction
+def fib(n: Match(OneOf(0, 1))):
+ return n
+
+@case_distinction
+def fib(n):
+ return fib(n - 2) + fib(n - 1)
+
+for i in range(0, 6):
+ print(fib(i))
+```
+
+#### Nota bene: Overloading using `@case_distinction`
+
+If not for its pattern matching capabilities, `@case_distinction` can be used
+to implement overloading. In fact, it can be imported as `@overload`.
+The mechanism is aware of arity and argument types.
+
+```python
+from apm.overload import overload
+
+@overload
+def add(a: str, b: str):
+ return "".join([a, b])
+
+@overload
+def add(a: int, b: int):
+ return a + b
+
+add("a", "b")
+add(1, 2)
+```
+
+### Terse style
+
+- 💚 has access to result captures
+- 💚 can use case guards via `guarded`
+- 💚 very concise
+- 💚 can return values
+- 🖤 very readable when formatted nicely
+- 🖤 not so well suited for larger match actions
+- 🖤 slowest of them all
+
+As the name indicates the "terse" style is terse. It is inspired by the `pampy`
+pattern matching library and mimics some of its behavior. Despite a slim surface
+area it also comes with some simplifications:
+
+- A type given as a pattern is matched against as if it was wrapped in an `InstanceOf`
+- `re.Pattern` objects (result of `re.compile`) are matched against as if it was given via `Regex`
+- Captures are passed to actions in the same order as they occur in the pattern (not by name)
+
+```python
+from apm import *
+
+def fibonacci(n):
+ return match(n,
+ 1, 1,
+ 2, 1,
+ _, lambda x: fibonacci(x - 1) + fibonacci(x - 2)
+ )
+
+fibonacci(6) # -> 8
+
+
+class Animal: pass
+class Hippo(Animal): pass
+class Zebra(Animal): pass
+class Horse(Animal): pass
+
+def what_am_i(x):
+ return match(x,
+ Hippo, 'hippopotamus',
+ Zebra, 'zebra',
+ Animal, 'some other animal',
+ _, 'not at all an animal',
+ )
+
+what_am_i(Hippo()) # -> 'hippopotamus'
+what_am_i(Zebra()) # -> 'zebra'
+what_am_i(Horse()) # -> 'some other animal'
+what_am_i(42) # -> 'not at all an animal'
+```
+
+
+## Available patterns
+
+### `Capture(pattern, name=<str>)`
+
+Captures a piece of the thing being matched by name.
+
+```python
+if result := match([1, 2, 3, 4], [1, 2, Capture(Remaining(InstanceOf(int)), name='tail')]):
+ print(result['tail']) ## -> [3, 4]
+```
+
+As this syntax is rather verbose, two shorthand notations can be used:
+
+```python
+# using the matrix multiplication operator '@' (syntax resembles that of Haskell and Scala)
+if result := match([1, 2, 3, 4], [1, 2, 'tail' @ Remaining(InstanceOf(int))]):
+ print(result['tail']) ## -> [3, 4]
+
+# using the right shift operator
+if result := match([1, 2, 3, 4], [1, 2, Remaining(InstanceOf(int)) >> 'tail']):
+ print(result['tail']) ## -> [3, 4]
+```
+
+
+### `Strict(pattern)`
+
+Performs a strict pattern match. A strict pattern match also compares the type of verbatim values. That is, while
+_`apm`_ would match `3` with `3.0` it would not do so when using `Strict`. Also _`apm`_ performs partial matches of
+dictionaries (that is: it ignores unknown keys). It will perform an exact match for dictionaries using `Strict`.
+
+```python
+# The following will match
+match({"a": 3, "b": 7}, {"a": ...})
+match(3.0, 3)
+
+# These will not match
+match({"a": 3, "b": 7}, Strict({"a": ...}))
+match(3.0, Strict(3))
+```
+
+
+### `OneOf(*pattern)`
+
+Matches against any of the provided patterns. Equivalent to `p1 | p2 | p3 | ..`
+(but operator overloading does not work with values that do not inherit from `Pattern`)
+
+```python
+match("quux", OneOf("bar", "baz", "quux"))
+```
+
+```python
+match(3, OneOf(InstanceOf(int), None))
+```
+
+Patterns can also be joined using `|` to form a `OneOf` pattern:
+
+```python
+match(3, InstanceOf(int) | InstanceOf(float))
+```
+
+The above example is rather contrived, as `InstanceOf` already accepts multiple types natively:
+
+```python
+match(3, InstanceOf(int, float))
+```
+
+Since bare values do not inherit from `Pattern` they can be wrapped in `Value`:
+
+```python
+match("quux", Value("foo") | Value("quux"))
+```
+
+
+### `AllOf(*pattern)`
+
+Checks whether the value matches all of the given pattern. Equivalent to `p1 & p2 & p3 & ..`
+(but operator overloading does not work with values that do not inherit from `Pattern`)
+
+```python
+match("quux", AllOf(InstanceOf("str"), Regex("[a-z]+")))
+```
+
+
+### `NoneOf(*pattern)`
+
+Same as `Not(OneOf(*pattern))` (also `~OneOf(*pattern)`).
+
+
+### `Not(pattern)`
+
+Matches if the given pattern does not match.
+
+```python
+match(3, Not(4)) # matches
+match(5, Not(4)) # matches
+match(4, Not(4)) # does not match
+```
+
+The bitflip prefix operator (`~`) can be used to express the same thing. Note that it does not work on bare values,
+so they need to be wrapped in `Value`.
+
+```python
+match(3, ~Value(4)) # matches
+match(5, ~Value(4)) # matches
+match(4, ~Value(4)) # does not match
+```
+
+`Not` can be used do create a `NoneOf` kind of pattern:
+
+```python
+match("string", ~OneOf("foo", "bar")) # matches everything except "foo" and "bar"
+```
+
+`Not` can be used to create a pattern that never matches:
+
+```python
+Not(...)
+```
+
+
+### `Each(pattern [, at_least=]`
+
+Matches each item in an iterable.
+
+```python
+match(range(1, 10), Each(Between(1, 9)))
+```
+
+
+### `EachItem(key_pattern, value_pattern)`
+
+Matches an object if each key satisfies `key_pattern` and each value satisfies `value_pattern`.
+
+```python
+match({"a": 1, "b": 2}, EachItem(Regex("[a-z]+"), InstanceOf(int)))
+```
+
+
+### `Some(pattern)` (aka `Many` and `Remaining`)
+
+Matches a sequence of items within a list:
+
+```python
+if result := match(range(1, 10), [1, 'a' @ Some(...), 4, 'b' @ Some(...), 8, 9]):
+ print(result['a']) # [2, 3]
+ print(result['b']) # [5, 6, 7]
+```
+
+Takes the optional values `exactly`, `at_least`, and `at_most` which makes `Some` match
+either `exactly` _n_ items, `at_least` _n_, or `at_most` _n_ items (`at_least` and `at_most` can be given at the same
+time, but not together with `exactly`).
+
+Note the difference between `Some(1, 2)` and `Some([1, 2])`. The first version matches subsequences, the second
+version matches items which are themselves lists:
+
+```python
+match([0, 1, 2 , 1, 2 , 3], [0, Some( 1, 2 ), 3]) # matches the subsequence 1, 2 twice
+match([0, [1, 2], [1, 2], 3], [0, Some([1, 2]), 3]) # matches the item [1, 2] twice, which happen to be lists
+```
+
+`Some` also goes by the names of `Many` and `Remaining`, which is sometimes nice to convey meaning:
+
+```python
+match(range(1, 10), [1, 2, 'remaining' @ Remaining()])
+match([0, 1, 1, 1, 2, 1], [0, Many(1), Remaining(InstanceOf(int))])
+```
+
+When used with no arguments, `Some()` is the same as `Some(...)`.
+
+
+### `Remainder(pattern)`
+
+Can be used to match the unmatched parts of a Dictionary/Mapping.
+
+```python
+result = match({
+ "foo": 1,
+ "bar": 2,
+ "qux": 4,
+ "quuz": 8,
+}, {"foo": 'foo' @ _, "bar": 'bar' @ _} ** Remainder('rs' @ _))
+print(result.foo) # 1
+print(result.bar) # 2
+print(result.rs) # {'qux': 4, 'quuz': 8}
+```
+
+`Remainder` is, strictly speaking, not a `Pattern` and only works in conjunction with `**` on dictionaries,
+and it only works on the right-hand side of the dictionary.
+
+
+### `Between(lower, upper)`
+
+Matches an object if it is between `lower` and `upper` (inclusive). The optional keyword arguments
+`lower_bound_exclusive` and `upper_bound_exclusive` can be set to `True` respectively to exclude the
+lower/upper from the range of matching values.
+
+
+### `Length(length)`
+
+Matches an object if it has the given length. Alternatively also accepts `at_least` and `at_most` keyword arguments.
+
+```python
+match("abc", Length(3))
+match("abc", Length(at_least=2))
+match("abc", Length(at_most=4))
+match("abc", Length(at_least=2, at_most=4))
+```
+
+
+### `Contains(item)`
+
+Matches an object if it contains the given item (as per the same logic as the `in` operator).
+
+```python
+match("hello there, world", Contains("there"))
+match([1, 2, 3], Contains(2) & Contains(3))
+match({'foo': 1, 'bar': 2}, Contains('quux') | Contains('bar'))
+```
+
+
+### `Regex(regex_pattern, bind_groups: bool = True)`
+
+Matches a string if it completely matches the given regex, as per `re.fullmatch`.
+If the regular expression pattern contains named capturing groups and `bind_groups` is set to `True`,
+this pattern will bind the captured results in the `MatchResult` (the default).
+
+To mimic `re.match` or `re.search` the given regular expression `x` can be augmented as `x.*` or `.*x.*`
+respectively.
+
+
+### `Check(predicate)`
+
+Matches an object if it satisfies the given predicate.
+
+```python
+match(2, Check(lambda x: x % 2 == 0))
+```
+
+
+### `InstanceOf(*types)`
+
+Matches an object if it is an instance of any of the given types.
+
+```python
+match(1, InstanceOf(int, flaot))
+```
+
+
+### `SubclassOf(*types)`
+
+Matches if the matched type is a subclass of any of the given types.
+
+```python
+match(int, SubclassOf(int, float))
+```
+
+
+### `Parameters(...)`
+
+Matches the parameters of a callable.
+
+```python
+def f(x: int, *xs: float, y: str, **kwargs: bool):
+ pass
+
+
+match(f, Parameters(int, VarArgs(float), y=str, KwArgs(bool)))
+```
+
+Each argument to Parameters is expected to be the type of a positional argument.
+
+`Parameters` matches function signatures if their positional arguments match completely, i.e.
+
+```python
+def f(x: int, y: float):
+ pass
+
+
+print(bool(match(f, Parameters(int)))) # False
+print(bool(match(f, Parameters(int, float)))) # True
+print(bool(match(f, Parameters(int, Remaining(_))))) # True
+```
+
+Keyword arguments are matched only if they are keyword only arguments. In contrast to positional arguments it matches
+also impartially (which aligns with the non-strict matching behavior with respect to dictionaries):
+
+```python
+def f(x: int, *, y: str, z: float):
+ pass
+
+
+print(bool(match(f, Parameters(int)))) # True
+print(bool(match(f, Parameters(y=str)))) # False – positional parameters not matched
+print(bool(match(f, Parameters(int, y=str)))) # True
+```
+
+This can be changed with `Strict`:
+
+```python
+def f(x: int, *, y: str, z: float):
+ pass
+
+
+print(bool(match(f, Strict(Parameters(int))))) # False
+print(bool(match(f, Strict(Parameters(int, y=str))))) # False (z not mentioned but present)
+print(bool(match(f, Strict(Parameters(int, y=str, z=float))))) # True (has y and z exactly)
+```
+
+
+### `Arguments(*types)`
+
+<span style="color: red">**DEPRECATED, use `Parameters` instead (see above)**</span>
+
+
+Matches a callable if it's type annotations correspond to the given types.
+
+```python
+def f(x: int, y: float, z):
+ ...
+
+
+match(f, Arguments(int, float, None))
+```
+
+Arguments has an alternate form which can be used to match keyword arguments:
+
+```python
+
+def f(x: int, y: float, z: str):
+ ...
+
+match(f, Arguments(x=int, y=float))
+```
+
+The strictness rules are the same as for dictionaries (which is why the above example works).
+
+```python
+# given the f from above
+match(f, Strict(Arguments(x=int, y=float))) # does not match
+match(f, Strict(Arguments(x=int, y=float, z=str))) # matches
+```
+
+
+### `Returns(type)`
+
+Matches a callable if it's type annotations denote the given return type.
+
+```python
+def g(x: int) -> str:
+ ...
+
+
+match(g, Arguments(int) & Returns(str))
+```
+
+
+### `Transformed(function, pattern)`
+
+Transforms the currently looked at value by applying `function` on it and matches the result against `pattern`. In
+Haskell and other languages this is known as a [_view
+pattern_](https://gitlab.haskell.org/ghc/ghc/-/wikis/view-patterns).
+
+```python
+def sha256(v: str) -> str:
+ import hashlib
+ return hashlib.new('sha256', v.encode('utf8')).hexdigest()
+
+match("hello", Transformed(sha256, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"))
+```
+
+This is handy for matching data types like `datetime.date` as this pattern won't match if the transformation
+function errored out with an exception.
+
+```python
+from apm import *
+from datetime import date
+
+if result := match("2020-08-27", Transformed(date.fromisoformat, 'date' @ _):
+ print(repr(result['date'])) # result['date'] is a datetime.date
+```
+
+
+### `At(path, pattern)`
+
+Checks whether the nested object to be matched satisfies pattern at the given path. The match fails if the given path
+can not be resolved.
+
+```python
+record = {
+ "foo": {
+ "bar": {
+ "quux": {
+ "value": "deeply nested"
+ }
+ }
+ }
+}
+
+result := match(record, At("foo.bar.quux", {"value": Capture(..., name="value")}))
+result['value'] # "deeply nested"
+
+# alternate form
+result := match(record, At(['foo', 'bar', 'quux'], {"value": Capture(..., name="value")}))
+```
+
+
+### `Items(**kwargs))`
+
+Mostly syntactic sugar to match a dictionary nicely (and anything that provides an `.items()` method).
+
+```python
+from apm import *
+from datetime import datetime
+
+request = {
+ "api_version": "v1",
+ "job": {
+ "run_at": "2020-08-27 14:09:30",
+ "command": "echo 'booya'",
+ }
+}
+
+if result := match(request, Items(
+ api_version="v1",
+ job=Object(
+ run_at=Transformed(datetime.fromisoformat, 'time' @ _),
+ ) & OneOf(
+ Items(command='command' @ InstanceOf(str)),
+ Items(spawn='container' @ InstanceOf(str)),
+ )
+)):
+ print(repr(result['time'])) # datetime(2020, 8, 27, 14, 9, 30)
+ print('container' not in result) # True
+ print(result['command']) # "echo 'booya'"
+```
+
+
+### `Object(type, *args, **kwargs)`
+
+Matches any object of the specific type with the given attrs as in `**kwargs`.
+It respects the `__match_args__` introduced by PEP-634.
+
+```python
+from apm import *
+from typing import Literal, Tuple
+
+class Click:
+ __match_args__ = ("position", "button")
+
+ def __init__(self, pos: Tuple[int, int], btn: Literal['left', 'right', 'middle']):
+ self.position = pos
+ self.button = btn
+
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2)))
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2), 'left'))
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2), button='left'))
+```
+
+
+## Extensible
+
+New patterns can be added, just like the ones in `apm.patterns.*`. Simply extend the `apm.Pattern` class:
+
+```python
+class Min(Pattern):
+ def __init__(self, min):
+ self.min = min
+
+ def match(self, value, *, ctx: MatchContext, strict=False) -> MatchResult:
+ return ctx.match_if(value >= self.min)
+
+match(3, Min(1)) # matches
+match(3, Min(5)) # does not match
+```
+
+
+
+
+%package -n python3-awesome-pattern-matching
+Summary: Awesome Pattern Matching
+Provides: python-awesome-pattern-matching
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-awesome-pattern-matching
+# Awesome Pattern Matching (_apm_) for Python
+
+[![Github Actions](https://github.com/scravy/awesome-pattern-matching/workflows/Python%20application/badge.svg)](https://github.com/scravy/awesome-pattern-matching/actions)
+[![Downloads](https://pepy.tech/badge/awesome-pattern-matching)](https://pepy.tech/project/awesome-pattern-matching)
+[![PyPI version](https://badge.fury.io/py/awesome-pattern-matching.svg)](https://pypi.org/project/awesome-pattern-matching/)
+
+```bash
+pip install awesome-pattern-matching
+```
+
+- Simple
+- Powerful
+- Extensible
+- Composable
+- Functional
+- Python 3.7+, PyPy3.7+
+- Typed (IDE friendly)
+- Offers different styles (expression, declarative, statement, ...)
+
+There's a ton of pattern matching libraries available for python, all with varying degrees of maintenance and usability;
+also [since Python 3.10 there is the PEP-634 `match` statement](https://www.python.org/dev/peps/pep-0634/). However,
+this library still offers functionality that PEP-634 doesn't offer, as well as pattern matching for python versions
+before 3.10. [A detailed comparison of PEP-634 and _`apm`_ is available](https://github.com/scravy/awesome-pattern-matching/blob/main/docs/apm_vs_pep634.md).
+
+_`apm`_ defines patterns as objects which are _composable_ and _reusable_. Pieces can be matched and captured into
+variables, much like pattern matching in Haskell or Scala (a feature which most libraries actually lack, but which also
+makes pattern matching useful in the first place - the capability to easily extract data). Here is an example:
+
+```python
+from apm import *
+
+if result := match([1, 2, 3, 4, 5], [1, '2nd' @ _, '3rd' @ _, 'tail' @ Remaining(...)]):
+ print(result['2nd']) # 2
+ print(result['3rd']) # 3
+ print(result['tail']) # [4, 5]
+
+# If you find it more readable, '>>' can be used instead of '@' to capture a variable
+match([1, 2, 3, 4, 5], [1, _ >> '2nd', _ >> '3rd', Remaining(...) >> 'tail'])
+```
+
+Patterns can be composed using `&`, `|`, and `^`, or via their more explicit counterparts `AllOf`, `OneOf`, and `Either`
+. Since patterns are objects, they can be stored in variables and be reused.
+
+```python
+positive_integer = InstanceOf(int) & Check(lambda x: x >= 0)
+```
+
+Some fancy matching patterns are available out of the box:
+
+```python
+from apm import *
+
+def f(x: int, y: float) -> int:
+ pass
+
+if match(f, Arguments(int, float) & Returns(int)):
+ print("Function satisfies required signature")
+```
+
+
+
+
+## Multiple Styles
+
+For matching and selecting from multiple cases, choose your style:
+
+```python
+from apm import *
+
+value = 7
+
+# The simple style
+if match(value, Between(1, 10)):
+ print("It's between 1 and 10")
+elif match(value, Between(11, 20)):
+ print("It's between 11 and 20")
+else:
+ print("It's not between 1 and 20")
+
+# The expression style
+case(value) \
+ .of(Between(1, 10), lambda: print("It's between 1 and 10")) \
+ .of(Between(11, 20), lambda: print("It's between 11 and 20")) \
+ .otherwise(lambda: print("It's not between 1 and 20"))
+
+# The statement style
+try:
+ match(value)
+except Case(Between(1, 10)):
+ print("It's between 1 and 10")
+except Case(Between(11, 20)):
+ print("It's between 11 and 20")
+except Default:
+ print("It's not between 1 and 20")
+
+# The declarative style
+@case_distinction
+def f(n: Match(Between(1, 10))):
+ print("It's between 1 and 10")
+
+@case_distinction
+def f(n: Match(Between(11, 20))):
+ print("It's between 11 and 20")
+
+@case_distinction
+def f(n):
+ print("It's not between 1 and 20")
+
+f(value)
+
+# The terse (pampy) style
+match(value,
+ Between( 1, 10), lambda: print("It's between 1 and 10"),
+ Between(11, 20), lambda: print("It's between 11 and 20"),
+ _, lambda: print("It's not between 1 and 20"))
+```
+
+
+## Nested pattern matches
+
+Patterns are applied recursively, such that nested structures can be matched arbitrarily deep.
+This is super useful for extracting data from complicated structures:
+
+```python
+from apm import *
+
+sample_k8s_response = {
+ "containers": [
+ {
+ "args": [
+ "--cert-dir=/tmp",
+ "--secure-port=4443",
+ "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
+ "--kubelet-use-node-status-port"
+ ],
+ "image": "k8s.gcr.io/metrics-server/metrics-server:v0.4.1",
+ "imagePullPolicy": "IfNotPresent",
+ "name": "metrics-server",
+ "ports": [
+ {
+ "containerPort": 4443,
+ "name": "https",
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+}
+
+if result := match(sample_k8s_response, {
+ "containers": Each({
+ "image": 'image' @ _,
+ "name": 'name' @ _,
+ "ports": Each({
+ "containerPort": 'port' @ _
+ }),
+ })
+ }):
+ print(f"Image: {result['image']}, Name: {result['name']}, Port: {result['port']}")
+```
+
+The above will print
+
+```
+Image: k8s.gcr.io/metrics-server/metrics-server:v0.4.1, Name: metrics-server, Port: 4443
+```
+
+
+## Multimatch
+
+By default `match` records only the last match for captures. If for example `'item' @ InstanceOf(int)` matches multiple times,
+the last match will be recorded in `result['item']`. `match` can record all captures using the `multimatch=True` flag:
+
+```python
+if result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...)), multimatch=True):
+ print(result['item']) # [5, 7]
+
+# The default since v0.15.0 is multimatch=False
+if result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...))):
+ print(result['item']) # 7
+```
+
+
+## Strict vs non-strict matches
+
+Any value which occurs verbatim in a pattern is matched verbatim (`int`, `str`, `list`, ...), except Dictionaries (
+anything which has an `items()` actually).
+
+Thus:
+
+```python
+some_very_complex_object = {
+ "A": 1,
+ "B": 2,
+ "C": 3,
+}
+match(some_very_complex_object, {"C": 3}) # matches!
+```
+
+If you do not want unknown keys to be ignored, wrap the pattern in a `Strict`:
+
+```python
+# does not match, only matches exactly `{"C": 3}`
+match(some_very_complex_object, Strict({"C": 3}))
+```
+
+Lists (anything iterable which does not have an `items()` actually) are also compared as they are, i.e.:
+
+```python
+ls = [1, 2, 3]
+match(ls, [1, 2, 3]) # matches
+match(ls, [1, 2]) # does not match
+```
+
+
+## Match head and tail of a list
+
+It is possible to match the remainder of a list though:
+
+```python
+match(ls, [1, 2, Remaining(InstanceOf(int))])
+```
+
+And each item:
+
+```python
+match(ls, Each(InstanceOf(int)))
+```
+
+Patterns can be joined using `&`, `|`, and `^`:
+
+```python
+match(ls, Each(InstanceOf(int) & Between(1, 3)))
+```
+
+Wild-card matches are supported using Ellipsis (`...`):
+
+```python
+match(ls, [1, Remaining(..., at_least=2)])
+```
+
+The above example also showcases how `Remaining` can be made to match
+`at_least` _n_ number of items (`Each` also has an `at_least` keyword argument).
+
+
+## Wildcard matches anything using `_`
+
+A wildcard pattern can be expressed using `_`. `_` is a `Pattern` and thus `>>` and `@` can be used with it.
+
+```python
+match([1, 2, 3, 4], [1, _, 3, _])
+```
+
+
+## Wildcard matches anything using `...`
+
+The `Ellipsis` can be used as a wildcard match, too. It is however not a `Pattern` (so `|`, `&`, `@`, etc. can not
+be used on it). If you actually want to match `Ellipsis`, wrap it using `Value(...)`.
+
+Otherwise `...` is equivalent for most intents and purposes to `_`:
+
+```python
+match([1, 2, 3, 4], [1, ..., 3, ...])
+```
+
+
+## Support for dataclasses
+
+```python
+@dataclass
+class User:
+ first_name: str
+ last_name: str
+
+value = User("Jane", "Doe")
+
+if match(value, User(_, "Doe")):
+ print("Welcome, member of the Doe family!")
+elif match(value, User(_, _)):
+ print("Welcome, anyone!")
+```
+
+
+## The different styles in detail
+
+### Simple style
+
+- 💚 has access to result captures
+- 💚 vanilla python
+- 💔 no case guards
+- 💔 can not return values (since it's a statement, not an expression)
+- 🖤 a bit repetetive
+- 💚 simplest and most easy to understand style
+- 🖤 fastest of them all
+
+```python
+from apm import *
+
+value = {"a": 7, "b": "foo", "c": "bar"}
+
+if result := match(value, EachItem(_, 'value' @ InstanceOf(str) | ...), multimatch=True):
+ print(result['value']) # ["foo", "bar"]
+```
+
+#### pre `:=` version (Python 3.7)
+
+`bind()` can be used on a `MatchResult` to bind the matched items to an existing dictionary.
+
+```python
+from apm import *
+
+value = {"a": 7, "b": "foo", "c": "bar"}
+
+result = {}
+if match(value, EachItem(_, 'value' @ InstanceOf(str) | ...)).bind(result):
+ print(result['value']) # ["foo", "bar"]
+elif match(value, {"quux": _ >> 'quux'}).bind(result):
+ print(result['quux'])
+```
+
+### Expression style
+
+- 💚 has access to result captures
+- 💚 vanilla python
+- 💚 can return values directly as it is an expression
+- 💚 can use case guards via `when=` or `guarded`
+- 🖤 so terse that it is sometimes hard to read
+
+The expression style is summarized:
+
+```python
+case(value).of(pattern, action) ... .otherwise(default_action)
+```
+
+...where action is either a value or a callable. The captures from the matching result are bound to the named
+parameters of the given callable, i.e. `result['foo']` and `result['bar']` from `'foo' @ _` and `'bar' @ _` will be
+bound to `foo` and `bar` respectively in `lambda foo, bar: ...`.
+
+```python
+from apm import *
+
+display_name = case({'user': 'some-user-id', 'first_name': "Jane", 'last_name': "Doe"}) \
+ .of({'first_name': 'first' @ _, 'last_name': 'last' @ _}, lambda first, last: f"{first}, {last}") \
+ .of({'user': 'user_id' @ _}, lambda user_id: f"#{user_id}") \
+ .otherwise("anonymous")
+```
+
+_Note: To return a value an `.otherwise(...)` case must always be present._
+
+
+### Statement style
+
+This is arguable the most hacky style in _`apm`_, as it re-uses the `try .. except`
+mechanism. It is nevertheless quite readable.
+
+- 💚 has access to result captures
+- 💚 very readable
+- 💔 can not return values (since it's a statement, not an expression)
+- 💚 can use case guards via `when=`
+- 🖤 misuse of the `try .. except` statement
+
+```python
+from apm import *
+
+try:
+ match({'user': 'some-user-id', 'first_name': "Jane", 'last_name': "Doe"})
+except Case({'first_name': 'first' @ _, 'last_name': 'last' @ _}) as result:
+ user = f"{result['first']} {result['last']}"
+except Case({'user': 'user_id' @ _}) as result:
+ user = f"#{result['user_id']}"
+except Default:
+ user = "anonymous"
+
+print(user) # "Jane Doe"
+```
+
+
+### Declarative style
+
+- 💔 does not have access to result captures
+- 💚 very readable
+- 💚 can use case guards via `when=`
+- 💚 can return values
+- 🖤 the most bloated version of all styles
+
+```python
+from apm import *
+
+@case_distinction
+def fib(n: Match(OneOf(0, 1))):
+ return n
+
+@case_distinction
+def fib(n):
+ return fib(n - 2) + fib(n - 1)
+
+for i in range(0, 6):
+ print(fib(i))
+```
+
+#### Nota bene: Overloading using `@case_distinction`
+
+If not for its pattern matching capabilities, `@case_distinction` can be used
+to implement overloading. In fact, it can be imported as `@overload`.
+The mechanism is aware of arity and argument types.
+
+```python
+from apm.overload import overload
+
+@overload
+def add(a: str, b: str):
+ return "".join([a, b])
+
+@overload
+def add(a: int, b: int):
+ return a + b
+
+add("a", "b")
+add(1, 2)
+```
+
+### Terse style
+
+- 💚 has access to result captures
+- 💚 can use case guards via `guarded`
+- 💚 very concise
+- 💚 can return values
+- 🖤 very readable when formatted nicely
+- 🖤 not so well suited for larger match actions
+- 🖤 slowest of them all
+
+As the name indicates the "terse" style is terse. It is inspired by the `pampy`
+pattern matching library and mimics some of its behavior. Despite a slim surface
+area it also comes with some simplifications:
+
+- A type given as a pattern is matched against as if it was wrapped in an `InstanceOf`
+- `re.Pattern` objects (result of `re.compile`) are matched against as if it was given via `Regex`
+- Captures are passed to actions in the same order as they occur in the pattern (not by name)
+
+```python
+from apm import *
+
+def fibonacci(n):
+ return match(n,
+ 1, 1,
+ 2, 1,
+ _, lambda x: fibonacci(x - 1) + fibonacci(x - 2)
+ )
+
+fibonacci(6) # -> 8
+
+
+class Animal: pass
+class Hippo(Animal): pass
+class Zebra(Animal): pass
+class Horse(Animal): pass
+
+def what_am_i(x):
+ return match(x,
+ Hippo, 'hippopotamus',
+ Zebra, 'zebra',
+ Animal, 'some other animal',
+ _, 'not at all an animal',
+ )
+
+what_am_i(Hippo()) # -> 'hippopotamus'
+what_am_i(Zebra()) # -> 'zebra'
+what_am_i(Horse()) # -> 'some other animal'
+what_am_i(42) # -> 'not at all an animal'
+```
+
+
+## Available patterns
+
+### `Capture(pattern, name=<str>)`
+
+Captures a piece of the thing being matched by name.
+
+```python
+if result := match([1, 2, 3, 4], [1, 2, Capture(Remaining(InstanceOf(int)), name='tail')]):
+ print(result['tail']) ## -> [3, 4]
+```
+
+As this syntax is rather verbose, two shorthand notations can be used:
+
+```python
+# using the matrix multiplication operator '@' (syntax resembles that of Haskell and Scala)
+if result := match([1, 2, 3, 4], [1, 2, 'tail' @ Remaining(InstanceOf(int))]):
+ print(result['tail']) ## -> [3, 4]
+
+# using the right shift operator
+if result := match([1, 2, 3, 4], [1, 2, Remaining(InstanceOf(int)) >> 'tail']):
+ print(result['tail']) ## -> [3, 4]
+```
+
+
+### `Strict(pattern)`
+
+Performs a strict pattern match. A strict pattern match also compares the type of verbatim values. That is, while
+_`apm`_ would match `3` with `3.0` it would not do so when using `Strict`. Also _`apm`_ performs partial matches of
+dictionaries (that is: it ignores unknown keys). It will perform an exact match for dictionaries using `Strict`.
+
+```python
+# The following will match
+match({"a": 3, "b": 7}, {"a": ...})
+match(3.0, 3)
+
+# These will not match
+match({"a": 3, "b": 7}, Strict({"a": ...}))
+match(3.0, Strict(3))
+```
+
+
+### `OneOf(*pattern)`
+
+Matches against any of the provided patterns. Equivalent to `p1 | p2 | p3 | ..`
+(but operator overloading does not work with values that do not inherit from `Pattern`)
+
+```python
+match("quux", OneOf("bar", "baz", "quux"))
+```
+
+```python
+match(3, OneOf(InstanceOf(int), None))
+```
+
+Patterns can also be joined using `|` to form a `OneOf` pattern:
+
+```python
+match(3, InstanceOf(int) | InstanceOf(float))
+```
+
+The above example is rather contrived, as `InstanceOf` already accepts multiple types natively:
+
+```python
+match(3, InstanceOf(int, float))
+```
+
+Since bare values do not inherit from `Pattern` they can be wrapped in `Value`:
+
+```python
+match("quux", Value("foo") | Value("quux"))
+```
+
+
+### `AllOf(*pattern)`
+
+Checks whether the value matches all of the given pattern. Equivalent to `p1 & p2 & p3 & ..`
+(but operator overloading does not work with values that do not inherit from `Pattern`)
+
+```python
+match("quux", AllOf(InstanceOf("str"), Regex("[a-z]+")))
+```
+
+
+### `NoneOf(*pattern)`
+
+Same as `Not(OneOf(*pattern))` (also `~OneOf(*pattern)`).
+
+
+### `Not(pattern)`
+
+Matches if the given pattern does not match.
+
+```python
+match(3, Not(4)) # matches
+match(5, Not(4)) # matches
+match(4, Not(4)) # does not match
+```
+
+The bitflip prefix operator (`~`) can be used to express the same thing. Note that it does not work on bare values,
+so they need to be wrapped in `Value`.
+
+```python
+match(3, ~Value(4)) # matches
+match(5, ~Value(4)) # matches
+match(4, ~Value(4)) # does not match
+```
+
+`Not` can be used do create a `NoneOf` kind of pattern:
+
+```python
+match("string", ~OneOf("foo", "bar")) # matches everything except "foo" and "bar"
+```
+
+`Not` can be used to create a pattern that never matches:
+
+```python
+Not(...)
+```
+
+
+### `Each(pattern [, at_least=]`
+
+Matches each item in an iterable.
+
+```python
+match(range(1, 10), Each(Between(1, 9)))
+```
+
+
+### `EachItem(key_pattern, value_pattern)`
+
+Matches an object if each key satisfies `key_pattern` and each value satisfies `value_pattern`.
+
+```python
+match({"a": 1, "b": 2}, EachItem(Regex("[a-z]+"), InstanceOf(int)))
+```
+
+
+### `Some(pattern)` (aka `Many` and `Remaining`)
+
+Matches a sequence of items within a list:
+
+```python
+if result := match(range(1, 10), [1, 'a' @ Some(...), 4, 'b' @ Some(...), 8, 9]):
+ print(result['a']) # [2, 3]
+ print(result['b']) # [5, 6, 7]
+```
+
+Takes the optional values `exactly`, `at_least`, and `at_most` which makes `Some` match
+either `exactly` _n_ items, `at_least` _n_, or `at_most` _n_ items (`at_least` and `at_most` can be given at the same
+time, but not together with `exactly`).
+
+Note the difference between `Some(1, 2)` and `Some([1, 2])`. The first version matches subsequences, the second
+version matches items which are themselves lists:
+
+```python
+match([0, 1, 2 , 1, 2 , 3], [0, Some( 1, 2 ), 3]) # matches the subsequence 1, 2 twice
+match([0, [1, 2], [1, 2], 3], [0, Some([1, 2]), 3]) # matches the item [1, 2] twice, which happen to be lists
+```
+
+`Some` also goes by the names of `Many` and `Remaining`, which is sometimes nice to convey meaning:
+
+```python
+match(range(1, 10), [1, 2, 'remaining' @ Remaining()])
+match([0, 1, 1, 1, 2, 1], [0, Many(1), Remaining(InstanceOf(int))])
+```
+
+When used with no arguments, `Some()` is the same as `Some(...)`.
+
+
+### `Remainder(pattern)`
+
+Can be used to match the unmatched parts of a Dictionary/Mapping.
+
+```python
+result = match({
+ "foo": 1,
+ "bar": 2,
+ "qux": 4,
+ "quuz": 8,
+}, {"foo": 'foo' @ _, "bar": 'bar' @ _} ** Remainder('rs' @ _))
+print(result.foo) # 1
+print(result.bar) # 2
+print(result.rs) # {'qux': 4, 'quuz': 8}
+```
+
+`Remainder` is, strictly speaking, not a `Pattern` and only works in conjunction with `**` on dictionaries,
+and it only works on the right-hand side of the dictionary.
+
+
+### `Between(lower, upper)`
+
+Matches an object if it is between `lower` and `upper` (inclusive). The optional keyword arguments
+`lower_bound_exclusive` and `upper_bound_exclusive` can be set to `True` respectively to exclude the
+lower/upper from the range of matching values.
+
+
+### `Length(length)`
+
+Matches an object if it has the given length. Alternatively also accepts `at_least` and `at_most` keyword arguments.
+
+```python
+match("abc", Length(3))
+match("abc", Length(at_least=2))
+match("abc", Length(at_most=4))
+match("abc", Length(at_least=2, at_most=4))
+```
+
+
+### `Contains(item)`
+
+Matches an object if it contains the given item (as per the same logic as the `in` operator).
+
+```python
+match("hello there, world", Contains("there"))
+match([1, 2, 3], Contains(2) & Contains(3))
+match({'foo': 1, 'bar': 2}, Contains('quux') | Contains('bar'))
+```
+
+
+### `Regex(regex_pattern, bind_groups: bool = True)`
+
+Matches a string if it completely matches the given regex, as per `re.fullmatch`.
+If the regular expression pattern contains named capturing groups and `bind_groups` is set to `True`,
+this pattern will bind the captured results in the `MatchResult` (the default).
+
+To mimic `re.match` or `re.search` the given regular expression `x` can be augmented as `x.*` or `.*x.*`
+respectively.
+
+
+### `Check(predicate)`
+
+Matches an object if it satisfies the given predicate.
+
+```python
+match(2, Check(lambda x: x % 2 == 0))
+```
+
+
+### `InstanceOf(*types)`
+
+Matches an object if it is an instance of any of the given types.
+
+```python
+match(1, InstanceOf(int, flaot))
+```
+
+
+### `SubclassOf(*types)`
+
+Matches if the matched type is a subclass of any of the given types.
+
+```python
+match(int, SubclassOf(int, float))
+```
+
+
+### `Parameters(...)`
+
+Matches the parameters of a callable.
+
+```python
+def f(x: int, *xs: float, y: str, **kwargs: bool):
+ pass
+
+
+match(f, Parameters(int, VarArgs(float), y=str, KwArgs(bool)))
+```
+
+Each argument to Parameters is expected to be the type of a positional argument.
+
+`Parameters` matches function signatures if their positional arguments match completely, i.e.
+
+```python
+def f(x: int, y: float):
+ pass
+
+
+print(bool(match(f, Parameters(int)))) # False
+print(bool(match(f, Parameters(int, float)))) # True
+print(bool(match(f, Parameters(int, Remaining(_))))) # True
+```
+
+Keyword arguments are matched only if they are keyword only arguments. In contrast to positional arguments it matches
+also impartially (which aligns with the non-strict matching behavior with respect to dictionaries):
+
+```python
+def f(x: int, *, y: str, z: float):
+ pass
+
+
+print(bool(match(f, Parameters(int)))) # True
+print(bool(match(f, Parameters(y=str)))) # False – positional parameters not matched
+print(bool(match(f, Parameters(int, y=str)))) # True
+```
+
+This can be changed with `Strict`:
+
+```python
+def f(x: int, *, y: str, z: float):
+ pass
+
+
+print(bool(match(f, Strict(Parameters(int))))) # False
+print(bool(match(f, Strict(Parameters(int, y=str))))) # False (z not mentioned but present)
+print(bool(match(f, Strict(Parameters(int, y=str, z=float))))) # True (has y and z exactly)
+```
+
+
+### `Arguments(*types)`
+
+<span style="color: red">**DEPRECATED, use `Parameters` instead (see above)**</span>
+
+
+Matches a callable if it's type annotations correspond to the given types.
+
+```python
+def f(x: int, y: float, z):
+ ...
+
+
+match(f, Arguments(int, float, None))
+```
+
+Arguments has an alternate form which can be used to match keyword arguments:
+
+```python
+
+def f(x: int, y: float, z: str):
+ ...
+
+match(f, Arguments(x=int, y=float))
+```
+
+The strictness rules are the same as for dictionaries (which is why the above example works).
+
+```python
+# given the f from above
+match(f, Strict(Arguments(x=int, y=float))) # does not match
+match(f, Strict(Arguments(x=int, y=float, z=str))) # matches
+```
+
+
+### `Returns(type)`
+
+Matches a callable if it's type annotations denote the given return type.
+
+```python
+def g(x: int) -> str:
+ ...
+
+
+match(g, Arguments(int) & Returns(str))
+```
+
+
+### `Transformed(function, pattern)`
+
+Transforms the currently looked at value by applying `function` on it and matches the result against `pattern`. In
+Haskell and other languages this is known as a [_view
+pattern_](https://gitlab.haskell.org/ghc/ghc/-/wikis/view-patterns).
+
+```python
+def sha256(v: str) -> str:
+ import hashlib
+ return hashlib.new('sha256', v.encode('utf8')).hexdigest()
+
+match("hello", Transformed(sha256, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"))
+```
+
+This is handy for matching data types like `datetime.date` as this pattern won't match if the transformation
+function errored out with an exception.
+
+```python
+from apm import *
+from datetime import date
+
+if result := match("2020-08-27", Transformed(date.fromisoformat, 'date' @ _):
+ print(repr(result['date'])) # result['date'] is a datetime.date
+```
+
+
+### `At(path, pattern)`
+
+Checks whether the nested object to be matched satisfies pattern at the given path. The match fails if the given path
+can not be resolved.
+
+```python
+record = {
+ "foo": {
+ "bar": {
+ "quux": {
+ "value": "deeply nested"
+ }
+ }
+ }
+}
+
+result := match(record, At("foo.bar.quux", {"value": Capture(..., name="value")}))
+result['value'] # "deeply nested"
+
+# alternate form
+result := match(record, At(['foo', 'bar', 'quux'], {"value": Capture(..., name="value")}))
+```
+
+
+### `Items(**kwargs))`
+
+Mostly syntactic sugar to match a dictionary nicely (and anything that provides an `.items()` method).
+
+```python
+from apm import *
+from datetime import datetime
+
+request = {
+ "api_version": "v1",
+ "job": {
+ "run_at": "2020-08-27 14:09:30",
+ "command": "echo 'booya'",
+ }
+}
+
+if result := match(request, Items(
+ api_version="v1",
+ job=Object(
+ run_at=Transformed(datetime.fromisoformat, 'time' @ _),
+ ) & OneOf(
+ Items(command='command' @ InstanceOf(str)),
+ Items(spawn='container' @ InstanceOf(str)),
+ )
+)):
+ print(repr(result['time'])) # datetime(2020, 8, 27, 14, 9, 30)
+ print('container' not in result) # True
+ print(result['command']) # "echo 'booya'"
+```
+
+
+### `Object(type, *args, **kwargs)`
+
+Matches any object of the specific type with the given attrs as in `**kwargs`.
+It respects the `__match_args__` introduced by PEP-634.
+
+```python
+from apm import *
+from typing import Literal, Tuple
+
+class Click:
+ __match_args__ = ("position", "button")
+
+ def __init__(self, pos: Tuple[int, int], btn: Literal['left', 'right', 'middle']):
+ self.position = pos
+ self.button = btn
+
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2)))
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2), 'left'))
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2), button='left'))
+```
+
+
+## Extensible
+
+New patterns can be added, just like the ones in `apm.patterns.*`. Simply extend the `apm.Pattern` class:
+
+```python
+class Min(Pattern):
+ def __init__(self, min):
+ self.min = min
+
+ def match(self, value, *, ctx: MatchContext, strict=False) -> MatchResult:
+ return ctx.match_if(value >= self.min)
+
+match(3, Min(1)) # matches
+match(3, Min(5)) # does not match
+```
+
+
+
+
+%package help
+Summary: Development documents and examples for awesome-pattern-matching
+Provides: python3-awesome-pattern-matching-doc
+%description help
+# Awesome Pattern Matching (_apm_) for Python
+
+[![Github Actions](https://github.com/scravy/awesome-pattern-matching/workflows/Python%20application/badge.svg)](https://github.com/scravy/awesome-pattern-matching/actions)
+[![Downloads](https://pepy.tech/badge/awesome-pattern-matching)](https://pepy.tech/project/awesome-pattern-matching)
+[![PyPI version](https://badge.fury.io/py/awesome-pattern-matching.svg)](https://pypi.org/project/awesome-pattern-matching/)
+
+```bash
+pip install awesome-pattern-matching
+```
+
+- Simple
+- Powerful
+- Extensible
+- Composable
+- Functional
+- Python 3.7+, PyPy3.7+
+- Typed (IDE friendly)
+- Offers different styles (expression, declarative, statement, ...)
+
+There's a ton of pattern matching libraries available for python, all with varying degrees of maintenance and usability;
+also [since Python 3.10 there is the PEP-634 `match` statement](https://www.python.org/dev/peps/pep-0634/). However,
+this library still offers functionality that PEP-634 doesn't offer, as well as pattern matching for python versions
+before 3.10. [A detailed comparison of PEP-634 and _`apm`_ is available](https://github.com/scravy/awesome-pattern-matching/blob/main/docs/apm_vs_pep634.md).
+
+_`apm`_ defines patterns as objects which are _composable_ and _reusable_. Pieces can be matched and captured into
+variables, much like pattern matching in Haskell or Scala (a feature which most libraries actually lack, but which also
+makes pattern matching useful in the first place - the capability to easily extract data). Here is an example:
+
+```python
+from apm import *
+
+if result := match([1, 2, 3, 4, 5], [1, '2nd' @ _, '3rd' @ _, 'tail' @ Remaining(...)]):
+ print(result['2nd']) # 2
+ print(result['3rd']) # 3
+ print(result['tail']) # [4, 5]
+
+# If you find it more readable, '>>' can be used instead of '@' to capture a variable
+match([1, 2, 3, 4, 5], [1, _ >> '2nd', _ >> '3rd', Remaining(...) >> 'tail'])
+```
+
+Patterns can be composed using `&`, `|`, and `^`, or via their more explicit counterparts `AllOf`, `OneOf`, and `Either`
+. Since patterns are objects, they can be stored in variables and be reused.
+
+```python
+positive_integer = InstanceOf(int) & Check(lambda x: x >= 0)
+```
+
+Some fancy matching patterns are available out of the box:
+
+```python
+from apm import *
+
+def f(x: int, y: float) -> int:
+ pass
+
+if match(f, Arguments(int, float) & Returns(int)):
+ print("Function satisfies required signature")
+```
+
+
+
+
+## Multiple Styles
+
+For matching and selecting from multiple cases, choose your style:
+
+```python
+from apm import *
+
+value = 7
+
+# The simple style
+if match(value, Between(1, 10)):
+ print("It's between 1 and 10")
+elif match(value, Between(11, 20)):
+ print("It's between 11 and 20")
+else:
+ print("It's not between 1 and 20")
+
+# The expression style
+case(value) \
+ .of(Between(1, 10), lambda: print("It's between 1 and 10")) \
+ .of(Between(11, 20), lambda: print("It's between 11 and 20")) \
+ .otherwise(lambda: print("It's not between 1 and 20"))
+
+# The statement style
+try:
+ match(value)
+except Case(Between(1, 10)):
+ print("It's between 1 and 10")
+except Case(Between(11, 20)):
+ print("It's between 11 and 20")
+except Default:
+ print("It's not between 1 and 20")
+
+# The declarative style
+@case_distinction
+def f(n: Match(Between(1, 10))):
+ print("It's between 1 and 10")
+
+@case_distinction
+def f(n: Match(Between(11, 20))):
+ print("It's between 11 and 20")
+
+@case_distinction
+def f(n):
+ print("It's not between 1 and 20")
+
+f(value)
+
+# The terse (pampy) style
+match(value,
+ Between( 1, 10), lambda: print("It's between 1 and 10"),
+ Between(11, 20), lambda: print("It's between 11 and 20"),
+ _, lambda: print("It's not between 1 and 20"))
+```
+
+
+## Nested pattern matches
+
+Patterns are applied recursively, such that nested structures can be matched arbitrarily deep.
+This is super useful for extracting data from complicated structures:
+
+```python
+from apm import *
+
+sample_k8s_response = {
+ "containers": [
+ {
+ "args": [
+ "--cert-dir=/tmp",
+ "--secure-port=4443",
+ "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
+ "--kubelet-use-node-status-port"
+ ],
+ "image": "k8s.gcr.io/metrics-server/metrics-server:v0.4.1",
+ "imagePullPolicy": "IfNotPresent",
+ "name": "metrics-server",
+ "ports": [
+ {
+ "containerPort": 4443,
+ "name": "https",
+ "protocol": "TCP"
+ }
+ ]
+ }
+ ]
+}
+
+if result := match(sample_k8s_response, {
+ "containers": Each({
+ "image": 'image' @ _,
+ "name": 'name' @ _,
+ "ports": Each({
+ "containerPort": 'port' @ _
+ }),
+ })
+ }):
+ print(f"Image: {result['image']}, Name: {result['name']}, Port: {result['port']}")
+```
+
+The above will print
+
+```
+Image: k8s.gcr.io/metrics-server/metrics-server:v0.4.1, Name: metrics-server, Port: 4443
+```
+
+
+## Multimatch
+
+By default `match` records only the last match for captures. If for example `'item' @ InstanceOf(int)` matches multiple times,
+the last match will be recorded in `result['item']`. `match` can record all captures using the `multimatch=True` flag:
+
+```python
+if result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...)), multimatch=True):
+ print(result['item']) # [5, 7]
+
+# The default since v0.15.0 is multimatch=False
+if result := match([{'foo': 5}, 3, {'foo': 7, 'bar': 9}], Each(OneOf({'foo': 'item' @ _}, ...))):
+ print(result['item']) # 7
+```
+
+
+## Strict vs non-strict matches
+
+Any value which occurs verbatim in a pattern is matched verbatim (`int`, `str`, `list`, ...), except Dictionaries (
+anything which has an `items()` actually).
+
+Thus:
+
+```python
+some_very_complex_object = {
+ "A": 1,
+ "B": 2,
+ "C": 3,
+}
+match(some_very_complex_object, {"C": 3}) # matches!
+```
+
+If you do not want unknown keys to be ignored, wrap the pattern in a `Strict`:
+
+```python
+# does not match, only matches exactly `{"C": 3}`
+match(some_very_complex_object, Strict({"C": 3}))
+```
+
+Lists (anything iterable which does not have an `items()` actually) are also compared as they are, i.e.:
+
+```python
+ls = [1, 2, 3]
+match(ls, [1, 2, 3]) # matches
+match(ls, [1, 2]) # does not match
+```
+
+
+## Match head and tail of a list
+
+It is possible to match the remainder of a list though:
+
+```python
+match(ls, [1, 2, Remaining(InstanceOf(int))])
+```
+
+And each item:
+
+```python
+match(ls, Each(InstanceOf(int)))
+```
+
+Patterns can be joined using `&`, `|`, and `^`:
+
+```python
+match(ls, Each(InstanceOf(int) & Between(1, 3)))
+```
+
+Wild-card matches are supported using Ellipsis (`...`):
+
+```python
+match(ls, [1, Remaining(..., at_least=2)])
+```
+
+The above example also showcases how `Remaining` can be made to match
+`at_least` _n_ number of items (`Each` also has an `at_least` keyword argument).
+
+
+## Wildcard matches anything using `_`
+
+A wildcard pattern can be expressed using `_`. `_` is a `Pattern` and thus `>>` and `@` can be used with it.
+
+```python
+match([1, 2, 3, 4], [1, _, 3, _])
+```
+
+
+## Wildcard matches anything using `...`
+
+The `Ellipsis` can be used as a wildcard match, too. It is however not a `Pattern` (so `|`, `&`, `@`, etc. can not
+be used on it). If you actually want to match `Ellipsis`, wrap it using `Value(...)`.
+
+Otherwise `...` is equivalent for most intents and purposes to `_`:
+
+```python
+match([1, 2, 3, 4], [1, ..., 3, ...])
+```
+
+
+## Support for dataclasses
+
+```python
+@dataclass
+class User:
+ first_name: str
+ last_name: str
+
+value = User("Jane", "Doe")
+
+if match(value, User(_, "Doe")):
+ print("Welcome, member of the Doe family!")
+elif match(value, User(_, _)):
+ print("Welcome, anyone!")
+```
+
+
+## The different styles in detail
+
+### Simple style
+
+- 💚 has access to result captures
+- 💚 vanilla python
+- 💔 no case guards
+- 💔 can not return values (since it's a statement, not an expression)
+- 🖤 a bit repetetive
+- 💚 simplest and most easy to understand style
+- 🖤 fastest of them all
+
+```python
+from apm import *
+
+value = {"a": 7, "b": "foo", "c": "bar"}
+
+if result := match(value, EachItem(_, 'value' @ InstanceOf(str) | ...), multimatch=True):
+ print(result['value']) # ["foo", "bar"]
+```
+
+#### pre `:=` version (Python 3.7)
+
+`bind()` can be used on a `MatchResult` to bind the matched items to an existing dictionary.
+
+```python
+from apm import *
+
+value = {"a": 7, "b": "foo", "c": "bar"}
+
+result = {}
+if match(value, EachItem(_, 'value' @ InstanceOf(str) | ...)).bind(result):
+ print(result['value']) # ["foo", "bar"]
+elif match(value, {"quux": _ >> 'quux'}).bind(result):
+ print(result['quux'])
+```
+
+### Expression style
+
+- 💚 has access to result captures
+- 💚 vanilla python
+- 💚 can return values directly as it is an expression
+- 💚 can use case guards via `when=` or `guarded`
+- 🖤 so terse that it is sometimes hard to read
+
+The expression style is summarized:
+
+```python
+case(value).of(pattern, action) ... .otherwise(default_action)
+```
+
+...where action is either a value or a callable. The captures from the matching result are bound to the named
+parameters of the given callable, i.e. `result['foo']` and `result['bar']` from `'foo' @ _` and `'bar' @ _` will be
+bound to `foo` and `bar` respectively in `lambda foo, bar: ...`.
+
+```python
+from apm import *
+
+display_name = case({'user': 'some-user-id', 'first_name': "Jane", 'last_name': "Doe"}) \
+ .of({'first_name': 'first' @ _, 'last_name': 'last' @ _}, lambda first, last: f"{first}, {last}") \
+ .of({'user': 'user_id' @ _}, lambda user_id: f"#{user_id}") \
+ .otherwise("anonymous")
+```
+
+_Note: To return a value an `.otherwise(...)` case must always be present._
+
+
+### Statement style
+
+This is arguable the most hacky style in _`apm`_, as it re-uses the `try .. except`
+mechanism. It is nevertheless quite readable.
+
+- 💚 has access to result captures
+- 💚 very readable
+- 💔 can not return values (since it's a statement, not an expression)
+- 💚 can use case guards via `when=`
+- 🖤 misuse of the `try .. except` statement
+
+```python
+from apm import *
+
+try:
+ match({'user': 'some-user-id', 'first_name': "Jane", 'last_name': "Doe"})
+except Case({'first_name': 'first' @ _, 'last_name': 'last' @ _}) as result:
+ user = f"{result['first']} {result['last']}"
+except Case({'user': 'user_id' @ _}) as result:
+ user = f"#{result['user_id']}"
+except Default:
+ user = "anonymous"
+
+print(user) # "Jane Doe"
+```
+
+
+### Declarative style
+
+- 💔 does not have access to result captures
+- 💚 very readable
+- 💚 can use case guards via `when=`
+- 💚 can return values
+- 🖤 the most bloated version of all styles
+
+```python
+from apm import *
+
+@case_distinction
+def fib(n: Match(OneOf(0, 1))):
+ return n
+
+@case_distinction
+def fib(n):
+ return fib(n - 2) + fib(n - 1)
+
+for i in range(0, 6):
+ print(fib(i))
+```
+
+#### Nota bene: Overloading using `@case_distinction`
+
+If not for its pattern matching capabilities, `@case_distinction` can be used
+to implement overloading. In fact, it can be imported as `@overload`.
+The mechanism is aware of arity and argument types.
+
+```python
+from apm.overload import overload
+
+@overload
+def add(a: str, b: str):
+ return "".join([a, b])
+
+@overload
+def add(a: int, b: int):
+ return a + b
+
+add("a", "b")
+add(1, 2)
+```
+
+### Terse style
+
+- 💚 has access to result captures
+- 💚 can use case guards via `guarded`
+- 💚 very concise
+- 💚 can return values
+- 🖤 very readable when formatted nicely
+- 🖤 not so well suited for larger match actions
+- 🖤 slowest of them all
+
+As the name indicates the "terse" style is terse. It is inspired by the `pampy`
+pattern matching library and mimics some of its behavior. Despite a slim surface
+area it also comes with some simplifications:
+
+- A type given as a pattern is matched against as if it was wrapped in an `InstanceOf`
+- `re.Pattern` objects (result of `re.compile`) are matched against as if it was given via `Regex`
+- Captures are passed to actions in the same order as they occur in the pattern (not by name)
+
+```python
+from apm import *
+
+def fibonacci(n):
+ return match(n,
+ 1, 1,
+ 2, 1,
+ _, lambda x: fibonacci(x - 1) + fibonacci(x - 2)
+ )
+
+fibonacci(6) # -> 8
+
+
+class Animal: pass
+class Hippo(Animal): pass
+class Zebra(Animal): pass
+class Horse(Animal): pass
+
+def what_am_i(x):
+ return match(x,
+ Hippo, 'hippopotamus',
+ Zebra, 'zebra',
+ Animal, 'some other animal',
+ _, 'not at all an animal',
+ )
+
+what_am_i(Hippo()) # -> 'hippopotamus'
+what_am_i(Zebra()) # -> 'zebra'
+what_am_i(Horse()) # -> 'some other animal'
+what_am_i(42) # -> 'not at all an animal'
+```
+
+
+## Available patterns
+
+### `Capture(pattern, name=<str>)`
+
+Captures a piece of the thing being matched by name.
+
+```python
+if result := match([1, 2, 3, 4], [1, 2, Capture(Remaining(InstanceOf(int)), name='tail')]):
+ print(result['tail']) ## -> [3, 4]
+```
+
+As this syntax is rather verbose, two shorthand notations can be used:
+
+```python
+# using the matrix multiplication operator '@' (syntax resembles that of Haskell and Scala)
+if result := match([1, 2, 3, 4], [1, 2, 'tail' @ Remaining(InstanceOf(int))]):
+ print(result['tail']) ## -> [3, 4]
+
+# using the right shift operator
+if result := match([1, 2, 3, 4], [1, 2, Remaining(InstanceOf(int)) >> 'tail']):
+ print(result['tail']) ## -> [3, 4]
+```
+
+
+### `Strict(pattern)`
+
+Performs a strict pattern match. A strict pattern match also compares the type of verbatim values. That is, while
+_`apm`_ would match `3` with `3.0` it would not do so when using `Strict`. Also _`apm`_ performs partial matches of
+dictionaries (that is: it ignores unknown keys). It will perform an exact match for dictionaries using `Strict`.
+
+```python
+# The following will match
+match({"a": 3, "b": 7}, {"a": ...})
+match(3.0, 3)
+
+# These will not match
+match({"a": 3, "b": 7}, Strict({"a": ...}))
+match(3.0, Strict(3))
+```
+
+
+### `OneOf(*pattern)`
+
+Matches against any of the provided patterns. Equivalent to `p1 | p2 | p3 | ..`
+(but operator overloading does not work with values that do not inherit from `Pattern`)
+
+```python
+match("quux", OneOf("bar", "baz", "quux"))
+```
+
+```python
+match(3, OneOf(InstanceOf(int), None))
+```
+
+Patterns can also be joined using `|` to form a `OneOf` pattern:
+
+```python
+match(3, InstanceOf(int) | InstanceOf(float))
+```
+
+The above example is rather contrived, as `InstanceOf` already accepts multiple types natively:
+
+```python
+match(3, InstanceOf(int, float))
+```
+
+Since bare values do not inherit from `Pattern` they can be wrapped in `Value`:
+
+```python
+match("quux", Value("foo") | Value("quux"))
+```
+
+
+### `AllOf(*pattern)`
+
+Checks whether the value matches all of the given pattern. Equivalent to `p1 & p2 & p3 & ..`
+(but operator overloading does not work with values that do not inherit from `Pattern`)
+
+```python
+match("quux", AllOf(InstanceOf("str"), Regex("[a-z]+")))
+```
+
+
+### `NoneOf(*pattern)`
+
+Same as `Not(OneOf(*pattern))` (also `~OneOf(*pattern)`).
+
+
+### `Not(pattern)`
+
+Matches if the given pattern does not match.
+
+```python
+match(3, Not(4)) # matches
+match(5, Not(4)) # matches
+match(4, Not(4)) # does not match
+```
+
+The bitflip prefix operator (`~`) can be used to express the same thing. Note that it does not work on bare values,
+so they need to be wrapped in `Value`.
+
+```python
+match(3, ~Value(4)) # matches
+match(5, ~Value(4)) # matches
+match(4, ~Value(4)) # does not match
+```
+
+`Not` can be used do create a `NoneOf` kind of pattern:
+
+```python
+match("string", ~OneOf("foo", "bar")) # matches everything except "foo" and "bar"
+```
+
+`Not` can be used to create a pattern that never matches:
+
+```python
+Not(...)
+```
+
+
+### `Each(pattern [, at_least=]`
+
+Matches each item in an iterable.
+
+```python
+match(range(1, 10), Each(Between(1, 9)))
+```
+
+
+### `EachItem(key_pattern, value_pattern)`
+
+Matches an object if each key satisfies `key_pattern` and each value satisfies `value_pattern`.
+
+```python
+match({"a": 1, "b": 2}, EachItem(Regex("[a-z]+"), InstanceOf(int)))
+```
+
+
+### `Some(pattern)` (aka `Many` and `Remaining`)
+
+Matches a sequence of items within a list:
+
+```python
+if result := match(range(1, 10), [1, 'a' @ Some(...), 4, 'b' @ Some(...), 8, 9]):
+ print(result['a']) # [2, 3]
+ print(result['b']) # [5, 6, 7]
+```
+
+Takes the optional values `exactly`, `at_least`, and `at_most` which makes `Some` match
+either `exactly` _n_ items, `at_least` _n_, or `at_most` _n_ items (`at_least` and `at_most` can be given at the same
+time, but not together with `exactly`).
+
+Note the difference between `Some(1, 2)` and `Some([1, 2])`. The first version matches subsequences, the second
+version matches items which are themselves lists:
+
+```python
+match([0, 1, 2 , 1, 2 , 3], [0, Some( 1, 2 ), 3]) # matches the subsequence 1, 2 twice
+match([0, [1, 2], [1, 2], 3], [0, Some([1, 2]), 3]) # matches the item [1, 2] twice, which happen to be lists
+```
+
+`Some` also goes by the names of `Many` and `Remaining`, which is sometimes nice to convey meaning:
+
+```python
+match(range(1, 10), [1, 2, 'remaining' @ Remaining()])
+match([0, 1, 1, 1, 2, 1], [0, Many(1), Remaining(InstanceOf(int))])
+```
+
+When used with no arguments, `Some()` is the same as `Some(...)`.
+
+
+### `Remainder(pattern)`
+
+Can be used to match the unmatched parts of a Dictionary/Mapping.
+
+```python
+result = match({
+ "foo": 1,
+ "bar": 2,
+ "qux": 4,
+ "quuz": 8,
+}, {"foo": 'foo' @ _, "bar": 'bar' @ _} ** Remainder('rs' @ _))
+print(result.foo) # 1
+print(result.bar) # 2
+print(result.rs) # {'qux': 4, 'quuz': 8}
+```
+
+`Remainder` is, strictly speaking, not a `Pattern` and only works in conjunction with `**` on dictionaries,
+and it only works on the right-hand side of the dictionary.
+
+
+### `Between(lower, upper)`
+
+Matches an object if it is between `lower` and `upper` (inclusive). The optional keyword arguments
+`lower_bound_exclusive` and `upper_bound_exclusive` can be set to `True` respectively to exclude the
+lower/upper from the range of matching values.
+
+
+### `Length(length)`
+
+Matches an object if it has the given length. Alternatively also accepts `at_least` and `at_most` keyword arguments.
+
+```python
+match("abc", Length(3))
+match("abc", Length(at_least=2))
+match("abc", Length(at_most=4))
+match("abc", Length(at_least=2, at_most=4))
+```
+
+
+### `Contains(item)`
+
+Matches an object if it contains the given item (as per the same logic as the `in` operator).
+
+```python
+match("hello there, world", Contains("there"))
+match([1, 2, 3], Contains(2) & Contains(3))
+match({'foo': 1, 'bar': 2}, Contains('quux') | Contains('bar'))
+```
+
+
+### `Regex(regex_pattern, bind_groups: bool = True)`
+
+Matches a string if it completely matches the given regex, as per `re.fullmatch`.
+If the regular expression pattern contains named capturing groups and `bind_groups` is set to `True`,
+this pattern will bind the captured results in the `MatchResult` (the default).
+
+To mimic `re.match` or `re.search` the given regular expression `x` can be augmented as `x.*` or `.*x.*`
+respectively.
+
+
+### `Check(predicate)`
+
+Matches an object if it satisfies the given predicate.
+
+```python
+match(2, Check(lambda x: x % 2 == 0))
+```
+
+
+### `InstanceOf(*types)`
+
+Matches an object if it is an instance of any of the given types.
+
+```python
+match(1, InstanceOf(int, flaot))
+```
+
+
+### `SubclassOf(*types)`
+
+Matches if the matched type is a subclass of any of the given types.
+
+```python
+match(int, SubclassOf(int, float))
+```
+
+
+### `Parameters(...)`
+
+Matches the parameters of a callable.
+
+```python
+def f(x: int, *xs: float, y: str, **kwargs: bool):
+ pass
+
+
+match(f, Parameters(int, VarArgs(float), y=str, KwArgs(bool)))
+```
+
+Each argument to Parameters is expected to be the type of a positional argument.
+
+`Parameters` matches function signatures if their positional arguments match completely, i.e.
+
+```python
+def f(x: int, y: float):
+ pass
+
+
+print(bool(match(f, Parameters(int)))) # False
+print(bool(match(f, Parameters(int, float)))) # True
+print(bool(match(f, Parameters(int, Remaining(_))))) # True
+```
+
+Keyword arguments are matched only if they are keyword only arguments. In contrast to positional arguments it matches
+also impartially (which aligns with the non-strict matching behavior with respect to dictionaries):
+
+```python
+def f(x: int, *, y: str, z: float):
+ pass
+
+
+print(bool(match(f, Parameters(int)))) # True
+print(bool(match(f, Parameters(y=str)))) # False – positional parameters not matched
+print(bool(match(f, Parameters(int, y=str)))) # True
+```
+
+This can be changed with `Strict`:
+
+```python
+def f(x: int, *, y: str, z: float):
+ pass
+
+
+print(bool(match(f, Strict(Parameters(int))))) # False
+print(bool(match(f, Strict(Parameters(int, y=str))))) # False (z not mentioned but present)
+print(bool(match(f, Strict(Parameters(int, y=str, z=float))))) # True (has y and z exactly)
+```
+
+
+### `Arguments(*types)`
+
+<span style="color: red">**DEPRECATED, use `Parameters` instead (see above)**</span>
+
+
+Matches a callable if it's type annotations correspond to the given types.
+
+```python
+def f(x: int, y: float, z):
+ ...
+
+
+match(f, Arguments(int, float, None))
+```
+
+Arguments has an alternate form which can be used to match keyword arguments:
+
+```python
+
+def f(x: int, y: float, z: str):
+ ...
+
+match(f, Arguments(x=int, y=float))
+```
+
+The strictness rules are the same as for dictionaries (which is why the above example works).
+
+```python
+# given the f from above
+match(f, Strict(Arguments(x=int, y=float))) # does not match
+match(f, Strict(Arguments(x=int, y=float, z=str))) # matches
+```
+
+
+### `Returns(type)`
+
+Matches a callable if it's type annotations denote the given return type.
+
+```python
+def g(x: int) -> str:
+ ...
+
+
+match(g, Arguments(int) & Returns(str))
+```
+
+
+### `Transformed(function, pattern)`
+
+Transforms the currently looked at value by applying `function` on it and matches the result against `pattern`. In
+Haskell and other languages this is known as a [_view
+pattern_](https://gitlab.haskell.org/ghc/ghc/-/wikis/view-patterns).
+
+```python
+def sha256(v: str) -> str:
+ import hashlib
+ return hashlib.new('sha256', v.encode('utf8')).hexdigest()
+
+match("hello", Transformed(sha256, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"))
+```
+
+This is handy for matching data types like `datetime.date` as this pattern won't match if the transformation
+function errored out with an exception.
+
+```python
+from apm import *
+from datetime import date
+
+if result := match("2020-08-27", Transformed(date.fromisoformat, 'date' @ _):
+ print(repr(result['date'])) # result['date'] is a datetime.date
+```
+
+
+### `At(path, pattern)`
+
+Checks whether the nested object to be matched satisfies pattern at the given path. The match fails if the given path
+can not be resolved.
+
+```python
+record = {
+ "foo": {
+ "bar": {
+ "quux": {
+ "value": "deeply nested"
+ }
+ }
+ }
+}
+
+result := match(record, At("foo.bar.quux", {"value": Capture(..., name="value")}))
+result['value'] # "deeply nested"
+
+# alternate form
+result := match(record, At(['foo', 'bar', 'quux'], {"value": Capture(..., name="value")}))
+```
+
+
+### `Items(**kwargs))`
+
+Mostly syntactic sugar to match a dictionary nicely (and anything that provides an `.items()` method).
+
+```python
+from apm import *
+from datetime import datetime
+
+request = {
+ "api_version": "v1",
+ "job": {
+ "run_at": "2020-08-27 14:09:30",
+ "command": "echo 'booya'",
+ }
+}
+
+if result := match(request, Items(
+ api_version="v1",
+ job=Object(
+ run_at=Transformed(datetime.fromisoformat, 'time' @ _),
+ ) & OneOf(
+ Items(command='command' @ InstanceOf(str)),
+ Items(spawn='container' @ InstanceOf(str)),
+ )
+)):
+ print(repr(result['time'])) # datetime(2020, 8, 27, 14, 9, 30)
+ print('container' not in result) # True
+ print(result['command']) # "echo 'booya'"
+```
+
+
+### `Object(type, *args, **kwargs)`
+
+Matches any object of the specific type with the given attrs as in `**kwargs`.
+It respects the `__match_args__` introduced by PEP-634.
+
+```python
+from apm import *
+from typing import Literal, Tuple
+
+class Click:
+ __match_args__ = ("position", "button")
+
+ def __init__(self, pos: Tuple[int, int], btn: Literal['left', 'right', 'middle']):
+ self.position = pos
+ self.button = btn
+
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2)))
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2), 'left'))
+assert match(Click((1, 2), 'left'), Object(Click, (1, 2), button='left'))
+```
+
+
+## Extensible
+
+New patterns can be added, just like the ones in `apm.patterns.*`. Simply extend the `apm.Pattern` class:
+
+```python
+class Min(Pattern):
+ def __init__(self, min):
+ self.min = min
+
+ def match(self, value, *, ctx: MatchContext, strict=False) -> MatchResult:
+ return ctx.match_if(value >= self.min)
+
+match(3, Min(1)) # matches
+match(3, Min(5)) # does not match
+```
+
+
+
+
+%prep
+%autosetup -n awesome-pattern-matching-0.24.4
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-awesome-pattern-matching -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Thu May 18 2023 Python_Bot <Python_Bot@openeuler.org> - 0.24.4-1
+- Package Spec generated
diff --git a/sources b/sources
new file mode 100644
index 0000000..3565f7f
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+7eafd9f106b6cbf0dd9f6f6ee512e5b6 awesome-pattern-matching-0.24.4.tar.gz