PRegEx - Programmable Regular Expressions

repository·main·Indexed 21 days ago

https://github.com/manoss96/pregex

A Python library for constructing complex regular expressions using an imperative, modular, and human-readable syntax. PRegEx abstracts RegEx complexities like escaping and manual grouping by allowing users to compose Pregex objects using classes and operators. It provides a high-level API wrapper over Python's re module, featuring core components for quantifiers, tokens, and character classes, as well as ready-made patterns in the pregex.meta subpackage.

Tokens
19.6K
Snippets
68
Records
85
Agent score
73%

What's inside pregex

  1. Use the empty string pattern as a starting point

    main

    Initializing Pregex() without arguments creates an empty string pattern (''). This is useful for building patterns dynamically in loops.

    An empty Pregex instance has unique identity properties that allow for concise conditional pattern building:

    1. OneOrMore(Pregex()) returns Pregex()
    2. Group(Pregex()) returns Pregex()
    3. Either(Pregex(), 'pattern') returns 'pattern'
    4. FollowedBy('pattern', Pregex()) returns 'pattern'
    from pregex.core.pre import Pregex
    from pregex.core.groups import Capture
    from pregex.core.operators import Either
    from pregex.core.quantifiers import OneOrMore
    
    # Building a pattern conditionally using the empty string pattern
    # If i=11, result is 'a|b|c+'
    pre = Either(
       'a',
       'b' if i > 5 else Pregex(),
       OneOrMore('c' if i > 10 else Pregex())
    ) + Capture('d' if i > 15 else Pregex())
  2. Understand the difference between pregex.core and pregex.meta

    main

    PRegEx is organized into two primary subpackages:

    1. pregex.core: Contains fundamental building blocks. These modules consist of classes that represent individual RegEx operators (like quantifiers), single-character tokens, or character classes. These classes are generally independent of one another.

    2. pregex.meta: Contains high-level, ready-made patterns. These modules provide complex classes that combine multiple pregex.core.pre.Pregex instances to form sophisticated patterns (e.g., matching an integer within a specific range) that can be used "straight out of the box."

  3. Understand the Pregex class and its hierarchy

    main

    The Pregex class is the base class for all pattern abstractions in PRegEx. Every component—whether it is a character class (e.g., AnyDigit), a quantifier (e.g., Optional), an operator (e.g., Either), or an assertion (e.g., FollowedBy)—is an instance of Pregex. This uniform type allows you to compose complex patterns by nesting these instances within one another.

    from pregex.core.pre import Pregex
    from pregex.core.classes import AnyDigit
    from pregex.core.operators import Either
    from pregex.core.assertions import FollowedBy
    
    digit: Pregex = AnyDigit()
    either_a_or_b: Pregex = Either('a', 'b')
    
    # Complex patterns are built by nesting Pregex instances
    digit_followed_by_either_a_or_b: Pregex = FollowedBy(digit, either_a_or_b)
  4. How positive lookahead assertions work in PRegEx

    main

    In RegEx, a positive lookahead assertion checks if a pattern is followed by another pattern without actually consuming any characters. In PRegEx, this is implemented via the FollowedBy class or the .followed_by() method.

    • Using the class: FollowedBy(p1, p2) where p1 and p2 are strings or Pregex instances.
    • Using the method: p1.followed_by(p2) where p1 is a Pregex instance.

    This is useful for asserting properties about a pattern (like 'this word must contain the letter A') before the main pattern construction begins.

    from pregex.core.assertions import FollowedBy
    from pregex.core.pre import Pregex
    
    # Using the class
    pre = FollowedBy('pattern1', 'pattern2')
    
    # Using the method on an existing instance
    wordle = Pregex()
    wordle = wordle.followed_by('some_pattern')
  5. How PRegEx works

    main

    PRegEx (Programmable Regular Expressions) is a Python package that allows you to construct complex Regular Expression patterns using an imperative, human-friendly syntax. Instead of writing raw, declarative RegEx strings, you compose Pregex objects using classes and operators.

    Key benefits include:

    • Imperative Syntax: Uses code structures that resemble standard programming.
    • Automatic Handling: Internally manages grouping and escaping of meta-characters.
    • Modularity: Allows breaking complex patterns into smaller, reusable Pregex components.
    • High-level API: Provides a wrapper over Python's re module, simplifying access to matches and captures without manually handling re.Match objects.
  6. Construct complex patterns with Pregex

    main

    In PRegEx, everything is a Pregex instance. You can build complex patterns by combining simple Pregex instances using operators like + (concatenation), | (alternation/either), and - (subtraction/exclusion).

    Key components include:

    • Classes: AnyLetter(), AnyDigit(), AnyFrom(chars)
    • Quantifiers: Optional(pattern), AtLeastAtMost(pattern, n, m), .at_most(n), .at_least_at_most(n, m)
    • Operators: Either(p1, p2), p1 | p2, p1 + p2, p1 - p2
    • Groups: Capture(pattern)
    from pregex.core.classes import AnyLetter, AnyDigit, AnyFrom
    from pregex.core.quantifiers import Optional, AtLeastAtMost
    from pregex.core.operators import Either
    from pregex.core.groups import Capture
    from pregex.core.pre import Pregex
    
    # Example: Building a pattern for a domain name
    alphanum = AnyLetter() | AnyDigit()
    
    domain_name = \
        alphanum +
        AtLeastAtMost(alphanum | AnyFrom('-', '.'), n=1, m=61) +
        alphanum
  7. Use pregex.core for fundamental RegEx operators and tokens

    main

    The pregex.core subpackage provides the atomic components of a regular expression:

    Quantifiers

    Found in pregex.core.quantifiers, these classes represent standard RegEx quantifiers:

    • Optional: ?
    • Indefinite: *
    • OneOrMore: +
    • Exactly: {n}
    • AtLeast: {n,}
    • AtMost: {,n}
    • AtLeastAtMost: {n,m}

    Tokens

    Found in pregex.core.tokens, these classes act as wrappers for single-character patterns. They help avoid character-escape issues with backslashes and provide easy access to Unicode symbols.

    Character Classes

    Found in pregex.core.classes, this module provides common character classes and a framework to manipulate them using set-like operations (subtraction -, union |, and negation ~).

    from pregex.core.quantifiers import *
    from pregex.core.tokens import Newline, Copyright
    from pregex.core.classes import AnyLetter, AnyDigit
    
    # Quantifiers
    # (Uses classes like Optional, Indefinite, etc.)
    
    # Tokens
    Newline().is_exact_match('\n')
    Copyright().is_exact_match('©')
    
    # Character Class Set Operations
    letter = AnyLetter() # '[A-Za-z]'
    digit_but_five = AnyDigit() - '5' # '[0-4 6-9]'
    letter_or_digit_but_five = letter | digit_but_five # '[A-Za-z0-46-9]'
    any_but_letter_or_digit_but_five = ~ letter_or_digit_but_five # '[^A-Za-z0-46-9]'
  8. Use pre-built patterns from pregex.meta

    main

    For common patterns like URLs or IP addresses, use the pregex.meta subpackage. These classes build upon pregex.core to provide ready-to-use abstractions.

    Commonly used meta classes include:

    • HttpUrl(capture_domain=True, is_extensible=True)
    • IPv4(is_extensible=True)
    from pregex.meta.essentials import HttpUrl, IPv4
    from pregex.core.pre import Pregex
    from pregex.core.operators import Either
    
    pre: Pregex = Either(
        HttpUrl(capture_domain=True, is_extensible=True),
        IPv4(is_extensible=True) + ':1234'
    )
  9. Use pregex.meta for complex ready-made patterns

    main

    The pregex.meta subpackage provides complex patterns that are difficult to build manually. For example, pregex.meta.essentials.Integer allows you to match integers within a specific range.

    Because meta patterns are built from Pregex instances, they can be combined with core components to create even larger patterns. When using a meta pattern as a building block for a larger pattern, set the is_extensible=True parameter. This prevents certain internal assertions from being applied that might otherwise interfere with the combined pattern's ability to match correctly.

    from pregex.meta.essentials import Integer
    from pregex.core.classes import AnyLetter
    
    # Using a meta pattern standalone
    text = "1 5 11 23 77 117 512 789 1011"
    pre = Integer(start=50, end=1000)
    print(pre.get_matches(text)) # ['77', '117', '512', '789']
    
    # Combining meta and core patterns
    # Use is_extensible=True when building larger patterns
    pre = AnyLetter() + Integer(start=50, end=1000, is_extensible=True)
    text = "a1 b5 c11 d23 e77 f117 g512 h789 i1011"
    print(pre.get_matches(text)) # ['e77', 'f117', 'g512', 'h789', 'i1011']
  10. Construct a Pregex pattern using core components

    main

    You can build custom patterns by importing classes from pregex.core. These components (classes, quantifiers, operators, and groups) can be combined using standard Python operators like + (concatenation) and | (alternation/either).

    Common modules in pregex.core include:

    • classes: e.g., AnyLetter, AnyDigit, AnyFrom
    • quantifiers: e.g., Optional, AtLeastAtMost
    • operators: e.g., Either
    • groups: e.g., Capture
    • pre: The base Pregex class
    from pregex.core.classes import AnyLetter, AnyDigit, AnyFrom
    from pregex.core.quantifiers import Optional, AtLeastAtMost
    from pregex.core.operators import Either
    from pregex.core.groups import Capture
    from pregex.core.pre import Pregex
    
    # Define sub-patterns
    http_protocol = Optional('http' + Optional('s') + '://')
    www = Optional('www.')
    alphanum = AnyLetter() | AnyDigit()
    domain_name = alphanum + AtLeastAtMost(alphanum | AnyFrom('-', '.'), n=1, m=61) + alphanum
    tld = '.' + Either('com', 'org')
    
    # Combine into a final Pregex
    pre: Pregex = http_protocol + Either(www + Capture(domain_name) + tld, 'other_pattern')
  11. Importing PRegEx modules

    main

    To avoid importing every class individually, PRegEx provides several convenient ways to handle imports. Using the core import method is recommended as it provides short aliases that reveal the functional category of each class.

    Use from pregex.core import * to access modules via these aliases:

    • asr: pregex.core.assertions
    • cl: pregex.core.classes
    • gr: pregex.core.groups
    • op: pregex.core.operators
    • qu: pregex.core.quantifiers
    • tk: pregex.core.tokens
    • Pregex: The main class pregex.core.pre.Pregex is imported directly.

    Alternative Imports

    • from pregex.meta import *: Imports every class defined within any of the meta modules.
    • from pregex import *: A single statement that replaces both of the above.
    from pregex.core import *
    
    pre = op.Either("Hello", "Bye") + " World" + qu.Optional("!")
    pre.print_pattern() # Prints "(?:Hello|Bye) World!?"