diff --git a/cmd/wf/commands.go b/cmd/wf/commands.go index 1a9e6c4..8f450e3 100644 --- a/cmd/wf/commands.go +++ b/cmd/wf/commands.go @@ -1,198 +1,274 @@ package main import ( "fmt" + "io/ioutil" "os" "path/filepath" "strings" + "github.com/hashicorp/go-multierror" "github.com/writeas/writeas-cli/api" "github.com/writeas/writeas-cli/commands" "github.com/writeas/writeas-cli/config" "github.com/writeas/writeas-cli/executable" "github.com/writeas/writeas-cli/log" cli "gopkg.in/urfave/cli.v1" ) func requireAuth(f cli.ActionFunc, action string) cli.ActionFunc { return func(c *cli.Context) error { // check for logged in users when host is provided without user if c.GlobalIsSet("host") && !c.GlobalIsSet("user") { // multiple users should display a list if num, users, err := usersLoggedIn(c); num > 1 && err == nil { return cli.NewExitError(fmt.Sprintf("Multiple logged in users, please use '-u' or '-user' to specify one of:\n%s", strings.Join(users, ", ")), 1) } else if num == 1 && err == nil { // single user found for host should be set as user flag so LoadUser can // succeed, and notify the client if err := c.GlobalSet("user", users[0]); err != nil { return cli.NewExitError(fmt.Sprintf("Failed to set user flag for only logged in user at host %s: %v", users[0], err), 1) } log.Info(c, "Host specified without user flag, using logged in user: %s\n", users[0]) } else if err != nil { return cli.NewExitError(fmt.Sprintf("Failed to check for logged in users: %v", err), 1) } } else if !c.GlobalIsSet("host") && !c.GlobalIsSet("user") { // check for global configured pair host/user cfg, err := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"])) if err != nil { return cli.NewExitError(fmt.Sprintf("Failed to load config from file: %v", err), 1) // set flags if found } // set flags if both were found in config if cfg.Default.Host != "" && cfg.Default.User != "" { err = c.GlobalSet("host", cfg.Default.Host) if err != nil { return cli.NewExitError(fmt.Sprintf("Failed to set host from global config: %v", err), 1) } err = c.GlobalSet("user", cfg.Default.User) if err != nil { return cli.NewExitError(fmt.Sprintf("Failed to set user from global config: %v", err), 1) } } else { num, err := totalUsersLoggedIn(c) if err != nil { return cli.NewExitError(fmt.Sprintf("Failed to check for logged in users: %v", err), 1) } else if num > 0 { return cli.NewExitError("You are authenticated, but have no default user/host set. Supply -user and -host flags.", 1) } } } u, err := config.LoadUser(c) if err != nil { return cli.NewExitError(fmt.Sprintf("Couldn't load user: %v", err), 1) } if u == nil { return cli.NewExitError("You must be authenticated to "+action+".\nLog in first with: "+executable.Name()+" auth ", 1) } return f(c) } } // usersLoggedIn checks for logged in users for the set host flag // it returns the number of users and a slice of usernames func usersLoggedIn(c *cli.Context) (int, []string, error) { path, err := config.UserHostDir(c) if err != nil { return 0, nil, err } dir, err := os.Open(path) if err != nil { return 0, nil, err } contents, err := dir.Readdir(0) if err != nil { return 0, nil, err } var names []string for _, file := range contents { if file.IsDir() { // stat user.json if _, err := os.Stat(filepath.Join(path, file.Name(), "user.json")); err == nil { names = append(names, file.Name()) } } } return len(names), names, nil } // totalUsersLoggedIn checks for logged in users for any host // it returns the number of users and an error if any func totalUsersLoggedIn(c *cli.Context) (int, error) { path := config.UserDataDir(c.App.ExtraInfo()["configDir"]) dir, err := os.Open(path) if err != nil { return 0, err } contents, err := dir.Readdir(0) if err != nil { return 0, err } count := 0 for _, file := range contents { if file.IsDir() { subDir, err := os.Open(filepath.Join(path, file.Name())) if err != nil { return 0, err } subContents, err := subDir.Readdir(0) if err != nil { return 0, err } for _, subFile := range subContents { if subFile.IsDir() { if _, err := os.Stat(filepath.Join(path, file.Name(), subFile.Name(), "user.json")); err == nil { count++ } } } } } return count, nil } func cmdAuth(c *cli.Context) error { err := commands.CmdAuth(c) if err != nil { return err } // Get the username from the command, just like commands.CmdAuth does username := c.Args().Get(0) // Update config if this is user's first auth cfg, err := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"])) if err != nil { log.Errorln("Not saving config. Unable to load config: %s", err) return err } if cfg.Default.Host == "" && cfg.Default.User == "" { // This is user's first auth, so save defaults cfg.Default.Host = api.HostURL(c) cfg.Default.User = username err = config.SaveConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]), cfg) if err != nil { log.Errorln("Not saving config. Unable to save config: %s", err) return err } fmt.Printf("Set %s on %s as default account.\n", username, c.GlobalString("host")) } return nil } func cmdLogOut(c *cli.Context) error { err := commands.CmdLogOut(c) if err != nil { return err } // Remove this from config if it's the default account cfg, err := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"])) if err != nil { log.Errorln("Not updating config. Unable to load: %s", err) return err } username, err := config.CurrentUser(c) if err != nil { log.Errorln("Not updating config. Unable to load current user: %s", err) return err } reqHost := api.HostURL(c) if reqHost == "" { // No --host given, so we're using the default host reqHost = cfg.Default.Host } if cfg.Default.Host == reqHost && cfg.Default.User == username { // We're logging out of default username + host, so remove from config file cfg.Default.Host = "" cfg.Default.User = "" err = config.SaveConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]), cfg) if err != nil { log.Errorln("Not updating config. Unable to save config: %s", err) return err } } return nil } + +func cmdAccounts(c *cli.Context) error { + // get user config dir + userDir := config.UserDataDir(c.App.ExtraInfo()["configDir"]) + // load defaults + cfg, err := config.LoadConfig(userDir) + if err != nil { + return cli.NewExitError("Could not load default user configuration", 1) + } + defaultUser := cfg.Default.User + defaultHost := cfg.Default.Host + if parts := strings.Split(defaultHost, "://"); len(parts) > 1 { + defaultHost = parts[1] + } + // get each host dir + files, err := ioutil.ReadDir(userDir) + if err != nil { + return cli.NewExitError("Could not read user configuration directory", 1) + } + accounts := make(map[string][]string) + for _, file := range files { + if file.IsDir() { + dirName := file.Name() + // get each user in host dir + users, err := usersFromDir(filepath.Join(userDir, dirName)) + if err != nil { + log.Info(c, "Failed to get users from %s: %v", dirName, err) + continue + } + if len(users) != 0 { + for _, user := range users { + accounts[dirName] = append(accounts[dirName], user) + } + } + } + } + + // print out all logged in accounts + for host := range accounts { + numAccounts := len(accounts[host]) - 1 + fmt.Printf("[%s] ", host) + for i, username := range accounts[host] { + fmt.Print(username) + if host == defaultHost && username == defaultUser { + fmt.Print(" (default)") + } + if i < numAccounts { + fmt.Print(", ") + } + } + fmt.Print("\n") + } + return nil +} + +func usersFromDir(path string) ([]string, error) { + users := make([]string, 0, 4) + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + var errs error + for _, file := range files { + if file.IsDir() { + _, err := os.Stat(filepath.Join(path, file.Name(), "user.json")) + if err != nil { + err = multierror.Append(errs, err) + continue + } + users = append(users, file.Name()) + } + } + return users, errs +} diff --git a/cmd/wf/main.go b/cmd/wf/main.go index 7cce4c9..ac726cf 100644 --- a/cmd/wf/main.go +++ b/cmd/wf/main.go @@ -1,265 +1,275 @@ package main import ( "os" "github.com/writeas/writeas-cli/commands" "github.com/writeas/writeas-cli/config" cli "gopkg.in/urfave/cli.v1" ) func main() { appInfo := map[string]string{ "configDir": configDir, "version": "1.0", } config.DirMustExist(config.UserDataDir(appInfo["configDir"])) cli.VersionFlag = cli.BoolFlag{ Name: "version, V", Usage: "print the version", } // Run the app app := cli.NewApp() app.Name = "wf" app.Version = appInfo["version"] app.Usage = "Publish to any WriteFreely instance from the command-line." // TODO: who is the author? the contributors? link to GH? app.Authors = []cli.Author{ { Name: "Write.as", Email: "hello@write.as", }, } app.ExtraInfo = func() map[string]string { return appInfo } app.Action = requireAuth(commands.CmdPost, "publish") app.Flags = append(config.PostFlags, flags...) app.Commands = []cli.Command{ { Name: "post", Usage: "Alias for default action: create post from stdin", Action: requireAuth(commands.CmdPost, "publish"), Flags: config.PostFlags, Description: `Create a new post on WriteFreely from stdin. Use the --code flag to indicate that the post should use syntax highlighting. Or use the --font [value] argument to set the post's appearance, where [value] is mono, monospace (default), wrap (monospace font with word wrapping), serif, or sans.`, }, { Name: "new", Usage: "Compose a new post from the command-line and publish", Description: `An alternative to piping data to the program. On Windows, this will use 'copy con' to start reading what you input from the prompt. Press F6 or Ctrl-Z then Enter to end input. On *nix, this will use the best available text editor, starting with the value set to the WRITEAS_EDITOR or EDITOR environment variable, or vim, or finally nano. Use the --code flag to indicate that the post should use syntax highlighting. Or use the --font [value] argument to set the post's appearance, where [value] is mono, monospace (default), wrap (monospace font with word wrapping), serif, or sans. If posting fails for any reason, 'wf' will show you the temporary file location and how to pipe it to 'wf' to retry.`, Action: requireAuth(commands.CmdNew, "publish"), Flags: config.PostFlags, }, { Name: "publish", Usage: "Publish a file", Action: requireAuth(commands.CmdPublish, "publish"), Flags: config.PostFlags, }, { Name: "delete", Usage: "Delete a post", Action: requireAuth(commands.CmdDelete, "delete"), Flags: []cli.Flag{ cli.BoolFlag{ Name: "tor, t", Usage: "Delete via Tor hidden service", }, cli.IntFlag{ Name: "tor-port", Usage: "Use a different port to connect to Tor", Value: 9150, }, cli.BoolFlag{ Name: "verbose, v", Usage: "Make the operation more talkative", }, }, }, { Name: "update", Usage: "Update (overwrite) a post", Action: requireAuth(commands.CmdUpdate, "update"), Flags: []cli.Flag{ cli.BoolFlag{ Name: "tor, t", Usage: "Update via Tor hidden service", }, cli.IntFlag{ Name: "tor-port", Usage: "Use a different port to connect to Tor", Value: 9150, }, cli.BoolFlag{ Name: "code", Usage: "Specifies this post is code", }, cli.StringFlag{ Name: "font", Usage: "Sets post font to given value", }, cli.BoolFlag{ Name: "verbose, v", Usage: "Make the operation more talkative", }, }, }, { Name: "get", Usage: "Read a raw post", Action: commands.CmdGet, Flags: []cli.Flag{ cli.BoolFlag{ Name: "tor, t", Usage: "Get from Tor hidden service", }, cli.IntFlag{ Name: "tor-port", Usage: "Use a different port to connect to Tor", Value: 9150, }, cli.BoolFlag{ Name: "verbose, v", Usage: "Make the operation more talkative", }, }, }, { Name: "posts", Usage: "List all of your posts", Description: "This will list only local posts.", Action: requireAuth(commands.CmdListPosts, "posts"), Flags: []cli.Flag{ cli.BoolFlag{ Name: "id", Usage: "Show list with post IDs (default)", }, cli.BoolFlag{ Name: "md", Usage: "Use with --url to return URLs with Markdown enabled", }, cli.BoolFlag{ Name: "url", Usage: "Show list with URLs", }, cli.BoolFlag{ Name: "verbose, v", Usage: "Show verbose post listing, including Edit Tokens", }, }, }, { Name: "blogs", Usage: "List blogs", Action: requireAuth(commands.CmdCollections, "blogs"), Flags: []cli.Flag{ cli.BoolFlag{ Name: "tor, t", Usage: "Authenticate via Tor hidden service", }, cli.IntFlag{ Name: "tor-port", Usage: "Use a different port to connect to Tor", Value: 9150, }, cli.BoolFlag{ Name: "url", Usage: "Show list with URLs", }, }, }, { Name: "claim", Usage: "Claim local unsynced posts", Action: requireAuth(commands.CmdClaim, "claim"), Description: "This will claim any unsynced posts local to this machine. To see which posts these are run: wf posts.", Flags: []cli.Flag{ cli.BoolFlag{ Name: "tor, t", Usage: "Authenticate via Tor hidden service", }, cli.IntFlag{ Name: "tor-port", Usage: "Use a different port to connect to Tor", Value: 9150, }, cli.BoolFlag{ Name: "verbose, v", Usage: "Make the operation more talkative", }, }, }, { + Name: "accounts", + Usage: "List all currently logged in accounts", + Action: cmdAccounts, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "verbose, v", + Usage: "Make the operation more talkative", + }, + }, + }, { Name: "auth", Usage: "Authenticate with a WriteFreely instance", Action: cmdAuth, Flags: []cli.Flag{ cli.BoolFlag{ Name: "tor, t", Usage: "Authenticate via Tor hidden service", }, cli.IntFlag{ Name: "tor-port", Usage: "Use a different port to connect to Tor", Value: 9150, }, cli.BoolFlag{ Name: "verbose, v", Usage: "Make the operation more talkative", }, }, }, { Name: "logout", Usage: "Log out of a WriteFreely instance", Action: requireAuth(cmdLogOut, "logout"), Flags: []cli.Flag{ cli.BoolFlag{ Name: "tor, t", Usage: "Authenticate via Tor hidden service", }, cli.IntFlag{ Name: "tor-port", Usage: "Use a different port to connect to Tor", Value: 9150, }, cli.BoolFlag{ Name: "verbose, v", Usage: "Make the operation more talkative", }, }, }, } cli.CommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: wf {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} {{end}}{{ end }} ` app.Run(os.Args) } diff --git a/go.mod b/go.mod index 90d8f91..f03be5d 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,26 @@ module github.com/writeas/writeas-cli require ( code.as/core/socks v1.0.0 github.com/atotto/clipboard v0.1.1 github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect + github.com/hashicorp/go-multierror v1.0.0 github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c github.com/jtolds/gls v4.2.1+incompatible // indirect github.com/microcosm-cc/bluemonday v1.0.1 // indirect github.com/mitchellh/go-homedir v1.0.0 github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/gomega v1.5.0 // indirect github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect github.com/writeas/go-writeas/v2 v2.0.2 github.com/writeas/saturday v0.0.0-20170402010311-f455b05c043f // indirect github.com/writeas/web-core v0.0.0-20181111165528-05f387ffa1b3 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 // indirect gopkg.in/ini.v1 v1.39.3 gopkg.in/urfave/cli.v1 v1.20.0 ) diff --git a/go.sum b/go.sum index 86825a8..449244e 100644 --- a/go.sum +++ b/go.sum @@ -1,69 +1,73 @@ code.as/core/socks v0.0.0-20180906144846-5be269b4e664 h1:zWSFbwkYSuZ2PjvHqYDE/dhd9CCcsbSvUIRx8hIed3I= code.as/core/socks v0.0.0-20180906144846-5be269b4e664/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY= code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs= code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY= github.com/atotto/clipboard v0.1.1 h1:WSoEbAS70E5gw8FbiqFlp69MGsB6dUb4l+0AGGLiVGw= github.com/atotto/clipboard v0.1.1/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/writeas/go-writeas/v2 v2.0.2 h1:akvdMg89U5oBJiCkBwOXljVLTqP354uN6qnG2oOMrbk= github.com/writeas/go-writeas/v2 v2.0.2/go.mod h1:9sjczQJKmru925fLzg0usrU1R1tE4vBmQtGnItUMR0M= github.com/writeas/impart v0.0.0-20180808220913-fef51864677b h1:vsZIsYneuNwXMsnh0lKviEVc8WeIqBG4RTmGWU86HpI= github.com/writeas/impart v0.0.0-20180808220913-fef51864677b/go.mod h1:sUkQZZHJfrVNsoD4QbkrYrRSQtCN3SaUPWKdohmFKT8= github.com/writeas/impart v1.1.0 h1:nPnoO211VscNkp/gnzir5UwCDEvdHThL5uELU60NFSE= github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y= github.com/writeas/saturday v0.0.0-20170402010311-f455b05c043f h1:yyFguE0EopK8e7I7/AB1JWM925OFOI1uFhTM/SwXAnQ= github.com/writeas/saturday v0.0.0-20170402010311-f455b05c043f/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ= github.com/writeas/web-core v0.0.0-20181111165528-05f387ffa1b3 h1:mKD4DMZuiZWrn1k/f+1wLmBu9SYMrydy9om+eeo9kjA= github.com/writeas/web-core v0.0.0-20181111165528-05f387ffa1b3/go.mod h1:Si3chV7VWgY8CsV+3gRolMXSO2Vx1ZFAQ/mkrpvmyEE= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4= golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM= golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.39.3 h1:+LGDwGPQXrK1zLmDY5GMdgX7uNvs4iS+9fIRAGaDBbg= gopkg.in/ini.v1 v1.39.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=