package config import ( "fmt" "log" "os/exec" "strconv" "strings" "github.com/BurntSushi/toml" ) // maybeInt is an integer that may be nil type maybeInt struct { int bool } func (m maybeInt) empty() bool { return !m.bool } func (m maybeInt) value() int { return m.int } func initMaybeInt(value int) maybeInt { return maybeInt{value, true} } func (m *maybeInt) UnmarshalText(text []byte) error { var err error var i int64 if text != nil { str := string(text) i, err = strconv.ParseInt(str, 10, 64) m.int = int(i) m.bool = true } return err } type fileConfig struct { Default string `toml:"default_account"` MailboxPath string `toml:"mailbox_path"` Accounts []account `toml:"account"` DefaultMessages maybeInt `toml:"default_messages"` } type account struct { Name string `toml:"name"` MailboxPath string `toml:"mailbox_path"` ConnectionInfo *connectionInfo `toml:"connection"` ExcludedFolders []string `toml:"excluded_folders"` Messages maybeInt `toml:"messages"` } type connectionInfo struct { Host string `toml:"hostname"` Port int `toml:"port"` Username string `toml:"username"` PasswordExec *ShellScript `toml:"password_exec"` PasswordFile string `toml:"password_file"` Password string `toml:"password"` } func (c *connectionInfo) String() string { var res string res += "{" res += fmt.Sprintf("Host: %s, ", c.Host) res += fmt.Sprintf("Port: %d, ", c.Port) res += fmt.Sprintf("Username: %s, ", c.Username) res += fmt.Sprintf("Password: **********, ") res += fmt.Sprintf("PasswordFile: %s, ", c.PasswordFile) res += fmt.Sprintf("PasswordExec: %s}", c.PasswordExec) return res } // ShellScript might be either: // - the path to an executable // - an executable in path // - a shell command with its argument // all these must return on stdout the password after successful execution. type ShellScript struct { Executable string } func (s *ShellScript) UnmarshalText(text []byte) error { s.Executable = string(text) return nil } func (s *ShellScript) String() string { return s.Executable } // Present indicates if the value has been filled in into the config func (s *ShellScript) Present() bool { if s != nil { return s.Executable != "" } return false } // Run tries to execute the command, first looking for a corresponding executable and // invoking it, then invoking the string as a command followed by its parameters. func (s *ShellScript) Run() (string, error) { if path, err := exec.LookPath(s.Executable); err == nil { return doExec(path) } parts := strings.Split(s.Executable, " ") return doExec(parts[0], parts[1:]...) } func doExec(path string, args ...string) (string, error) { cmd := exec.Command(path, args...) out, err := cmd.Output() if err != nil { log.Println("Error running:", cmd) return "", err } return string(out), nil } // parseFile reads the given config file and, if succeeding, returns a *FileConfig. func parseFile(path string) (*fileConfig, error) { newConfig := fileConfig{} _, err := toml.DecodeFile(path, &newConfig) return &newConfig, err }