Parses a versionupgrade configuration file. Example:

      tag v{VERSION}
      branch v{VERSION}
      message Prepare {VERSION} release
      upgrade setup.py:  version = '{VERSION}'
      upgrade __init__.py:__version__ = '{VERSION}'
      sub docs/changelog/v{VERSION}.md:# v{VERSION} (unreleased):# v{VERSION} ({DATE})

  Available commands:

    - tag: Create a Git tag with the specified name.
    - branch: Create a Git branch with the specified name.
    - message: The commit message for upgraded version numbers.
    - upgrade: Upgrade the version number in the file matching the pattern.
               The same file may be listed multiple times. The pattern may
               actually be a regular expression and will be searched in
               every line of the file.
    - sub: Specify a file where the part of the file matching the first string
           will be replaced by the second string.

  Returns a #Config object.

def parse_config(filename):
  """
  Parses a versionupgrade configuration file. Example:

      tag v{VERSION}
      branch v{VERSION}
      message Prepare {VERSION} release
      upgrade setup.py:  version = '{VERSION}'
      upgrade __init__.py:__version__ = '{VERSION}'
      sub docs/changelog/v{VERSION}.md:# v{VERSION} (unreleased):# v{VERSION} ({DATE})

  Available commands:

    - tag: Create a Git tag with the specified name.
    - branch: Create a Git branch with the specified name.
    - message: The commit message for upgraded version numbers.
    - upgrade: Upgrade the version number in the file matching the pattern.
               The same file may be listed multiple times. The pattern may
               actually be a regular expression and will be searched in
               every line of the file.
    - sub: Specify a file where the part of the file matching the first string
           will be replaced by the second string.

  Returns a #Config object.
  """

  tag = None
  branch = None
  message = 'Prepare {VERSION} release.'
  upgrades = {}
  subs = {}

  with open(filename) as fp:
    for i, line in enumerate(fp):
      line = line.strip()
      if not line or line.startswith('#'): continue

      key, sep, value = line.partition(' ')
      if not key or not value:
        raise ValueError('invalid configuration file at line {}'.format(i+1))

      if key == 'tag':
        tag = value.strip()
      elif key == 'branch':
        branch = value.strip()
      elif key == 'message':
        message = value.strip()
      elif key == 'upgrade':
        filename, sep, pattern = value.partition(':')
        if not filename or not sep or not pattern or '{VERSION}' not in pattern:
          raise ValueError('invalid upgrade argument at line {}'.format(i+1))
        upgrade = upgrades.setdefault(filename, [])
        upgrade.append(pattern)
      elif key == 'sub':
        filename, sep, pattern = value.partition(':')
        pattern = pattern.partition(':')[::2]
        if not pattern[0] or not pattern[1]:
          raise ValueError('invalid sub argument at line {}'.format(i+1))
        subs.setdefault(filename, []).append(pattern)
      else:
        raise ValueError('invalid command {!r} at line {}'.format(key, i+1))

  return Config(tag, branch, message, upgrades, subs)