Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobwgillespie committed Nov 21, 2015
0 parents commit a105e18
Show file tree
Hide file tree
Showing 13 changed files with 551 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.go]
indent_style = tab
indent_size = 4

[Makefile]
indent_style = tab
indent_size = 4
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mc
*.exe
*.*~
*.swp
.env*
Empty file added README.md
Empty file.
64 changes: 64 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"fmt"
"os"
"strings"

flag "github.com/bgentry/pflag"
)

type Command struct {
Run func(cmd *Command, args []string)
Flag flag.FlagSet

Usage string // first word must be command name
Category string
Short string
Long string
}

func (c *Command) PrintUsage() {
if c.Runnable() {
fmt.Fprintf(os.Stderr, "Usage: mc %s\n", c.FullUsage())
}
fmt.Fprintf(os.Stderr, "Use 'mc help %s' for more information.\n", c.Name())
}

func (c *Command) PrintLongUsage() {
if c.Runnable() {
fmt.Fprintf(os.Stderr, "Usage: mc %s\n", c.FullUsage())
}
fmt.Println(strings.Trim(c.Long, "\n"))
}

func (c *Command) FullUsage() string {
return c.Usage
}

func (c *Command) Name() string {
name := c.Usage
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}

func (c *Command) Runnable() bool {
return c.Run != nil
}

const extra = " (extra)"

func (c *Command) List() bool {
return c.Short != "" && !strings.HasSuffix(c.Short, extra)
}

func (c *Command) ListAsExtra() bool {
return c.Short != "" && strings.HasSuffix(c.Short, extra)
}

func (c *Command) ShortExtra() string {
return c.Short[:len(c.Short)-len(extra)]
}
9 changes: 9 additions & 0 deletions dev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// +build !release

package main

const (
Version = "dev"
)

var updater *Updater
92 changes: 92 additions & 0 deletions help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"io"
"log"
"os"
"text/template"
)

var cmdHelp = &Command{
Usage: "help [<topic>]",
Category: "mc",
Long: `Help shows usage for a command or other topic.`,
}

func init() {
cmdHelp.Run = runHelp // breaks init loop
}

func runHelp(cmd *Command, args []string) {
if len(args) == 0 {
printUsageTo(os.Stdout)
return
}

if len(args) != 1 {
printFatal("too many arguments")
}

switch args[0] {
default:
}

for _, cmd := range commands {
if cmd.Name() == args[0] {
cmd.PrintLongUsage()
return
}
}

log.Printf("Unknown help topic: %q. Run 'mc help'.\n", args[0])
os.Exit(2)
}

func maxStrLen(strs []string) (strlen int) {
for i := range strs {
if len(strs[i]) > strlen {
strlen = len(strs[i])
}
}
return
}

var usageTemplate = template.Must(template.New("usage").Parse(`
Usage: mc <command> [options] [arguments]
Commands:
{{range .Commands}}{{if .Runnable}}{{if .List}}
{{.Name | printf (print "%-" $.MaxRunListName "s")}} {{.Short}}{{end}}{{end}}{{end}}
{{range .Plugins}}
{{.Name | printf (print "%-" $.MaxRunListName "s")}} {{.Short}} (plugin){{end}}
Run 'mc help [command]' for details.
Additional help topics:
{{range .Commands}}{{if not .Runnable}}
{{.Name | printf "%-8s"}} {{.Short}}{{end}}{{end}}
{{if .Dev}}This dev build of mc cannot auto-update itself.
{{end}}`[1:]))

var extraTemplate = template.Must(template.New("usage").Parse(`
Additional commands:
{{range .Commands}}{{if .Runnable}}{{if .ListAsExtra}}
{{.Name | printf (print "%-" $.MaxRunExtraName "s")}} {{.ShortExtra}}{{end}}{{end}}{{end}}
Run 'mc help [command]' for details.
`[1:]))

func printUsageTo(w io.Writer) {
var runListNames []string
for i := range commands {
if commands[i].Runnable() && commands[i].List() {
runListNames = append(runListNames, commands[i].Name())
}
}

usageTemplate.Execute(w, struct {
Commands []*Command
Dev bool
MaxRunListName int
}{
commands,
Version == "dev",
maxStrLen(runListNames),
})
}
103 changes: 103 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"flag"
"fmt"
"log"
"os"
"strings"

