feat(PROJ-15): CLI Import & Export als Subcommands

- archivmail import: EML + MBOX, --file/--dir/--recursive/--dry-run/--json
- archivmail export: EML + MBOX, Filter --from/--to/--date-from/--date-to/--query/--force/--json
- archivmail help / version
- MBOX Parser (SplitMbox) in pkg/mailparser/mbox.go
- Subcommand-Router in main.go ohne externe Abhängigkeit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-14 18:25:21 +01:00
parent dbcc980343
commit 4f93b9667d
6 changed files with 623 additions and 20 deletions
+43
View File
@@ -0,0 +1,43 @@
package mailparser
import (
"bufio"
"bytes"
"strings"
)
// SplitMbox splits a raw mbox file into individual RFC 2822 message bytes.
// Each message starts with a "From " separator line which is skipped.
func SplitMbox(data []byte) [][]byte {
var messages [][]byte
var current bytes.Buffer
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
inMessage := false
for scanner.Scan() {
line := scanner.Text()
// mbox separator: line starts with "From " but not "From:" header
if strings.HasPrefix(line, "From ") && !strings.HasPrefix(line, "From: ") {
if inMessage && current.Len() > 0 {
messages = append(messages, bytes.TrimSpace(current.Bytes()))
current.Reset()
}
inMessage = true
continue
}
if inMessage {
// unescape ">From " lines (mbox quoting)
if strings.HasPrefix(line, ">From ") {
line = line[1:]
}
current.WriteString(line)
current.WriteByte('\n')
}
}
if inMessage && current.Len() > 0 {
messages = append(messages, bytes.TrimSpace(current.Bytes()))
}
return messages
}