<commit_before>package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"

	"github.com/caelifer/webtime/client"
	"github.com/caelifer/webtime/service"
)

var (
	port = flag.String("port", "8888", "HTTP port number")
)

// Parse our templates
var homeTemplate = template.Must(template.New("time").Parse(htmlTemplate))

// Start TimeTicker service
var timeTicker = service.NewTimeTicker()

func main() {
	flag.Parse()
	host := "localhost"

	// Setup routes
	http.HandleFunc("/time/", homeHandler)
	http.HandleFunc("/ws/", wsHandler)
	http.HandleFunc("/", notFoundHandler)

	// Run HTTP server
	log.Printf("Running webserver on http://%s:%s/\n", host, *port)
	log.Fatal(http.ListenAndServe(":"+*port, nil))
}

func handleError(err error, w http.ResponseWriter, r *http.Request) {
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

func notFoundHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprint(w, "custom 404")
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	if err := homeTemplate.Execute(w, r.Host); err != nil {
		handleError(err, w, r)
	}
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	client.NewWSClient(timeTicker).ServeHTTP(w, r)
}

const htmlTemplate = `

<title>WebTime via Websocket Example</title>

`
<commit_msg>Since client is HTTP Hanler use it directly<commit_after>package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"

	"github.com/caelifer/webtime/client"
	"github.com/caelifer/webtime/service"
)

var (
	port = flag.String("port", "8888", "HTTP port number")
)

// Parse our templates
var homeTemplate = template.Must(template.New("time").Parse(htmlTemplate))

// Start TimeTicker service
var timeTicker = service.NewTimeTicker()

func main() {
	flag.Parse()
	host := "localhost"

	// Setup routes
	http.Handle("/ws/", client.NewWSClient(timeTicker))

	// more routes
	http.HandleFunc("/time/", homeHandler)
	http.HandleFunc("/", notFoundHandler)

	// Run HTTP server
	log.Printf("Running webserver on http://%s:%s/\n", host, *port)
	log.Fatal(http.ListenAndServe(":"+*port, nil))
}

func handleError(err error, w http.ResponseWriter, r *http.Request) {
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

func notFoundHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprint(w, "custom 404")
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	if err := homeTemplate.Execute(w, r.Host); err != nil {
		handleError(err, w, r)
	}
}

const htmlTemplate = `

<title>WebTime via Websocket Example</title>

`
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/chzyer/readline"
	"github.com/codegangsta/cli"
	"github.com/madcitygg/rcon"
	"github.com/mitchellh/colorstring"
	"io"
	"os"
	"strconv"
	"strings"
)

// Server addresses

const (
	defaultPort = 27015
)

type Address struct {
	Host string
	Port int
}

func (a *Address) String() string {
	return fmt.Sprintf("%s:%d", a.Host, a.Port)
}

func ParseAddress(address string) (a *Address, err error) {
	var host string
	var port int

	parts := strings.Split(address, ":")
	if len(parts) > 2 {
		err = errors.New("address contains multiple colons")
	}

	if len(parts) == 1 {
		host = parts[0]
		port = defaultPort
	} else if len(parts) == 2 {
		host = parts[0]
		port, err = strconv.Atoi(parts[1])
		if err != nil {
			return
		}
	}

	a = &Address{host, port}
	return
}

// Main loop

func main() {
	// global vars
	password := ""
	authenticated := false

	// set up cli app
	app := cli.NewApp()
	app.Name = "rcc"
	app.Usage = "control a Source RCON server"
	app.UsageText = "rcc [global options] server"
	app.Version = "0.0.1"

	// add global command line flags
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:        "password, p",
			Usage:       "server rcon password",
			Destination: &password,
		},
	}

	// default action
	app.Action = func(ctx *cli.Context) {
		if len(ctx.Args()) == 0 {
			// the user did not provide a server to connect to
			cli.ShowAppHelp(ctx)
			os.Exit(1)
		}

		// parse server address
		address, err := ParseAddress(ctx.Args()[0])
		if err != nil {
			println(err.Error())
			os.Exit(1)
		}

		// dial the server
		colorstring.Printf("Connecting to %s\n", address)
		rc, err := rcon.Dial(address.String())
		if err != nil {
			println(err.Error())
			os.Exit(1)
		}
		defer rc.Close()
		colorstring.Printf("[green]Connection successfull![reset]\n")

		// create readline instance
		rl, err := readline.NewEx(&readline.Config{
			Prompt:          "> ",
			InterruptPrompt: "\nPress Ctrl+D to exit",
			EOFPrompt:       "exit",
		})
		if err != nil {
			panic(err)
		}
		defer rl.Close()

		passwordConfig := rl.GenPasswordConfig()
		passwordConfig.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
			rl.SetPrompt("Enter password: ")
			rl.Refresh()
			return nil, 0, false
		})

		// start inner console
		console := cli.NewApp()
		console.Commands = []cli.Command{
			{
				Name:  "login",
				Usage: "Log in to rcon server",
				Action: func(c *cli.Context) {
					askForPassword := func(destination *string) {
						for len(*destination) == 0 {
							// read a password from command line
							pw, err := rl.ReadPasswordWithConfig(passwordConfig)
							if err == io.EOF {
								os.Exit(1)
							}
							if err != nil {
								colorstring.Printf("Press Ctrl+D to exit\n")
							}
							// clean it up and check if it makes sense
							*destination = strings.TrimSpace(string(pw))
							if len(*destination) == 0 {
								colorstring.Printf("[red]Please enter a valid password[reset]")
							}
						}
					}

					// ask for a password until we authenticate successfully
					for !authenticated {
						askForPassword(&password)
						err := rc.Authenticate(password)
						if err != nil {
							colorstring.Printf("[red]Authentication failed. Incorrect password?[reset]")
							password = ""
							continue
						}

						authenticated = true
					}
				},
			},
			{
				Name: "clear",
				Action: func(c *cli.Context) {
					colorstring.Printf("[red]`clear` has not been implemented yet.[reset]\n")
				},
			},
			{
				Name:    "exit",
				Aliases: []string{"quit", "q"},
				Action: func(c *cli.Context) {
					os.Exit(1)
				},
			},
		}
		console.Action = func(c *cli.Context) {
			command := strings.Join(c.Args(), " ")
			response, err := rc.Execute(command)
			if err != nil {
				colorstring.Printf("[red]Error: %s[reset]\n", err)
			}

			fmt.Println(response.Body)
		}

		// perform login to authenticate with rcon server
		console.Run(strings.Fields("cmd login"))

		// set a nice prompt with server name and port
		prompt := "[white]%s[reset]:[blue]%d[reset]> "
		rl.SetPrompt(colorstring.Color(fmt.Sprintf(prompt, address.Host, address.Port)))

		// start readline loop
		for {
			line, err := rl.Readline()
			if err == io.EOF {
				break
			}
			if err != nil {
				continue
			}

			console.Run([]string{"cmd", line})
		}
	}

	app.Run(os.Args)
}
<commit_msg>Change spelling mistakes<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/chzyer/readline"
	"github.com/codegangsta/cli"
	"github.com/madcitygg/rcon"
	"github.com/mitchellh/colorstring"
	"io"
	"os"
	"strconv"
	"strings"
)

// Server addresses

const (
	defaultPort = 27015
)

type Address struct {
	Host string
	Port int
}

func (a *Address) String() string {
	return fmt.Sprintf("%s:%d", a.Host, a.Port)
}

func ParseAddress(address string) (a *Address, err error) {
	var host string
	var port int

	parts := strings.Split(address, ":")
	if len(parts) > 2 {
		err = errors.New("address contains multiple colons")
	}

	if len(parts) == 1 {
		host = parts[0]
		port = defaultPort
	} else if len(parts) == 2 {
		host = parts[0]
		port, err = strconv.Atoi(parts[1])
		if err != nil {
			return
		}
	}

	a = &Address{host, port}
	return
}

// Main loop

func main() {
	// global vars
	password := ""
	authenticated := false

	// set up cli app
	app := cli.NewApp()
	app.Name = "rcc"
	app.Usage = "control a Source RCON server"
	app.UsageText = "rcc [global options] server"
	app.Version = "0.0.1"

	// add global command line flags
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:        "password, p",
			Usage:       "rcon password",
			Destination: &password,
		},
	}

	// default action
	app.Action = func(ctx *cli.Context) {
		if len(ctx.Args()) == 0 {
			// the user did not provide a server to connect to
			cli.ShowAppHelp(ctx)
			os.Exit(1)
		}

		// parse server address
		address, err := ParseAddress(ctx.Args()[0])
		if err != nil {
			println(err.Error())
			os.Exit(1)
		}

		// dial the server
		colorstring.Printf("Connecting to %s\n", address)
		rc, err := rcon.Dial(address.String())
		if err != nil {
			println(err.Error())
			os.Exit(1)
		}
		defer rc.Close()
		colorstring.Printf("[green]Connection successful![reset]\n")

		// create readline instance
		rl, err := readline.NewEx(&readline.Config{
			Prompt:          "> ",
			InterruptPrompt: "\nPress Ctrl+D to exit",
			EOFPrompt:       "exit",
		})
		if err != nil {
			panic(err)
		}
		defer rl.Close()

		passwordConfig := rl.GenPasswordConfig()
		passwordConfig.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
			rl.SetPrompt("Enter password: ")
			rl.Refresh()
			return nil, 0, false
		})

		// start inner console
		console := cli.NewApp()
		console.Commands = []cli.Command{
			{
				Name:  "login",
				Usage: "Log in to rcon server",
				Action: func(c *cli.Context) {
					askForPassword := func(destination *string) {
						for len(*destination) == 0 {
							// read a password from command line
							pw, err := rl.ReadPasswordWithConfig(passwordConfig)
							if err == io.EOF {
								os.Exit(1)
							}
							if err != nil {
								colorstring.Printf("Press Ctrl+D to exit\n")
							}
							// clean it up and check if it makes sense
							*destination = strings.TrimSpace(string(pw))
							if len(*destination) == 0 {
								colorstring.Printf("[red]Please enter a valid password[reset]")
							}
						}
					}

					// ask for a password until we authenticate successfully
					for !authenticated {
						askForPassword(&password)
						err := rc.Authenticate(password)
						if err != nil {
							colorstring.Printf("[red]Authentication failed. Incorrect password?[reset]")
							password = ""
							continue
						}

						authenticated = true
					}
				},
			},
			{
				Name: "clear",
				Action: func(c *cli.Context) {
					colorstring.Printf("[red]`clear` has not been implemented yet.[reset]\n")
				},
			},
			{
				Name:    "exit",
				Aliases: []string{"quit", "q"},
				Action: func(c *cli.Context) {
					os.Exit(1)
				},
			},
		}
		console.Action = func(c *cli.Context) {
			command := strings.Join(c.Args(), " ")
			response, err := rc.Execute(command)
			if err != nil {
				colorstring.Printf("[red]Error: %s[reset]\n", err)
			}

			fmt.Println(response.Body)
		}

		// perform login to authenticate with rcon server
		console.Run(strings.Fields("cmd login"))

		// set a nice prompt with server name and port
		prompt := "[white]%s[reset]:[blue]%d[reset]> "
		rl.SetPrompt(colorstring.Color(fmt.Sprintf(prompt, address.Host, address.Port)))

		// start readline loop
		for {
			line, err := rl.Readline()
			if err == io.EOF {
				break
			}
			if err != nil {
				continue
			}

			console.Run([]string{"cmd", line})
		}
	}

	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"regexp"
	"sort"
	"time"

	"github.com/Sirupsen/logrus"
	humanize "github.com/dustin/go-humanize"
	"github.com/nlopes/slack"
)

const extractMsgGroupName = "msg"

// extract suffixes of Slack messages starting with @bot-name
var extractMsgPattern string = `(?m)^\s*<@%s>:?\s*(?P<` + extractMsgGroupName + `>.*)$`

// A standupMsg is a Slack message directed to the arriba bot (i.e. with a @botname prefix)
type standupMsg struct {
	ts   time.Time
	text string
}

// channelStandup contains the latest standup message of each user in a Slack channel.
type channelStandup map[string]standupMsg

// sortableChannelStandup is a channelStandup, sortable by the timestamp of its messages
// sortableChannelStandup implements sort.Interface to sort the keys of the channelStandup
type sortableChannelStandup struct {
	keys []string
	cs   channelStandup
}