"github.com/mgutz/ansi"
"github.com/the-obsidian/mc/rollbar"
"github.com/the-obsidian/mc/term"
)

var commands = []*Command{
//cmdInstall,
//cmdVersion,
cmdHelp,

//helpCommands,
//helpAbout,

// unlisted
//cmdUpdate,
}

func main() {
log.SetFlags(0)

args := os.Args[1:]
if len(args) < 1 || strings.IndexRune(args[0], '-') == 0 {
printUsageTo(os.Stderr)
os.Exit(2)
}

// Perform updates as early as possible
if args[0] == cmdUpdate.Name() {
cmdUpdate.Run(cmdUpdate, args)
return
} else if updater != nil {
defer updater.backgroundRun()
}

if !term.IsANSI(os.Stdout) {
ansi.DisableColors(true)
}

for _, cmd := range commands {
if cmd.Name() != args[0] || cmd.Run == nil {
continue
}

defer recoverPanic()

cmd.Flag.SetDisableDuplicates(true) // allow duplicate flag options
cmd.Flag.Usage = func() {
cmd.PrintUsage()
}
if err := cmd.Flag.Parse(args[1:]); err == flag.ErrHelp {
cmdHelp.Run(cmdHelp, args[:1])
} else if err != nil {
printError(err.Error())
os.Exit(2)
}
cmd.Run(cmd, cmd.Flag.Args())
return
}

fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0])
if g := suggest(args[0]); len(g) > 0 {
fmt.Fprintf(os.Stderr, "Possible alternatives: %v\n", strings.Join(g, " "))
}
fmt.Fprintf(os.Stderr, "Run 'mc help' for usage.\n")
os.Exit(2)
}

var rollbarClient = &rollbar.Client{
AppName: "mc",
AppVersion: Version,
Endpoint: "https://api.rollbar.com/api/1/item/",
Token: "c0fd71dc4a724934b0826c73ef3cd269",
}

func recoverPanic() {
if Version == "dev" {
return
}

if rec := recover(); rec != nil {
message := ""
switch rec := rec.(type) {
case error:
message = rec.Error()
default:
message = fmt.Sprintf("%v", rec)
}
if err := rollbarClient.Report(message); err != nil {
printError("reporting crash failed: %s", err.Error())
panic(err)
}
printFatal("mc encountered and reported an internal client issue")
}
}
95 changes: 95 additions & 0 deletions rollbar/rollbar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package rollbar

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"runtime"
"time"
)

type Client struct {
AppName string
AppVersion string
Endpoint string
Token string
}

func (c *Client) Report(message string) error {
body := c.buildBody()
data := body["data"].(map[string]interface{})
data["body"] = c.errorBody(message, 2)

jsonBody, err := json.Marshal(body)
if err != nil {
return err
}

resp, err := http.Post(c.Endpoint, "application/json", bytes.NewReader(jsonBody))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode/100 != 2 { // 200, 201, 202, etc
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
return nil
}

func (c *Client) errorBody(message string, skip int) map[string]interface{} {
return map[string]interface{}{
"trace": map[string]interface{}{
"frames": c.stacktraceFrames(3 + skip),
"exception": map[string]interface{}{
"class": "",
"message": message,
},
},
}
}

func (c *Client) stacktraceFrames(skip int) []map[string]interface{} {
frames := []map[string]interface{}{}

for i := skip; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
f := runtime.FuncForPC(pc)
fname := "unknown"
if f != nil {
fname = f.Name()
}
frames = append(frames, map[string]interface{}{
"filename": file,
"lineno": line,
"method:": fname,
})
}
return frames
}

func (c *Client) buildBody() map[string]interface{} {
timestamp := time.Now().UTC().Unix()

return map[string]interface{}{
"access_token": c.Token,
"data": map[string]interface{}{
"environment": "production",
"timestamp": timestamp,
"platform": "client",
"language": "go",
"notifier": map[string]interface{}{
"name": c.AppName,
"version": c.AppVersion,
},
"client": map[string]interface{}{
"arch": runtime.GOARCH,
"platform": runtime.GOOS,
},
},
}
}
Loading

0 comments on commit a105e18

Please sign in to comment.