Parsers
Contents
Parsers¶
Parsers convert strings to values. To create a parser for a new type, you can simply subclass
the configpile.parsers.Parser
class, not forgetting that this class takes a type
parameter corresponding to the type of the value it parses.
from configpile import *
class FloatParser(Parser[float]):
def parse(self, arg: str) -> Res[float]:
try:
return float(arg)
except ValueError as e:
return Err.make(str(e))
fp = FloatParser()
fp.parse("2")
2.0
fp.parse("invalid")
Err1(msg="could not convert string to float: 'invalid'", contexts=[])
Parser construction¶
A parser can also be constructed in several ways.
From multiple (string) choices¶
If the multiple choices are strings, use from_choices()
. In
particular, one can force lower or upper case (which we do here).
cp = Parser.from_choices(["red", "green", "blue"], force_case= ForceCase.LOWER)
cp.parse("yellow")
Err1(msg='Value yellow not in choices red,green,blue', contexts=[])
cp.parse("blue")
'blue'
From multiple choices corresponding to values¶
When the strings correspond to values, use from_mapping()
.
The aliases
are alternate strings, they are accepted during the parse but not showed in the
usage description.
bp = Parser.from_mapping({"true": True, "false": False},force_case=ForceCase.LOWER, aliases={"0": False, "1": True})
From functions that raise exceptions¶
In this case, the exceptions will be converted to configpile.userr.Err
instances;
one can specify which exceptions should be caught, and the rest will be propagated.
The static method from_function_that_raises()
is useful for Python
code coming from standard or external libraries.
fp1 = Parser.from_function_that_raises(float, ValueError)
fp1.parse("2")
2.0
fp1.parse("invalid")
Err1(msg="Error 'could not convert string to float: 'invalid'' in 'invalid'", contexts=[])
From functions that return a result¶
One does not need the big ceremonial of constructing a configpile.parsers.Parser
instance
as only one method needs to be implemented.
The static method configpile.parsers.Parser.from_function()
is useful for functions that
you will implement yourself: those functions take a string and return a
configpile.userr.Res
, as discussed in Error handling in configpile.
def parse_fun(arg: str) -> Res[float]:
try:
return float(arg)
except ValueError as e:
return Err.make(str(e))
fp2 = Parser.from_function(parse_fun)
fp2.parse("2")
2.0
fp2.parse("invalid")
Err1(msg="could not convert string to float: 'invalid'", contexts=[])
From parsy parsers¶
parsy
is a parser combinator library, useful to parse more involved expressions.
Here is how you would use this library. Note that configpile
has parsy
as an optional
dependency, so that every configpile
user is not forced to have parsy
as a dependency.
Note that parsy
does not support type hints, so one needs
to provide the type as a first argument to from_parsy_parser()
.
import parsy
int_parsy_parser = parsy.regex('[0-9]+').map(int)
ip: Parser[int] = Parser.from_parsy_parser(int_parsy_parser)
ip.parse("1234")
1234
Parser manipulation¶
Parsers can be adapted as to change their return value, or even modify the parsing behavior.
Handling optional parameters¶
Here, we mean optional in the sense of possibly having a None
value. We take, for example,
our float
parser, and make it optional.
from typing import Optional
fp_opt: Parser[Optional[float]] = fp.empty_means_none()
fp_opt.parse("") is None
True
fp_opt.parse("3.14")
3.14
Handling sequences¶
One can parse sequences of values. Note that the pattern we show below is very crude, and appropriate only when the separator is never part of the strings representing the values.
fp_seq = fp.separated_by(",")
fp_seq.parse("1,2,3,4")
[1.0, 2.0, 3.0, 4.0]
Transforming the parser output¶
One can map the result of a parser through a function.
rounded_fp: Parser[int] = fp.map(int)
rounded_fp.parse("1.23")
1