Package: ezregex
EZRegexA readable and intuitive way to generate Regular ExpressionsTry my new frontend for this library atezregex.org!TLDR: This is to regular expressions what CMake is to makefilesTable of ContentsUsageInstallationInvertGenerateDialectsDocumentationExplanationTodoLicenseUsageQuickstartfromezregeximport*'foo'+number+optional(whitespace)+word# Matches `foo123abc` and `foo123 abc`# but not `abc123foo` or  `foo bar`Importing as a named package is recommendedimportezregexaser# ow is part of er already as optional whitespaceparams=er.group(er.atLeastNone(er.ow+er.word+er.ow+er.optional(',')+er.ow))function=er.word+er.ow+'('+params+')'# Automatically calls the re.search() function for youfunction%'some string containing func( param1 , param2)'# The test() method is helpful for debugging, and color codes groups for youfunction.test('this should match func(param1,\tparam2 ), foo(), and bar( foo,)').test() will print all the matches, color coded to match and group (not shown here):╭───────────────────────────── Testing Regex ──────────────────────────────╮
│ Testing expression:                                                      │
│         \w+(?:\s+)?\(((?:(?:\s+)?\w+(?:\s+)?,?(?:\s+)?)*)\)              │
│ for matches in:                                                          │
│         this should match func(param1,  param2 ), foo(), and bar( foo,)  │
│                                                                          │
│ Match = "func(param1,  param2 )" (18:39)                                 │
│ Unnamed Groups:                                                          │
│         1: "param1, param2 " (23:38)                                     │
│                                                                          │
│ Match = "foo()" (41:46)                                                  │
│ Unnamed Groups:                                                          │
│         1: "" (45:45)                                                    │
│                                                                          │
│ Match = "bar( foo,)" (52:62)                                             │
│ Unnamed Groups:                                                          │
│         1: " foo," (56:61)                                               │
│                                                                          │
│                                                                          │
╰───────────────────────────────── Found  ─────────────────────────────────╯InstallationEZRegex is distributed onPyPIas a universal
wheel and is available on Linux, macOS and Windows and supports
Python 3.10+ and PyPy.$pipinstallezregexThe import name is the same as the package name:importezregexaserInvertingTheinvertfunction provided (available as er.invert(),expression.invert(), or ~expression) is useful for debugging. You pass it an expression, and it returns an example of a string that is guaranteed to match the provided expression.GenerationIn version 1.7.0 we introduced a new function:generate_regex. It takes in 2 sets of strings, and returns a regular expression that will match everything in the first set and nothing in the second set. It may be a bit crude, but it can be a good starting point if you don't know where to start. It's also really good atregex golf.DialectsAs of version 1.6.0, the concepts ofdialectswas introduced. Different languages often have slight variations on the regular expression syntax. As this library is meant to be language independent (even though it's written in Python), you should be able to compile regular expressions to work with other languages as well. To do that, you can simply import a sub-package, and they should work identically (though some languages may have more features than others):>>>importezregexaser# The python dialect is the defualt dialect>>>er.group(digit,'name')+er.earlierGroup('name')EZRegex("(?P<name>\d)(?P=name)")>>>importezregex.perlaser>>>er.group(digit,'name')+er.earlierGroup('name')EZRegex("?P<name>\d)(\g<name>")The currently implemented dialects are:PythonWell tested, ~99% implementedPerlAlmost identical to the Python dialect because I don't know Perl.If you know a particular flavor of regex and would like to contribute, feel free to make a pull request, or email me atsmartycope@gmail.comDocumentationNotes and GotchasThis documentation is for the Python dialect specifically, as it really is the only one currently implemented.When using the re library, functions like search() and sub() don't accept EZRegexs as valid regex patterns. Be sure to call either .str() or .compile(), or cast to a string when passing to those. Also, be careful to call the function on the entire pattern: chunk + whitespace.str() is not the same as (chunk + whitespace).str().In regular Regex, a lot of random things capture groups for no reason. I find this annoying. All regexes in EZRegex intentionally capture passively, so to capture any groups, use group(), with the optionalnameparameter.All EZRegexs (except forraw) auto-sanitize strings given to them, so there's no need to escape characters or use r strings. Thisdoesmean, however, that you cannot pass actual regex strings to any of them, as they'll think you're talking about it literally (unless you want that, of course). To include already written regex strings, userawNote that I have camelCase and snake_case versions of each of the functions, because I waver back and forth between which I like better. Both versions function identically.Theinputparameter can accept strings, other EZRegexs, or entire sequences of EZRegex patterns.A few of these havegreedyandpossessiveoptional parameters. They can be useful, but can get complicated. Refer tothe Python re docsfor details.PositionalsThese differentiate thestringstarting with a sequence, and alinestarting with a sequence. Do note that the start of the string is also the start of a line. These can also be called without parameters to denote the start/end of a string/line without something specific having to be next to it.stringStartstringEndlineStartlineEndwordBoundaryMatches the boundary of a word, i.e. the empty space between a word character and not a word character, or the end of a string.notWordBoundaryThe opposite ofwordBoundaryLiteralstabspacespaceOrTabnewlinecarriageReturnquoteMatches ', ", and `verticalTabformFeedcommaperiodunderscoreNot LiteralsnotWhitespacenotDigitnotWordCatagorieswhitespacewhitechunkA "chunk" of whitespace. Just any amount of whitespace togetherdigitletterMatches just a letter -- not numbers or _ like wordChar.numberMatches multiple digits next to each other. Does not match negatives or decimalswordwordCharMatches just a single "word character", defined as any letter, number, or _anythingMatches any single character, except a newline. To also match a newline, use literallyAnythingchunkA "chunk": Any clump of characters up until the next newlineuppercaselowercasehexDigitoctDigitpunctuationMatches punctuation. In the Python dialect, there isn't a built-in method of doing this, so I probably forgot a bunch of them.controllerMatches a metadata ASCII charactersprintableMatches printable ASCII charactersprintableAndSpacealphaNumunicodeMatches a unicode character by nameanyBetween(char, and_char)Match any char betweencharandand_char, using the ASCII table for referenceAmountsmatchMax(input)Match as many ofinputin the string as you can. This is equivelent to using the unary + operator.
Ifinputis not provided, it works on the previous regex pattern. That's not recommended for
clarity's sake thoughamt(num, input)Matchnumamount ofinputin the stringmoreThan(min, input)Match more thanminsequences ofinputin the stringmatchRange(min, max, input, greedy=True, possessive=False)Match betweenminandmaxsequences ofinputin the string. This also acceptsgreedyandpossessiveparameters
Max can be an empty string to indicate no maximum
greedy means it will try to match as many repititions as possible
non-greedy will try to match as few repititions as possible
possessive means it won't backtrack to try to find any repitions
seehttps://docs.python.org/3/library/re.htmlfor more helpatLeast(min, input)Match at leastminsequences ofinputin the stringatMost(max, input)Match at mostmaxinstances ofinputin the stringatLeastOne(input, greedy=True, possessive=False)Match at least one ofinputin the string. This also acceptsgreedyandpossessiveparameters
greedy means it will try to match as many repititions as possible
non-greedy will try to match as few repititions as possible
possessive means it won't backtrack to try to find any repitions
seehttps://docs.python.org/3/library/re.htmlfor more helpatLeastNone(input, greedy=True, possessive=False)Match 0 or more sequences ofinput. This also acceptsgreedyandpossessiveparameters
greedy means it will try to match as many repititions as possible
non-greedy will try to match as few repititions as possible
possessive means it won't backtrack to try to find any repitions
seehttps://docs.python.org/3/library/re.htmlfor more helpChoicesoptional(input, greedy=True, possessive=False)Matchinputif it's there. This also acceptsgreedyandpossessiveparameters
greedy means it will try to match as many repititions as possible
non-greedy will try to match as few repititions as possible
possessive means it won't backtrack to try to find any repitions
seehttps://docs.python.org/3/library/re.htmlfor more helpeither(input, or_input)oneOf(*inputs, chars=None, split=None)Match any of the giveninputs. Note thatinputscan be multiple parameters,
or a single string. Can also accept parameters chars and split. If char is set
to True, theninputsmust only be a single string, it interpretsinputsas characters, and splits it up to find any of the chars in the string. If
split is set to true, it forces the ?(...) regex syntax instead of the [...]
syntax. It should act the same way, but your output regex will look different.
By default, it just optimizes it for you.anyCharExcept(*inputs)This matches any char that is NOT ininputs.inputscan be multiple parameters, or a single string of chars to split.anyExcept(input, type='.*')Matches anything other thaninput, which must be a single string or
EZRegex chain,nota list. Also optionally accepts thetypeparameter,
which works like this: "Match anytypeother thaninput". For example,
"match any word which is not foo". Do note that this function is new, and
I'm still working out the kinks.ConditionalsifFollowedBy(input)Matches the pattern if it hasinputcoming after it. Can only be used once in a given pattern,
as it only applies to the endifNotFollowedBy(input)Matches the pattern if it doesnothaveinputcoming after it. Can only be used once in
a given pattern, as it only applies to the endifPrecededBy(input)Matches the pattern if it hasinputcoming before it. Can only be used once in a given pattern,
as it only applies to the beginningifNotPrecededBy(input)Matches the pattern if it doesnothaveinputcoming before it. Can only be used once
in a given pattern, as it only applies to the beginningifEnclosedWith(open, stuff, close=None)Matches if the string hasopen, thenstuff, thenclose, but only "matches"
stuff. Just a convenience combination of ifProceededBy and ifPreceededBy.Groupinggroup(input, name: str = None)Causesinputto be captured as an unnamed group. Only useful when replacing regexsearlierGroup(num_or_name)Matches whatever the group referenced bynum_or_namematched earlier. Must beaftera
group which would matchnum_or_name.ifExists(num_or_name, does, doesnt=None)Matchesdoesif the groupnum_or_nameexists, otherwise it matchesdoesntpassiveGroup(input)As all regexs in EZRegex capture passively, this is entirely useless. But if you really want to, here it isnamedGroup(name, input)Causesinputto be captured as a named group, with the namename. Only useful when replacing regexsReplacementIn the intrest of "I don't want to think about any syntax at all", I have included replace members. Do note that they are not interoperable with the other EZRegexs, and can only be used with other strings and each other.rgroup(num_or_name: str | int)Puts in its place the group specified, either by group number (for unnamed
groups) or group name (for named groups). Named groups are also counted by
number, I'm pretty sure. Groups are numbered starting from 1.replaceEntirePuts in its place the entire matchreplace(string: str, rtn_str: bool=False)Generates a valid regex replacement string, using Python f-string like syntax.Example:replace("named: {group}, numbered: {1}, entire: {0}")Like Python f-strings, use {{ and }} to specify { and }Set thertn_strparameter to True to have it return an EZRegex type instead of a stringNote: Remember that index 0 is the entire matchThere's a few of advantages to using this instead of just the regular regex replacement syntax:It's consistent between dialectsIt's closer to Python f-string syntax, which is cleaner and more familiarIt handles numbered, named, and entire replacement types the samePremadeThese are some useful combinations that may be commonly used. They are not as stable, and may be changed and added to in later versions to make them more accurateliterallyAnythingAnycharacter, include newlinesigneda signed number, including 123, -123, and +123unsignedSame as number. Will not match +123plain_floatWill match 123.45 and 123.full_floatWill match plain_float as well as things like 1.23e-10 and 1.23e+10int_or_floatow"Optional Whitechunk"emailMatches an emailversionTheofficialregex for matching version numbers fromhttps://semver.org/. It includes 5 groups that can be matched/replaced:major,minor,patch,prerelease, andbuildmetadataversion_numberedSame asversion, but it uses numbered groups for each version number instead of named groupsMiscliteral(input)This is a redundant function. You should always be able to use... + 'stuff'just as easily as... + literal('stuff')isExactly(input)This matches the string if and only if the entire string is exactly equal toinputraw(regex)If you already have some regular regex written, and you want to incorperate
it, this will allow you to include it without sanatizing all the backslaches
and such, which all the other EZRegexs do automatically.FlagsThese shadow python regex flags, and can just as easily be specified directly to the re library instead. They're provided here for compatibility with other regex dialects. Seehttps://docs.python.org/3/library/re.html#flagsfor detailsASCIIDOTALLIGNORECASELOCALEMULTILINEUNICODEOperators+,<<,>>These all do the same thing: combine expressions*Multiplies an expression a number of times.expr * 3is equivelent toexpr + expr + expr. Can also be used likeexpr * ...is equivalent toanyAmt(expr)+A unary + operator acts exactly as a match_max() does, or, if you're familiar with regex syntax, the + operator[]expr[2, 3] is equivalent tomatch_range(2, 3, expr)expr[2, ...] or expr[2,] is equivalent toat_least(2, expr)expr[... , 2] is equivalent toat_most(2, expr)expr[...] or expr[0, ...] is equivelent toat_least_0(expr)expr[1, ...] is equivalent toat_least_1(expr)&Coming soon! This will work like the + operator, but they can be out of order. Like an and operation.|Coming soon! This will work like an or operation, which will work just like anyOf()%This automatically calls re.search() for you and returns the match object (or None). Use like this:(digit * 2) % '99 beers on the wall'~This inverts the expression. This is equivalent to calling the .invert() methodExplanation of How it WorksEverything relies on the EZRegex class. In the __init__ file of the package, I have defined a ton of pre-made EZRegexs which mimic all (or at least as many as I can) fundamental parts of the regex syntax, plus a few others which are common combinations (like chunk or whitechunk). These have operators overloaded so you can combine them in intuitive ways and call them by intuitive names. All EZRegexs take a function parameter (or a string which gets converted to a function for convenience), which gets called with the current regex expression and any parameters passed along when the instance gets called with the () operator. That way you can add things to the front or back of an expression for example, and you can change what exactly gets added to the current expression based on other parameters. You can also chain strings together, and pass them as parameters to other EZRegexs, which auto-compiles them and adds them appropriately.I also have everything which could capture a group capture it passively, except for actual group operators, and always have the (?m) (multiline) flag automatically asserted whenever lineStart/lineEnd are used so as to differentiate between capturing at the beginning/end of a string and the beginning/end of a line.TodoSeethe todo.
Eventually, I would like to move the todo to GitHub issues.LicenseEZRegex is distributed under theMIT License