Getting Started

Install

go get github.com/floatpane/go-mailpatch

Requires Go 1.26+. No other dependencies.

Your first parse

Pipe a real patch straight from git:

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/floatpane/go-mailpatch"
)

func main() {
	p, err := mailpatch.Parse(os.Stdin)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s <%s>\n", p.AuthorName, p.AuthorEmail)
	fmt.Printf("Subject: %s\n", p.Subject)
	if p.Series.Total > 0 {
		fmt.Printf("Patch %d of %d (v%d)\n",
			p.Series.Index, p.Series.Total, p.Series.Version)
	}
	for _, f := range p.Files {
		fmt.Printf("  %-8s %s  +%d -%d\n",
			f.Type, f.Path(), f.Additions, f.Deletions)
	}
}
$ git format-patch -1 --stdout | go run .
Ada Lovelace <ada@example.com>
Subject: parser: handle empty input
Patch 2 of 3 (v1)
  modified parser.go  +3 -0

The three entry points

FunctionInputReturns
Parse / ParseBytesone emaila single *Patch
ParseMboxan mbox stream[]*Patch, one per message, in file order
ParseSeriesan mbox streama *Series: cover letter + ordered patches
ParseDiffa bare diff (no email)[]FileChange
p, err := mailpatch.Parse(reader)          // single message
p, err := mailpatch.ParseBytes(data)       // single message, in memory
all, err := mailpatch.ParseMbox(reader)    // every message
s, err := mailpatch.ParseSeries(reader)    // grouped into a series
files, err := mailpatch.ParseDiff(text)    // just a diff

Errors

import "errors"

p, err := mailpatch.ParseBytes(data)
switch {
case errors.Is(err, mailpatch.ErrEmpty):
	// no message in the input
case errors.Is(err, mailpatch.ErrMalformed):
	// not a parseable RFC 5322 message
case err != nil:
	// other I/O error
}
Note

A message with no diff — most often a 0/n cover letter — is not an error. It parses into a Patch whose HasDiff() is false and IsCoverLetter() is true.