Mercurial > projects > sievemgr
view cmd/sievemgr/edit.go @ 22:fc5e6970a0d5 default tip
Add support for specifying an authorization identity on the command line
author | Guido Berhoerster <guido+sievemgr@berhoerster.name> |
---|---|
date | Wed, 17 Feb 2021 07:50:55 +0100 |
parents | 29769b9e2f09 |
children |
line wrap: on
line source
// Copyright (C) 2020 Guido Berhoerster <guido+sievemgr@berhoerster.name> // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package main import ( "fmt" "io" "io/ioutil" "os" "os/exec" "path/filepath" "runtime" "strings" "go.guido-berhoerster.org/managesieve" "go.guido-berhoerster.org/sievemgr/cmd/sievemgr/internal/config" "golang.org/x/term" ) func init() { cmdEdit.Flag.StringVar(&acctName, "a", "", "Select the account") cmdEdit.Flag.StringVar(&authzID, "A", "", "Specify the authorization identity") } var cmdEdit = &command{ UsageLine: "edit [options] name", Run: runEdit, } func promptYesNo(prompt string) (yesNo bool, err error) { var tty *os.File var r io.Reader var w io.Writer if runtime.GOOS == "windows" { r = os.Stdin w = os.Stdout } else { tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0666) if err != nil { return } r = tty w = tty defer tty.Close() } for { io.WriteString(w, prompt) var response string _, err = fmt.Fscanln(r, &response) if err != nil { err = fmt.Errorf("failed to read response: %s", err) return } switch strings.ToLower(response) { case "y", "yes": yesNo = true return case "n", "no": return } } } func getScript(acct *config.Account, scriptName string) (string, error) { c, err := dialPlainAuth(acct) if err != nil { return "", err } defer c.Logout() return c.GetScript(scriptName) } func readScript(filename string) (string, error) { f, err := os.Open(filename) if err != nil { return "", err } defer f.Close() return readLimitedString(f, managesieve.ReadLimit) } func putScript(acct *config.Account, scriptName, script string) (string, error) { c, err := dialPlainAuth(acct) if err != nil { return "", err } defer c.Logout() return c.PutScript(scriptName, script) } func runEdit(cmd *command, args []string) error { if len(args) != 1 { return usageError("invalid number of arguments") } scriptName := args[0] if !term.IsTerminal(int(os.Stdin.Fd())) || !term.IsTerminal(int(os.Stdout.Fd())) { return fmt.Errorf("the edit subcommand can only be used interactively\n") } editor := os.Getenv("EDITOR") if editor == "" { return fmt.Errorf("EDITOR environment variable not set") } acct, err := getAccount(&conf, acctName) if err != nil { return err } if err := lookupHostPort(acct); err != nil { return err } if err := readPassword(acct); err != nil { return err } c, err := dialPlainAuth(acct) if err != nil { return err } defer c.Logout() script, err := getScript(acct, scriptName) if err != nil { return err } tmpDir, err := ioutil.TempDir(os.TempDir(), "sievemgr*") if err != nil { return fmt.Errorf("failed to create temporary directory: %s", err) } tmpFile := filepath.Join(tmpDir, scriptName) if err = ioutil.WriteFile(tmpFile, []byte(script), 0640); err != nil { return fmt.Errorf("failed to create script file: %s", err) } defer func() { // show filename if an error has occured and the file is // preserved if tmpFile != "" { fmt.Fprintf(os.Stderr, "the script has been preserved as %s\n", tmpFile) } }() // modification time is used to detect changes info, err := os.Stat(tmpFile) if err != nil { return fmt.Errorf("failed to stat file: %s", err) } origModTime := info.ModTime() for { cmd := exec.Command(editor, tmpFile) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return fmt.Errorf("failed to run editor: %s", err) } // quit if the script has not been changed if info, err := os.Stat(tmpFile); err != nil { return fmt.Errorf("failed to stat file: %s", err) } else if info.ModTime() == origModTime { fmt.Fprintln(os.Stderr, "aborting, script was not modified") break } script, err = readScript(tmpFile) if err != nil { return fmt.Errorf("failed to read script: %s", err) } warnings, err := putScript(acct, scriptName, script) if err != nil { // show error and try again if the script was rejected // by the server fmt.Fprintln(os.Stderr, err) yesNo, err := promptYesNo("edit again [y/n]? ") if err != nil { return err } else if !yesNo { return fmt.Errorf("script not saved") } } else if warnings != "" { fmt.Fprintln(os.Stderr, warnings) break } else { break } } os.RemoveAll(tmpDir) tmpFile = "" return nil }