func (s sortableChannelStandup) Swap(i, j int) { s.keys[i], s.keys[j] = s.keys[j], s.keys[i] }
func (s sortableChannelStandup) Len() int      { return len(s.keys) }
func (s sortableChannelStandup) Less(i, j int) bool {
	return s.cs[s.keys[i]].ts.After(s.cs[s.keys[j]].ts)
}

// getKeysByTimestamp returns the userIDs of the standup ordered by their message timestamp (newer first).
func (cs channelStandup) getKeysByTimestamp() []string {
	keys := make([]string, 0, len(cs))
	for k := range cs {
		keys = append(keys, k)
	}
	scs := sortableChannelStandup{
		cs:   cs,
		keys: keys,
	}
	sort.Sort(scs)
	return scs.keys
}

// standups contains the channelStandup of all Slack channels known to the bot.
type standups map[string]channelStandup

// conversation is a generic way to access the IDs, Names and history of both
// slack.Channel and slack.Group. Unfortunately nlopes/slack doesn't expose the
// underlying common type (groupConversation) and we cannot define methods for
// non-local types, which would allow to make things much cleaner ...
type conversation interface {
	getID() string
	getName() string
	getHistory(*slack.RTM, slack.HistoryParameters) (*slack.History, error)
}

type channel slack.Channel

func (c channel) getID() string   { return c.ID }
func (c channel) getName() string { return c.Name }
func (c channel) getHistory(rtm *slack.RTM, params slack.HistoryParameters) (*slack.History, error) {
	return rtm.GetChannelHistory(c.getID(), params)
}

type group slack.Group

func (g group) getID() string   { return g.ID }
func (g group) getName() string { return g.Name }
func (g group) getHistory(rtm *slack.RTM, params slack.HistoryParameters) (*slack.History, error) {
	return rtm.GetGroupHistory(g.getID(), params)
}

type arriba struct {
	rtm              *slack.RTM
	botID            string
	botName          string
	extractMsgRE     *regexp.Regexp
	historyDaysLimit int
	standups         standups
}

func newArriba(rtm *slack.RTM, historyDaysLimit int) arriba {
	return arriba{
		rtm:              rtm,
		historyDaysLimit: historyDaysLimit,
		standups:         make(standups),
	}
}

func parseSlackTimeStamp(ts string) (time.Time, error) {
	var seconds, milliseconds int64
	_, err := fmt.Sscanf(ts, "%d.%d", &seconds, &milliseconds)
	if err != nil {
		logrus.Warn("Can't parse timestamp ", ts)
		return time.Now(), err
	}
	return time.Unix(seconds, milliseconds*1000), nil
}

// extractStandupMsg parses Slack messages starting with @bot-name
func (a arriba) extractChannelStandupMsg(msg slack.Msg) (standupMsg, bool) {
	if msg.Type != "message" || msg.SubType != "" {
		return standupMsg{}, false
	}
	standupText := a.extractMsgRE.ReplaceAllString(msg.Text, "$"+extractMsgGroupName)
	if len(standupText) == len(msg.Text) {
		// Nothing was extracted
		return standupMsg{}, false
	}
	ts, err := parseSlackTimeStamp(msg.Timestamp)
	if err != nil {
		return standupMsg{}, false
	}
	return standupMsg{ts, standupText}, true
}

func (a arriba) retrieveChannelStandup(c conversation) (channelStandup, error) {
	params := slack.NewHistoryParameters()
	params.Count = 1000
	params.Oldest = fmt.Sprintf(
		"%d",
		time.Now().UTC().AddDate(0, 0, -a.historyDaysLimit).Unix(),
	)
	// It would be way more efficient to use slack.SearchMsgs instead
	// of traversing the whole history, but that's not allowed for bots :(
	cstandup := make(channelStandup)
	for {
		history, error := c.getHistory(a.rtm, params)
		if error != nil {
			return cstandup, error
		}

		for _, msg := range history.Messages {
			if _, ok := cstandup[msg.User]; ok {
				// we already have the latest standup message for this user
				continue
			}
			standupMsg, ok := a.extractChannelStandupMsg(msg.Msg)
			if ok && standupMsg.text != "" {
				cstandup[msg.User] = standupMsg
			}
		}

		if !history.HasMore {
			break
		}
		oldestMsg := history.Messages[len(history.Messages)-1]
		params.Oldest = oldestMsg.Timestamp
		params.Inclusive = false
	}
	return cstandup, nil
}

func (a arriba) retrieveStandups(conversations []conversation) {
	for _, c := range conversations {
		logrus.Infof("Retrieveing standup for conversation #%s (%s)", c.getName(), c.getID())
		cstandup, err := a.retrieveChannelStandup(c)
		if err != nil {
			logrus.Errorf("Can't retrieve channel standup for conversation #%s: %s", c.getName(), err)
		}
		a.standups[c.getID()] = cstandup
		logrus.Infof("Standup for conversation #%s (%s) updated to %#v", c.getName(), c.getID(), cstandup)
	}
}

func (a arriba) getUserName(userID string) string {
	info, err := a.rtm.GetUserInfo(userID)
	userName := "id" + userID
	if err != nil {
		logrus.Errorf("Couldn't get user information for user %s: %s", userID, err)
	} else {
		userName = info.Name
	}
	return userName
}

func (a arriba) removeOldMessages(channelID string) {
	cstandup, ok := a.standups[channelID]
	if !ok {
		return
	}
	oldestAllowed := time.Now().UTC().AddDate(0, 0, -a.historyDaysLimit)
	for userID, msg := range cstandup {
		if msg.ts.Before(oldestAllowed) {
			delete(cstandup, userID)
		}
	}
}

func (a arriba) prettyPrintChannelStandup(cstandup channelStandup) string {
	text := "¡Ándale! ¡Ándale! here's the standup status :tada:\n"
	for _, userID := range cstandup.getKeysByTimestamp() {
		standupMsg := cstandup[userID]
		humanTime := humanize.Time(standupMsg.ts)
		userName := a.getUserName(userID)
		text += fmt.Sprintf("*%s*: %s _(%s)_\n", userName, standupMsg.text, humanTime)
	}
	return text
}

func (a arriba) sendStatus(channelID string) {
	var statusText string
	if cstandup, ok := a.standups[channelID]; ok && len(cstandup) > 0 {
		statusText = a.prettyPrintChannelStandup(cstandup)
	} else {
		statusText = fmt.Sprintf("No standup messages found\nType a message starting with *@%s* to record your standup message", a.botName)
	}
	a.rtm.SendMessage(a.rtm.NewOutgoingMessage(statusText, channelID))

}

func (a arriba) updateLastStandup(channelID, userID string, msg standupMsg) {
	if _, ok := a.standups[channelID]; !ok {
		a.standups[channelID] = make(channelStandup)
	}
	a.standups[channelID][userID] = msg
	confirmationText := fmt.Sprintf("<@%s>: ¡Yeppa! standup status recorded :taco:", userID)
	a.rtm.SendMessage(a.rtm.NewOutgoingMessage(confirmationText, channelID))
}

func (a *arriba) handleConnectedEvent(ev *slack.ConnectedEvent) {
	if a.botID != "" {
		logrus.Warn("Received unexpected Connected event")
		return
	}
	logrus.Infof(
		"Connected as user %s (%s) to team %s (%s)",
		ev.Info.User.Name,
		ev.Info.User.ID,
		ev.Info.Team.Name,
		ev.Info.Team.ID,
	)
	a.botID = ev.Info.User.ID
	a.botName = ev.Info.User.Name
	a.extractMsgRE = regexp.MustCompile(fmt.Sprintf(extractMsgPattern, a.botID))

	// Retrieve standups for public channels and private groups
	var conversations []conversation
	for _, c := range ev.Info.Channels {
		if c.IsMember {
			conversations = append(conversations, channel(c))
		}
	}
	for _, g := range ev.Info.Groups {
		conversations = append(conversations, group(g))
	}
	a.retrieveStandups(conversations)
}

func (a arriba) handleMessageEvent(ev *slack.MessageEvent) {
	logrus.Debugf("Message received %+v", ev)
	if a.botID == "" {
		logrus.Warn("Received message event before finishing initialization")
		return
	}
	if ev.Channel == "" {
		logrus.Warn("Received message with empty channel")
		return
	}
	switch ev.Channel[0] {
	case 'C', 'G':
		// Public and private (group) channels
		smsg, ok := a.extractChannelStandupMsg(ev.Msg)
		if !ok {
			return
		}
		logrus.Infof("Received standup message in channel %s: %+v", ev.Channel, smsg)
		// Garbage-collect old messages
		a.removeOldMessages(ev.Msg.Channel)
		if smsg.text == "" {
			a.sendStatus(ev.Msg.Channel)
		} else {
			a.updateLastStandup(ev.Msg.Channel, ev.Msg.User, smsg)
		}

	case 'D':
		// Direct messages are not supported yet
	}
}

func (a arriba) run() {
	go a.rtm.ManageConnection()

	for {
		select {
		case msg := <-a.rtm.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.ConnectedEvent:
				a.handleConnectedEvent(ev)
			case *slack.MessageEvent:
				a.handleMessageEvent(ev)
			case *slack.RTMError:
				logrus.Error("Invalid credentials", ev.Error())
			case *slack.InvalidAuthEvent:
				logrus.Error("Invalid credentials")
				os.Exit(1)
			}
		}
	}
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s [ flags ] <SlackAPItoken>\n", os.Args[0])
	flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, "You can obtain <SlackAPItoken> from https://<yourteam>.slack.com/services/new/bot\n")
}

func main() {
	var (
		debug            bool
		historyDaysLimit int
	)

	flag.Usage = usage
	flag.BoolVar(&debug, "debug", false, "Print debug information")
	flag.IntVar(&historyDaysLimit, "history-limit", 7, "History limit (in days)")
	flag.Parse()
	if len(flag.Args()) < 1 || historyDaysLimit < 1 {
		usage()
		os.Exit(1)
	}

	logrus.SetOutput(os.Stderr)
	if debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	api := slack.New(flag.Arg(0))
	api.SetDebug(debug)

	newArriba(api.NewRTM(), historyDaysLimit).run()
}
<commit_msg>Fix history traversal<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"regexp"
	"sort"
	"time"

	"github.com/Sirupsen/logrus"
	humanize "github.com/dustin/go-humanize"
	"github.com/nlopes/slack"
)

const extractMsgGroupName = "msg"

// extract suffixes of Slack messages starting with @bot-name
var extractMsgPattern string = `(?m)^\s*<@%s>:?\s*(?P<` + extractMsgGroupName + `>.*)$`

// A standupMsg is a Slack message directed to the arriba bot (i.e. with a @botname prefix)
type standupMsg struct {
	ts   time.Time
	text string
}

// channelStandup contains the latest standup message of each user in a Slack channel.
type channelStandup map[string]standupMsg

// sortableChannelStandup is a channelStandup, sortable by the timestamp of its messages
// sortableChannelStandup implements sort.Interface to sort the keys of the channelStandup
type sortableChannelStandup struct {
	keys []string
	cs   channelStandup
}

func (s sortableChannelStandup) Swap(i, j int) { s.keys[i], s.keys[j] = s.keys[j], s.keys[i] }
func (s sortableChannelStandup) Len() int      { return len(s.keys) }
func (s sortableChannelStandup) Less(i, j int) bool {
	return s.cs[s.keys[i]].ts.After(s.cs[s.keys[j]].ts)
}

// getKeysByTimestamp returns the userIDs of the standup ordered by their message timestamp (newer first).
func (cs channelStandup) getKeysByTimestamp() []string {
	keys := make([]string, 0, len(cs))
	for k := range cs {
		keys = append(keys, k)
	}
	scs := sortableChannelStandup{
		cs:   cs,
		keys: keys,
	}
	sort.Sort(scs)
	return scs.keys
}

// standups contains the channelStandup of all Slack channels known to the bot.
type standups map[string]channelStandup

// conversation is a generic way to access the IDs, Names and history of both
// slack.Channel and slack.Group. Unfortunately nlopes/slack doesn't expose the
// underlying common type (groupConversation) and we cannot define methods for
// non-local types, which would allow to make things much cleaner ...
type conversation interface {
	getID() string
	getName() string
	getHistory(*slack.RTM, slack.HistoryParameters) (*slack.History, error)
}

