mail2couch/go/config/config.go
Ole-Morten Duesund 88a5bfb42b feat: add bash completion generation for command line interface
- Add --generate-bash-completion flag to output bash completion script
- Provide intelligent completions for config files (*.json) and message counts
- Support tab completion for all available command line options
- Generate professional script with proper comments and error handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-02 19:13:15 +02:00

221 lines
6.5 KiB
Go

package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/spf13/pflag"
)
type Config struct {
CouchDb CouchDbConfig `json:"couchDb"`
MailSources []MailSource `json:"mailSources"`
}
type CouchDbConfig struct {
URL string `json:"url"`
User string `json:"user"`
Password string `json:"password"`
}
type MailSource struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
Protocol string `json:"protocol"`
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Mode string `json:"mode"` // "sync" or "archive"
FolderFilter FolderFilter `json:"folderFilter"`
MessageFilter MessageFilter `json:"messageFilter"`
}
type FolderFilter struct {
Include []string `json:"include"`
Exclude []string `json:"exclude"`
}
type MessageFilter struct {
Since string `json:"since,omitempty"`
SubjectKeywords []string `json:"subjectKeywords,omitempty"` // Filter by keywords in subject
SenderKeywords []string `json:"senderKeywords,omitempty"` // Filter by keywords in sender addresses
RecipientKeywords []string `json:"recipientKeywords,omitempty"` // Filter by keywords in recipient addresses
}
func LoadConfig(path string) (*Config, error) {
configFile, err := os.Open(path)
if err != nil {
return nil, err
}
defer configFile.Close()
var config Config
jsonParser := json.NewDecoder(configFile)
if err = jsonParser.Decode(&config); err != nil {
return nil, err
}
// Validate and set defaults for mail sources
for i := range config.MailSources {
source := &config.MailSources[i]
if source.Mode == "" {
source.Mode = "archive" // Default to archive mode
}
if source.Mode != "sync" && source.Mode != "archive" {
return nil, fmt.Errorf("invalid mode '%s' for mail source '%s': must be 'sync' or 'archive'", source.Mode, source.Name)
}
}
return &config, nil
}
// IsSyncMode returns true if the mail source is in sync mode
func (ms *MailSource) IsSyncMode() bool {
return ms.Mode == "sync"
}
// IsArchiveMode returns true if the mail source is in archive mode
func (ms *MailSource) IsArchiveMode() bool {
return ms.Mode == "archive" || ms.Mode == "" // Default to archive
}
// CommandLineArgs holds parsed command line arguments
type CommandLineArgs struct {
ConfigPath string
MaxMessages int
}
// ParseCommandLine parses command line arguments using GNU-style options
func ParseCommandLine() *CommandLineArgs {
var args CommandLineArgs
// Define long options with -- and short options with -
pflag.StringVarP(&args.ConfigPath, "config", "c", "", "Path to configuration file")
pflag.IntVarP(&args.MaxMessages, "max-messages", "m", 0, "Maximum number of messages to process per mailbox per run (0 = no limit)")
// Add utility options
pflag.BoolP("help", "h", false, "Show help message")
pflag.Bool("generate-bash-completion", false, "Generate bash completion script and exit")
pflag.Parse()
// Handle help flag
if help, _ := pflag.CommandLine.GetBool("help"); help {
fmt.Fprintf(os.Stderr, "mail2couch - Email backup utility for CouchDB\n\n")
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Options:\n")
pflag.PrintDefaults()
os.Exit(0)
}
// Handle bash completion generation
if generateCompletion, _ := pflag.CommandLine.GetBool("generate-bash-completion"); generateCompletion {
GenerateBashCompletion()
os.Exit(0)
}
return &args
}
// GenerateBashCompletion generates a bash completion script for mail2couch
func GenerateBashCompletion() {
appName := filepath.Base(os.Args[0])
script := fmt.Sprintf(`#!/bin/bash
# Bash completion script for %s
# Generated automatically by %s --generate-bash-completion
_%s_completions() {
local cur prev words cword
_init_completion || return
case $prev in
-c|--config)
# Complete config files (*.json)
_filedir "json"
return
;;
-m|--max-messages)
# Complete with numbers, suggest common values
COMPREPLY=($(compgen -W "10 50 100 500 1000" -- "$cur"))
return
;;
esac
if [[ $cur == -* ]]; then
# Complete with available options
local opts="-c --config -m --max-messages -h --help --generate-bash-completion"
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
return
fi
# No default completion for other cases
}
# Register the completion function
complete -F _%s_completions %s
# Enable completion for common variations of the command name
if [[ "$(%s --help 2>/dev/null)" =~ "mail2couch" ]]; then
complete -F _%s_completions mail2couch
fi
`, appName, appName, appName, appName, appName, appName, appName)
fmt.Print(script)
}
// FindConfigFile searches for config.json in the following order:
// 1. Path specified by --config/-c flag
// 2. ./config.json (current directory)
// 3. ./config/config.json (config subdirectory)
// 4. ~/.config/mail2couch/config.json (user config directory)
// 5. ~/.mail2couch.json (user home directory)
func FindConfigFile(args *CommandLineArgs) (string, error) {
if args.ConfigPath != "" {
if _, err := os.Stat(args.ConfigPath); err == nil {
return args.ConfigPath, nil
}
return "", fmt.Errorf("specified config file not found: %s", args.ConfigPath)
}
// List of possible config file locations in order of preference
candidates := []string{
"config.json", // Current directory
"config/config.json", // Config subdirectory
}
// Add user directory paths
if homeDir, err := os.UserHomeDir(); err == nil {
candidates = append(candidates,
filepath.Join(homeDir, ".config", "mail2couch", "config.json"),
filepath.Join(homeDir, ".mail2couch.json"),
)
}
// Try each candidate location
for _, candidate := range candidates {
if _, err := os.Stat(candidate); err == nil {
return candidate, nil
}
}
return "", fmt.Errorf("no configuration file found. Searched locations: %v", candidates)
}
// LoadConfigWithDiscovery loads configuration using automatic file discovery
func LoadConfigWithDiscovery(args *CommandLineArgs) (*Config, error) {
configPath, err := FindConfigFile(args)
if err != nil {
return nil, err
}
fmt.Printf("Using configuration file: %s\n", configPath)
if args.MaxMessages > 0 {
fmt.Printf("Maximum messages per mailbox: %d\n", args.MaxMessages)
} else {
fmt.Printf("Maximum messages per mailbox: unlimited\n")
}
return LoadConfig(configPath)
}