{"id":21158367,"url":"https://github.com/toilal/rebulk","last_synced_at":"2025-04-05T07:08:07.578Z","repository":{"id":55523829,"uuid":"41951206","full_name":"Toilal/rebulk","owner":"Toilal","description":"Define simple search patterns in bulk to perform advanced matching on any string","archived":false,"fork":false,"pushed_at":"2023-12-14T15:00:10.000Z","size":506,"stargazers_count":55,"open_issues_count":1,"forks_count":9,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2024-04-14T13:53:46.883Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Toilal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-09-05T06:11:51.000Z","updated_at":"2024-04-11T02:43:51.000Z","dependencies_parsed_at":"2023-12-13T11:38:35.471Z","dependency_job_id":"808fb91c-e1ac-4225-acc9-f41d640fb225","html_url":"https://github.com/Toilal/rebulk","commit_stats":{"total_commits":254,"total_committers":4,"mean_commits":63.5,"dds":"0.027559055118110187","last_synced_commit":"ee938ca3c1ce9981a171f8f124271424d6774da9"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toilal%2Frebulk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toilal%2Frebulk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toilal%2Frebulk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toilal%2Frebulk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Toilal","download_url":"https://codeload.github.com/Toilal/rebulk/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247299833,"owners_count":20916190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T12:21:59.472Z","updated_at":"2025-04-05T07:08:07.561Z","avatar_url":"https://github.com/Toilal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"ReBulk\n======\n\n[![Latest Version](http://img.shields.io/pypi/v/rebulk.svg)](https://pypi.python.org/pypi/rebulk)\n[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg)](https://pypi.python.org/pypi/rebulk)\n[![Build Status](https://img.shields.io/github/workflow/status/Toilal/rebulk/ci)](https://github.com/Toilal/rebulk/actions?query=workflow%3Aci)\n[![Coveralls](http://img.shields.io/coveralls/Toilal/rebulk.svg)](https://coveralls.io/r/Toilal/rebulk?branch=master)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/relekang/python-semantic-release)\n\n\nReBulk is a python library that performs advanced searches in strings\nthat would be hard to implement using [re\nmodule](https://docs.python.org/3/library/re.html) or [String\nmethods](https://docs.python.org/3/library/stdtypes.html#str) only.\n\nIt includes some features like `Patterns`, `Match`, `Rule` that allows\ndevelopers to build a custom and complex string matcher using a readable\nand extendable API.\n\nThis project is hosted on GitHub: \u003chttps://github.com/Toilal/rebulk\u003e\n\nInstall\n=======\n\n```sh\n$ pip install rebulk\n```\n\nUsage\n=====\n\nRegular expression, string and function based patterns are declared in a\n`Rebulk` object. It use a fluent API to chain `string`, `regex`, and\n`functional` methods to define various patterns types.\n\n```python\n\u003e\u003e\u003e from rebulk import Rebulk\n\u003e\u003e\u003e bulk = Rebulk().string('brown').regex(r'qu\\w+').functional(lambda s: (20, 25))\n```\n\nWhen `Rebulk` object is fully configured, you can call `matches` method\nwith an input string to retrieve all `Match` objects found by registered\npattern.\n\n```python\n\u003e\u003e\u003e bulk.matches(\"The quick brown fox jumps over the lazy dog\")\n[\u003cbrown:(10, 15)\u003e, \u003cquick:(4, 9)\u003e, \u003cjumps:(20, 25)\u003e]\n```\n\nIf multiple `Match` objects are found at the same position, only the\nlonger one is kept.\n\n```python\n\u003e\u003e\u003e bulk = Rebulk().string('lakers').string('la')\n\u003e\u003e\u003e bulk.matches(\"the lakers are from la\")\n[\u003clakers:(4, 10)\u003e, \u003cla:(20, 22)\u003e]\n```\n\nString Patterns\n===============\n\nString patterns are based on\n[str.find](https://docs.python.org/3/library/stdtypes.html#str.find)\nmethod to find matches, but returns all matches in the string.\n`ignore_case` can be enabled to ignore case.\n\n```python\n\u003e\u003e\u003e Rebulk().string('la').matches(\"lalalilala\")\n[\u003cla:(0, 2)\u003e, \u003cla:(2, 4)\u003e, \u003cla:(6, 8)\u003e, \u003cla:(8, 10)\u003e]\n\n\u003e\u003e\u003e Rebulk().string('la').matches(\"LalAlilAla\")\n[\u003cla:(8, 10)\u003e]\n\n\u003e\u003e\u003e Rebulk().string('la', ignore_case=True).matches(\"LalAlilAla\")\n[\u003cLa:(0, 2)\u003e, \u003clA:(2, 4)\u003e, \u003clA:(6, 8)\u003e, \u003cla:(8, 10)\u003e]\n```\n\nYou can define several patterns with a single `string` method call.\n\n```python\n\u003e\u003e\u003e Rebulk().string('Winter', 'coming').matches(\"Winter is coming...\")\n[\u003cWinter:(0, 6)\u003e, \u003ccoming:(10, 16)\u003e]\n```\n\nRegular Expression Patterns\n===========================\n\nRegular Expression patterns are based on a compiled regular expression.\n[re.finditer](https://docs.python.org/3/library/re.html#re.finditer)\nmethod is used to find matches.\n\nIf [regex module](https://pypi.python.org/pypi/regex) is available, it\ncan be used by rebulk instead of default [re\nmodule](https://docs.python.org/3/library/re.html). Enable it with `REBULK_REGEX_ENABLED=1` environment variable.\n\n```python\n\u003e\u003e\u003e Rebulk().regex(r'l\\w').matches(\"lolita\")\n[\u003clo:(0, 2)\u003e, \u003cli:(2, 4)\u003e]\n```\n\nYou can define several patterns with a single `regex` method call.\n\n```python\n\u003e\u003e\u003e Rebulk().regex(r'Wint\\wr', r'com\\w{3}').matches(\"Winter is coming...\")\n[\u003cWinter:(0, 6)\u003e, \u003ccoming:(10, 16)\u003e]\n```\n\nAll keyword arguments from\n[re.compile](https://docs.python.org/3/library/re.html#re.compile) are\nsupported.\n\n```python\n\u003e\u003e\u003e import re  # import required for flags constant\n\u003e\u003e\u003e Rebulk().regex('L[A-Z]KERS', flags=re.IGNORECASE) \\\n...         .matches(\"The LaKeRs are from La\")\n[\u003cLaKeRs:(4, 10)\u003e]\n\n\u003e\u003e\u003e Rebulk().regex('L[A-Z]', 'L[A-Z]KERS', flags=re.IGNORECASE) \\\n...         .matches(\"The LaKeRs are from La\")\n[\u003cLa:(20, 22)\u003e, \u003cLaKeRs:(4, 10)\u003e]\n\n\u003e\u003e\u003e Rebulk().regex(('L[A-Z]', re.IGNORECASE), ('L[a-z]KeRs')) \\\n...         .matches(\"The LaKeRs are from La\")\n[\u003cLa:(20, 22)\u003e, \u003cLaKeRs:(4, 10)\u003e]\n```\n\nIf [regex module](https://pypi.python.org/pypi/regex) is available, it\nautomatically supports repeated captures.\n\n```python\n\u003e\u003e\u003e # If regex module is available, repeated_captures is True by default.\n\u003e\u003e\u003e matches = Rebulk().regex(r'(\\d+)(?:-(\\d+))+').matches(\"01-02-03-04\")\n\u003e\u003e\u003e matches[0].children # doctest:+SKIP\n[\u003c01:(0, 2)\u003e, \u003c02:(3, 5)\u003e, \u003c03:(6, 8)\u003e, \u003c04:(9, 11)\u003e]\n\n\u003e\u003e\u003e # If regex module is not available, or if repeated_captures is forced to False.\n\u003e\u003e\u003e matches = Rebulk().regex(r'(\\d+)(?:-(\\d+))+', repeated_captures=False) \\\n...                   .matches(\"01-02-03-04\")\n\u003e\u003e\u003e matches[0].children\n[\u003c01:(0, 2)+initiator=01-02-03-04\u003e, \u003c04:(9, 11)+initiator=01-02-03-04\u003e]\n```\n\n-   `abbreviations`\n\n    Defined as a list of 2-tuple, each tuple is an abbreviation. It\n    simply replace `tuple[0]` with `tuple[1]` in the expression.\n\n    \\\u003e\\\u003e\\\u003e Rebulk().regex(r\\'Custom-separators\\',\n    abbreviations=\\[(\\\"-\\\", r\\\"\\[W\\_\\]+\\\")\\])\\...\n    .matches(\\\"Custom\\_separators using-abbreviations\\\")\n    \\[\\\u003cCustom\\_separators:(0, 17)\\\u003e\\]\n\nFunctional Patterns\n===================\n\nFunctional Patterns are based on the evaluation of a function.\n\nThe function should have the same parameters as `Rebulk.matches` method,\nthat is the input string, and must return at least start index and end\nindex of the `Match` object.\n\n```python\n\u003e\u003e\u003e def func(string):\n...     index = string.find('?')\n...     if index \u003e -1:\n...         return 0, index - 11\n\u003e\u003e\u003e Rebulk().functional(func).matches(\"Why do simple ? Forget about it ...\")\n[\u003cWhy:(0, 3)\u003e]\n```\n\nYou can also return a dict of keywords arguments for `Match` object.\n\nYou can define several patterns with a single `functional` method call,\nand function used can return multiple matches.\n\nChain Patterns\n==============\n\nChain Patterns are ordered composition of string, functional and regex\npatterns. Repeater can be set to define repetition on chain part.\n\n```python\n\u003e\u003e\u003e r = Rebulk().regex_defaults(flags=re.IGNORECASE)\\\n...             .defaults(children=True, formatter={'episode': int, 'version': int})\\\n...             .chain()\\\n...             .regex(r'e(?P\u003cepisode\u003e\\d{1,4})').repeater(1)\\\n...             .regex(r'v(?P\u003cversion\u003e\\d+)').repeater('?')\\\n...             .regex(r'[ex-](?P\u003cepisode\u003e\\d{1,4})').repeater('*')\\\n...             .close() # .repeater(1) could be omitted as it's the default behavior\n\u003e\u003e\u003e r.matches(\"This is E14v2-15-16-17\").to_dict()  # converts matches to dict\nMatchesDict([('episode', [14, 15, 16, 17]), ('version', 2)])\n```\n\nPatterns parameters\n===================\n\nAll patterns have options that can be given as keyword arguments.\n\n-   `validator`\n\n    Function to validate `Match` value given by the pattern. Can also be\n    a `dict`, to use `validator` with pattern named with key.\n\n    ```python\n    \u003e\u003e\u003e def check_leap_year(match):\n    ...     return int(match.value) in [1980, 1984, 1988]\n    \u003e\u003e\u003e matches = Rebulk().regex(r'\\d{4}', validator=check_leap_year) \\\n    ...                   .matches(\"In year 1982 ...\")\n    \u003e\u003e\u003e len(matches)\n    0\n    \u003e\u003e\u003e matches = Rebulk().regex(r'\\d{4}', validator=check_leap_year) \\\n    ...                   .matches(\"In year 1984 ...\")\n    \u003e\u003e\u003e len(matches)\n    1\n    ```\n\nSome base validator functions are available in `rebulk.validators`\nmodule. Most of those functions have to be configured using\n`functools.partial` to map them to function accepting a single `match`\nargument.\n\n-   `formatter`\n\n    Function to convert `Match` value given by the pattern. Can also be\n    a `dict`, to use `formatter` with matches named with key.\n\n    ```python\n    \u003e\u003e\u003e def year_formatter(value):\n    ...     return int(value)\n    \u003e\u003e\u003e matches = Rebulk().regex(r'\\d{4}', formatter=year_formatter) \\\n    ...                   .matches(\"In year 1982 ...\")\n    \u003e\u003e\u003e isinstance(matches[0].value, int)\n    True\n    ```\n\n-   `pre_match_processor` / `post_match_processor`\n\n    Function to mutagen or invalidate a match generated by a pattern.\n\n    Function has a single parameter which is the Match object. If\n    function returns False, it will be considered as an invalid match.\n    If function returns a match instance, it will replace the original\n    match with this instance in the process.\n\n-   `post_processor`\n\n    Function to change the default output of the pattern. Function\n    parameters are Matches list and Pattern object.\n\n-   `name`\n\n    The name of the pattern. It is automatically passed to `Match`\n    objects generated by this pattern.\n\n-   `tags`\n\n    A list of string that qualifies this pattern.\n\n-   `value`\n\n    Override value property for generated `Match` objects. Can also be a\n    `dict`, to use `value` with pattern named with key.\n\n-   `validate_all`\n\n    By default, validator is called for returned `Match` objects only.\n    Enable this option to validate them all, parent and children\n    included.\n\n-   `format_all`\n\n    By default, formatter is called for returned `Match` values only.\n    Enable this option to format them all, parent and children included.\n\n-   `disabled`\n\n    A `function(context)` to disable the pattern if returning `True`.\n\n-   `children`\n\n    If `True`, all children `Match` objects will be retrieved instead of\n    a single parent `Match` object.\n\n-   `private`\n\n    If `True`, `Match` objects generated from this pattern are available\n    internally only. They will be removed at the end of `Rebulk.matches`\n    method call.\n\n-   `private_parent`\n\n    Force parent matches to be returned and flag them as private.\n\n-   `private_children`\n\n    Force children matches to be returned and flag them as private.\n\n-   `private_names`\n\n    Matches names that will be declared as private\n\n-   `ignore_names`\n\n    Matches names that will be ignored from the pattern output, after\n    validation.\n\n-   `marker`\n\n    If `true`, `Match` objects generated from this pattern will be\n    markers matches instead of standard matches. They won\\'t be included\n    in `Matches` sequence, but will be available in `Matches.markers`\n    sequence (see `Markers` section).\n\nMatch\n=====\n\nA `Match` object is the result created by a registered pattern.\n\nIt has a `value` property defined, and position indices are available\nthrough `start`, `end` and `span` properties.\n\nIn some case, it contains children `Match` objects in `children`\nproperty, and each child `Match` object reference its parent in `parent`\nproperty. Also, a `name` property can be defined for the match.\n\nIf groups are defined in a Regular Expression pattern, each group match\nwill be converted to a single `Match` object. If a group has a name\ndefined (`(?P\u003cname\u003egroup)`), it is set as `name` property in a child\n`Match` object. The whole regexp match (`re.group(0)`) will be converted\nto the main `Match` object, and all subgroups (1, 2, \\... n) will be\nconverted to `children` matches of the main `Match` object.\n\n```python\n\u003e\u003e\u003e matches = Rebulk() \\\n...         .regex(r\"One, (?P\u003cone\u003e\\w+), Two, (?P\u003ctwo\u003e\\w+), Three, (?P\u003cthree\u003e\\w+)\") \\\n...         .matches(\"Zero, 0, One, 1, Two, 2, Three, 3, Four, 4\")\n\u003e\u003e\u003e matches\n[\u003cOne, 1, Two, 2, Three, 3:(9, 33)\u003e]\n\u003e\u003e\u003e for child in matches[0].children:\n...     '%s = %s' % (child.name, child.value)\n'one = 1'\n'two = 2'\n'three = 3'\n```\n\nIt\\'s possible to retrieve only children by using `children` parameters.\nYou can also customize the way structure is generated with `every`,\n`private_parent` and `private_children` parameters.\n\n```python\n\u003e\u003e\u003e matches = Rebulk() \\\n...         .regex(r\"One, (?P\u003cone\u003e\\w+), Two, (?P\u003ctwo\u003e\\w+), Three, (?P\u003cthree\u003e\\w+)\", children=True) \\\n...         .matches(\"Zero, 0, One, 1, Two, 2, Three, 3, Four, 4\")\n\u003e\u003e\u003e matches\n[\u003c1:(14, 15)+name=one+initiator=One, 1, Two, 2, Three, 3\u003e, \u003c2:(22, 23)+name=two+initiator=One, 1, Two, 2, Three, 3\u003e, \u003c3:(32, 33)+name=three+initiator=One, 1, Two, 2, Three, 3\u003e]\n```\n\nMatch object has the following properties that can be given to Pattern\nobjects\n\n-   `formatter`\n\n    Function to convert `Match` value given by the pattern. Can also be\n    a `dict`, to use `formatter` with matches named with key.\n\n    ```python\n    \u003e\u003e\u003e def year_formatter(value):\n    ...     return int(value)\n    \u003e\u003e\u003e matches = Rebulk().regex(r'\\d{4}', formatter=year_formatter) \\\n    ...                   .matches(\"In year 1982 ...\")\n    \u003e\u003e\u003e isinstance(matches[0].value, int)\n    True\n    ```\n\n-   `format_all`\n\n    By default, formatter is called for returned `Match` values only.\n    Enable this option to format them all, parent and children included.\n\n-   `conflict_solver`\n\n    A `function(match, conflicting_match)` used to solve conflict.\n    Returned object will be removed from matches by `ConflictSolver`\n    default rule. If `__default__` string is returned, it will fallback\n    to default behavior keeping longer match.\n\nMatches\n=======\n\nA `Matches` object holds the result of `Rebulk.matches` method call.\nIt\\'s a sequence of `Match` objects and it behaves like a list.\n\nAll methods accepts a `predicate` function to filter `Match` objects\nusing a callable, and an `index` int to retrieve a single element from\ndefault returned matches.\n\nIt has the following additional methods and properties on it.\n\n-   `starting(index, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects that starts at given index.\n\n-   `ending(index, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects that ends at given index.\n\n-   `previous(match, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects that are previous and nearest to\n    match.\n\n-   `next(match, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects that are next and nearest to\n    match.\n\n-   `tagged(tag, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects that have the given tag defined.\n\n-   `named(name, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects that have the given name.\n\n-   `range(start=0, end=None, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects for given range, sorted from\n    start to end.\n\n-   `holes(start=0, end=None, formatter=None, ignore=None, predicate=None, index=None)`\n\n    Retrieves a list of *hole* `Match` objects for given range. A hole\n    match is created for each range where no match is available.\n\n-   `conflicting(match, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects that conflicts with given match.\n\n-   `chain_before(self, position, seps, start=0, predicate=None, index=None)`:\n\n    Retrieves a list of chained matches, before position, matching\n    predicate and separated by characters from seps only.\n\n-   `chain_after(self, position, seps, end=None, predicate=None, index=None)`:\n\n    Retrieves a list of chained matches, after position, matching\n    predicate and separated by characters from seps only.\n\n-   `at_match(match, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects at the same position as match.\n\n-   `at_span(span, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects from given (start, end) tuple.\n\n-   `at_index(pos, predicate=None, index=None)`\n\n    Retrieves a list of `Match` objects from given position.\n\n-   `names`\n\n    Retrieves a sequence of all `Match.name` properties.\n\n-   `tags`\n\n    Retrieves a sequence of all `Match.tags` properties.\n\n-   `to_dict(details=False, first_value=False, enforce_list=False)`\n\n    Convert to an ordered dict, with `Match.name` as key and\n    `Match.value` as value.\n\n    It\\'s a subclass of\n    [OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict),\n    that contains a `matches` property which is a dict with `Match.name`\n    as key and list of `Match` objects as value.\n\n    If `first_value` is `True` and distinct values are found for the\n    same name, value will be wrapped to a list. If `False`, first value\n    only will be kept and values lists can be retrieved with\n    `values_list` which is a dict with `Match.name` as key and list of\n    `Match.value` as value.\n\n    if `enforce_list` is `True`, all values will be wrapped to a list,\n    even if a single value is found.\n\n    If `details` is True, `Match.value` objects are replaced with\n    complete `Match` object.\n\n-   `markers`\n\n    A custom `Matches` sequences specialized for `markers` matches (see\n    below)\n\nMarkers\n=======\n\nIf you have defined some patterns with `markers` property, then\n`Matches.markers` points to a special `Matches` sequence that contains\nonly `markers` matches. This sequence supports all methods from\n`Matches`.\n\nMarkers matches are not intended to be used in final result, but can be\nused to implement a `Rule`.\n\nRules\n=====\n\nRules are a convenient and readable way to implement advanced\nconditional logic involving several `Match` objects. When a rule is\ntriggered, it can perform an action on `Matches` object, like filtering\nout, adding additional tags or renaming.\n\nRules are implemented by extending the abstract `Rule` class. They are\nregistered using `Rebulk.rule` method by giving either a `Rule`\ninstance, a `Rule` class or a module containing `Rule classes` only.\n\nFor a rule to be triggered, `Rule.when` method must return `True`, or a\nnon empty list of `Match` objects, or any other truthy object. When\ntriggered, `Rule.then` method is called to perform the action with\n`when_response` parameter defined as the response of `Rule.when` call.\n\nInstead of implementing `Rule.then` method, you can define `consequence`\nclass property with a Consequence classe or instance, like\n`RemoveMatch`, `RenameMatch` or `AppendMatch`. You can also use a list\nof consequence when required : `when_response` must then be iterable,\nand elements of this iterable will be given to each consequence in the\nsame order.\n\nWhen many rules are registered, it can be useful to set `priority` class\nvariable to define a priority integer between all rule executions\n(higher priorities will be executed first). You can also define\n`dependency` to declare another Rule class as dependency for the current\nrule, meaning that it will be executed before.\n\nFor all rules with the same `priority` value, `when` is called before,\nand `then` is called after all.\n\n```python\n\u003e\u003e\u003e from rebulk import Rule, RemoveMatch\n\n\u003e\u003e\u003e class FirstOnlyRule(Rule):\n...     consequence = RemoveMatch\n...\n...     def when(self, matches, context):\n...         grabbed = matches.named(\"grabbed\", 0)\n...         if grabbed and matches.previous(grabbed):\n...             return grabbed\n\n\u003e\u003e\u003e rebulk = Rebulk()\n\n\u003e\u003e\u003e rebulk.regex(\"This match(.*?)grabbed\", name=\"grabbed\")\n\u003c...Rebulk object ...\u003e\n\u003e\u003e\u003e rebulk.regex(\"if it's(.*?)first match\", private=True)\n\u003c...Rebulk object at ...\u003e\n\u003e\u003e\u003e rebulk.rules(FirstOnlyRule)\n\u003c...Rebulk object at ...\u003e\n\n\u003e\u003e\u003e rebulk.matches(\"This match is grabbed only if it's the first match\")\n[\u003cThis match is grabbed:(0, 21)+name=grabbed\u003e]\n\u003e\u003e\u003e rebulk.matches(\"if it's NOT the first match, This match is NOT grabbed\")\n[]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoilal%2Frebulk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoilal%2Frebulk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoilal%2Frebulk/lists"}