Announcing yamlparse-applicative, a self-documenting Yaml parsing library

This post announces and showcases yamlparse-applicative.

I am a long-time user of optparse-applicative and am very happy and impressed with it. I particularly like that it does not just handle the parsing of arguments, but it also takes care of documenting the parser that you write automatically. Here is an example output for optparse-applicative:

Usage: intray COMMAND [--config-file FILEPATH] [--url URL]
              [--cache-dir FILEPATH] [--data-dir FILEPATH] ([--no-sync] |
              [--sync])

Available options:
  -h,--help                Show this help text
  --config-file FILEPATH   Give the path to an altenative config file
  --url URL                The url of the server.
  --cache-dir FILEPATH     The directory to use for caching
  --data-dir FILEPATH      The directory to use for data
  --no-sync                Do not try to sync.
  --sync                   Definitely try to sync.

It is beautiful and self-evident to write arguments that would satisfy this parser. However, when it comes to parsing the (Yaml) config files that the same program would read, things were not so easy. After I had written the yaml parser, I would quickly forget how to write a configuration file that would satisfy the parser.

I procrastinated writing documentation for my config file format for a while. At some point my procrastination in writing documentation just became too much for me to handle, and I made yamlparse-applicative instead.

A parser with two functions

Instead of using FromJSON to parse your config file using yaml and then writing documentation for your format, yamlparse-applicative combines those two functions into one type.

A yamlparse-applicative parser allows both actual parsing and generating documentation about what it does. In code, there exists a type Parser i o and these two functions:

data Parser i o where
  [...]

implementParser :: Parser i o -> (i -> Data.Yaml.Parser o)
explainParser :: Parser i o -> Schema

Now you can use a single value of type Parser Yaml.Value FooBar to both parse a FooBar from a Yaml.Value, and to generate a schema that describes how to write a Yaml value that will satisfy this parser.

Usage example

Let's look at an example of a configuration file that is parsed into a value of type Configuration:

data Configuration
  { confPort :: Int
  , confHost :: Maybe Text
  }

I will not even describe what the fields of this type do here, because we will document them as part of the implementation of the yaml parser:

instance YamlSchema Configuration where
  yamlSchema =
    objectParser "Configuration" $ -- Configuration is an object within the yaml file
      Configuration
        <$> optionalFieldWithDefault "port" 8000 "The port to serve web requests on"
        <*> optionalField "host" "The host to serve web requests on"

Now we can get a FromJSON instance for free:

instance FromJSON Configuration where
  parseJSON = viaYamlSchema

We can generate the following documentation automatically using prettySchemaDoc:

# Configuration
port: # optional, default: 8000
  # The port to serve web requests on
  <number>
host: # optional
 # The host to serve web requests on
 <string>

We can even get syntax-highlighted documentation using prettyColourisedSchemaDoc:

Colourised schema

References

This package can be used via Hackage, Stackage and the source is on GitHub.

Previous
Introduction to self-management with Smos: the next-action report

Start your Haskell project from a template

Haskell templates
Next
Self-management: Clarify and Process