type channel slack.Channel

func (c channel) getID() string   { return c.ID }
func (c channel) getName() string { return c.Name }
func (c channel) getHistory(rtm *slack.RTM, params slack.HistoryParameters) (*slack.History, error) {
	return rtm.GetChannelHistory(c.getID(), params)
}

type group slack.Group

func (g group) getID() string   { return g.ID }
func (g group) getName() string { return g.Name }
func (g group) getHistory(rtm *slack.RTM, params slack.HistoryParameters) (*slack.History, error) {
	return rtm.GetGroupHistory(g.getID(), params)
}

type arriba struct {
	rtm              *slack.RTM
	botID            string
	botName          string
	extractMsgRE     *regexp.Regexp
	historyDaysLimit int
	standups         standups
}

func newArriba(rtm *slack.RTM, historyDaysLimit int) arriba {
	return arriba{
		rtm:              rtm,
		historyDaysLimit: historyDaysLimit,
		standups:         make(standups),
	}
}

func parseSlackTimeStamp(ts string) (time.Time, error) {
	var seconds, milliseconds int64
	_, err := fmt.Sscanf(ts, "%d.%d", &seconds, &milliseconds)
	if err != nil {
		logrus.Warn("Can't parse timestamp ", ts)
		return time.Now(), err
	}
	return time.Unix(seconds, milliseconds*1000), nil
}

// extractStandupMsg parses Slack messages starting with @bot-name
func (a arriba) extractChannelStandupMsg(msg slack.Msg) (standupMsg, bool) {
	if msg.Type != "message" || msg.SubType != "" {
		return standupMsg{}, false
	}
	standupText := a.extractMsgRE.ReplaceAllString(msg.Text, "$"+extractMsgGroupName)
	if len(standupText) == len(msg.Text) {
		// Nothing was extracted
		return standupMsg{}, false
	}
	ts, err := parseSlackTimeStamp(msg.Timestamp)
	if err != nil {
		return standupMsg{}, false
	}
	return standupMsg{ts, standupText}, true
}

func (a arriba) retrieveChannelStandup(c conversation) (channelStandup, error) {
	params := slack.NewHistoryParameters()
	params.Count = 1000
	now := time.Now().UTC()
	params.Latest = fmt.Sprintf("%d", now.Unix())
	params.Oldest = fmt.Sprintf("%d", now.AddDate(0, 0, -a.historyDaysLimit).Unix())

	// It would be way more efficient to use slack.SearchMsgs instead
	// of traversing the whole history, but that's not allowed for bots :(
	cstandup := make(channelStandup)
	for {
		history, error := c.getHistory(a.rtm, params)
		if error != nil || history == nil || len(history.Messages) == 0 {
			return cstandup, error
		}

		logrus.Debugf(
			"Got history chunk (from %s to %s, latest %s) for conversation %s",
			history.Messages[len(history.Messages)-1].Msg.Timestamp,
			history.Messages[0].Msg.Timestamp, history.Latest, c.getID())

		// Messages are increasingly ordered by time, traverse them in reverse order
		for i, _ := range history.Messages {
			msg := history.Messages[len(history.Messages)-1-i]
			if _, ok := cstandup[msg.User]; ok {
				// we already have the latest standup message for this user
				continue
			}
			standupMsg, ok := a.extractChannelStandupMsg(msg.Msg)
			if ok && standupMsg.text != "" {
				cstandup[msg.User] = standupMsg
			}
		}

		if !history.HasMore {
			break
		}
		latestMsg := history.Messages[len(history.Messages)-1]
		params.Latest = latestMsg.Timestamp
		params.Inclusive = false
	}
	return cstandup, nil
}

func (a arriba) retrieveStandups(conversations []conversation) {
	for _, c := range conversations {
		logrus.Infof("Retrieveing standup for conversation #%s (%s)", c.getName(), c.getID())
		cstandup, err := a.retrieveChannelStandup(c)
		if err != nil {
			logrus.Errorf("Can't retrieve channel standup for conversation #%s: %s", c.getName(), err)
		}
		a.standups[c.getID()] = cstandup
		logrus.Infof("Standup for conversation #%s (%s) updated to %#v", c.getName(), c.getID(), cstandup)
	}
}

func (a arriba) getUserName(userID string) string {
	info, err := a.rtm.GetUserInfo(userID)
	userName := "id" + userID
	if err != nil {
		logrus.Errorf("Couldn't get user information for user %s: %s", userID, err)
	} else {
		userName = info.Name
	}
	return userName
}

func (a arriba) removeOldMessages(channelID string) {
	cstandup, ok := a.standups[channelID]
	if !ok {
		return
	}
	oldestAllowed := time.Now().UTC().AddDate(0, 0, -a.historyDaysLimit)
	for userID, msg := range cstandup {
		if msg.ts.Before(oldestAllowed) {
			delete(cstandup, userID)
		}
	}
}

func (a arriba) prettyPrintChannelStandup(cstandup channelStandup) string {
	text := "¡Ándale! ¡Ándale! here's the standup status :tada:\n"
	for _, userID := range cstandup.getKeysByTimestamp() {
		standupMsg := cstandup[userID]
		humanTime := humanize.Time(standupMsg.ts)
		userName := a.getUserName(userID)
		text += fmt.Sprintf("*%s*: %s _(%s)_\n", userName, standupMsg.text, humanTime)
	}
	return text
}

func (a arriba) sendStatus(channelID string) {
	var statusText string
	if cstandup, ok := a.standups[channelID]; ok && len(cstandup) > 0 {
		statusText = a.prettyPrintChannelStandup(cstandup)
	} else {
		statusText = fmt.Sprintf("No standup messages found\nType a message starting with *@%s* to record your standup message", a.botName)
	}
	a.rtm.SendMessage(a.rtm.NewOutgoingMessage(statusText, channelID))

}

func (a arriba) updateLastStandup(channelID, userID string, msg standupMsg) {
	if _, ok := a.standups[channelID]; !ok {
		a.standups[channelID] = make(channelStandup)
	}
	a.standups[channelID][userID] = msg
	confirmationText := fmt.Sprintf("<@%s>: ¡Yeppa! standup status recorded :taco:", userID)
	a.rtm.SendMessage(a.rtm.NewOutgoingMessage(confirmationText, channelID))
}

func (a *arriba) handleConnectedEvent(ev *slack.ConnectedEvent) {
	if a.botID != "" {
		logrus.Warn("Received unexpected Connected event")
		return
	}
	logrus.Infof(
		"Connected as user %s (%s) to team %s (%s)",
		ev.Info.User.Name,
		ev.Info.User.ID,
		ev.Info.Team.Name,
		ev.Info.Team.ID,
	)
	a.botID = ev.Info.User.ID
	a.botName = ev.Info.User.Name
	a.extractMsgRE = regexp.MustCompile(fmt.Sprintf(extractMsgPattern, a.botID))

	// Retrieve standups for public channels and private groups
	var conversations []conversation
	for _, c := range ev.Info.Channels {
		if c.IsMember {
			conversations = append(conversations, channel(c))
		}
	}
	for _, g := range ev.Info.Groups {
		conversations = append(conversations, group(g))
	}
	a.retrieveStandups(conversations)
}

func (a arriba) handleMessageEvent(ev *slack.MessageEvent) {
	logrus.Debugf("Message received %+v", ev)
	if a.botID == "" {
		logrus.Warn("Received message event before finishing initialization")
		return
	}
	if ev.Channel == "" {
		logrus.Warn("Received message with empty channel")
		return
	}
	switch ev.Channel[0] {
	case 'C', 'G':
		// Public and private (group) channels
		smsg, ok := a.extractChannelStandupMsg(ev.Msg)
		if !ok {
			return
		}
		logrus.Infof("Received standup message in channel %s: %+v", ev.Channel, smsg)
		// Garbage-collect old messages
		a.removeOldMessages(ev.Msg.Channel)
		if smsg.text == "" {
			a.sendStatus(ev.Msg.Channel)
		} else {
			a.updateLastStandup(ev.Msg.Channel, ev.Msg.User, smsg)
		}

	case 'D':
		// Direct messages are not supported yet
	}
}

func (a arriba) run() {
	go a.rtm.ManageConnection()

	for {
		select {
		case msg := <-a.rtm.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.ConnectedEvent:
				a.handleConnectedEvent(ev)
			case *slack.MessageEvent:
				a.handleMessageEvent(ev)
			case *slack.RTMError:
				logrus.Error("Invalid credentials", ev.Error())
			case *slack.InvalidAuthEvent:
				logrus.Error("Invalid credentials")
				os.Exit(1)
			}
		}
	}
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s [ flags ] <SlackAPItoken>\n", os.Args[0])
	flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, "You can obtain <SlackAPItoken> from https://<yourteam>.slack.com/services/new/bot\n")
}

func main() {
	var (
		debug            bool
		historyDaysLimit int
	)

	flag.Usage = usage
	flag.BoolVar(&debug, "debug", false, "Print debug information")
	flag.IntVar(&historyDaysLimit, "history-limit", 7, "History limit (in days)")
	flag.Parse()
	if len(flag.Args()) < 1 || historyDaysLimit < 1 {
		usage()
		os.Exit(1)
	}

	logrus.SetOutput(os.Stderr)
	if debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	api := slack.New(flag.Arg(0))
	api.SetDebug(debug)

	newArriba(api.NewRTM(), historyDaysLimit).run()
}
<|endoftext|>


<commit_before>package main

import (
	//"sse"
	"helper"
)

func main() {
	//sse.Run()

	helper.GetServerExternalIPAddress()

	return
}
<commit_msg>cleanup<commit_after>package main

import (
	"sse"
)

func main() {
	sse.Run()

	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha1"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/dchest/uniuri"
	"github.com/ewhal/pygments"
	_ "github.com/go-sql-driver/mysql"
	"github.com/gorilla/mux"
)

const (
	ADDRESS  = "http://localhost:9900"
	LENGTH   = 6
	PORT     = ":9900"
	USERNAME = ""
	PASS     = ""
	NAME     = ""
	DATABASE = USERNAME + ":" + PASS + "@/" + NAME + "?charset=utf8"
)

type Response struct {
	ID     string `json:"id"`
	TITLE  string `json:"title"`
	HASH   string `json:"hash"`
	URL    string `json:"url"`
	SIZE   int    `json:"size"`
	DELKEY string `json:"delkey"`
}

type Page struct {
	Title    string
	Body     []byte
	Raw      string
	Home     string
	Download string
	Clone    string
}

func check(err error) {
	if err != nil {
		fmt.Println(err)
	}
}

func generateName() string {
	s := uniuri.NewLen(LENGTH)
	db, err := sql.Open("mysql", DATABASE)
	check(err)

	query, err := db.Query("select id from pastebin")
	for query.Next() {
		var id string
		err := query.Scan(&id)
		if err != nil {

		}
		if id == s {
			generateName()
		}
	}
	db.Close()

	return s

}
func hash(paste string) string {
	hasher := sha1.New()

	hasher.Write([]byte(paste))
	sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
	return sha
}

func save(raw string, lang string, title string, expiry string) []string {
	db, err := sql.Open("mysql", DATABASE)
	check(err)

	sha := hash(raw)
	query, err := db.Query("select id, title, hash, data, delkey from pastebin")
	for query.Next() {
		var id, title, hash, paste, delkey string
		err := query.Scan(&id, &title, &hash, &paste, &delkey)
		check(err)
		if hash == sha {
			url := ADDRESS + "/p/" + id
			return []string{id, title, hash, url, paste, delkey}
		}
	}
	id := generateName()
	var url string
	if lang == "" {
		url = ADDRESS + "/p/" + id
	} else {
		url = ADDRESS + "/p/" + id + "/" + lang
	}
	now := time.Now()
	var expiryTime string

	switch expiry {
	case "5 minutes":
		expiryTime = now.Add(time.Minute * 5).Format("2006-01-02 15:04:05")
		break

	case "1 hour":
		expiryTime = now.Add(time.Hour + 1).Format("2006-01-02 15:04:05")
		break

	case "1 day":
		expiryTime = now.Add(time.Hour * 24 * 1).Format("2006-01-02 15:04:05")
		break

	case "1 week":
		expiryTime = now.Add(time.Hour * 24 * 7).Format("2006-01-02 15:04:05")
		break

	case "1 month":
		expiryTime = now.Add(time.Hour * 24 * 30).Format("2006-01-02 15:04:05")
		break

	case "1 year":
		expiryTime = now.Add(time.Hour * 24 * 365).Format("2006-01-02 15:04:05")
		break

	default:
		expiryTime = now.Add(((time.Hour * 24) * 365) * 200).Format("2006-01-02 15:04:05")
		break

	}
	delKey := uniuri.NewLen(40)
	paste := html.EscapeString(raw)

	stmt, err := db.Prepare("INSERT INTO pastebin(id, title, hash, data, delkey, expiry) values(?,?,?,?,?,?)")
	check(err)
	if title == "" {
		_, err = stmt.Exec(id, id, sha, paste, delKey, expiryTime)
		check(err)
	} else {
		_, err = stmt.Exec(id, html.EscapeString(title), sha, paste, delKey, expiryTime)
		check(err)
	}
	db.Close()
	return []string{id, title, sha, url, paste, delKey}
}

func delHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	delkey := vars["delKey"]

	db, err := sql.Open("mysql", DATABASE)
	check(err)

	stmt, err := db.Prepare("delete from pastebin where delkey=?")
	check(err)

	res, err := stmt.Exec(html.EscapeString(delkey))
	check(err)

	_, err = res.RowsAffected()
	if err == sql.ErrNoRows {
		io.WriteString(w, "Error invalid paste")
	} else {
		io.WriteString(w, paste+" deleted")
	}
	db.Close()

}
func saveHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	output := vars["output"]
	switch r.Method {
	case "POST":
		paste := r.FormValue("p")
		lang := r.FormValue("lang")
		title := r.FormValue("title")
		expiry := r.FormValue("expiry")
		if paste == "" {
			http.Error(w, "Empty paste", 500)
			return
		}
		values := save(paste, lang, title, expiry)
		b := &Response{
			ID:     values[0],
			TITLE:  values[1],
			HASH:   values[2],
			URL:    values[3],
			SIZE:   len(values[4]),
			DELKEY: values[5],
		}

		switch output {
		case "json":

			w.Header().Set("Content-Type", "application/json")
			err := json.NewEncoder(w).Encode(b)

			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
		case "xml":
			x, err := xml.MarshalIndent(b, "", "  ")
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			w.Header().Set("Content-Type", "application/xml")
			w.Write(x)

		case "html":
			w.Header().Set("Content-Type", "text/html")
			io.WriteString(w, "
URL: "+b.URL+"
")
			io.WriteString(w, "
Delete Key: "+b.DELKEY+"
")

		case "redirect":
			http.Redirect(w, r, b.URL, 301)

		default:
			w.Header().Set("Content-Type", "plain/text")
			io.WriteString(w, b.URL+"\n")
			io.WriteString(w, "delete key: "+b.DELKEY+"\n")
		}
	}

}

func highlight(s string, lang string) (string, error) {

	highlight, err := pygments.Highlight(html.UnescapeString(s), html.EscapeString(lang), "html", "style=autumn,linenos=True, lineanchors=True,anchorlinenos=True,noclasses=True,", "utf-8")
	if err != nil {
		return "", err
	}
	return highlight, nil

}

func getPaste(paste string, lang string) (string, string) {
	param1 := html.EscapeString(paste)
	db, err := sql.Open("mysql", DATABASE)
	var title, s string
	var expiry string
	err = db.QueryRow("select title, data, expiry from pastebin where id=?", param1).Scan(&title, &s, &expiry)
	check(err)
	if time.Now().Format("2006-01-02 15:04:05") > expiry {
		stmt, err := db.Prepare("delete from pastebin where id=?")
		check(err)
		_, err = stmt.Exec(param1)
		check(err)
		return "Error invalid paste", ""
	}
	db.Close()

	if err == sql.ErrNoRows {
		return "Error invalid paste", ""
	} else {
		if lang == "" {
			return html.UnescapeString(s), html.UnescapeString(title)
		} else {
			high, err := highlight(s, lang)
			check(err)
			return high, html.UnescapeString(title)

		}
	}

}

var templates = template.Must(template.ParseFiles("assets/paste.html", "assets/index.html", "assets/clone.html"))
var syntax, _ = ioutil.ReadFile("assets/syntax.html")

func pasteHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	lang := vars["lang"]
	s, title := getPaste(paste, lang)
	link := ADDRESS + "/raw/" + paste
	download := ADDRESS + "/download/" + paste
	clone := ADDRESS + "/clone/" + paste
	if lang == "" {
		p := &Page{
			Title:    title,
			Body:     []byte(s),
			Raw:      link,
			Home:     ADDRESS,
			Download: download,
			Clone:    clone,
		}
		err := templates.ExecuteTemplate(w, "paste.html", p)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}

	} else {
		fmt.Fprintf(w, string(syntax), paste, paste, s, ADDRESS, download, link, clone)

	}
}

func cloneHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	s, title := getPaste(paste, "")
	link := ADDRESS + "/raw/" + paste
	download := ADDRESS + "/download/" + paste
	clone := ADDRESS + "/clone/" + paste
	p := &Page{
		Title:    title,
		Body:     []byte(s),
		Raw:      link,
		Home:     ADDRESS,
		Download: download,
		Clone:    clone,
	}
	err := templates.ExecuteTemplate(w, "clone.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

}
func downloadHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	s, _ := getPaste(paste, "")
	w.Header().Set("Content-Disposition", "attachment; filename="+paste)
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	io.WriteString(w, s)

}
func rawHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	s, _ := getPaste(paste, "")
	io.WriteString(w, s)

}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/p/{pasteId}", pasteHandler)
	router.HandleFunc("/raw/{pasteId}", rawHandler)
	router.HandleFunc("/p/{pasteId}/{lang}", pasteHandler)
	router.HandleFunc("/clone/{pasteId}", cloneHandler)
	router.HandleFunc("/download/{pasteId}", downloadHandler)
	router.HandleFunc("/save", saveHandler)
	router.HandleFunc("/save/{output}", saveHandler)
	router.HandleFunc("/del/{pasteId}/{delKey}", delHandler)
	router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("assets/"))))
	err := http.ListenAndServe(PORT, router)
	if err != nil {
		log.Fatal(err)
	}

}
<commit_msg>Try 20 years for 'forever expiry'<commit_after>package main

import (
	"crypto/sha1"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/dchest/uniuri"
	"github.com/ewhal/pygments"
	_ "github.com/go-sql-driver/mysql"
	"github.com/gorilla/mux"
)

const (
	ADDRESS  = "http://localhost:9900"
	LENGTH   = 6
	PORT     = ":9900"
	USERNAME = ""
	PASS     = ""
	NAME     = ""
	DATABASE = USERNAME + ":" + PASS + "@/" + NAME + "?charset=utf8"
)

type Response struct {
	ID     string `json:"id"`
	TITLE  string `json:"title"`
	HASH   string `json:"hash"`
	URL    string `json:"url"`
	SIZE   int    `json:"size"`
	DELKEY string `json:"delkey"`
}

type Page struct {
	Title    string
	Body     []byte
	Raw      string
	Home     string
	Download string
	Clone    string
}

func check(err error) {
	if err != nil {
		fmt.Println(err)
	}
}

func generateName() string {
	s := uniuri.NewLen(LENGTH)
	db, err := sql.Open("mysql", DATABASE)
	check(err)

	query, err := db.Query("select id from pastebin")
	for query.Next() {
		var id string
		err := query.Scan(&id)
		if err != nil {

		}
		if id == s {
			generateName()
		}
	}
	db.Close()

	return s

}
func hash(paste string) string {
	hasher := sha1.New()

	hasher.Write([]byte(paste))
	sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
	return sha
}

func save(raw string, lang string, title string, expiry string) []string {
	db, err := sql.Open("mysql", DATABASE)
	check(err)

	sha := hash(raw)
	query, err := db.Query("select id, title, hash, data, delkey from pastebin")
	for query.Next() {
		var id, title, hash, paste, delkey string
		err := query.Scan(&id, &title, &hash, &paste, &delkey)
		check(err)
		if hash == sha {
			url := ADDRESS + "/p/" + id
			return []string{id, title, hash, url, paste, delkey}
		}
	}
	id := generateName()
	var url string
	if lang == "" {
		url = ADDRESS + "/p/" + id
	} else {
		url = ADDRESS + "/p/" + id + "/" + lang
	}
	now := time.Now()
	var expiryTime string

	switch expiry {
	case "5 minutes":
		expiryTime = now.Add(time.Minute * 5).Format("2006-01-02 15:04:05")
		break

	case "1 hour":
		expiryTime = now.Add(time.Hour + 1).Format("2006-01-02 15:04:05")
		break

	case "1 day":
		expiryTime = now.Add(time.Hour * 24 * 1).Format("2006-01-02 15:04:05")
		break

	case "1 week":
		expiryTime = now.Add(time.Hour * 24 * 7).Format("2006-01-02 15:04:05")
		break

	case "1 month":
		expiryTime = now.Add(time.Hour * 24 * 30).Format("2006-01-02 15:04:05")
		break

	case "1 year":
		expiryTime = now.Add(time.Hour * 24 * 365).Format("2006-01-02 15:04:05")
		break

	case "forever":
		expiryTime = now.Add(time.Hour * 24 * (365 * 20)).Format("2006-01-02 15:04:05")
		break

	default:
		expiryTime = now.Add(time.Hour * 24 * (365 * 20)).Format("2006-01-02 15:04:05")
		break

	}
	delKey := uniuri.NewLen(40)
	paste := html.EscapeString(raw)

	stmt, err := db.Prepare("INSERT INTO pastebin(id, title, hash, data, delkey, expiry) values(?,?,?,?,?,?)")
	check(err)
	if title == "" {
		_, err = stmt.Exec(id, id, sha, paste, delKey, expiryTime)
		check(err)
	} else {
		_, err = stmt.Exec(id, html.EscapeString(title), sha, paste, delKey, expiryTime)
		check(err)
	}
	db.Close()
	return []string{id, title, sha, url, paste, delKey}
}

func delHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	delkey := vars["delKey"]

	db, err := sql.Open("mysql", DATABASE)
	check(err)

	stmt, err := db.Prepare("delete from pastebin where delkey=?")
	check(err)

	res, err := stmt.Exec(html.EscapeString(delkey))
	check(err)

	_, err = res.RowsAffected()
	if err == sql.ErrNoRows {
		io.WriteString(w, "Error invalid paste")
	} else {
		io.WriteString(w, paste+" deleted")
	}
	db.Close()

}
func saveHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	output := vars["output"]
	switch r.Method {
	case "POST":
		paste := r.FormValue("p")
		lang := r.FormValue("lang")
		title := r.FormValue("title")
		expiry := r.FormValue("expiry")
		if paste == "" {
			http.Error(w, "Empty paste", 500)
			return
		}
		values := save(paste, lang, title, expiry)
		b := &Response{
			ID:     values[0],
			TITLE:  values[1],
			HASH:   values[2],
			URL:    values[3],
			SIZE:   len(values[4]),
			DELKEY: values[5],
		}

		switch output {
		case "json":

			w.Header().Set("Content-Type", "application/json")
			err := json.NewEncoder(w).Encode(b)

			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
		case "xml":
			x, err := xml.MarshalIndent(b, "", "  ")
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			w.Header().Set("Content-Type", "application/xml")
			w.Write(x)

		case "html":
			w.Header().Set("Content-Type", "text/html")
			io.WriteString(w, "
URL: "+b.URL+"
")
			io.WriteString(w, "
Delete Key: "+b.DELKEY+"
")

		case "redirect":
			http.Redirect(w, r, b.URL, 301)

		default:
			w.Header().Set("Content-Type", "plain/text")
			io.WriteString(w, b.URL+"\n")
			io.WriteString(w, "delete key: "+b.DELKEY+"\n")
		}
	}

}

func highlight(s string, lang string) (string, error) {

	highlight, err := pygments.Highlight(html.UnescapeString(s), html.EscapeString(lang), "html", "style=autumn,linenos=True, lineanchors=True,anchorlinenos=True,noclasses=True,", "utf-8")
	if err != nil {
		return "", err
	}
	return highlight, nil

}

func getPaste(paste string, lang string) (string, string) {
	param1 := html.EscapeString(paste)
	db, err := sql.Open("mysql", DATABASE)
	var title, s string
	var expiry string
	err = db.QueryRow("select title, data, expiry from pastebin where id=?", param1).Scan(&title, &s, &expiry)
	check(err)
	if time.Now().Format("2006-01-02 15:04:05") > expiry {
		stmt, err := db.Prepare("delete from pastebin where id=?")
		check(err)
		_, err = stmt.Exec(param1)
		check(err)
		return "Error invalid paste", ""
	}
	db.Close()

	if err == sql.ErrNoRows {
		return "Error invalid paste", ""
	} else {
		if lang == "" {
			return html.UnescapeString(s), html.UnescapeString(title)
		} else {
			high, err := highlight(s, lang)
			check(err)
			return high, html.UnescapeString(title)

		}
	}

}

var templates = template.Must(template.ParseFiles("assets/paste.html", "assets/index.html", "assets/clone.html"))
var syntax, _ = ioutil.ReadFile("assets/syntax.html")

func pasteHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	lang := vars["lang"]
	s, title := getPaste(paste, lang)
	link := ADDRESS + "/raw/" + paste
	download := ADDRESS + "/download/" + paste
	clone := ADDRESS + "/clone/" + paste
	if lang == "" {
		p := &Page{
			Title:    title,
			Body:     []byte(s),
			Raw:      link,
			Home:     ADDRESS,
			Download: download,
			Clone:    clone,
		}
		err := templates.ExecuteTemplate(w, "paste.html", p)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}

	} else {
		fmt.Fprintf(w, string(syntax), paste, paste, s, ADDRESS, download, link, clone)

	}
}

func cloneHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	s, title := getPaste(paste, "")
	link := ADDRESS + "/raw/" + paste
	download := ADDRESS + "/download/" + paste
	clone := ADDRESS + "/clone/" + paste
	p := &Page{
		Title:    title,
		Body:     []byte(s),
		Raw:      link,
		Home:     ADDRESS,
		Download: download,
		Clone:    clone,
	}
	err := templates.ExecuteTemplate(w, "clone.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

}
func downloadHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	s, _ := getPaste(paste, "")
	w.Header().Set("Content-Disposition", "attachment; filename="+paste)
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	io.WriteString(w, s)

}
func rawHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	paste := vars["pasteId"]
	s, _ := getPaste(paste, "")
	io.WriteString(w, s)

}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/p/{pasteId}", pasteHandler)
	router.HandleFunc("/raw/{pasteId}", rawHandler)
	router.HandleFunc("/p/{pasteId}/{lang}", pasteHandler)
	router.HandleFunc("/clone/{pasteId}", cloneHandler)
	router.HandleFunc("/download/{pasteId}", downloadHandler)
	router.HandleFunc("/save", saveHandler)
	router.HandleFunc("/save/{output}", saveHandler)
	router.HandleFunc("/del/{pasteId}/{delKey}", delHandler)
	router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("assets/"))))
	err := http.ListenAndServe(PORT, router)
	if err != nil {
		log.Fatal(err)
	}

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
)

import (
	"github.com/alfredxing/calc/compute"
)

import (
	"golang.org/x/crypto/ssh/terminal"
)

// Stores the state of the terminal before making it raw
var regularState *terminal.State

func main() {
	if len(os.Args) > 1 {
		input := strings.Replace(strings.Join(os.Args[1:], ""), " ", "", -1)
		res, err := compute.Evaluate(input)
		if err != nil {
			fmt.Println("Error: " + err.Error())
			return
		}
		fmt.Printf("%s\n", strconv.FormatFloat(res, 'G', -1, 64))
		return
	}

	var err error
	regularState, err = terminal.MakeRaw(0)
	if err != nil {
		panic(err)
	}
	defer terminal.Restore(0, regularState)

	term := terminal.NewTerminal(os.Stdin, "> ")
	term.AutoCompleteCallback = handleKey
	for {
		text, err := term.ReadLine()
		if err != nil {
			if err == io.EOF {
				// Quit without error on Ctrl^D
				break
			}
			panic(err)
		}

		text = strings.Replace(text, " ", "", -1)
		if text == "exit" || text == "quit" {
			break
		}

		res, err := compute.Evaluate(text)
		if err != nil {
			fmt.Println("Error: " + err.Error())
			continue
		}
		fmt.Printf("%s\n", strconv.FormatFloat(res, 'G', -1, 64))
	}
}

func handleKey(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	if key == '\x03' {
		fmt.Println()
		terminal.Restore(0, regularState)
		os.Exit(0)
	}
	return "", 0, false
}
<commit_msg>Have EOF insert a newline before returning<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
)

import (
	"github.com/alfredxing/calc/compute"
)

import (
	"golang.org/x/crypto/ssh/terminal"
)

// Stores the state of the terminal before making it raw
var regularState *terminal.State

func main() {
	if len(os.Args) > 1 {
		input := strings.Replace(strings.Join(os.Args[1:], ""), " ", "", -1)
		res, err := compute.Evaluate(input)
		if err != nil {
			fmt.Println("Error: " + err.Error())
			return
		}
		fmt.Printf("%s\n", strconv.FormatFloat(res, 'G', -1, 64))
		return
	}

	var err error
	regularState, err = terminal.MakeRaw(0)
	if err != nil {
		panic(err)
	}
	defer terminal.Restore(0, regularState)

	term := terminal.NewTerminal(os.Stdin, "> ")
	term.AutoCompleteCallback = handleKey
	for {
		text, err := term.ReadLine()
		if err != nil {
			if err == io.EOF {
				// Quit without error on Ctrl^D
				fmt.Println()
				break
			}
			panic(err)
		}

		text = strings.Replace(text, " ", "", -1)
		if text == "exit" || text == "quit" {
			break
		}

		res, err := compute.Evaluate(text)
		if err != nil {
			fmt.Println("Error: " + err.Error())
			continue
		}
		fmt.Printf("%s\n", strconv.FormatFloat(res, 'G', -1, 64))
	}
}

func handleKey(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	if key == '\x03' {
		fmt.Println()
		terminal.Restore(0, regularState)
		os.Exit(0)
	}
	return "", 0, false
}
<|endoftext|>


<commit_before>/*
	Base package for the ATOM server.

	Server periodically queries the ATOM sources of its users, stores updates

*/
package main

import (
	"fmt"
	"log"
	"os"
	"net/http"

	"github.com/clbanning/mxj"
	// "database/sql"
	// "github.com/lib/pq"
)

var testFeed = "https://www.blogger.com/feeds/8100407163665430627/posts/default"

func main() {
	xml := fetchFeedXML(testFeed)

	file, err := os.Create("output.json")
	if err != nil {
		fmt.Println("Error creating test output file.", err)
	}
	writeXMLAsJSON(xml, file) // should be writing to a db or server response
}

func fetchFeedXML(feedURI string) (mxj.Map) {
	response, err := http.Get(feedURI)
	if err != nil {
		fmt.Println("ATOM GET error:", err)
		log.Fatal(err)
	}
	defer response.Body.Close()
	// fmt.Printf("response body: %s", response.Body)

	xmlMap, err := mxj.NewMapXmlReader(response.Body, true)
	if err != nil {
		fmt.Println("Error creating map from XML reader", err)
	}

	return xmlMap
}

func writeXMLAsJSON(xmlMap mxj.Map, destination *os.File) () {
	xmlMap.JsonIndentWriter(destination, "", "\t", true)
	return
}

/*func fetchFeed(feedURI string) (io.Reader) {
	response, err := http.Get(feedURI)
	if err != nil {
		fmt.Println("ATOM GET error:", err)
		log.Fatal(err)
	}
	defer response.Body.Close()
	// fmt.Printf("response body: %s", response.Body)

	// We're getting a bug where feedToXML is trying to parse the closed response.
	// A couple solutions come to mind: create a new reader: response.Body like bufio.NewReader(response.Body),
	// copy the data: (ioutils.ReadAll(body)), or combine the functions.
	// I imagine that this is a common problem that has a solid solution

	return response.Body
}

func feedToXML(feed io.Reader) (mxj.Map) {
	// Use mxj package to translate XML structured bytes to a map[string]interface{}
	fmt.Printf("converting to xml %S", feed)
	_ = "breakpoint"
	xmlMap, err := mxj.NewMapXmlReader(feed, true)
	if err != nil {
		fmt.Println("Error creating map from XML reader", err)
	}
	return xmlMap
}*/
<commit_msg>(feat) mxj.Map.Json is the json representation of our feed's xml. So the writer we were using may or may not be what we want depending on what we do with this xmlMap. I imagine we will be 'writing' it to the no-sql sql database and our server's response to clients so we probably will still be using the writer anyway.<commit_after>/*
	Base package for the ATOM server.

	Server periodically queries the ATOM sources of its users, stores updates

*/
package main

import (
	"fmt"
	"log"
	"os"
	"net/http"

	"github.com/clbanning/mxj"
	// "database/sql"
	// "github.com/lib/pq"
)

var testFeed = "https://www.blogger.com/feeds/8100407163665430627/posts/default"

func main() {
	xml := fetchFeedXML(testFeed)

	file, err := os.Create("output.json")
	if err != nil {
		fmt.Println("Error creating test output file.", err)
	}
	writeXMLAsJSON(xml, file) // should be writing to a db or server response
}

func fetchFeedXML(feedURI string) (mxj.Map) {
	response, err := http.Get(feedURI)
	if err != nil {
		fmt.Println("ATOM GET error:", err)
		log.Fatal(err)
	}
	defer response.Body.Close()
	// fmt.Printf("response body: %s", response.Body)

	xmlMap, err := mxj.NewMapXmlReader(response.Body, true)
	if err != nil {
		fmt.Println("Error creating map from XML reader", err)
	}

	// Okay! This is the JSON representation of an mxj.Map!
	jsonBytes, err := xmlMap.Json(true)
	fmt.Printf("json marshall: %s", jsonBytes)

	return xmlMap
}

func writeXMLAsJSON(xmlMap mxj.Map, destination *os.File) () {
	xmlMap.JsonIndentWriter(destination, "", "\t", true)
	return
}

/*func fetchFeed(feedURI string) (io.Reader) {
	response, err := http.Get(feedURI)
	if err != nil {
		fmt.Println("ATOM GET error:", err)
		log.Fatal(err)
	}
	defer response.Body.Close()
	// fmt.Printf("response body: %s", response.Body)

	// We're getting a bug where feedToXML is trying to parse the closed response.
	// A couple solutions come to mind: create a new reader: response.Body like bufio.NewReader(response.Body),
	// copy the data: (ioutils.ReadAll(body)), or combine the functions.
	// I imagine that this is a common problem that has a solid solution

	return response.Body
}

func feedToXML(feed io.Reader) (mxj.Map) {
	// Use mxj package to translate XML structured bytes to a map[string]interface{}
	fmt.Printf("converting to xml %S", feed)
	_ = "breakpoint"
	xmlMap, err := mxj.NewMapXmlReader(feed, true)
	if err != nil {
		fmt.Println("Error creating map from XML reader", err)
	}
	return xmlMap
}*/
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/bcicen/acrophone/alphabets"
)

//type Acrophone struct {
//	cmap charMap
//}
//
//func (a *Acrophone) Lookup(char string) (string, error) {
//	char = strings.ToLower(char)
//
//	if len(char) > 1 {
//		return "", fmt.Errorf("Lookup() accepts single character argument only")
//	}
//
//	if res, ok := a.cmap[char]; ok == false {
//		return "", fmt.Errorf("unable to find match for character: %s", char)
//	} else {
//		return res, nil
//	}
//}

func main() {
	var output []string
	var cmap alphabets.Alphabet

	if len(os.Args) < 2 {
		fmt.Println("usage: acrophone <input>")
		os.Exit(1)
	}

	cmap = alphabets.Nato

	input := strings.Join(os.Args[1:], "")
	for _, char := range input {
		result, err := cmap.Lookup(string(char))
		if err != nil {
			panic(err)
		}
		output = append(output, result)
	}

	fmt.Println(strings.Join(output, " "))
}
<commit_msg>add build, version to cli<commit_after>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/bcicen/acrophone/alphabets"
)

var (
	build   = ""
	version = "dev-build"
)

