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