diff --git a/Makefile b/Makefile index 7e3adb3..dcb1f5f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ GLIDE = glide all: bin/notify_slack bin/output -bin/notify_slack: cmd/notify_slack/main.go slack/*.go throttle/*.go config/*.go +bin/notify_slack: cmd/notify_slack/main.go slack/*.go throttle/*.go config/*.go cli/*.go go build -o bin/notify_slack cmd/notify_slack/main.go bin/output: cmd/output/main.go diff --git a/cli/cli.go b/cli/cli.go new file mode 100644 index 0000000..276dbf8 --- /dev/null +++ b/cli/cli.go @@ -0,0 +1,167 @@ +package cli + +import ( + "context" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "os/signal" + "syscall" + "time" + + "github.com/catatsuy/notify_slack/config" + "github.com/catatsuy/notify_slack/slack" + "github.com/catatsuy/notify_slack/throttle" + "github.com/pkg/errors" +) + +const ( + ExitCodeOK = 0 + ExitCodeParseFlagError = 1 + ExitCodeFail = 1 +) + +type CLI struct { + outStream, errStream io.Writer + inputStream io.Reader + + sClient *slack.Client + conf *config.Config +} + +func NewCLI(outStream, errStream io.Writer, inputStream io.Reader) *CLI { + return &CLI{outStream: outStream, errStream: errStream, inputStream: inputStream} +} + +func (c *CLI) Run(args []string) int { + var ( + tomlFile string + duration time.Duration + ) + + c.conf = config.NewConfig() + + flags := flag.NewFlagSet("notify_slack", flag.ContinueOnError) + flags.SetOutput(c.errStream) + + flags.StringVar(&c.conf.Channel, "channel", "", "specify channel") + flags.StringVar(&c.conf.SlackURL, "slack-url", "", "slack url") + flags.StringVar(&c.conf.Token, "token", "", "token") + flags.StringVar(&c.conf.Username, "username", "", "specify username") + flags.StringVar(&c.conf.IconEmoji, "icon-emoji", "", "specify icon emoji") + + flags.DurationVar(&duration, "interval", time.Second, "interval") + flags.StringVar(&tomlFile, "c", "", "config file name") + + err := flags.Parse(args[1:]) + if err != nil { + return ExitCodeParseFlagError + } + + argv := flags.Args() + filename := "" + if len(argv) == 1 { + filename = argv[0] + } + + tomlFile = config.LoadTOMLFilename(tomlFile) + + if tomlFile != "" { + c.conf.LoadTOML(tomlFile) + } + + if c.conf.SlackURL == "" { + fmt.Fprintln(c.errStream, "provide Slack URL") + return ExitCodeFail + } + + c.sClient, err = slack.NewClient(c.conf.SlackURL, nil) + if err != nil { + fmt.Fprintln(c.errStream, err) + return ExitCodeFail + } + + if filename != "" { + if c.conf.Token == "" { + fmt.Fprintln(c.errStream, "provide Slack token") + return ExitCodeFail + } + + err := c.uploadSnippet(context.Background(), filename) + if err != nil { + fmt.Fprintln(c.errStream, err) + return ExitCodeFail + } + + return ExitCodeOK + } + + copyStdin := io.TeeReader(c.inputStream, c.outStream) + + ex := throttle.NewExec(copyStdin) + + exitC := make(chan os.Signal, 0) + signal.Notify(exitC, syscall.SIGTERM, syscall.SIGINT) + + param := &slack.PostTextParam{ + Channel: c.conf.Channel, + Username: c.conf.Username, + IconEmoji: c.conf.IconEmoji, + } + + flushCallback := func(_ context.Context, output string) error { + param.Text = output + return c.sClient.PostText(context.Background(), param) + } + + done := make(chan struct{}, 0) + + doneCallback := func(ctx context.Context, output string) error { + defer func() { + done <- struct{}{} + }() + + return flushCallback(ctx, output) + } + + interval := time.Tick(duration) + ctx, cancel := context.WithCancel(context.Background()) + + ex.Start(ctx, interval, flushCallback, doneCallback) + + select { + case <-exitC: + case <-ex.Wait(): + } + cancel() + + <-done + + return ExitCodeOK +} + +func (c *CLI) uploadSnippet(ctx context.Context, filename string) error { + _, err := os.Stat(filename) + if err != nil { + return errors.Wrapf(err, "%s does not exist", filename) + } + + content, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + + param := &slack.PostFileParam{ + Channel: c.conf.Channel, + Filename: filename, + Content: string(content), + } + err = c.sClient.PostFile(ctx, c.conf.Token, param) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/notify_slack/main.go b/cmd/notify_slack/main.go index 85f48fb..bbea0ff 100644 --- a/cmd/notify_slack/main.go +++ b/cmd/notify_slack/main.go @@ -1,127 +1,12 @@ package main import ( - "context" - "flag" - "io" - "io/ioutil" - "log" "os" - "os/signal" - "syscall" - "time" - "github.com/catatsuy/notify_slack/config" - "github.com/catatsuy/notify_slack/slack" - "github.com/catatsuy/notify_slack/throttle" + "github.com/catatsuy/notify_slack/cli" ) func main() { - var ( - tomlFile string - duration time.Duration - ) - - conf := config.NewConfig() - - flag.StringVar(&conf.Channel, "channel", "", "specify channel") - flag.StringVar(&conf.SlackURL, "slack-url", "", "slack url") - flag.StringVar(&conf.Token, "token", "", "token") - flag.StringVar(&conf.Username, "username", "", "specify username") - flag.StringVar(&conf.IconEmoji, "icon-emoji", "", "specify icon emoji") - - flag.DurationVar(&duration, "interval", time.Second, "interval") - flag.StringVar(&tomlFile, "c", "", "config file name") - - flag.Parse() - - args := flag.Args() - filename := "" - if len(args) == 1 { - filename = args[0] - } - - tomlFile = config.LoadTOMLFilename(tomlFile) - - if tomlFile != "" { - conf.LoadTOML(tomlFile) - } - - if conf.SlackURL == "" { - log.Fatal("provide Slack URL") - } - - sClient, err := slack.NewClient(conf.SlackURL, nil) - if err != nil { - log.Fatal(err) - } - - if filename != "" { - if conf.Token == "" { - log.Fatal("provide Slack token") - } - - _, err = os.Stat(filename) - if err != nil { - log.Fatalf("%s does not exist", filename) - } - - content, err := ioutil.ReadFile(filename) - if err != nil { - log.Fatal(err) - } - - param := &slack.PostFileParam{ - Channel: conf.Channel, - Filename: filename, - Content: string(content), - } - err = sClient.PostFile(context.Background(), conf.Token, param) - if err != nil { - log.Fatal(err) - } - - return - } - - copyStdin := io.TeeReader(os.Stdin, os.Stdout) - - ex := throttle.NewExec(copyStdin) - - c := make(chan os.Signal, 0) - signal.Notify(c, syscall.SIGTERM, syscall.SIGINT) - - param := &slack.PostTextParam{ - Channel: conf.Channel, - Username: conf.Username, - IconEmoji: conf.IconEmoji, - } - - flushCallback := func(_ context.Context, output string) error { - param.Text = output - return sClient.PostText(context.Background(), param) - } - - done := make(chan struct{}, 0) - - doneCallback := func(ctx context.Context, output string) error { - defer func() { - done <- struct{}{} - }() - - return flushCallback(ctx, output) - } - - interval := time.Tick(duration) - ctx, cancel := context.WithCancel(context.Background()) - - ex.Start(ctx, interval, flushCallback, doneCallback) - - select { - case <-c: - case <-ex.Wait(): - } - cancel() - - <-done + c := cli.NewCLI(os.Stdout, os.Stderr, os.Stdin) + os.Exit(c.Run(os.Args)) }