//type Acrophone struct {
//	cmap charMap
//}
//
//func (a *Acrophone) Lookup(char string) (string, error) {
//	char = strings.ToLower(char)
//
//	if len(char) > 1 {
//		return "", fmt.Errorf("Lookup() accepts single character argument only")
//	}
//
//	if res, ok := a.cmap[char]; ok == false {
//		return "", fmt.Errorf("unable to find match for character: %s", char)
//	} else {
//		return res, nil
//	}
//}

func main() {
	var output []string
	var cmap alphabets.Alphabet

	if len(os.Args) < 2 {
		fmt.Printf("acrophone v%s build %s\n", version, build)
		fmt.Println("usage: acrophone <input>")
		os.Exit(1)
	}

	cmap = alphabets.Nato

	input := strings.Join(os.Args[1:], "")
	for _, char := range input {
		result, err := cmap.Lookup(string(char))
		if err != nil {
			panic(err)
		}
		output = append(output, result)
	}

	fmt.Println(strings.Join(output, " "))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/HouzuoGuo/laitos/launcher"
	"github.com/HouzuoGuo/laitos/launcher/encarchive"
	"github.com/HouzuoGuo/laitos/launcher/passwdserver"
	"github.com/HouzuoGuo/laitos/misc"
	"github.com/HouzuoGuo/laitos/toolbox"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"
)

var logger = misc.Logger{ComponentName: "laitos", ComponentID: strconv.Itoa(os.Getpid())}

// ExtractEncryptedArchive reads password from standard input and extracts archive file into the directory.
func ExtractEncryptedArchive(destDir, archivePath string) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Println("Please enter password to decrypt archive:")
	password, _, err := reader.ReadLine()
	if err != nil {
		misc.DefaultLogger.Fatalf("ExtractEncryptedArchive", "main", err, "failed to read password")
		return
	}
	/*
		This time, the temp file does not have to live in a ramdisk, because the extracted content does not have to be
		in the memory anyways.
	*/
	tmpFile, err := ioutil.TempFile("", "laitos-launcher-utility-extract")
	if err != nil {
		misc.DefaultLogger.Fatalf("ExtractEncryptedArchive", "main", err, "failed to create temporary file")
		return
	}
	tmpFile.Close()
	defer func() {
		if err := os.Remove(tmpFile.Name()); err != nil {
			misc.DefaultLogger.Printf("ExtractEncryptedArchive", "main", err, "failed to delete temporary file")
		}
	}()
	password = []byte(strings.TrimSpace(string(password)))
	fmt.Println("Result is (nil means success): ", encarchive.Extract(archivePath, tmpFile.Name(), destDir, password))
}

// MakeEncryptedArchive reads password from standard input and uses it to encrypt and archive the directory.
func MakeEncryptedArchive(srcDir, archivePath string) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Println("Please enter a password to encrypt the archive:")
	password, _, err := reader.ReadLine()
	if err != nil {
		misc.DefaultLogger.Fatalf("ExtractEncryptedArchive", "main", err, "failed to read password")
		return
	}
	password = []byte(strings.TrimSpace(string(password)))
	fmt.Println("Result is (nil means success): ", encarchive.Archive(srcDir, archivePath, password))
}

// StartPasswordWebServer starts the password input web server.
func StartPasswordWebServer(port int, url, archivePath string) {
	ws := passwdserver.WebServer{
		Port:            port,
		URL:             url,
		ArchiveFilePath: archivePath,
	}
	ws.Start()
	// Wait almost indefinitely (~5 years) because this is the main routine of this CLI action
	time.Sleep(5 * 365 * 24 * time.Hour)
}

/*
main runs one of several modes, as dictated by input command line flags:

- Utilities for maintaining encrypted data archive (-slu extract|archive)

- (Optional) encrypted data launcher (-sl) will eventually start the supervisor (-supervisor=true)

- Supervisor (-supervisor=true) is responsible for forking main process to launch daemons:

- The forked main process runs with flag -supervisor=false
*/
func main() {
	// Process command line flags
	var daemonList string
	var disableConflicts, tuneSystem, debug, swapOff bool
	var gomaxprocs int
	flag.StringVar(&misc.ConfigFilePath, launcher.ConfigFlagName, "", "(Mandatory) path to configuration file in JSON syntax")
	flag.StringVar(&daemonList, launcher.DaemonsFlagName, "", "(Mandatory) comma-separated daemons to start (dnsd, httpd, insecurehttpd, maintenance, plainsocket, smtpd, telegram)")
	flag.BoolVar(&disableConflicts, "disableconflicts", false, "(Optional) automatically stop and disable other daemon programs that may cause port usage conflicts")
	flag.BoolVar(&swapOff, "swapoff", false, "(Optional) turn off all swap files and partitions for improved system security")
	flag.BoolVar(&tuneSystem, "tunesystem", false, "(Optional) tune operating system parameters for optimal performance")
	flag.BoolVar(&debug, "debug", false, "(Optional) print goroutine stack traces upon receiving interrupt signal")
	flag.IntVar(&gomaxprocs, "gomaxprocs", 0, "(Optional) set gomaxprocs")
	// Process launcher flags
	var sl bool
	var slPort int
	var slArchivePath string
	var slURL string
	flag.BoolVar(&sl, passwdserver.CLIFlag, false, "(Optional) trigger \"special-launch\"")
	flag.IntVar(&slPort, "slport", 80, "(Optional) special-launch: port number")
	flag.StringVar(&slArchivePath, "slarchive", "", "(Optional) special-launch: archive path")
	flag.StringVar(&slURL, "slurl", "", "(Optional) special-launch: url that must include prefix slash")
	// Process launcher utility mode flags
	var slu, sluDir, sluFile string
	flag.StringVar(&slu, "slu", "", "(Optional) special-launch: utility name (extract, archive)")
	flag.StringVar(&sluDir, "sludir", "", "(Optional) special-launch: source/target directory")
	flag.StringVar(&sluFile, "slufile", "", "(Optional) special-launch: source/target archive file")
	// Internal supervisor flag
	var isSupervisor bool = true
	flag.BoolVar(&isSupervisor, launcher.SupervisorFlagName, true, "(Internal use only) enter supervisor mode")

	flag.Parse()

	// ========================================================================
	// Utility mode - encrypted data launcher utilities do not run daemons.
	// ========================================================================
	if slu != "" {
		switch slu {
		case "extract":
			ExtractEncryptedArchive(sluDir, sluFile)
		case "archive":
			MakeEncryptedArchive(sluDir, sluFile)
		default:
			logger.Fatalf("main", "", nil, "please provide mode of operation (extract|archive) for parameter slu")
		}
		return
	}

	// ========================================================================
	// Encrypted data launcher mode - launch the password input web server.
	// ========================================================================
	if sl {
		StartPasswordWebServer(slPort, slURL, slArchivePath)
		return
	}
	/*
		Encrypted data launcher (if any) has finished its task to decrypt program data, now it is time to launch supervisor
		which then launches daemons.
	*/

	// ========================================================================
	// Prepare configuration for supervisor mode or daemon mode.
	// ========================================================================
	// Parse configuration JSON file
	if misc.ConfigFilePath == "" {
		logger.Fatalf("main", "", nil, "please provide a configuration file (-config)")
		return
	}
	var err error
	misc.ConfigFilePath, err = filepath.Abs(misc.ConfigFilePath)
	if err != nil {
		logger.Fatalf("main", "", err, "failed to determine absolute path of config file \"%s\"", misc.ConfigFilePath)
	}
	var config launcher.Config
	configBytes, err := ioutil.ReadFile(misc.ConfigFilePath)
	if err != nil {
		logger.Fatalf("main", "", err, "failed to read config file \"%s\"", misc.ConfigFilePath)
		return
	}
	if err := config.DeserialiseFromJSON(configBytes); err != nil {
		logger.Fatalf("main", "", err, "failed to deserialise config file \"%s\"", misc.ConfigFilePath)
		return
	}
	// Figure out what daemons are to be started
	daemonNames := regexp.MustCompile(`\w+`).FindAllString(daemonList, -1)
	if daemonNames == nil || len(daemonNames) == 0 {
		logger.Fatalf("main", "", nil, "please provide comma-separated list of daemon services to start (-daemons).")
		return
	}
	// Make sure all daemon names are valid
	for _, daemonName := range daemonNames {
		var found bool
		for _, goodName := range launcher.AllDaemons {
			if daemonName == goodName {
				found = true
			}
		}
		if !found {
			logger.Fatalf("main", "", err, "unknown daemon name \"%s\"", daemonName)
		}
	}

	// ========================================================================
	// Supervisor mode - fork a main process to run daemons.
	// This mode flag is turned on by default so that when laitos daemons are
	// protected by supervisor by default.
	// ========================================================================
	if isSupervisor {
		supervisor := &launcher.Supervisor{CLIFlags: os.Args[1:], Config: config, DaemonNames: daemonNames}
		supervisor.Start()
		return
	}

	// ========================================================================
	// Daemon mode - launch all daemons at once.
	// This is the mode launched by supervisor in a forked process.
	// ========================================================================
	// Prepare some environmental changes
	if gomaxprocs > 0 {
		oldGomaxprocs := runtime.GOMAXPROCS(gomaxprocs)
		logger.Warningf("main", "", nil, "GOMAXPROCS has been changed from %d to %d", oldGomaxprocs, gomaxprocs)
	} else {
		logger.Warningf("main", "", nil, "GOMAXPROCS is unchanged at %d", runtime.GOMAXPROCS(0))
	}
	if debug {
		DumpGoroutinesOnInterrupt()
	}
	if disableConflicts {
		DisableConflictingDaemons()
	}
	if swapOff {
		SwapOff()
	}
	if tuneSystem {
		logger.Warningf("main", "", nil, "System tuning result is: \n%s", toolbox.TuneLinux())
	}
	ReseedPseudoRand()
	daemonErrs := make(chan error, len(daemonNames))
	for _, daemonName := range daemonNames {
		// Daemons are started asynchronously, the order of startup does not matter.
		switch daemonName {
		case launcher.DNSDName:
			go func() {
				daemonErrs <- config.GetDNSD().StartAndBlock()
			}()
		case launcher.HTTPDName:
			go func() {
				daemonErrs <- config.GetHTTPD().StartAndBlock()
			}()
		case launcher.InsecureHTTPDName:
			go func() {
				daemonErrs <- config.GetInsecureHTTPD().StartAndBlock()
			}()
		case launcher.MaintenanceName:
			go func() {
				daemonErrs <- config.GetMaintenance().StartAndBlock()
			}()
		case launcher.PlainSocketName:
			go func() {
				daemonErrs <- config.GetPlainSocketDaemon().StartAndBlock()
			}()
		case launcher.SMTPDName:
			go func() {
				daemonErrs <- config.GetMailDaemon().StartAndBlock()
			}()
		case launcher.SOCKDName:
			go func() {
				daemonErrs <- config.GetSockDaemon().StartAndBlock()
			}()
		case launcher.TelegramName:
			go func() {
				daemonErrs <- config.GetTelegramBot().StartAndBlock()
			}()
		}
	}
	for i := 0; i < len(daemonNames); i++ {
		err := <-daemonErrs
		logger.Warningf("main", "", err, "a daemon has encountered and failed to start")
	}
	logger.Fatalf("main", "", nil, "all daemons have failed to start, laitos will now exit.")
}
<commit_msg>let laitos exit with an error if password web server cannot be started<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/HouzuoGuo/laitos/launcher"
	"github.com/HouzuoGuo/laitos/launcher/encarchive"
	"github.com/HouzuoGuo/laitos/launcher/passwdserver"
	"github.com/HouzuoGuo/laitos/misc"
	"github.com/HouzuoGuo/laitos/toolbox"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"
)

var logger = misc.Logger{ComponentName: "laitos", ComponentID: strconv.Itoa(os.Getpid())}

// ExtractEncryptedArchive reads password from standard input and extracts archive file into the directory.
func ExtractEncryptedArchive(destDir, archivePath string) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Println("Please enter password to decrypt archive:")
	password, _, err := reader.ReadLine()
	if err != nil {
		misc.DefaultLogger.Fatalf("ExtractEncryptedArchive", "main", err, "failed to read password")
		return
	}
	/*
		This time, the temp file does not have to live in a ramdisk, because the extracted content does not have to be
		in the memory anyways.
	*/
	tmpFile, err := ioutil.TempFile("", "laitos-launcher-utility-extract")
	if err != nil {
		misc.DefaultLogger.Fatalf("ExtractEncryptedArchive", "main", err, "failed to create temporary file")
		return
	}
	tmpFile.Close()
	defer func() {
		if err := os.Remove(tmpFile.Name()); err != nil {
			misc.DefaultLogger.Printf("ExtractEncryptedArchive", "main", err, "failed to delete temporary file")
		}
	}()
	password = []byte(strings.TrimSpace(string(password)))
	fmt.Println("Result is (nil means success): ", encarchive.Extract(archivePath, tmpFile.Name(), destDir, password))
}

