docs: def read (path, tabwidth=8, **kwargs):
      """Read a typed tabular text file into a stream of Holders.
  
      Arguments:
  
      path
        The path of the file to read.
      tabwidth=8
        The tab width to assume. Please don't monkey with it.
      mode='rt'
        The file open mode (passed to io.open()).
      noexistok=False
        If True and the file is missing, treat it as empty.
      ``**kwargs``
        Passed to io.open ().
  
      Returns a generator for a stream of `pwkit.Holder`s, each of which will
      contain ints, strings, or some kind of measurement (cf `pwkit.msmt`).
  
      """
      datamode = False
      fixedcols = {}
  
      for text in _trimmedlines (path, **kwargs):
          text = text.expandtabs (tabwidth)
  
          if datamode:
              # table row
              h = Holder ()
              h.set (**fixedcols)
              for name, cslice, parser in info:
                  try:
                      v = parser (text[cslice].strip ())
                  except:
                      reraise_context ('while parsing "%s"', text[cslice].strip ())
                  h.set_one (name, v)
              yield h
          elif text[0] != '@':
              # fixed column
              padnamekind, padval = text.split ('=', 1)
              name, parser = _getparser (padnamekind.strip ())
              fixedcols[name] = parser (padval.strip ())
          else:
              # column specification
              n = len (text)
              assert n > 1
              start = 0
              info = []
  
              while start < n:
                  end = start + 1
                  while end < n and (not text[end].isspace ()):
                      end += 1
  
                  if start == 0:
                      namekind = text[start+1:end] # eat leading @
                  else:
                      namekind = text[start:end]
  
                  while end < n and text[end].isspace ():
                      end += 1
  
                  name, parser = _getparser (namekind)
                  if parser is None: # allow columns to be ignored
                      skippedlast = True
                  else:
                      skippedlast = False
                      info.append ((name, slice (start, end), parser))
                  start = end
  
              datamode = True
  
              if not skippedlast:
                  # make our last column go as long as the line goes
                  # (e.g. for "comments" columns)
                  # but if the real last column is ":x"-type, then info[-1]
                  # doesn't run up to the end of the line, so do nothing in that case.
                  lname, lslice, lparser = info[-1]
                  info[-1] = lname, slice (lslice.start, None), lparser