// MakeEncryptedArchive reads password from standard input and uses it to encrypt and archive the directory.
func MakeEncryptedArchive(srcDir, archivePath string) {
	reader := bufio.NewReader(os.Stdin)
	fmt.Println("Please enter a password to encrypt the archive:")
	password, _, err := reader.ReadLine()
	if err != nil {
		misc.DefaultLogger.Fatalf("ExtractEncryptedArchive", "main", err, "failed to read password")
		return
	}
	password = []byte(strings.TrimSpace(string(password)))
	fmt.Println("Result is (nil means success): ", encarchive.Archive(srcDir, archivePath, password))
}

// StartPasswordWebServer starts the password input web server.
func StartPasswordWebServer(port int, url, archivePath string) {
	ws := passwdserver.WebServer{
		Port:            port,
		URL:             url,
		ArchiveFilePath: archivePath,
	}
	if err := ws.Start(); err != nil {
		logger.Fatalf("StartPasswordWebServer", "main", err, "failed to start the web server")
		return
	}
	// Wait almost indefinitely (~5 years) because this is the main routine of this CLI action
	time.Sleep(5 * 365 * 24 * time.Hour)
}

/*
main runs one of several modes, as dictated by input command line flags:

- Utilities for maintaining encrypted data archive (-slu extract|archive)

- (Optional) encrypted data launcher (-sl) will eventually start the supervisor (-supervisor=true)

- Supervisor (-supervisor=true) is responsible for forking main process to launch daemons:

- The forked main process runs with flag -supervisor=false
*/
func main() {
	// Process command line flags
	var daemonList string
	var disableConflicts, tuneSystem, debug, swapOff bool
	var gomaxprocs int
	flag.StringVar(&misc.ConfigFilePath, launcher.ConfigFlagName, "", "(Mandatory) path to configuration file in JSON syntax")
	flag.StringVar(&daemonList, launcher.DaemonsFlagName, "", "(Mandatory) comma-separated daemons to start (dnsd, httpd, insecurehttpd, maintenance, plainsocket, smtpd, telegram)")
	flag.BoolVar(&disableConflicts, "disableconflicts", false, "(Optional) automatically stop and disable other daemon programs that may cause port usage conflicts")
	flag.BoolVar(&swapOff, "swapoff", false, "(Optional) turn off all swap files and partitions for improved system security")
	flag.BoolVar(&tuneSystem, "tunesystem", false, "(Optional) tune operating system parameters for optimal performance")
	flag.BoolVar(&debug, "debug", false, "(Optional) print goroutine stack traces upon receiving interrupt signal")
	flag.IntVar(&gomaxprocs, "gomaxprocs", 0, "(Optional) set gomaxprocs")
	// Process launcher flags
	var sl bool
	var slPort int
	var slArchivePath string
	var slURL string
	flag.BoolVar(&sl, passwdserver.CLIFlag, false, "(Optional) trigger \"special-launch\"")
	flag.IntVar(&slPort, "slport", 80, "(Optional) special-launch: port number")
	flag.StringVar(&slArchivePath, "slarchive", "", "(Optional) special-launch: archive path")
	flag.StringVar(&slURL, "slurl", "", "(Optional) special-launch: url that must include prefix slash")
	// Process launcher utility mode flags
	var slu, sluDir, sluFile string
	flag.StringVar(&slu, "slu", "", "(Optional) special-launch: utility name (extract, archive)")
	flag.StringVar(&sluDir, "sludir", "", "(Optional) special-launch: source/target directory")
	flag.StringVar(&sluFile, "slufile", "", "(Optional) special-launch: source/target archive file")
	// Internal supervisor flag
	var isSupervisor bool = true
	flag.BoolVar(&isSupervisor, launcher.SupervisorFlagName, true, "(Internal use only) enter supervisor mode")

	flag.Parse()

	// ========================================================================
	// Utility mode - encrypted data launcher utilities do not run daemons.
	// ========================================================================
	if slu != "" {
		switch slu {
		case "extract":
			ExtractEncryptedArchive(sluDir, sluFile)
		case "archive":
			MakeEncryptedArchive(sluDir, sluFile)
		default:
			logger.Fatalf("main", "", nil, "please provide mode of operation (extract|archive) for parameter slu")
		}
		return
	}

	// ========================================================================
	// Encrypted data launcher mode - launch the password input web server.
	// ========================================================================
	if sl {
		StartPasswordWebServer(slPort, slURL, slArchivePath)
		return
	}
	/*
		Encrypted data launcher (if any) has finished its task to decrypt program data, now it is time to launch supervisor
		which then launches daemons.
	*/

	// ========================================================================
	// Prepare configuration for supervisor mode or daemon mode.
	// ========================================================================
	// Parse configuration JSON file
	if misc.ConfigFilePath == "" {
		logger.Fatalf("main", "", nil, "please provide a configuration file (-config)")
		return
	}
	var err error
	misc.ConfigFilePath, err = filepath.Abs(misc.ConfigFilePath)
	if err != nil {
		logger.Fatalf("main", "", err, "failed to determine absolute path of config file \"%s\"", misc.ConfigFilePath)
	}
	var config launcher.Config
	configBytes, err := ioutil.ReadFile(misc.ConfigFilePath)
	if err != nil {
		logger.Fatalf("main", "", err, "failed to read config file \"%s\"", misc.ConfigFilePath)
		return
	}
	if err := config.DeserialiseFromJSON(configBytes); err != nil {
		logger.Fatalf("main", "", err, "failed to deserialise config file \"%s\"", misc.ConfigFilePath)
		return
	}
	// Figure out what daemons are to be started
	daemonNames := regexp.MustCompile(`\w+`).FindAllString(daemonList, -1)
	if daemonNames == nil || len(daemonNames) == 0 {
		logger.Fatalf("main", "", nil, "please provide comma-separated list of daemon services to start (-daemons).")
		return
	}
	// Make sure all daemon names are valid
	for _, daemonName := range daemonNames {
		var found bool
		for _, goodName := range launcher.AllDaemons {
			if daemonName == goodName {
				found = true
			}
		}
		if !found {
			logger.Fatalf("main", "", err, "unknown daemon name \"%s\"", daemonName)
		}
	}

	// ========================================================================
	// Supervisor mode - fork a main process to run daemons.
	// This mode flag is turned on by default so that when laitos daemons are
	// protected by supervisor by default.
	// ========================================================================
	if isSupervisor {
		supervisor := &launcher.Supervisor{CLIFlags: os.Args[1:], Config: config, DaemonNames: daemonNames}
		supervisor.Start()
		return
	}

	// ========================================================================
	// Daemon mode - launch all daemons at once.
	// This is the mode launched by supervisor in a forked process.
	// ========================================================================
	// Prepare some environmental changes
	if gomaxprocs > 0 {
		oldGomaxprocs := runtime.GOMAXPROCS(gomaxprocs)
		logger.Warningf("main", "", nil, "GOMAXPROCS has been changed from %d to %d", oldGomaxprocs, gomaxprocs)
	} else {
		logger.Warningf("main", "", nil, "GOMAXPROCS is unchanged at %d", runtime.GOMAXPROCS(0))
	}
	if debug {
		DumpGoroutinesOnInterrupt()
	}
	if disableConflicts {
		DisableConflictingDaemons()
	}
	if swapOff {
		SwapOff()
	}
	if tuneSystem {
		logger.Warningf("main", "", nil, "System tuning result is: \n%s", toolbox.TuneLinux())
	}
	ReseedPseudoRand()
	daemonErrs := make(chan error, len(daemonNames))
	for _, daemonName := range daemonNames {
		// Daemons are started asynchronously, the order of startup does not matter.
		switch daemonName {
		case launcher.DNSDName:
			go func() {
				daemonErrs <- config.GetDNSD().StartAndBlock()
			}()
		case launcher.HTTPDName:
			go func() {
				daemonErrs <- config.GetHTTPD().StartAndBlock()
			}()
		case launcher.InsecureHTTPDName:
			go func() {
				daemonErrs <- config.GetInsecureHTTPD().StartAndBlock()
			}()
		case launcher.MaintenanceName:
			go func() {
				daemonErrs <- config.GetMaintenance().StartAndBlock()
			}()
		case launcher.PlainSocketName:
			go func() {
				daemonErrs <- config.GetPlainSocketDaemon().StartAndBlock()
			}()
		case launcher.SMTPDName:
			go func() {
				daemonErrs <- config.GetMailDaemon().StartAndBlock()
			}()
		case launcher.SOCKDName:
			go func() {
				daemonErrs <- config.GetSockDaemon().StartAndBlock()
			}()
		case launcher.TelegramName:
			go func() {
				daemonErrs <- config.GetTelegramBot().StartAndBlock()
			}()
		}
	}
	for i := 0; i < len(daemonNames); i++ {
		err := <-daemonErrs
		logger.Warningf("main", "", err, "a daemon has encountered and failed to start")
	}
	logger.Fatalf("main", "", nil, "all daemons have failed to start, laitos will now exit.")
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"

	"gopkg.in/v1/yaml"
)

var (
	upgrade   = flag.Bool("upgrade", false, "force upgrade even if older version exists")
	gopath    = flag.Bool("gopath", false, "use GOPATH from environment instead of downloading all dependencies")
	onlyBuild = flag.Bool("build", false, "only build, do not run command (can pass multiple spec files)")
)

func getCacheDir() (string, error) {
	var cacheDir string
	cacheDir = os.Getenv("DEMAND_CACHE_DIR")
	if cacheDir == "" {
		u, err := user.Current()
		if err != nil {
			return "", fmt.Errorf("cannot determine home directory: %v", err)
		}
		cacheDir = filepath.Join(u.HomeDir, ".cache/demand")
	}
	return cacheDir, nil
}

// Create directories if they don't exist.
func maybeMkdirs(perm os.FileMode, paths ...string) error {
	for _, path := range paths {
		err := os.Mkdir(path, perm)
		if err != nil && !os.IsExist(err) {
			return err
		}
	}
	return nil
}

// like os.Readlink but returns empty string and success for non-symlinks
func maybeReadlink(path string) (string, error) {
	dest, err := os.Readlink(path)
	if err == nil {
		return dest, nil
	}
	err2, ok := err.(*os.PathError)
	if !ok {
		return path, err
	}
	switch err2.Err {
	case syscall.EINVAL, syscall.ENOENT:
		// not a symlink, never mind
		return "", nil
	default:
		return path, err
	}
}

// copy environment, but override GOPATH
func copyEnvWithGopath(gopath string) []string {
	old := os.Environ()
	env := make([]string, 0, len(old))
	for _, kv := range old {
		if strings.HasPrefix(kv, "GOPATH=") {
			continue
		}
		env = append(env, kv)
	}

	env = append(env, "GOPATH="+gopath)
	return env
}

func runBinary(binary string, args []string, env []string) error {
	// can't use os/exec etc, we want exec not fork+exec, and we want
	// to pass all fds to the child
	return syscall.Exec(binary, args, env)
}

type specification struct {
	Go struct {
		Import string
	}
}

func build(cacheDir, cacheBinDir, cacheBinArchDir string,
	specPath string, specFile io.Reader, binary string) error {
	err := maybeMkdirs(0750, cacheDir, cacheBinDir, cacheBinArchDir)
	if err != nil {
		return fmt.Errorf("cannot create cache directory: %v", err)
	}

	var spec specification
	specData, err := ioutil.ReadAll(specFile)
	err = yaml.Unmarshal(specData, &spec)
	if err != nil {
		return fmt.Errorf("cannot parse spec file: %v", err)
	}
	if spec.Go.Import == "" {
		return fmt.Errorf("spec file does not specify import path: %s", specPath)
	}

	tmpGopath, err := ioutil.TempDir("", "demand-gopath-")
	if err != nil {
		return fmt.Errorf("cannot create temp directory: %v", err)
	}
	defer func() {
		err := os.RemoveAll(tmpGopath)
		if err != nil {
			log.Printf("tempdir cleanup failed: %v", err)
		}
	}()

	envGopath := tmpGopath
	if *gopath {
		oldGopath := os.Getenv("GOPATH")
		if oldGopath != "" {
			envGopath = envGopath + string(filepath.ListSeparator) + oldGopath
		}
	}
	env := copyEnvWithGopath(envGopath)

	// TODO -upgrade should be handled just by the fact that we have a
	// clean GOPATH, but double check what happens on -gopath -upgrade

	// need to do this in two steps, as "go get" won't let us control
	// destination
	cmd := exec.Command("go", "get", "-d", "--", spec.Go.Import)
	cmd.Dir = tmpGopath
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	cmd.Env = env
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("could not get go package: %v", err)
	}

	// poor man's tempfile atomicity; go build complains if
	// destination exists
	tmpBin := fmt.Sprintf("%s.%d.tmp", binary, os.Getpid())
	defer func() {
		err := os.Remove(tmpBin)
		if err != nil && !os.IsNotExist(err) {
			log.Printf("temp binary cleanup failed: %v", err)
		}
	}()
	cmd = exec.Command("go", "build", "-o", tmpBin, "--", spec.Go.Import)
	cmd.Dir = tmpGopath
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	cmd.Env = env
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("could not build go package: %v", err)
	}

	err = os.Rename(tmpBin, binary)
	if err != nil {
		return fmt.Errorf("could put new binary in place: %v", err)
	}
	return nil
}

func doit(args []string) error {
	specPath := args[0]

	specBase := filepath.Base(specPath)
	if specBase[0] == '.' {
		return fmt.Errorf("refusing to run hidden spec file: %s", specPath)
	}

	dest, err := maybeReadlink(specPath)
	if err != nil {
		return fmt.Errorf("readlink: %v", err)
	}
	if dest != "" {
		specBase = filepath.Base(dest)
	}

	// open it here to guard against typos; we don't need to read
	// until we know it's a cache miss
	specFile, err := os.Open(specPath)
	if err != nil {
		return fmt.Errorf("cannot open spec file: %v", err)
	}
	// we rely on Go's automatic use of O_CLOEXEC to close this in
	// syscall.Exec, this defer is a nice gesture for error cases
	defer func() {
		// silence errcheck
		_ = specFile.Close()
	}()

	cacheDir, err := getCacheDir()
	if err != nil {
		return err
	}
	cacheBinDir := filepath.Join(cacheDir, "bin")
	arch := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
	cacheBinArchDir := filepath.Join(cacheBinDir, arch)

	binary := filepath.Join(cacheBinArchDir, specBase)

	if !*onlyBuild && !*upgrade {
		err = runBinary(binary, flag.Args(), os.Environ())
		if err != nil && !os.IsNotExist(err) {
			return fmt.Errorf("cannot exec %s: %v", binary, err)
		}
	}

	// if we're still here, we don't have a cached binary, or we're
	// upgrading
	err = build(cacheDir, cacheBinDir, cacheBinArchDir, specPath, specFile, binary)
	if err != nil {
		return err
	}

	if !*onlyBuild {
		// now run it (again); this time ENOENT means trouble
		err = runBinary(binary, flag.Args(), os.Environ())
		if err != nil {
			return fmt.Errorf("cannot exec %s: %v", binary, err)
		}
	}
	return nil
}

func dobuild(specs []string) error {
	var err error
	for i := range specs {
		args := specs[i : i+1]
		err = doit(args)
		if err != nil {
			return err
		}
	}
	return nil
}

var prog = filepath.Base(os.Args[0])

func usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", prog)
	fmt.Fprintf(os.Stderr, "  %s [OPTS] SPEC_PATH [ARGS..]\n", prog)
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "Only build, do not run command:\n")
	fmt.Fprintf(os.Stderr, "  %s -build [OPTS] SPEC_PATH..\n", prog)
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "Options:\n")
	flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "Use as an interpreter:\n")
	fmt.Fprintf(os.Stderr, "  #!/usr/bin/env demand\n")
	fmt.Fprintf(os.Stderr, "  go:\n")
	fmt.Fprintf(os.Stderr, "    import: GO_IMPORT_PATH_HERE\n")
}

func main() {
	log.SetFlags(0)
	log.SetPrefix(prog + ": ")

	flag.Usage = usage
	flag.Parse()
	if flag.NArg() == 0 {
		flag.Usage()
		os.Exit(2)
	}

	var err error
	if *onlyBuild {
		err = dobuild(flag.Args())
	} else {
		err = doit(flag.Args())
	}

	if err != nil {
		log.Print(err)
		os.Exit(1)
	}
}
<commit_msg>Follow gopkg.in URL churn<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"

	"gopkg.in/yaml.v1"
)

var (
	upgrade   = flag.Bool("upgrade", false, "force upgrade even if older version exists")
	gopath    = flag.Bool("gopath", false, "use GOPATH from environment instead of downloading all dependencies")
	onlyBuild = flag.Bool("build", false, "only build, do not run command (can pass multiple spec files)")
)

func getCacheDir() (string, error) {
	var cacheDir string
	cacheDir = os.Getenv("DEMAND_CACHE_DIR")
	if cacheDir == "" {
		u, err := user.Current()
		if err != nil {
			return "", fmt.Errorf("cannot determine home directory: %v", err)
		}
		cacheDir = filepath.Join(u.HomeDir, ".cache/demand")
	}
	return cacheDir, nil
}

// Create directories if they don't exist.
func maybeMkdirs(perm os.FileMode, paths ...string) error {
	for _, path := range paths {
		err := os.Mkdir(path, perm)
		if err != nil && !os.IsExist(err) {
			return err
		}
	}
	return nil
}

// like os.Readlink but returns empty string and success for non-symlinks
func maybeReadlink(path string) (string, error) {
	dest, err := os.Readlink(path)
	if err == nil {
		return dest, nil
	}
	err2, ok := err.(*os.PathError)
	if !ok {
		return path, err
	}
	switch err2.Err {
	case syscall.EINVAL, syscall.ENOENT:
		// not a symlink, never mind
		return "", nil
	default:
		return path, err
	}
}

// copy environment, but override GOPATH
func copyEnvWithGopath(gopath string) []string {
	old := os.Environ()
	env := make([]string, 0, len(old))
	for _, kv := range old {
		if strings.HasPrefix(kv, "GOPATH=") {
			continue
		}
		env = append(env, kv)
	}

	env = append(env, "GOPATH="+gopath)
	return env
}

func runBinary(binary string, args []string, env []string) error {
	// can't use os/exec etc, we want exec not fork+exec, and we want
	// to pass all fds to the child
	return syscall.Exec(binary, args, env)
}

type specification struct {
	Go struct {
		Import string
	}
}

func build(cacheDir, cacheBinDir, cacheBinArchDir string,
	specPath string, specFile io.Reader, binary string) error {
	err := maybeMkdirs(0750, cacheDir, cacheBinDir, cacheBinArchDir)
	if err != nil {
		return fmt.Errorf("cannot create cache directory: %v", err)
	}

	var spec specification
	specData, err := ioutil.ReadAll(specFile)
	err = yaml.Unmarshal(specData, &spec)
	if err != nil {
		return fmt.Errorf("cannot parse spec file: %v", err)
	}
	if spec.Go.Import == "" {
		return fmt.Errorf("spec file does not specify import path: %s", specPath)
	}

	tmpGopath, err := ioutil.TempDir("", "demand-gopath-")
	if err != nil {
		return fmt.Errorf("cannot create temp directory: %v", err)
	}
	defer func() {
		err := os.RemoveAll(tmpGopath)
		if err != nil {
			log.Printf("tempdir cleanup failed: %v", err)
		}
	}()

	envGopath := tmpGopath
	if *gopath {
		oldGopath := os.Getenv("GOPATH")
		if oldGopath != "" {
			envGopath = envGopath + string(filepath.ListSeparator) + oldGopath
		}
	}
	env := copyEnvWithGopath(envGopath)

	// TODO -upgrade should be handled just by the fact that we have a
	// clean GOPATH, but double check what happens on -gopath -upgrade

	// need to do this in two steps, as "go get" won't let us control
	// destination
	cmd := exec.Command("go", "get", "-d", "--", spec.Go.Import)
	cmd.Dir = tmpGopath
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	cmd.Env = env
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("could not get go package: %v", err)
	}

	// poor man's tempfile atomicity; go build complains if
	// destination exists
	tmpBin := fmt.Sprintf("%s.%d.tmp", binary, os.Getpid())
	defer func() {
		err := os.Remove(tmpBin)
		if err != nil && !os.IsNotExist(err) {
			log.Printf("temp binary cleanup failed: %v", err)
		}
	}()
	cmd = exec.Command("go", "build", "-o", tmpBin, "--", spec.Go.Import)
	cmd.Dir = tmpGopath
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	cmd.Env = env
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("could not build go package: %v", err)
	}

	err = os.Rename(tmpBin, binary)
	if err != nil {
		return fmt.Errorf("could put new binary in place: %v", err)
	}
	return nil
}

func doit(args []string) error {
	specPath := args[0]

	specBase := filepath.Base(specPath)
	if specBase[0] == '.' {
		return fmt.Errorf("refusing to run hidden spec file: %s", specPath)
	}

	dest, err := maybeReadlink(specPath)
	if err != nil {
		return fmt.Errorf("readlink: %v", err)
	}
	if dest != "" {
		specBase = filepath.Base(dest)
	}

	// open it here to guard against typos; we don't need to read
	// until we know it's a cache miss
	specFile, err := os.Open(specPath)
	if err != nil {
		return fmt.Errorf("cannot open spec file: %v", err)
	}
	// we rely on Go's automatic use of O_CLOEXEC to close this in
	// syscall.Exec, this defer is a nice gesture for error cases
	defer func() {
		// silence errcheck
		_ = specFile.Close()
	}()

	cacheDir, err := getCacheDir()
	if err != nil {
		return err
	}
	cacheBinDir := filepath.Join(cacheDir, "bin")
	arch := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)
	cacheBinArchDir := filepath.Join(cacheBinDir, arch)

	binary := filepath.Join(cacheBinArchDir, specBase)

	if !*onlyBuild && !*upgrade {
		err = runBinary(binary, flag.Args(), os.Environ())
		if err != nil && !os.IsNotExist(err) {
			return fmt.Errorf("cannot exec %s: %v", binary, err)
		}
	}

	// if we're still here, we don't have a cached binary, or we're
	// upgrading
	err = build(cacheDir, cacheBinDir, cacheBinArchDir, specPath, specFile, binary)
	if err != nil {
		return err
	}

	if !*onlyBuild {
		// now run it (again); this time ENOENT means trouble
		err = runBinary(binary, flag.Args(), os.Environ())
		if err != nil {
			return fmt.Errorf("cannot exec %s: %v", binary, err)
		}
	}
	return nil
}

func dobuild(specs []string) error {
	var err error
	for i := range specs {
		args := specs[i : i+1]
		err = doit(args)
		if err != nil {
			return err
		}
	}
	return nil
}

var prog = filepath.Base(os.Args[0])

func usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", prog)
	fmt.Fprintf(os.Stderr, "  %s [OPTS] SPEC_PATH [ARGS..]\n", prog)
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "Only build, do not run command:\n")
	fmt.Fprintf(os.Stderr, "  %s -build [OPTS] SPEC_PATH..\n", prog)
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "Options:\n")
	flag.PrintDefaults()
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "Use as an interpreter:\n")
	fmt.Fprintf(os.Stderr, "  #!/usr/bin/env demand\n")
	fmt.Fprintf(os.Stderr, "  go:\n")
	fmt.Fprintf(os.Stderr, "    import: GO_IMPORT_PATH_HERE\n")
}

func main() {
	log.SetFlags(0)
	log.SetPrefix(prog + ": ")

	flag.Usage = usage
	flag.Parse()
	if flag.NArg() == 0 {
		flag.Usage()
		os.Exit(2)
	}

	var err error
	if *onlyBuild {
		err = dobuild(flag.Args())
	} else {
		err = doit(flag.Args())
	}

	if err != nil {
		log.Print(err)
		os.Exit(1)
	}
}
<|endoftext|>