<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"

	log "github.com/nickjones/proc_box/Godeps/_workspace/src/github.com/Sirupsen/logrus"
	"github.com/nickjones/proc_box/Godeps/_workspace/src/github.com/streadway/amqp"
	"github.com/nickjones/proc_box/agents"
)

var (
	uri              = flag.String("uri", "amqp://guest:guest@localhost:5672", "AMQP connection URI.")
	exchange         = flag.String("exchange", "amq.topic", "AMQP exchange to bind.")
	rmtKey           = flag.String("rkey", "proc_box.remote_control", "AMQP routing key for remote process control.")
	procStatsKey     = flag.String("skey", "proc_box.stats", "AMQP routing key prefix for proc stats.")
	statsInterval    = flag.Duration("sint", 1*time.Minute, "Interval to emit process statistics.")
	wallclockTimeout = flag.Duration("timeout", 10*time.Minute, "The time until wallclock timeout of the process.")
	stdoutByteLimit  = flag.Int64("stdoutlimit", 100*1024*1024*1024, "Threshold of emitted bytes to STDOUT by the child before calling Stop on the process (default 100GB).")
	debugMode        = flag.Bool("debug", false, "Debug logging enable")
	noWarn           = flag.Bool("nowarn", false, "Disable warnings on stats collection.")
	// Deprecated but left for backwards compatability for now.  Assumed this is
	// enabled by default.
	runAnyway   = flag.Bool("runanyway", false, "Ignore all AMQP errors (connection, message generation, etc.).")
	msgTimeout  = flag.Duration("msgtimeout", 30*time.Second, "The time allowed for a statistics mesage to be sent before giving up. (0 means never)")
	userJSON    = flag.String("json", "", "User provided JSON to include in the statistic sample.")
	multiRmtKey = flag.Bool("multiRmtKey", false, "Enable splitting multiple remote control keys with commas.")
	version     = flag.Bool("version", false, "Show version.")

	VERSION   = "development"
	BUILDDATE = "now-ish"
)

type session struct {
	amqpConn     *amqp.Connection
	job          agents.JobControl
	exchange     string
	rcRoutingKey string
	psChan       chan agents.ProcessStatCommand
	rcChan       chan chan agents.RemoteControlCommand
	amqpConfig   amqp.Config
	multiRmtKey  bool
}

func init() {
	flag.Parse()

	// Create a map of pointers to all the current flags
	flags := map[string]*flag.Flag{}
	flag.VisitAll(func(f *flag.Flag) {
		flags[f.Name] = f
	})

	// Remove the flags that were set on the command line
	flag.Visit(func(f *flag.Flag) {
		delete(flags, f.Name)
	})

	// Now for the flags that weren't set on the cli,
	// Look at the env for 'PBOX_<uppercase flag name>'
	// If it exists, use it to set the corresponding flag.
	for _, f := range flags {
		var buffer bytes.Buffer
		buffer.WriteString("PBOX_")
		buffer.WriteString(strings.ToUpper(f.Name))
		if os.Getenv(buffer.String()) != "" {
			f.Value.Set(os.Getenv(buffer.String()))
		}
	}
}

func main() {

	if *version {
		fmt.Printf("proc_box version %s build on %s\n", VERSION, BUILDDATE)
		os.Exit(0)
	}
	if *debugMode {
		log.SetLevel(log.DebugLevel)
	} else if *noWarn {
		log.SetLevel(log.ErrorLevel)
	}

	// Create channel for ProcessStats to trigger a sample
	psChan := make(chan agents.ProcessStatCommand)
	// Incoming remote command channel (new with each reconnect)
	rcChan := make(chan chan agents.RemoteControlCommand)

	args := flag.Args()
	var cmdArgs []string
	var cmd string
	if len(args) > 0 {
		cmd = args[0]
	} else {
		log.Fatal("Did you forget a command to run?")
		return
	}

	log.Debugf("cmd: %s cmdArgs: %q\n", cmd, cmdArgs)

	done := make(chan error)

	// Initialize job
	job, err := agents.NewControlledProcess(cmd, args, done, *stdoutByteLimit)
	if err != nil {
		log.Fatalf("Failed to create a NewControlledProcess: %s\n", err)
		return
	}

	log.Debugf("%#v\n", job)

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)

	timer, err := agents.NewTimer(*wallclockTimeout)
	if err != nil {
		log.Warnln("Error returned creating timeout agent", err)
	}
	log.Debugf("Starting timer with timeout of: %v\n", *wallclockTimeout)
	timer.Start()

	sess := session{
		job:          job,
		exchange:     *exchange,
		rcRoutingKey: *rmtKey,
		psChan:       psChan,
		rcChan:       rcChan,
		amqpConfig: amqp.Config{
			Properties: amqp.Table{
				"product": "proc_box",
				"version": VERSION,
			},
		},
		multiRmtKey: *multiRmtKey,
	}
	go redial(sess)

	go monitor(signals, rcChan, job, psChan, timer, done)

	err = <-done

	elapsedTime, _ := timer.ElapsedTime()
	// Print to standard out
	fmt.Printf("Task elapsed time: %.2f seconds.\n", elapsedTime.Seconds())

	if err != nil {
		if exiterr, ok := err.(*exec.ExitError); ok {
			// Non-zero exit code
			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
				exitStatus := status.ExitStatus()
				log.Debugf("Exit Status: %d\n", exitStatus)
				os.Exit(exitStatus)
			}
		} else {
			log.Debugf("cmd.Wait: %v\n", err)
		}
	} else {
		os.Exit(0)
	}
}

func redial(sess session) {
	var err error
	var stats agents.ProcessStats
	var rc *agents.RemoteControl

	// Initialize mini-router for incoming stats agent requests
	go func() {
		for s := range sess.psChan {
			if stats == nil {
				log.Warnln("No stats agents available (yet), dropping request")
			} else if s.TimeUpdate {
				stats.NewTicker(s.NewTime)
			} else {
				stats.Sample()
			}
		}
	}()

	rcKeys := []string{*rmtKey}
	if sess.multiRmtKey {
		rcKeys = deleteEmpty(strings.Split(*rmtKey, ","))
	}
	for {
		sess.amqpConn, err = amqp.DialConfig(*uri, sess.amqpConfig)

		if err != nil {
			log.Warnf("Failed to connect to AMQP: %q", err)
			// Rate limit reconnection attempts
			time.Sleep(5 * time.Second)
		} else {
			rc, err = agents.NewRemoteControl(sess.amqpConn, rcKeys, *exchange)
			if err != nil {
				log.Warnf("Failed creating NewRemoteControl: %s", err)
			} else {
				sess.rcChan <- rc.Commands
			}

			if stats == nil {
				// initial setup
				stats, err = agents.NewProcessStats(
					sess.amqpConn,
					*procStatsKey,
					*exchange,
					&sess.job,
					*statsInterval,
					*msgTimeout,
					*userJSON,
				)
				if err != nil {
					log.Warnf("Failed creating NewProcessStats: %s", err)
				}
			} else {
				err = stats.ReinitializeConnection(sess.amqpConn)
				if err != nil {
					log.Warnf("Failed to reinitialize process stats: %s", err)
				}
			}
			closings := sess.amqpConn.NotifyClose(make(chan *amqp.Error))

			// Wait for close notification and loop back around to reconnect
			_ = <-closings
			log.Debugln("Saw a notification for closed connection, looping")
		}

	}
}

func monitor(
	signals chan os.Signal,
	rcChanChan chan chan agents.RemoteControlCommand,
	job agents.JobControl,
	psChan chan agents.ProcessStatCommand,
	timer agents.Timer,
	done chan error,
) {

	// Catch any panics here to ensure we kill the child process before going
	// to our own doom.
	defer func() {
		if e := recover(); e != nil {
			job.Kill(-9)
			panic(e)
		}
	}()
	var logSampling <-chan time.Time
	var err error

	if *stdoutByteLimit > 0 {
		ticker := time.NewTicker(100 * time.Millisecond)
		// Replace the time channel with an actual ticker if this is in use
		logSampling = ticker.C
	}

	var rcChan chan agents.RemoteControlCommand

	for {
		select {
		case rcChan = <-rcChanChan:
		// Catch incoming signals and operate on them as if they were remote commands
		case sig := <-signals:
			switch sig {
			case syscall.SIGINT:
				log.Debugln("Caught SIGINT, graceful shutdown")
				// Initiate non-blocking send
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("SIGINT failed to send a sample msg on the psChan")
				}
				err = job.Stop()
			case syscall.SIGTERM:
				log.Debugln("Caught SIGTERM, end abruptly")
				job.Kill(-9)
			case syscall.SIGHUP:
				log.Debugln("Caught SIGHUP, emit stats")
				// Initiate non-blocking send
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("SIGHUP failed to send a sample msg on the psChan")
				}
			case syscall.SIGQUIT:
				log.Debugln("Caught SIGQUIT, graceful shutdown")
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("SIGQUIT failed to send a sample msg on the psChan")
				}
				err = job.Stop()
			}
		// Process incoming remote commands, toss unknown requests
		case cmd := <-rcChan:
			log.Debugf("Got a command %#v\n", cmd)
			switch cmd.Command {
			case "suspend":
				log.Debugln("RemoteCommand: Suspend")
				job.Suspend()
			case "resume":
				log.Debugln("RemoteCommand: Resume")
				job.Resume()
			case "kill":
				log.Debugln("RemoteCommand: Kill")
				var args int64
				if len(cmd.Arguments) == 0 {
					args = -9
				} else {
					args, err = strconv.ParseInt(cmd.Arguments[0], 10, 32)
					if err != nil {
						log.Warnf("Unable to parse kill command argument[0] into int: %s\n", err)
						args = -9
					}
				}
				job.Kill(args)
			case "stop":
				log.Debugln("RemoteCommand: Stop")
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("RC Stop failed to send a sample msg on the psChan")
				}
				if err = job.Stop(); err != nil {
					log.Fatalf("Error received while stopping sub-process: %s\n", err)
				}
			case "sample":
				log.Debugln("RemoteCommand: Sample")
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("RC Sample failed to send a sample msg on the psChan")
				}
			case "change_sample_rate":
				log.Debugln("RemoteCommand: Change Stats Sample Rate")
				if len(cmd.Arguments) > 0 {
					log.Debugf("change_sample_rate arg[0]: %s\n", cmd.Arguments[0])
					d, err := time.ParseDuration(cmd.Arguments[0])
					if err == nil {
						select {
						case psChan <- agents.ProcessStatCommand{
							TimeUpdate: true,
							NewTime:    d}:
							log.Debugln("Sending psChan a msg to update the ticker")
						default:
							log.Debugln("RC change_sample_rate failed to send a msg")
						}
					} else {
						log.Warnf("Unparseable duration argument to command change_sample_rate")
					}
				} else {
					log.Warnf("Missing argument to command change_sample_rate")
				}
			case "timer_reset":
				log.Debugln("RemoteCommand: Timer Reset")
				if err = timer.Reset(); err != nil {
					log.Fatalf("Error received from timer calling Reset: %s\n", err)
				}
			case "timer_start":
				log.Debugln("RemoteCommand: Timer Start")
				if err = timer.Start(); err != nil {
					log.Fatalf("Error received from timer calling Start: %s\n", err)
				}
			case "timer_stop":
				log.Debugln("RemoteCommand: Timer Stop")
				if err = timer.Stop(); err != nil {
					log.Fatalf("Error received from timer calling Stop: %s\n", err)
				}
			case "timer_resume":
				log.Debugln("RemoteCommand: Timer Resume")
				if err = timer.Resume(); err != nil {
					log.Fatalf("Error received from timer calling Resume: %s\n", err)
				}
			default:
				log.Debugf("Unknown command: %s\n", cmd)
			}
		case timeoutMsg := <-timer.Done():
			log.Debugf("Timer timeout message: %s\n", timeoutMsg)
			if err = job.Stop(); err != nil {
				log.Fatalf("Error received while stopping sub-process: %v\n", err)
				// If there was an error stopping the process, kill the porcess.
				job.Kill(-9)
			}
		case jobDone := <-job.Done():
			log.Debugln("Command exited gracefully; shutting down.")
			done <- jobDone
		case _ = <-logSampling:
			if job.StdoutByteCount() > 2*(*stdoutByteLimit) {
				err = job.Kill(-9)
			} else if job.StdoutByteCount() > *stdoutByteLimit {
				err = job.Stop()
			}
		}
	}
}

func deleteEmpty(s []string) []string {
	var r []string
	for _, v := range s {
		if len(v) > 0 {
			r = append(r, v)
		}
	}
	return r
}
<commit_msg>Fix a typo.<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"

	log "github.com/nickjones/proc_box/Godeps/_workspace/src/github.com/Sirupsen/logrus"
	"github.com/nickjones/proc_box/Godeps/_workspace/src/github.com/streadway/amqp"
	"github.com/nickjones/proc_box/agents"
)

var (
	uri              = flag.String("uri", "amqp://guest:guest@localhost:5672", "AMQP connection URI.")
	exchange         = flag.String("exchange", "amq.topic", "AMQP exchange to bind.")
	rmtKey           = flag.String("rkey", "proc_box.remote_control", "AMQP routing key for remote process control.")
	procStatsKey     = flag.String("skey", "proc_box.stats", "AMQP routing key prefix for proc stats.")
	statsInterval    = flag.Duration("sint", 1*time.Minute, "Interval to emit process statistics.")
	wallclockTimeout = flag.Duration("timeout", 10*time.Minute, "The time until wallclock timeout of the process.")
	stdoutByteLimit  = flag.Int64("stdoutlimit", 100*1024*1024*1024, "Threshold of emitted bytes to STDOUT by the child before calling Stop on the process (default 100GB).")
	debugMode        = flag.Bool("debug", false, "Debug logging enable")
	noWarn           = flag.Bool("nowarn", false, "Disable warnings on stats collection.")
	// Deprecated but left for backwards compatability for now.  Assumed this is
	// enabled by default.
	runAnyway   = flag.Bool("runanyway", false, "Ignore all AMQP errors (connection, message generation, etc.).")
	msgTimeout  = flag.Duration("msgtimeout", 30*time.Second, "The time allowed for a statistics mesage to be sent before giving up. (0 means never)")
	userJSON    = flag.String("json", "", "User provided JSON to include in the statistic sample.")
	multiRmtKey = flag.Bool("multiRmtKey", false, "Enable splitting multiple remote control keys with commas.")
	version     = flag.Bool("version", false, "Show version.")

	VERSION   = "development"
	BUILDDATE = "now-ish"
)

type session struct {
	amqpConn     *amqp.Connection
	job          agents.JobControl
	exchange     string
	rcRoutingKey string
	psChan       chan agents.ProcessStatCommand
	rcChan       chan chan agents.RemoteControlCommand
	amqpConfig   amqp.Config
	multiRmtKey  bool
}

func init() {
	flag.Parse()

	// Create a map of pointers to all the current flags
	flags := map[string]*flag.Flag{}
	flag.VisitAll(func(f *flag.Flag) {
		flags[f.Name] = f
	})

	// Remove the flags that were set on the command line
	flag.Visit(func(f *flag.Flag) {
		delete(flags, f.Name)
	})

	// Now for the flags that weren't set on the cli,
	// Look at the env for 'PBOX_<uppercase flag name>'
	// If it exists, use it to set the corresponding flag.
	for _, f := range flags {
		var buffer bytes.Buffer
		buffer.WriteString("PBOX_")
		buffer.WriteString(strings.ToUpper(f.Name))
		if os.Getenv(buffer.String()) != "" {
			f.Value.Set(os.Getenv(buffer.String()))
		}
	}
}

func main() {

	if *version {
		fmt.Printf("proc_box version %s built %s\n", VERSION, BUILDDATE)
		os.Exit(0)
	}
	if *debugMode {
		log.SetLevel(log.DebugLevel)
	} else if *noWarn {
		log.SetLevel(log.ErrorLevel)
	}

	// Create channel for ProcessStats to trigger a sample
	psChan := make(chan agents.ProcessStatCommand)
	// Incoming remote command channel (new with each reconnect)
	rcChan := make(chan chan agents.RemoteControlCommand)

	args := flag.Args()
	var cmdArgs []string
	var cmd string
	if len(args) > 0 {
		cmd = args[0]
	} else {
		log.Fatal("Did you forget a command to run?")
		return
	}

	log.Debugf("cmd: %s cmdArgs: %q\n", cmd, cmdArgs)

	done := make(chan error)

	// Initialize job
	job, err := agents.NewControlledProcess(cmd, args, done, *stdoutByteLimit)
	if err != nil {
		log.Fatalf("Failed to create a NewControlledProcess: %s\n", err)
		return
	}

	log.Debugf("%#v\n", job)

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)

	timer, err := agents.NewTimer(*wallclockTimeout)
	if err != nil {
		log.Warnln("Error returned creating timeout agent", err)
	}
	log.Debugf("Starting timer with timeout of: %v\n", *wallclockTimeout)
	timer.Start()

	sess := session{
		job:          job,
		exchange:     *exchange,
		rcRoutingKey: *rmtKey,
		psChan:       psChan,
		rcChan:       rcChan,
		amqpConfig: amqp.Config{
			Properties: amqp.Table{
				"product": "proc_box",
				"version": VERSION,
			},
		},
		multiRmtKey: *multiRmtKey,
	}
	go redial(sess)

	go monitor(signals, rcChan, job, psChan, timer, done)

	err = <-done

	elapsedTime, _ := timer.ElapsedTime()
	// Print to standard out
	fmt.Printf("Task elapsed time: %.2f seconds.\n", elapsedTime.Seconds())

	if err != nil {
		if exiterr, ok := err.(*exec.ExitError); ok {
			// Non-zero exit code
			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
				exitStatus := status.ExitStatus()
				log.Debugf("Exit Status: %d\n", exitStatus)
				os.Exit(exitStatus)
			}
		} else {
			log.Debugf("cmd.Wait: %v\n", err)
		}
	} else {
		os.Exit(0)
	}
}

func redial(sess session) {
	var err error
	var stats agents.ProcessStats
	var rc *agents.RemoteControl

	// Initialize mini-router for incoming stats agent requests
	go func() {
		for s := range sess.psChan {
			if stats == nil {
				log.Warnln("No stats agents available (yet), dropping request")
			} else if s.TimeUpdate {
				stats.NewTicker(s.NewTime)
			} else {
				stats.Sample()
			}
		}
	}()

	rcKeys := []string{*rmtKey}
	if sess.multiRmtKey {
		rcKeys = deleteEmpty(strings.Split(*rmtKey, ","))
	}
	for {
		sess.amqpConn, err = amqp.DialConfig(*uri, sess.amqpConfig)

		if err != nil {
			log.Warnf("Failed to connect to AMQP: %q", err)
			// Rate limit reconnection attempts
			time.Sleep(5 * time.Second)
		} else {
			rc, err = agents.NewRemoteControl(sess.amqpConn, rcKeys, *exchange)
			if err != nil {
				log.Warnf("Failed creating NewRemoteControl: %s", err)
			} else {
				sess.rcChan <- rc.Commands
			}

			if stats == nil {
				// initial setup
				stats, err = agents.NewProcessStats(
					sess.amqpConn,
					*procStatsKey,
					*exchange,
					&sess.job,
					*statsInterval,
					*msgTimeout,
					*userJSON,
				)
				if err != nil {
					log.Warnf("Failed creating NewProcessStats: %s", err)
				}
			} else {
				err = stats.ReinitializeConnection(sess.amqpConn)
				if err != nil {
					log.Warnf("Failed to reinitialize process stats: %s", err)
				}
			}
			closings := sess.amqpConn.NotifyClose(make(chan *amqp.Error))

			// Wait for close notification and loop back around to reconnect
			_ = <-closings
			log.Debugln("Saw a notification for closed connection, looping")
		}

	}
}

func monitor(
	signals chan os.Signal,
	rcChanChan chan chan agents.RemoteControlCommand,
	job agents.JobControl,
	psChan chan agents.ProcessStatCommand,
	timer agents.Timer,
	done chan error,
) {

	// Catch any panics here to ensure we kill the child process before going
	// to our own doom.
	defer func() {
		if e := recover(); e != nil {
			job.Kill(-9)
			panic(e)
		}
	}()
	var logSampling <-chan time.Time
	var err error

	if *stdoutByteLimit > 0 {
		ticker := time.NewTicker(100 * time.Millisecond)
		// Replace the time channel with an actual ticker if this is in use
		logSampling = ticker.C
	}

	var rcChan chan agents.RemoteControlCommand

	for {
		select {
		case rcChan = <-rcChanChan:
		// Catch incoming signals and operate on them as if they were remote commands
		case sig := <-signals:
			switch sig {
			case syscall.SIGINT:
				log.Debugln("Caught SIGINT, graceful shutdown")
				// Initiate non-blocking send
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("SIGINT failed to send a sample msg on the psChan")
				}
				err = job.Stop()
			case syscall.SIGTERM:
				log.Debugln("Caught SIGTERM, end abruptly")
				job.Kill(-9)
			case syscall.SIGHUP:
				log.Debugln("Caught SIGHUP, emit stats")
				// Initiate non-blocking send
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("SIGHUP failed to send a sample msg on the psChan")
				}
			case syscall.SIGQUIT:
				log.Debugln("Caught SIGQUIT, graceful shutdown")
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("SIGQUIT failed to send a sample msg on the psChan")
				}
				err = job.Stop()
			}
		// Process incoming remote commands, toss unknown requests
		case cmd := <-rcChan:
			log.Debugf("Got a command %#v\n", cmd)
			switch cmd.Command {
			case "suspend":
				log.Debugln("RemoteCommand: Suspend")
				job.Suspend()
			case "resume":
				log.Debugln("RemoteCommand: Resume")
				job.Resume()
			case "kill":
				log.Debugln("RemoteCommand: Kill")
				var args int64
				if len(cmd.Arguments) == 0 {
					args = -9
				} else {
					args, err = strconv.ParseInt(cmd.Arguments[0], 10, 32)
					if err != nil {
						log.Warnf("Unable to parse kill command argument[0] into int: %s\n", err)
						args = -9
					}
				}
				job.Kill(args)
			case "stop":
				log.Debugln("RemoteCommand: Stop")
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("RC Stop failed to send a sample msg on the psChan")
				}
				if err = job.Stop(); err != nil {
					log.Fatalf("Error received while stopping sub-process: %s\n", err)
				}
			case "sample":
				log.Debugln("RemoteCommand: Sample")
				select {
				case psChan <- agents.ProcessStatCommand{}:
					log.Debugln("Sending psChan a msg to sample")
				default:
					log.Debugln("RC Sample failed to send a sample msg on the psChan")
				}
			case "change_sample_rate":
				log.Debugln("RemoteCommand: Change Stats Sample Rate")
				if len(cmd.Arguments) > 0 {
					log.Debugf("change_sample_rate arg[0]: %s\n", cmd.Arguments[0])
					d, err := time.ParseDuration(cmd.Arguments[0])
					if err == nil {
						select {
						case psChan <- agents.ProcessStatCommand{
							TimeUpdate: true,
							NewTime:    d}:
							log.Debugln("Sending psChan a msg to update the ticker")
						default:
							log.Debugln("RC change_sample_rate failed to send a msg")
						}
					} else {
						log.Warnf("Unparseable duration argument to command change_sample_rate")
					}
				} else {
					log.Warnf("Missing argument to command change_sample_rate")
				}
			case "timer_reset":
				log.Debugln("RemoteCommand: Timer Reset")
				if err = timer.Reset(); err != nil {
					log.Fatalf("Error received from timer calling Reset: %s\n", err)
				}
			case "timer_start":
				log.Debugln("RemoteCommand: Timer Start")
				if err = timer.Start(); err != nil {
					log.Fatalf("Error received from timer calling Start: %s\n", err)
				}
			case "timer_stop":
				log.Debugln("RemoteCommand: Timer Stop")
				if err = timer.Stop(); err != nil {
					log.Fatalf("Error received from timer calling Stop: %s\n", err)
				}
			case "timer_resume":
				log.Debugln("RemoteCommand: Timer Resume")
				if err = timer.Resume(); err != nil {
					log.Fatalf("Error received from timer calling Resume: %s\n", err)
				}
			default:
				log.Debugf("Unknown command: %s\n", cmd)
			}
		case timeoutMsg := <-timer.Done():
			log.Debugf("Timer timeout message: %s\n", timeoutMsg)
			if err = job.Stop(); err != nil {
				log.Fatalf("Error received while stopping sub-process: %v\n", err)
				// If there was an error stopping the process, kill the porcess.
				job.Kill(-9)
			}
		case jobDone := <-job.Done():
			log.Debugln("Command exited gracefully; shutting down.")
			done <- jobDone
		case _ = <-logSampling:
			if job.StdoutByteCount() > 2*(*stdoutByteLimit) {
				err = job.Kill(-9)
			} else if job.StdoutByteCount() > *stdoutByteLimit {
				err = job.Stop()
			}
		}
	}
}

func deleteEmpty(s []string) []string {
	var r []string
	for _, v := range s {
		if len(v) > 0 {
			r = append(r, v)
		}
	}
	return r
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	irc "github.com/fluffle/goirc/client"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var host *string = flag.String("host", "irc.ps0ke.de:2342", "IRC server")
var channel *string = flag.String("channel", "#metttest", "IRC channel")
var nick *string = flag.String("nick", "rohmett", "IRC nick")
var longnick *string = flag.String("longnick", "Le MettBot", "IRC fullname")
var timeformat *string = flag.String("timeformat", "2006-01-02T15:04", "Time format string (standard date: 2006-01-02T15:04:05")
var quotes *string = flag.String("quotes", "mett_quotes.txt", "Quote database file")

func init() {
	flag.Parse()
}

type Mettbot struct {
	*irc.Conn
	Quitted    chan bool
	Prnt       chan string
	Input      chan string
	ReallyQuit bool
	Topics     map[string]string
}

func NewMettbot(nick string, args ...string) *Mettbot {
	bot := &Mettbot{
		irc.SimpleClient(nick, args...), // *irc.Conn
		make(chan bool),                 // Quitted
		make(chan string),               // Prnt
		make(chan string, 4),            // Input
		false,                           // ReallyQuit
		make(map[string]string), // Topics
	}
	bot.EnableStateTracking()
	return bot
}

func (bot *Mettbot) hConnected()    { bot.Join(*channel) }
func (bot *Mettbot) hDisconnected() { bot.Quitted <- true }

func (bot *Mettbot) hJoin(line *irc.Line) {
	time.Sleep(1000 * time.Millisecond)
	actChannel := line.Args[0]
	bot.Topics[actChannel] = bot.ST.GetChannel(actChannel).Topic
}

func (bot *Mettbot) hTopic(line *irc.Line) {
	actChannel := line.Args[0]
	newTopic := line.Args[1]
	oldTopic := bot.Topics[actChannel]
	bot.Topics[actChannel] = newTopic
	bot.Notice(actChannel, "Old topic: " + oldTopic)
	//bot.Notice(actChannel, "New topic: "+newTopic)
}

func (bot *Mettbot) hPrivmsg(line *irc.Line) {
	actChannel := line.Args[0]
	msg := line.Args[1]

	if msg[0] == '!' {
		cmd := msg
		args := ""
		idx := strings.Index(msg, " ")
		if idx != -1 {
			cmd = msg[:idx]
			args = msg[idx+1:]
		}

		switch {
		case cmd == "!help":
			bot.Help(actChannel)
		case args == "":
			bot.Syntax(actChannel)
		case cmd == "!quote":
			bot.cQuote(actChannel, args, line.Time)
		case cmd == "!print":
			bot.cPrint(actChannel, args)
		default:
			bot.Syntax(actChannel)
		}
	}
}

func (bot *Mettbot) Syntax(channel string) {
	bot.Notice(channel, "Wrong Syntax. Try !help")
}

func (bot *Mettbot) Help(channel string) {
	bot.Notice(channel, "Mett")
}

func (bot *Mettbot) cQuote(channel string, msg string, t time.Time) {
	s := fmt.Sprintln(t.Format(*timeformat), msg)
	fmt.Print(s)
	bot.Prnt <- s
	bot.Notice(channel, "Added Quote to Database")
}

func (bot *Mettbot) cPrint(channel string, msg string) {
	num, err := strconv.Atoi(msg)
	if err != nil {
		bot.Syntax(channel)
		return
	}

	fi, err := os.Open(*quotes)
	if err != nil {
		log.Println(err)
		bot.Notice(channel, "Failed to open quote database")
		return
	}
	defer fi.Close()

	reader := bufio.NewReader(fi)
	quote := ""
	for ; num >= 0; num-- {
		quote, err = reader.ReadString('\n')
		if err == io.EOF {
			bot.Notice(channel, "Quote not found")
			return
		}
		if err != nil {
			log.Println(err)
			bot.Notice(channel, "Failed to read from quote database")
			return
		}
	}
	bot.Notice(channel, quote)
}

func (bot *Mettbot) readStdin() {
	con := bufio.NewReader(os.Stdin)
	for {
		s, err := con.ReadString('\n')
		if err != nil {
			// wha?, maybe ctrl-D...
			close(bot.Input)
			break
		}
		// no point in sending empty lines down the channel
		if len(s) > 2 {
			bot.Input <- s[0 : len(s)-1]
		}
	}
}

func (bot *Mettbot) parseStdin() {
	for cmd := range bot.Input {
		if cmd[0] == ':' {
			switch idx := strings.Index(cmd, " "); {
			case cmd[1] == 'd':
				fmt.Printf(bot.String())
			case cmd[1] == 'f':
				if len(cmd) > 2 && cmd[2] == 'e' {
					// enable flooding
					bot.Flood = true
				} else if len(cmd) > 2 && cmd[2] == 'd' {
					// disable flooding
					bot.Flood = false
				}
				for i := 0; i < 20; i++ {
					bot.Privmsg(*channel, "salami!1!")
				}
			case idx == -1:
				continue
			case cmd[1] == 'q':
				bot.ReallyQuit = true
				bot.Quit(cmd[idx+1 : len(cmd)])
			case cmd[1] == 'j':
				bot.Join(cmd[idx+1 : len(cmd)])
			case cmd[1] == 'p':
				bot.Part(cmd[idx+1 : len(cmd)])
			case cmd[1] == 'm':
				bot.Privmsg(*channel, cmd[idx+1:len(cmd)])
			case cmd[1] == 'a':
				bot.Action(*channel, cmd[idx+1:len(cmd)])
			}
		} else {
			bot.Raw(cmd)
		}
	}
}

func (bot *Mettbot) writeQuote() {
	for messages := range bot.Prnt {
		fo, err := os.OpenFile(*quotes, syscall.O_WRONLY+syscall.O_CREAT+syscall.O_APPEND, 0644)
		if err != nil {
			log.Println(err)
			bot.Notice(*channel, "Couldn't open quote database")
			continue
		}
		defer fo.Close()

		_, err = fo.WriteString(messages)
		if err != nil {
			log.Println(err)
			bot.Notice(*channel, "Couldn't write to quote database")
		}
		fo.Close()
	}
}

func main() {
	// create new IRC connection
	mett := NewMettbot(*nick, *nick, *longnick)

	// Set up handlers
	mett.AddHandler("connected", func(conn *irc.Conn, line *irc.Line) { mett.hConnected() })
	mett.AddHandler("disconnected", func(conn *irc.Conn, line *irc.Line) { mett.hDisconnected() })
	mett.AddHandler("join", func(conn *irc.Conn, line *irc.Line) { mett.hJoin(line) })
	mett.AddHandler("privmsg", func(conn *irc.Conn, line *irc.Line) { mett.hPrivmsg(line) })
	mett.AddHandler("topic", func(conn *irc.Conn, line *irc.Line) { mett.hTopic(line) })

	// set up a goroutine to read commands from stdin
	go mett.readStdin()

	// set up a goroutine to do parsey things with the stuff from stdin
	go mett.parseStdin()

	// Set up a go routine to write quotes to file
	go mett.writeQuote()

	for !mett.ReallyQuit {
		// connect to server
		if err := mett.Connect(*host); err != nil {
			fmt.Printf("Connection error: %s\n", err)
			return
		}

		// wait on quit channel
		<-mett.Quitted
	}
}
<commit_msg>removed the ability to print negative qoutenumbers<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	irc "github.com/fluffle/goirc/client"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var host *string = flag.String("host", "irc.ps0ke.de:2342", "IRC server")
var channel *string = flag.String("channel", "#metttest", "IRC channel")
var nick *string = flag.String("nick", "rohmett", "IRC nick")
var longnick *string = flag.String("longnick", "Le MettBot", "IRC fullname")
var timeformat *string = flag.String("timeformat", "2006-01-02T15:04", "Time format string (standard date: 2006-01-02T15:04:05")
var quotes *string = flag.String("quotes", "mett_quotes.txt", "Quote database file")

func init() {
	flag.Parse()
}

type Mettbot struct {
	*irc.Conn
	Quitted    chan bool
	Prnt       chan string
	Input      chan string
	ReallyQuit bool
	Topics     map[string]string
}

func NewMettbot(nick string, args ...string) *Mettbot {
	bot := &Mettbot{
		irc.SimpleClient(nick, args...), // *irc.Conn
		make(chan bool),                 // Quitted
		make(chan string),               // Prnt
		make(chan string, 4),            // Input
		false,                           // ReallyQuit
		make(map[string]string), // Topics
	}
	bot.EnableStateTracking()
	return bot
}

func (bot *Mettbot) hConnected()    { bot.Join(*channel) }
func (bot *Mettbot) hDisconnected() { bot.Quitted <- true }

func (bot *Mettbot) hJoin(line *irc.Line) {
	time.Sleep(1000 * time.Millisecond)
	actChannel := line.Args[0]
	bot.Topics[actChannel] = bot.ST.GetChannel(actChannel).Topic
}

func (bot *Mettbot) hTopic(line *irc.Line) {
	actChannel := line.Args[0]
	newTopic := line.Args[1]
	oldTopic := bot.Topics[actChannel]
	bot.Topics[actChannel] = newTopic
	bot.Notice(actChannel, "Old topic: " + oldTopic)
	//bot.Notice(actChannel, "New topic: "+newTopic)
}

func (bot *Mettbot) hPrivmsg(line *irc.Line) {
	actChannel := line.Args[0]
	msg := line.Args[1]

	if msg[0] == '!' {
		cmd := msg
		args := ""
		idx := strings.Index(msg, " ")
		if idx != -1 {
			cmd = msg[:idx]
			args = msg[idx+1:]
		}

		switch {
		case cmd == "!help":
			bot.Help(actChannel)
		case args == "":
			bot.Syntax(actChannel)
		case cmd == "!quote":
			bot.cQuote(actChannel, args, line.Time)
		case cmd == "!print":
			bot.cPrint(actChannel, args, line)
		default:
			bot.Syntax(actChannel)
		}
	}
}

func (bot *Mettbot) Syntax(channel string) {
	bot.Notice(channel, "Wrong Syntax. Try !help")
}

func (bot *Mettbot) Help(channel string) {
	bot.Notice(channel, "Mett")
}

func (bot *Mettbot) cQuote(channel string, msg string, t time.Time) {
	s := fmt.Sprintln(t.Format(*timeformat), msg)
	fmt.Print(s)
	bot.Prnt <- s
	bot.Notice(channel, "Added Quote to Database")
}

func (bot *Mettbot) cPrint(channel string, msg string, line *irc.Line) {
	num, err := strconv.Atoi(msg)
	if err != nil {
		bot.Syntax(channel)
		return
	}
	if num < 0 {
		bot.Action(channel, "slaps " + line.Nick)
		return
	}

	fi, err := os.Open(*quotes)
	if err != nil {
		log.Println(err)
		bot.Notice(channel, "Failed to open quote database")
		return
	}
	defer fi.Close()

	reader := bufio.NewReader(fi)
	quote := ""
	for ; num >= 0; num-- {
		quote, err = reader.ReadString('\n')
		if err == io.EOF {
			bot.Notice(channel, "Quote not found")
			return
		}
		if err != nil {
			log.Println(err)
			bot.Notice(channel, "Failed to read from quote database")
			return
		}
	}
	bot.Notice(channel, quote)
}

func (bot *Mettbot) readStdin() {
	con := bufio.NewReader(os.Stdin)
	for {
		s, err := con.ReadString('\n')
		if err != nil {
			// wha?, maybe ctrl-D...
			close(bot.Input)
			break
		}
		// no point in sending empty lines down the channel
		if len(s) > 2 {
			bot.Input <- s[0 : len(s)-1]
		}
	}
}

func (bot *Mettbot) parseStdin() {
	for cmd := range bot.Input {
		if cmd[0] == ':' {
			switch idx := strings.Index(cmd, " "); {
			case cmd[1] == 'd':
				fmt.Printf(bot.String())
			case cmd[1] == 'f':
				if len(cmd) > 2 && cmd[2] == 'e' {
					// enable flooding
					bot.Flood = true
				} else if len(cmd) > 2 && cmd[2] == 'd' {
					// disable flooding
					bot.Flood = false
				}
				for i := 0; i < 20; i++ {
					bot.Privmsg(*channel, "salami!1!")
				}
			case idx == -1:
				continue
			case cmd[1] == 'q':
				bot.ReallyQuit = true
				bot.Quit(cmd[idx+1 : len(cmd)])
			case cmd[1] == 'j':
				bot.Join(cmd[idx+1 : len(cmd)])
			case cmd[1] == 'p':
				bot.Part(cmd[idx+1 : len(cmd)])
			case cmd[1] == 'm':
				bot.Privmsg(*channel, cmd[idx+1:len(cmd)])
			case cmd[1] == 'a':
				bot.Action(*channel, cmd[idx+1:len(cmd)])
			}
		} else {
			bot.Raw(cmd)
		}
	}
}

func (bot *Mettbot) writeQuote() {
	for messages := range bot.Prnt {
		fo, err := os.OpenFile(*quotes, syscall.O_WRONLY+syscall.O_CREAT+syscall.O_APPEND, 0644)
		if err != nil {
			log.Println(err)
			bot.Notice(*channel, "Couldn't open quote database")
			continue
		}
		defer fo.Close()

		_, err = fo.WriteString(messages)
		if err != nil {
			log.Println(err)
			bot.Notice(*channel, "Couldn't write to quote database")
		}
		fo.Close()
	}
}

func main() {
	// create new IRC connection
	mett := NewMettbot(*nick, *nick, *longnick)

	// Set up handlers
	mett.AddHandler("connected", func(conn *irc.Conn, line *irc.Line) { mett.hConnected() })
	mett.AddHandler("disconnected", func(conn *irc.Conn, line *irc.Line) { mett.hDisconnected() })
	mett.AddHandler("join", func(conn *irc.Conn, line *irc.Line) { mett.hJoin(line) })
	mett.AddHandler("privmsg", func(conn *irc.Conn, line *irc.Line) { mett.hPrivmsg(line) })
	mett.AddHandler("topic", func(conn *irc.Conn, line *irc.Line) { mett.hTopic(line) })

	// set up a goroutine to read commands from stdin
	go mett.readStdin()

	// set up a goroutine to do parsey things with the stuff from stdin
	go mett.parseStdin()

	// Set up a go routine to write quotes to file
	go mett.writeQuote()

	for !mett.ReallyQuit {
		// connect to server
		if err := mett.Connect(*host); err != nil {
			fmt.Printf("Connection error: %s\n", err)
			return
		}

		// wait on quit channel
		<-mett.Quitted
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"os"
	"os/exec"
	"path"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/jbrukh/bayesian"
	"github.com/pkg/errors"
)

var (
	journal  = flag.String("j", "", "Existing journal to learn from.")
	debug    = flag.Bool("debug", false, "Additional debug information if set.")
	csv      = flag.String("csv", "", "File path of CSV file containing new transactions.")
	account  = flag.String("a", "", "Name of bank account to use.")
	currency = flag.String("c", "", "Set currency if any.")
	ignore   = flag.String("ic", "", "Comma separated list of colums to ignore in CSV.")
	rtxn     = regexp.MustCompile(`\d{4}/\d{2}/\d{2}[\W]*(\w.*)`)
	rto      = regexp.MustCompile(`\W*([:\w]+).*`)
	rfrom    = regexp.MustCompile(`\W*([:\w]+).*`)
	racc     = regexp.MustCompile(`^account[\W]+(.*)`)
	ralias   = regexp.MustCompile(`\balias\s(.*)`)
	stamp    = "2006/01/02"
)

func assert(err error) {
	if err != nil {
		log.Fatalf("%+v", errors.WithStack(err))
	}
}

func check(ok bool) {
	if !ok {
		log.Fatalf("%+v", errors.Errorf("Should be true, but is false"))
	}
}

type txn struct {
	date string
	desc string
	to   string
	from string
	cur  float64
}

type parser struct {
	data     []byte
	txns     []txn
	classes  []bayesian.Class
	cl       *bayesian.Classifier
	accounts map[string]string
}

func (p *parser) parseTransactions() {
	s := bufio.NewScanner(bytes.NewReader(p.data))
	var t txn
	for s.Scan() {
		t = txn{}
		m := rtxn.FindStringSubmatch(s.Text())
		if len(m) < 2 {
			continue
		}
		t.desc = strings.ToLower(m[1])

		check(s.Scan())
		m = rto.FindStringSubmatch(s.Text())
		check(len(m) > 1)
		t.to = m[1]
		if alias, has := p.accounts[t.to]; has {
			t.to = alias
		}

		check(s.Scan())
		m = rfrom.FindStringSubmatch(s.Text())
		check(len(m) > 1)
		t.from = m[1]
		p.txns = append(p.txns, t)
	}
}

func (p *parser) parseAccounts() {
	p.accounts = make(map[string]string)
	s := bufio.NewScanner(bytes.NewReader(p.data))
	for s.Scan() {
		m := racc.FindStringSubmatch(s.Text())
		if len(m) < 2 {
			continue
		}
		acc := m[1]

		check(s.Scan())
		m = ralias.FindStringSubmatch(s.Text())
		check(len(m) > 1)
		ali := m[1]
		p.accounts[acc] = ali
	}
}

func (p *parser) generateClasses() {
	p.classes = make([]bayesian.Class, 0, 10)
	tomap := make(map[string]bool)
	for _, t := range p.txns {
		tomap[t.to] = true
	}
	for to := range tomap {
		p.classes = append(p.classes, bayesian.Class(to))
	}

	p.cl = bayesian.NewClassifierTfIdf(p.classes...)
	for _, t := range p.txns {
		p.cl.Learn(strings.Split(t.desc, " "), bayesian.Class(t.to))
	}
	p.cl.ConvertTermsFreqToTfIdf()
}

type pair struct {
	score float64
	pos   int
}

type byScore []pair

func (b byScore) Len() int {
	return len(b)
}
func (b byScore) Less(i int, j int) bool {
	return b[i].score > b[j].score
}
func (b byScore) Swap(i int, j int) {
	b[i], b[j] = b[j], b[i]
}

func (p *parser) topHits(in string) []bayesian.Class {
	in = strings.ToLower(in)
	terms := strings.Split(in, " ")
	scores, _, _ := p.cl.LogScores(terms)
	pairs := make([]pair, 0, len(scores))

	var mean, stddev float64
	for pos, score := range scores {
		pairs = append(pairs, pair{score, pos})
		mean += score
	}
	mean /= float64(len(scores))
	for _, score := range scores {
		stddev += math.Pow(score-mean, 2)
	}
	stddev /= float64(len(scores) - 1)
	stddev = math.Sqrt(stddev)

	sort.Sort(byScore(pairs))
	result := make([]bayesian.Class, 0, 5)
	last := pairs[0].score
	for i := 0; i < 5; i++ {
		pr := pairs[i]
		if *debug {
			fmt.Printf("i=%d s=%f Class=%v\n", i, pr.score, p.classes[pr.pos])
		}
		if math.Abs(pr.score-last) > stddev {
			break
		}
		result = append(result, p.classes[pr.pos])
		last = pr.score
	}
	return result
}

func includeAll(dir string, data []byte) []byte {
	final := make([]byte, len(data))
	copy(final, data)

	b := bytes.NewBuffer(data)
	s := bufio.NewScanner(b)
	for s.Scan() {
		line := s.Text()
		if !strings.HasPrefix(line, "include ") {
			continue
		}
		fname := strings.Trim(line[8:], " \n")
		include, err := ioutil.ReadFile(path.Join(dir, fname))
		assert(err)
		final = append(final, include...)
	}
	return final
}

func parseDate(col string) (string, bool) {
	if tm, err := time.Parse("01/02/2006", col); err == nil {
		return tm.Format(stamp), true
	}
	if tm, err := time.Parse("02/01/2006", col); err == nil {
		return tm.Format(stamp), true
	}
	return "", false
}

func parseCurrency(col string) (float64, bool) {
	f, err := strconv.ParseFloat(col, 64)
	return f, err == nil
}

func parseDescription(col string) (string, bool) {
	if len(col) < 2 {
		return "", false
	}
	if col[0] != '"' || col[len(col)-1] != '"' {
		return "", false
	}
	return col[1 : len(col)-1], true
}

func parseTransactionsFromCSV(in []byte) []txn {
	s := bufio.NewScanner(bytes.NewReader(in))
	var t txn
	ignored := make(map[int]bool)
	for _, i := range strings.Split(*ignore, ",") {
		pos, err := strconv.Atoi(i)
		assert(err)
		ignored[pos] = true
	}
	result := make([]txn, 0, 100)
	for s.Scan() {
		t = txn{}
		line := s.Text()
		if len(line) == 0 {
			continue
		}
		cols := strings.Split(line, ",")
		for i, col := range cols {
			if ignored[i] {
				continue
			}
			if date, ok := parseDate(col); ok {
				t.date = date
			}
			if f, ok := parseCurrency(col); ok {
				t.cur = f
			}
			if d, ok := parseDescription(col); ok {
				t.desc = d
			}
		}
		if len(t.desc) != 0 && len(t.date) != 0 && t.cur != 0.0 {
			result = append(result, t)
		} else {
			log.Fatalf("Unable to parse txn for [%v]. Got: %+v\n", line, t)
		}
	}
	return result
}

func assignFor(opt string, cl bayesian.Class, keys map[rune]string) bool {
	for i := 0; i < len(opt); i++ {
		ch := rune(opt[i])
		if _, has := keys[ch]; !has {
			keys[ch] = string(cl)
			return true
		}
	}
	return false
}

func generateKeyMap(opts []bayesian.Class) map[rune]string {
	keys := make(map[rune]string)
	keys['b'] = ".back"
	keys['q'] = ".quit"
	for _, opt := range opts {
		if ok := assignFor(string(opt), opt, keys); ok {
			continue
		}
		if ok := assignFor(strings.ToUpper(string(opt)), opt, keys); ok {
			continue
		}
		if ok := assignFor("0123456789", opt, keys); ok {
			continue
		}
		log.Fatalf("Unable to assign any key for: %v", opt)
	}
	return keys
}

type kv struct {
	key rune
	val string
}

type byVal []kv

func (b byVal) Len() int {
	return len(b)
}

func (b byVal) Less(i int, j int) bool {
	return b[i].val < b[j].val
}

func (b byVal) Swap(i int, j int) {
	b[i], b[j] = b[j], b[i]
}

var allKeys map[rune]string

func singleCharMode() {
	// disable input buffering
	exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
	// do not display entered characters on the screen
	exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
}

func printSummary(t txn, idx, total int) {
	color.New(color.BgBlue, color.FgWhite).Printf(" [%2d of %2d] ", idx, total)
	color.New(color.BgYellow, color.FgBlack).Printf(" %10s ", t.date)
	desc := t.desc
	if len(desc) > 40 {
		desc = desc[:40]
	}
	color.New(color.BgWhite, color.FgBlack).Printf(" %-40s", desc)
	if len(t.to) > 0 {
		to := t.to
		if len(to) > 20 {
			to = to[len(to)-20:]
		}
		color.New(color.BgGreen, color.FgBlack).Printf(" %-20s ", to)
	}

	pomo := color.New(color.BgBlack, color.FgWhite).PrintfFunc()
	if t.cur > 0 {
		pomo = color.New(color.BgGreen, color.FgBlack).PrintfFunc()
	} else {
		pomo = color.New(color.BgRed, color.FgWhite).PrintfFunc()
	}
	pomo(" %7.2f ", t.cur)
	fmt.Println()
}

func clear() {
	cmd := exec.Command("clear")
	cmd.Stdout = os.Stdout
	cmd.Run()
	fmt.Println()
}

func printAndGetResult(keys map[rune]string, t *txn) int {
	kvs := make([]kv, 0, len(keys))
	for k, opt := range keys {
		kvs = append(kvs, kv{k, opt})
	}
	sort.Sort(byVal(kvs))
	for _, kv := range kvs {
		fmt.Printf("%q: %s\n", kv.key, kv.val)
	}

	r := make([]byte, 1)
	os.Stdin.Read(r)
	ch := rune(r[0])
	if ch == 'b' {
		return -1
	}
	if ch == 'q' {
		return 9999
	}
	if ch == rune(10) && len(t.to) > 0 {
		return 1
	}

	if opt, has := keys[ch]; has {
		t.to = opt
		return 1
	}
	return 0
}

func (p *parser) printTxn(t *txn, idx, total int) int {
	clear()
	printSummary(*t, idx, total)

	hits := p.topHits(t.desc)
	keys := generateKeyMap(hits)
	res := printAndGetResult(keys, t)
	if res != 0 {
		return res
	}

	clear()
	printSummary(*t, idx, total)
	return printAndGetResult(allKeys, t)
}

func (p *parser) showAndCategorizeTxns(txns []txn) {
	allKeys = generateKeyMap(p.classes)
	for i, t := range txns {
		printSummary(t, i, len(txns))
	}
	fmt.Println()

	fmt.Printf("Found %d transactions. Review (Y/n)? ", len(txns))
	b := make([]byte, 1)
	os.Stdin.Read(b)
	if b[0] == 'n' || b[0] == 'q' {
		return
	}

	for i := 0; i < len(txns) && i >= 0; {
		i += p.printTxn(&txns[i], i, len(txns))
	}
	for i, t := range txns {
		printSummary(t, i, len(txns))
	}
	fmt.Println()
}

func main() {
	singleCharMode()
	flag.Parse()
	data, err := ioutil.ReadFile(*journal)
	assert(err)
	alldata := includeAll(path.Dir(*journal), data)

	p := parser{data: alldata}
	p.parseAccounts()
	p.parseTransactions()

	// Scanning done. Now train classifier.
	p.generateClasses()

	in, err := ioutil.ReadFile(*csv)
	assert(err)
	txns := parseTransactionsFromCSV(in)

	p.showAndCategorizeTxns(txns)
}
<commit_msg>Infinitely show task summary and ask for review, until n or q.<commit_after>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"os"
	"os/exec"
	"path"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/jbrukh/bayesian"
	"github.com/pkg/errors"
)

var (
	journal  = flag.String("j", "", "Existing journal to learn from.")
	debug    = flag.Bool("debug", false, "Additional debug information if set.")
	csv      = flag.String("csv", "", "File path of CSV file containing new transactions.")
	account  = flag.String("a", "", "Name of bank account to use.")
	currency = flag.String("c", "", "Set currency if any.")
	ignore   = flag.String("ic", "", "Comma separated list of colums to ignore in CSV.")
	rtxn     = regexp.MustCompile(`\d{4}/\d{2}/\d{2}[\W]*(\w.*)`)
	rto      = regexp.MustCompile(`\W*([:\w]+).*`)
	rfrom    = regexp.MustCompile(`\W*([:\w]+).*`)
	racc     = regexp.MustCompile(`^account[\W]+(.*)`)
	ralias   = regexp.MustCompile(`\balias\s(.*)`)
	stamp    = "2006/01/02"
)

func assert(err error) {
	if err != nil {
		log.Fatalf("%+v", errors.WithStack(err))
	}
}

func check(ok bool) {
	if !ok {
		log.Fatalf("%+v", errors.Errorf("Should be true, but is false"))
	}
}

type txn struct {
	date string
	desc string
	to   string
	from string
	cur  float64
}

type parser struct {
	data     []byte
	txns     []txn
	classes  []bayesian.Class
	cl       *bayesian.Classifier
	accounts map[string]string
}

func (p *parser) parseTransactions() {
	s := bufio.NewScanner(bytes.NewReader(p.data))
	var t txn
	for s.Scan() {
		t = txn{}
		m := rtxn.FindStringSubmatch(s.Text())
		if len(m) < 2 {
			continue
		}
		t.desc = strings.ToLower(m[1])

		check(s.Scan())
		m = rto.FindStringSubmatch(s.Text())
		check(len(m) > 1)
		t.to = m[1]
		if alias, has := p.accounts[t.to]; has {
			t.to = alias
		}

		check(s.Scan())
		m = rfrom.FindStringSubmatch(s.Text())
		check(len(m) > 1)
		t.from = m[1]
		p.txns = append(p.txns, t)
	}
}

func (p *parser) parseAccounts() {
	p.accounts = make(map[string]string)
	s := bufio.NewScanner(bytes.NewReader(p.data))
	for s.Scan() {
		m := racc.FindStringSubmatch(s.Text())
		if len(m) < 2 {
			continue
		}
		acc := m[1]

		check(s.Scan())
		m = ralias.FindStringSubmatch(s.Text())
		check(len(m) > 1)
		ali := m[1]
		p.accounts[acc] = ali
	}
}

func (p *parser) generateClasses() {
	p.classes = make([]bayesian.Class, 0, 10)
	tomap := make(map[string]bool)
	for _, t := range p.txns {
		tomap[t.to] = true
	}
	for to := range tomap {
		p.classes = append(p.classes, bayesian.Class(to))
	}

	p.cl = bayesian.NewClassifierTfIdf(p.classes...)
	for _, t := range p.txns {
		p.cl.Learn(strings.Split(t.desc, " "), bayesian.Class(t.to))
	}
	p.cl.ConvertTermsFreqToTfIdf()
}

type pair struct {
	score float64
	pos   int
}

type byScore []pair

func (b byScore) Len() int {
	return len(b)
}
func (b byScore) Less(i int, j int) bool {
	return b[i].score > b[j].score
}
func (b byScore) Swap(i int, j int) {
	b[i], b[j] = b[j], b[i]
}

func (p *parser) topHits(in string) []bayesian.Class {
	in = strings.ToLower(in)
	terms := strings.Split(in, " ")
	scores, _, _ := p.cl.LogScores(terms)
	pairs := make([]pair, 0, len(scores))

	var mean, stddev float64
	for pos, score := range scores {
		pairs = append(pairs, pair{score, pos})
		mean += score
	}
	mean /= float64(len(scores))
	for _, score := range scores {
		stddev += math.Pow(score-mean, 2)
	}
	stddev /= float64(len(scores) - 1)
	stddev = math.Sqrt(stddev)

	sort.Sort(byScore(pairs))
	result := make([]bayesian.Class, 0, 5)
	last := pairs[0].score
	for i := 0; i < 5; i++ {
		pr := pairs[i]
		if *debug {
			fmt.Printf("i=%d s=%f Class=%v\n", i, pr.score, p.classes[pr.pos])
		}
		if math.Abs(pr.score-last) > stddev {
			break
		}
		result = append(result, p.classes[pr.pos])
		last = pr.score
	}
	return result
}

func includeAll(dir string, data []byte) []byte {
	final := make([]byte, len(data))
	copy(final, data)

	b := bytes.NewBuffer(data)
	s := bufio.NewScanner(b)
	for s.Scan() {
		line := s.Text()
		if !strings.HasPrefix(line, "include ") {
			continue
		}
		fname := strings.Trim(line[8:], " \n")
		include, err := ioutil.ReadFile(path.Join(dir, fname))
		assert(err)
		final = append(final, include...)
	}
	return final
}

func parseDate(col string) (string, bool) {
	if tm, err := time.Parse("01/02/2006", col); err == nil {
		return tm.Format(stamp), true
	}
	if tm, err := time.Parse("02/01/2006", col); err == nil {
		return tm.Format(stamp), true
	}
	return "", false
}

func parseCurrency(col string) (float64, bool) {
	f, err := strconv.ParseFloat(col, 64)
	return f, err == nil
}

func parseDescription(col string) (string, bool) {
	if len(col) < 2 {
		return "", false
	}
	if col[0] != '"' || col[len(col)-1] != '"' {
		return "", false
	}
	return col[1 : len(col)-1], true
}

func parseTransactionsFromCSV(in []byte) []txn {
	s := bufio.NewScanner(bytes.NewReader(in))
	var t txn
	ignored := make(map[int]bool)
	for _, i := range strings.Split(*ignore, ",") {
		pos, err := strconv.Atoi(i)
		assert(err)
		ignored[pos] = true
	}
	result := make([]txn, 0, 100)
	for s.Scan() {
		t = txn{}
		line := s.Text()
		if len(line) == 0 {
			continue
		}
		cols := strings.Split(line, ",")
		for i, col := range cols {
			if ignored[i] {
				continue
			}
			if date, ok := parseDate(col); ok {
				t.date = date
			}
			if f, ok := parseCurrency(col); ok {
				t.cur = f
			}
			if d, ok := parseDescription(col); ok {
				t.desc = d
			}
		}
		if len(t.desc) != 0 && len(t.date) != 0 && t.cur != 0.0 {
			result = append(result, t)
		} else {
			log.Fatalf("Unable to parse txn for [%v]. Got: %+v\n", line, t)
		}
	}
	return result
}

func assignFor(opt string, cl bayesian.Class, keys map[rune]string) bool {
	for i := 0; i < len(opt); i++ {
		ch := rune(opt[i])
		if _, has := keys[ch]; !has {
			keys[ch] = string(cl)
			return true
		}
	}
	return false
}

func generateKeyMap(opts []bayesian.Class) map[rune]string {
	keys := make(map[rune]string)
	keys['b'] = ".back"
	keys['q'] = ".quit"
	for _, opt := range opts {
		if ok := assignFor(string(opt), opt, keys); ok {
			continue
		}
		if ok := assignFor(strings.ToUpper(string(opt)), opt, keys); ok {
			continue
		}
		if ok := assignFor("0123456789", opt, keys); ok {
			continue
		}
		log.Fatalf("Unable to assign any key for: %v", opt)
	}
	return keys
}

type kv struct {
	key rune
	val string
}

type byVal []kv

func (b byVal) Len() int {
	return len(b)
}

func (b byVal) Less(i int, j int) bool {
	return b[i].val < b[j].val
}

func (b byVal) Swap(i int, j int) {
	b[i], b[j] = b[j], b[i]
}

var allKeys map[rune]string

func singleCharMode() {
	// disable input buffering
	exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
	// do not display entered characters on the screen
	exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
}

func printSummary(t txn, idx, total int) {
	color.New(color.BgBlue, color.FgWhite).Printf(" [%2d of %2d] ", idx, total)
	color.New(color.BgYellow, color.FgBlack).Printf(" %10s ", t.date)
	desc := t.desc
	if len(desc) > 40 {
		desc = desc[:40]
	}
	color.New(color.BgWhite, color.FgBlack).Printf(" %-40s", desc)
	if len(t.to) > 0 {
		to := t.to
		if len(to) > 20 {
			to = to[len(to)-20:]
		}
		color.New(color.BgGreen, color.FgBlack).Printf(" %-20s ", to)
	}

	pomo := color.New(color.BgBlack, color.FgWhite).PrintfFunc()
	if t.cur > 0 {
		pomo = color.New(color.BgGreen, color.FgBlack).PrintfFunc()
	} else {
		pomo = color.New(color.BgRed, color.FgWhite).PrintfFunc()
	}
	pomo(" %7.2f ", t.cur)
	fmt.Println()
}

func clear() {
	cmd := exec.Command("clear")
	cmd.Stdout = os.Stdout
	cmd.Run()
	fmt.Println()
}

func printAndGetResult(keys map[rune]string, t *txn) int {
	kvs := make([]kv, 0, len(keys))
	for k, opt := range keys {
		kvs = append(kvs, kv{k, opt})
	}
	sort.Sort(byVal(kvs))
	for _, kv := range kvs {
		fmt.Printf("%q: %s\n", kv.key, kv.val)
	}

	r := make([]byte, 1)
	os.Stdin.Read(r)
	ch := rune(r[0])
	if ch == 'b' {
		return -1
	}
	if ch == 'q' {
		return 9999
	}
	if ch == rune(10) && len(t.to) > 0 {
		return 1
	}

	if opt, has := keys[ch]; has {
		t.to = opt
		return 1
	}
	return 0
}

func (p *parser) printTxn(t *txn, idx, total int) int {
	clear()
	printSummary(*t, idx, total)

	hits := p.topHits(t.desc)
	keys := generateKeyMap(hits)
	res := printAndGetResult(keys, t)
	if res != 0 {
		return res
	}

	clear()
	printSummary(*t, idx, total)
	res = printAndGetResult(allKeys, t)
	if res == 0 {
		return 1
	}
	return res
}

func (p *parser) showAndCategorizeTxns(txns []txn) {
	allKeys = generateKeyMap(p.classes)
	for {
		for i, t := range txns {
			printSummary(t, i, len(txns))
		}
		fmt.Println()

		fmt.Printf("Found %d transactions. Review (Y/n/q)? ", len(txns))
		b := make([]byte, 1)
		os.Stdin.Read(b)
		if b[0] == 'n' || b[0] == 'q' {
			return
		}

		for i := 0; i < len(txns) && i >= 0; {
			i += p.printTxn(&txns[i], i, len(txns))
		}
	}
}

func main() {
	singleCharMode()
	flag.Parse()
	data, err := ioutil.ReadFile(*journal)
	assert(err)
	alldata := includeAll(path.Dir(*journal), data)

	p := parser{data: alldata}
	p.parseAccounts()
	p.parseTransactions()

	// Scanning done. Now train classifier.
	p.generateClasses()

	in, err := ioutil.ReadFile(*csv)
	assert(err)
	txns := parseTransactionsFromCSV(in)

	p.showAndCategorizeTxns(txns)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"io/ioutil"
	"encoding/json"
	"strings"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/go-redis/redis"
	"strconv"
	"math"
	"regexp"
)

type device struct {
	// Gps_num float32 `json:"gps_num"`
	// App string `json:"app"`
	// Gps_alt float32 `json:"gps_alt"`
	// Fmt_opt int `json:"fmt_opt"`
	// Device string `json:"device"`
	// S_d2 float32 `json:"s_d2"`
	S_d0 float32 `json:"s_d0"`
	// S_d1 float32 `json:"s_d1"`
	S_h0 float32 `json:"s_h0"`
	SiteName string `json:"SiteName"`
	// Gps_fix float32 `json:"gps_fix"`
	// Ver_app string `json:"ver_app"`
	Gps_lat float64 `json:"gps_lat"`
	S_t0 float32 `json:"s_t0"`
	Timestamp string `json:"timestamp"`
	Gps_lon float64 `json:"gps_lon"`
	// Date string `json:"date"`
	// Tick float32 `json:"tick"`
	Device_id string `json:"device_id"`
	// S_1 float32 `json:"s_1"`
	// S_0 float32 `json:"s_0"`
	// S_3 float32 `json:"s_3"`
	// S_2 float32 `json:"s_2"`
	// Ver_format string `json:"ver_format"`
	// Time string `json:"time"`
}

type airbox struct {
	Source string `json:"source"`
	Feeds []device `json:"feeds"`
	Version string `json:"version"`
	Num_of_records int `json:"num_of_records"`
}

type subscribeid struct {
	Device_id []string `json:"device_id"`
	Sitename []string `json:"sitename"`
}

var bot *linebot.Client
var airbox_json airbox
var lass_json airbox
var maps_json airbox
var all_device []device
var history_json subscribeid
var	client=redis.NewClient(&redis.Options{
		Addr:"hipposerver.ddns.net:6379",
		Password:"",
		DB:0,
	})

func main() {
	url := "https://data.lass-net.org/data/last-all-airbox.json"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	errs := json.Unmarshal(body, &airbox_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-lass.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &lass_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-maps.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &maps_json)
	if errs != nil {
		fmt.Println(errs)
	}

	all_device=append(maps_json.Feeds,lass_json.Feeds...)
	all_device=append(all_device,airbox_json.Feeds...)

	url = "https://data.lass-net.org/data/airbox_list.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &history_json)
	if errs != nil {
		fmt.Println(errs)
	}
	// pushmessage()
	// fmt.Println(airbox_json)
	var err error
	bot, err = linebot.New(os.Getenv("ChannelSecret"), os.Getenv("ChannelAccessToken"))
	// _,_=bot.PushMessage("U3617adbdd46283d7e859f36302f4f471", linebot.NewTextMessage("hi!")).Do()
	log.Println("Bot:", bot, " err:", err)
	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)

}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	events, err := bot.ParseRequest(r)

	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}

	for _, event := range events {
		if event.Type == linebot.EventTypeMessage {
			switch message := event.Message.(type) {
			case *linebot.TextMessage:
				var txtmessage string
				inText := strings.ToLower(message.Text)
				if strings.Contains(inText,"訂閱"){
					userID:=event.Source.UserID
					// pong, _ := client.Ping().Result()
					// txtmessage=pong
					for i:=0; i<len(history_json.Device_id); i++ {
						if strings.Contains(inText,strings.ToLower(history_json.Device_id[i]))||strings.Contains(inText,history_json.Sitename[i]) {
							val, err:=client.Get(history_json.Device_id[i]).Result()
							if err!=nil{
								if strings.Contains(inText,"取消"){
									txtmessage="你並沒有訂閱此ID。"
									break
								}
								client.Set(history_json.Device_id[i],userID,0)
								txtmessage="訂閱成功!"
								break
							}
							if strings.Contains(inText,"取消"){
								stringSlice:=strings.Split(val,",")
								if stringInSlice(userID,stringSlice){
									if len(stringSlice)==1{
										err=client.Del(history_json.Device_id[i]).Err()
										if err!=nil{
											txtmessage=err.Error()
											break
										}
										txtmessage="取消訂閱成功!"
										break
									}else{
										var s []string
										s = removeStringInSlice(stringSlice, userID)
										client.Set(history_json.Device_id[i],s,0)
										txtmessage="取消訂閱成功!"
										break
									}
								}else{
									txtmessage="你並沒有訂閱此ID。"
									break
								}
							}
							stringSlice:=strings.Split(val,",")
							if stringInSlice(userID,stringSlice){
								txtmessage="您已訂閱過此ID!"
								break
							} else{
								val=val+","+userID
								client.Set(history_json.Device_id[i],val,0)
								txtmessage="訂閱成功!"
								break
							}
						}
					}
				} else if strings.Contains(inText,"門檻值"){
					// 新增門檻值
					userID:=event.Source.UserID
					client2:=redis.NewClient(&redis.Options{
							Addr:"hipposerver.ddns.net:6379",
							Password:"",
							DB:1,
					})
					re:=regexp.MustCompile("[0-9]+")
					number:=re.FindAllString(inText,-1)
					threshold:=number[len(number)-1]
					val, err:=client2.Get(userID).Result()
					if err!=nil{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值設為"+strconv.Itoa(threshold)+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					} else{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值從"+val+"改為"+strconv.Itoa(threshold)+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					}
				} else{
					for i:=0; i<len(all_device); i++ {
						if strings.Contains(inText,strings.ToLower(all_device[i].Device_id)) {
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						} else if len(all_device[i].SiteName)!=0 && strings.Contains(inText,all_device[i].SiteName){
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						}
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉! 這個AirBox ID不存在或是不提供即時資訊查詢。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			case *linebot.LocationMessage:
				var txtmessage string
				lat := message.Latitude
				lon := message.Longitude
				minD := math.MaxFloat64
				for i:=0; i<len(all_device); i++ {
					if all_device[i].Gps_lat<=lat+0.05 && all_device[i].Gps_lat>=lat-0.05 && all_device[i].Gps_lon<=lon+0.05 && all_device[i].Gps_lon>=lon-0.05{
						D:=distanceInKmBetweenEarthCoordinates(lat, lon, all_device[i].Gps_lat, all_device[i].Gps_lon)
						if D<minD{
							minD=D
							txtmessage="離您所提供的位置最近的AirBox資訊如以下所示：\n"
							txtmessage=txtmessage+"Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
						}
					} else{
						continue
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉這附近1公里內沒有任何上線的AirBox。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			}
		}
	}
}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func removeStringInSlice(s []string, r string) []string {
    for i, v := range s {
        if v == r {
            return append(s[:i], s[i+1:]...)
        }
    }
    return s
}

func degreesToRadians(degrees float64) float64 {
  return degrees * math.Pi / 180;
}

func distanceInKmBetweenEarthCoordinates(lat1 float64, lon1 float64, lat2 float64, lon2 float64) float64 {
  var earthRadiusKm = 6371.0;

  var dLat = degreesToRadians(lat2-lat1);
  var dLon = degreesToRadians(lon2-lon1);

  lat1 = degreesToRadians(lat1);
  lat2 = degreesToRadians(lat2);

  var a = math.Sin(dLat/2) * math.Sin(dLat/2) +
          math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2);
  var c = 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a));
  return earthRadiusKm * c;
}

<commit_msg>resolve type errors<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"io/ioutil"
	"encoding/json"
	"strings"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/go-redis/redis"
	"strconv"
	"math"
	"regexp"
)

type device struct {
	// Gps_num float32 `json:"gps_num"`
	// App string `json:"app"`
	// Gps_alt float32 `json:"gps_alt"`
	// Fmt_opt int `json:"fmt_opt"`
	// Device string `json:"device"`
	// S_d2 float32 `json:"s_d2"`
	S_d0 float32 `json:"s_d0"`
	// S_d1 float32 `json:"s_d1"`
	S_h0 float32 `json:"s_h0"`
	SiteName string `json:"SiteName"`
	// Gps_fix float32 `json:"gps_fix"`
	// Ver_app string `json:"ver_app"`
	Gps_lat float64 `json:"gps_lat"`
	S_t0 float32 `json:"s_t0"`
	Timestamp string `json:"timestamp"`
	Gps_lon float64 `json:"gps_lon"`
	// Date string `json:"date"`
	// Tick float32 `json:"tick"`
	Device_id string `json:"device_id"`
	// S_1 float32 `json:"s_1"`
	// S_0 float32 `json:"s_0"`
	// S_3 float32 `json:"s_3"`
	// S_2 float32 `json:"s_2"`
	// Ver_format string `json:"ver_format"`
	// Time string `json:"time"`
}

type airbox struct {
	Source string `json:"source"`
	Feeds []device `json:"feeds"`
	Version string `json:"version"`
	Num_of_records int `json:"num_of_records"`
}

type subscribeid struct {
	Device_id []string `json:"device_id"`
	Sitename []string `json:"sitename"`
}

var bot *linebot.Client
var airbox_json airbox
var lass_json airbox
var maps_json airbox
var all_device []device
var history_json subscribeid
var	client=redis.NewClient(&redis.Options{
		Addr:"hipposerver.ddns.net:6379",
		Password:"",
		DB:0,
	})

func main() {
	url := "https://data.lass-net.org/data/last-all-airbox.json"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	errs := json.Unmarshal(body, &airbox_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-lass.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &lass_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-maps.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &maps_json)
	if errs != nil {
		fmt.Println(errs)
	}

	all_device=append(maps_json.Feeds,lass_json.Feeds...)
	all_device=append(all_device,airbox_json.Feeds...)

	url = "https://data.lass-net.org/data/airbox_list.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &history_json)
	if errs != nil {
		fmt.Println(errs)
	}
	// pushmessage()
	// fmt.Println(airbox_json)
	var err error
	bot, err = linebot.New(os.Getenv("ChannelSecret"), os.Getenv("ChannelAccessToken"))
	// _,_=bot.PushMessage("U3617adbdd46283d7e859f36302f4f471", linebot.NewTextMessage("hi!")).Do()
	log.Println("Bot:", bot, " err:", err)
	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)

}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	events, err := bot.ParseRequest(r)

	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}

	for _, event := range events {
		if event.Type == linebot.EventTypeMessage {
			switch message := event.Message.(type) {
			case *linebot.TextMessage:
				var txtmessage string
				inText := strings.ToLower(message.Text)
				if strings.Contains(inText,"訂閱"){
					userID:=event.Source.UserID
					// pong, _ := client.Ping().Result()
					// txtmessage=pong
					for i:=0; i<len(history_json.Device_id); i++ {
						if strings.Contains(inText,strings.ToLower(history_json.Device_id[i]))||strings.Contains(inText,history_json.Sitename[i]) {
							val, err:=client.Get(history_json.Device_id[i]).Result()
							if err!=nil{
								if strings.Contains(inText,"取消"){
									txtmessage="你並沒有訂閱此ID。"
									break
								}
								client.Set(history_json.Device_id[i],userID,0)
								txtmessage="訂閱成功!"
								break
							}
							if strings.Contains(inText,"取消"){
								stringSlice:=strings.Split(val,",")
								if stringInSlice(userID,stringSlice){
									if len(stringSlice)==1{
										err=client.Del(history_json.Device_id[i]).Err()
										if err!=nil{
											txtmessage=err.Error()
											break
										}
										txtmessage="取消訂閱成功!"
										break
									}else{
										var s []string
										s = removeStringInSlice(stringSlice, userID)
										client.Set(history_json.Device_id[i],s,0)
										txtmessage="取消訂閱成功!"
										break
									}
								}else{
									txtmessage="你並沒有訂閱此ID。"
									break
								}
							}
							stringSlice:=strings.Split(val,",")
							if stringInSlice(userID,stringSlice){
								txtmessage="您已訂閱過此ID!"
								break
							} else{
								val=val+","+userID
								client.Set(history_json.Device_id[i],val,0)
								txtmessage="訂閱成功!"
								break
							}
						}
					}
				} else if strings.Contains(inText,"門檻值"){
					// 新增門檻值
					userID:=event.Source.UserID
					client2:=redis.NewClient(&redis.Options{
							Addr:"hipposerver.ddns.net:6379",
							Password:"",
							DB:1,
					})
					re:=regexp.MustCompile("[0-9]+")
					number:=re.FindAllString(inText,-1)
					threshold:=number[len(number)-1]
					val, err:=client2.Get(userID).Result()
					if err!=nil{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值設為"+threshold+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					} else{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值從"+val+"改為"+threshold+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					}
				} else{
					for i:=0; i<len(all_device); i++ {
						if strings.Contains(inText,strings.ToLower(all_device[i].Device_id)) {
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						} else if len(all_device[i].SiteName)!=0 && strings.Contains(inText,all_device[i].SiteName){
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						}
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉! 這個AirBox ID不存在或是不提供即時資訊查詢。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			case *linebot.LocationMessage:
				var txtmessage string
				lat := message.Latitude
				lon := message.Longitude
				minD := math.MaxFloat64
				for i:=0; i<len(all_device); i++ {
					if all_device[i].Gps_lat<=lat+0.05 && all_device[i].Gps_lat>=lat-0.05 && all_device[i].Gps_lon<=lon+0.05 && all_device[i].Gps_lon>=lon-0.05{
						D:=distanceInKmBetweenEarthCoordinates(lat, lon, all_device[i].Gps_lat, all_device[i].Gps_lon)
						if D<minD{
							minD=D
							txtmessage="離您所提供的位置最近的AirBox資訊如以下所示：\n"
							txtmessage=txtmessage+"Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
						}
					} else{
						continue
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉這附近1公里內沒有任何上線的AirBox。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			}
		}
	}
}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func removeStringInSlice(s []string, r string) []string {
    for i, v := range s {
        if v == r {
            return append(s[:i], s[i+1:]...)
        }
    }
    return s
}

func degreesToRadians(degrees float64) float64 {
  return degrees * math.Pi / 180;
}

func distanceInKmBetweenEarthCoordinates(lat1 float64, lon1 float64, lat2 float64, lon2 float64) float64 {
  var earthRadiusKm = 6371.0;

  var dLat = degreesToRadians(lat2-lat1);
  var dLon = degreesToRadians(lon2-lon1);

  lat1 = degreesToRadians(lat1);
  lat2 = degreesToRadians(lat2);

  var a = math.Sin(dLat/2) * math.Sin(dLat/2) +
          math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2);
  var c = 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a));
  return earthRadiusKm * c;
}

<|endoftext|>


<commit_before>package main

import (
	"encoding/xml"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/tools/blog/atom"

	"github.com/ChimeraCoder/anaconda"
	"github.com/joho/godotenv"
)

const (
	MaxStatuses = 200
	MaxEntries  = 100
)

type atomEntrySlice []*atom.Entry

func (a atomEntrySlice) Len() int {
	return len(a)
}

func (a atomEntrySlice) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, string(a[i].Updated))
	if err != nil {
		return false
	}
	jt, err := time.Parse(time.RFC3339, string(a[j].Updated))
	if err != nil {
		return false
	}
	return it.After(jt)
}

func (a atomEntrySlice) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func main() {
	log.SetFlags(log.Lshortfile)

	if len(os.Args) > 0 {
		if err := godotenv.Load(os.Args...); err != nil {
			log.Fatal(err)
		}
	}

	anaconda.SetConsumerKey(os.Getenv("TWITTER_CONSUMER_KEY"))
	anaconda.SetConsumerSecret(os.Getenv("TWITTER_CONSUMER_SECRET"))

	api := anaconda.NewTwitterApi(os.Getenv("TWITTER_OAUTH_TOKEN"), os.Getenv("TWITTER_OAUTH_TOKEN_SECRET"))
	defer api.Close()

	feedUrl := os.Getenv("FEED_URL")
	if feedUrl == "" {
		log.Fatal("FEED_URL is required.")
	}
	template := os.Getenv("TEMPLATE")
	if template == "" {
		log.Fatal("TEMPLATE is required.")
	}

	userId := strings.SplitN(os.Getenv("TWITTER_OAUTH_TOKEN"), "-", 2)[0]
	if _, err := strconv.ParseInt(userId, 10, 64); err != nil {
		log.Fatal(err)
	}

	recentUrls := make(map[string]struct{})
	{
		v := url.Values{}
		v.Set("user_id", userId)
		v.Set("count", strconv.Itoa(MaxStatuses))
		timeline, err := api.GetUserTimeline(v)
		if err != nil {
			log.Fatal(err)
		}

		for _, status := range timeline {
			for _, url := range status.Entities.Urls {
				recentUrls[url.Expanded_url] = struct{}{}
			}
		}
	}

	var atom atom.Feed
	{
		resp, err := http.Get(feedUrl)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}

		if err := xml.Unmarshal(body, &atom); err != nil {
			log.Fatal(err)
		}

	}

	{
		t := time.Now().AddDate(0, 0, -1)

		entries := atomEntrySlice(atom.Entry)
		sort.Sort(sort.Reverse(entries))
		n := 0
		if len(entries) > MaxEntries {
			n = len(entries) - MaxEntries
		}
	entries:
		for _, entry := range entries[n:] {
			if len(entry.Link) == 0 {
				continue
			}
			link := entry.Link[0]
			if _, ok := recentUrls[link.Href]; ok {
				continue
			}

			if published, err := time.Parse(time.RFC3339, string(entry.Published)); err == nil {
				if !published.After(t) {
					continue
				}
			} else if updated, err := time.Parse(time.RFC3339, string(entry.Updated)); err == nil {
				if !updated.After(t) {
					continue
				}
			}

			replacer := strings.NewReplacer("{title}", entry.Title, "{url}", link.Href)
			text := replacer.Replace(template)
			if _, err := api.PostTweet(text, url.Values{}); err != nil {
				if apiErr, ok := err.(*anaconda.ApiError); ok {
					for _, err := range apiErr.Decoded.Errors {
						if err.Code == anaconda.TwitterErrorStatusIsADuplicate {
							continue entries
						}
					}
					log.Fatal(apiErr)
				} else {
					log.Fatal(err)
				}
			}
		}
	}
}
<commit_msg>Extend TLSHandshakeTimeout<commit_after>package main

import (
	"encoding/xml"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/tools/blog/atom"

	"github.com/ChimeraCoder/anaconda"
	"github.com/joho/godotenv"
)

const (
	MaxStatuses = 200
	MaxEntries  = 100
)

type atomEntrySlice []*atom.Entry

func (a atomEntrySlice) Len() int {
	return len(a)
}

func (a atomEntrySlice) Less(i, j int) bool {
	it, err := time.Parse(time.RFC3339, string(a[i].Updated))
	if err != nil {
		return false
	}
	jt, err := time.Parse(time.RFC3339, string(a[j].Updated))
	if err != nil {
		return false
	}
	return it.After(jt)
}

func (a atomEntrySlice) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func main() {
	log.SetFlags(log.Lshortfile)

	if transport, ok := http.DefaultTransport.(*http.Transport); ok {
		transport.TLSHandshakeTimeout = 30 * time.Second
	} else {
		log.Fatal("Change TLSHandshakeTimeout failed")
	}

	if len(os.Args) > 0 {
		if err := godotenv.Load(os.Args...); err != nil {
			log.Fatal(err)
		}
	}

	anaconda.SetConsumerKey(os.Getenv("TWITTER_CONSUMER_KEY"))
	anaconda.SetConsumerSecret(os.Getenv("TWITTER_CONSUMER_SECRET"))

	api := anaconda.NewTwitterApi(os.Getenv("TWITTER_OAUTH_TOKEN"), os.Getenv("TWITTER_OAUTH_TOKEN_SECRET"))
	defer api.Close()

	feedUrl := os.Getenv("FEED_URL")
	if feedUrl == "" {
		log.Fatal("FEED_URL is required.")
	}
	template := os.Getenv("TEMPLATE")
	if template == "" {
		log.Fatal("TEMPLATE is required.")
	}

	userId := strings.SplitN(os.Getenv("TWITTER_OAUTH_TOKEN"), "-", 2)[0]
	if _, err := strconv.ParseInt(userId, 10, 64); err != nil {
		log.Fatal(err)
	}

	recentUrls := make(map[string]struct{})
	{
		v := url.Values{}
		v.Set("user_id", userId)
		v.Set("count", strconv.Itoa(MaxStatuses))
		timeline, err := api.GetUserTimeline(v)
		if err != nil {
			log.Fatal(err)
		}

		for _, status := range timeline {
			for _, url := range status.Entities.Urls {
				recentUrls[url.Expanded_url] = struct{}{}
			}
		}
	}

	var atom atom.Feed
	{
		resp, err := http.Get(feedUrl)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}

		if err := xml.Unmarshal(body, &atom); err != nil {
			log.Fatal(err)
		}

	}

	{
		t := time.Now().AddDate(0, 0, -1)

		entries := atomEntrySlice(atom.Entry)
		sort.Sort(sort.Reverse(entries))
		n := 0
		if len(entries) > MaxEntries {
			n = len(entries) - MaxEntries
		}
	entries:
		for _, entry := range entries[n:] {
			if len(entry.Link) == 0 {
				continue
			}
			link := entry.Link[0]
			if _, ok := recentUrls[link.Href]; ok {
				continue
			}

			if published, err := time.Parse(time.RFC3339, string(entry.Published)); err == nil {
				if !published.After(t) {
					continue
				}
			} else if updated, err := time.Parse(time.RFC3339, string(entry.Updated)); err == nil {
				if !updated.After(t) {
					continue
				}
			}

			replacer := strings.NewReplacer("{title}", entry.Title, "{url}", link.Href)
			text := replacer.Replace(template)
			if _, err := api.PostTweet(text, url.Values{}); err != nil {
				if apiErr, ok := err.(*anaconda.ApiError); ok {
					for _, err := range apiErr.Decoded.Errors {
						if err.Code == anaconda.TwitterErrorStatusIsADuplicate {
							continue entries
						}
					}
					log.Fatal(apiErr)
				} else {
					log.Fatal(err)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/ogier/pflag"
	"github.com/yuya-takeyama/argf"
)

var (
	name    = "rl"
	version = "0.3.0"

	flag      = pflag.NewFlagSet(name, pflag.ContinueOnError)
	delimiter = flag.StringP("delimiter", "d", "", "")
	isHelp    = flag.BoolP("help", "h", false, "")
	isVersion = flag.BoolP("version", "v", false, "")
)

func printUsage() {
	fmt.Fprintf(os.Stderr, `
Usage: %s [OPTION]... [FILE]...
Reverse lines of FILE(s), or standard input.

Options:
  -d, --delimiter=DELIM    delimit lines by DELIM
  -h, --help               display this help text and exit
  -v, --version            output version information and exit
`[1:], name)
}

func printVersion() {
	fmt.Fprintln(os.Stderr, version)
}

func printErr(err error) {
	fmt.Fprintf(os.Stderr, "%s: %s\n", name, err)
}

func guideToHelp() {
	fmt.Fprintf(os.Stderr, "Try '%s --help' for more information.\n", name)
}

func do(rev *Reverser, r io.Reader) error {
	b := bufio.NewScanner(r)
	for b.Scan() {
		fmt.Println(rev.Reverse(b.Text()))
	}
	return b.Err()
}

func _main() int {
	flag.SetOutput(ioutil.Discard)
	if err := flag.Parse(os.Args[1:]); err != nil {
		printErr(err)
		guideToHelp()
		return 2
	}
	switch {
	case *isHelp:
		printUsage()
		return 0
	case *isVersion:
		printVersion()
		return 0
	}

	r, err := argf.From(flag.Args())
	if err != nil {
		printErr(err)
		return 2
	}
	rev := NewReverser()
	rev.SetDelimiter(*delimiter)
	if err = do(rev, r); err != nil {
		printErr(err)
		return 1
	}
	return 0
}

func main() {
	e := _main()
	os.Exit(e)
}
<commit_msg>Upgrade to release<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/ogier/pflag"
	"github.com/yuya-takeyama/argf"
)

var (
	name    = "rl"
	version = "0.3.1"

	flag      = pflag.NewFlagSet(name, pflag.ContinueOnError)
	delimiter = flag.StringP("delimiter", "d", "", "")
	isHelp    = flag.BoolP("help", "h", false, "")
	isVersion = flag.BoolP("version", "v", false, "")
)

func printUsage() {
	fmt.Fprintf(os.Stderr, `
Usage: %s [OPTION]... [FILE]...
Reverse lines of FILE(s), or standard input.

Options:
  -d, --delimiter=DELIM    delimit lines by DELIM
  -h, --help               display this help text and exit
  -v, --version            output version information and exit
`[1:], name)
}

func printVersion() {
	fmt.Fprintln(os.Stderr, version)
}

func printErr(err error) {
	fmt.Fprintf(os.Stderr, "%s: %s\n", name, err)
}

func guideToHelp() {
	fmt.Fprintf(os.Stderr, "Try '%s --help' for more information.\n", name)
}

func do(rev *Reverser, r io.Reader) error {
	b := bufio.NewScanner(r)
	for b.Scan() {
		fmt.Println(rev.Reverse(b.Text()))
	}
	return b.Err()
}

func _main() int {
	flag.SetOutput(ioutil.Discard)
	if err := flag.Parse(os.Args[1:]); err != nil {
		printErr(err)
		guideToHelp()
		return 2
	}
	switch {
	case *isHelp:
		printUsage()
		return 0
	case *isVersion:
		printVersion()
		return 0
	}

	r, err := argf.From(flag.Args())
	if err != nil {
		printErr(err)
		return 2
	}
	rev := NewReverser()
	rev.SetDelimiter(*delimiter)
	if err = do(rev, r); err != nil {
		printErr(err)
		return 1
	}
	return 0
}

func main() {
	e := _main()
	os.Exit(e)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"os"

	"github.com/yuya-takeyama/argf"
)

func printErr(err error) {
	fmt.Fprintln(os.Stderr, "rl:", err)
}

func reverse(s string) string {
	a := []rune(s)
	b := make([]rune, len(a))
	for i := 0; i < len(a); i++ {
		b[len(a)-i-1] = a[i]
	}
	return string(b)
}

func do(r io.Reader) error {
	b := bufio.NewScanner(r)
	for b.Scan() {
		fmt.Println(reverse(b.Text()))
	}
	return b.Err()
}

func _main() int {
	r, err := argf.From(os.Args[1:])
	if err != nil {
		printErr(err)
		return 2
	}

	if err = do(r); err != nil {
		printErr(err)
		return 1
	}
	return 0
}

func main() {
	e := _main()
	os.Exit(e)
}
<commit_msg>Remove reverse<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"os"

	"github.com/yuya-takeyama/argf"
)

func printErr(err error) {
	fmt.Fprintln(os.Stderr, "rl:", err)
}

func do(r io.Reader) error {
	b := bufio.NewScanner(r)
	for b.Scan() {
		fmt.Println(reverse(b.Text()))
	}
	return b.Err()
}

func _main() int {
	r, err := argf.From(os.Args[1:])
	if err != nil {
		printErr(err)
		return 2
	}

	if err = do(r); err != nil {
		printErr(err)
		return 1
	}
	return 0
}

func main() {
	e := _main()
	os.Exit(e)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"github.com/yhat/scrape"
	"golang.org/x/net/html"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"
)

type space struct {
	Name, Status, Updated, Available string
}

type structure struct {
	Name, URLCode string
	Number        int
	Spaces        []space
}

func main() {

	c := make(chan structure)
	go getStructures(c)
	for i := range c {
		writeData(i)
	}

}

func getStructures(c chan<- structure) {
	for i := 1; i < 9; i++ {
		if i == 7 || i == 4 {
			continue
		}

		s := structure{
			Name:    "Structure" + strconv.Itoa(i),
			Number:  i,
			URLCode: strconv.Itoa(i + 88),
		}
		s.Spaces = s.getSpaces()

		c <- s
	}
	close(c)
}

func (s structure) getSpaces() []space {

	spaces := []space{
		space{Name: "WSU Permit"},
		space{Name: "Student OneCard"},
		space{Name: "Visitor"},
	}
	re := map[string]*regexp.Regexp{
		"avail":   regexp.MustCompile(`[0-9]+`),
		"status":  regexp.MustCompile(`(OPEN|CLOSED|FULL)`),
		"updated": regexp.MustCompile(`(?P<1>^.+: )(?P<2>.+)`),
	}

	// Request
	client := &http.Client{}
	req, err := http.NewRequest("GET", "http://m.wayne.edu/parking.php?location="+s.URLCode, nil)
	if err != nil {
		log.Fatalln("Error making request:", err)
	}
	req.Header.Set("User-Agent", "Apple-iPhone6C1/")

	// Response
	resp, err := client.Do(req)
	if err != nil {
		log.Fatalln("Error sending request:", err)
	}
	defer resp.Body.Close()

	body, err := html.Parse(resp.Body)
	if err != nil {
		log.Fatalln("Error parsing response body:", err)
	}

	// Parse relevant response data
	dataString, _ := scrape.Find(body, scrape.ByClass("available"))
	lastUpdated, _ := scrape.Find(body, scrape.ByClass("last_updated"))

	avail := re["avail"].FindAllString(scrape.Text(dataString), -1)
	if len(avail) == 0 {
		avail = []string{"0", "0", "0"}
	}
	status := re["status"].FindAllString(scrape.Text(dataString), -1)
	updated := re["updated"].FindStringSubmatch(scrape.Text(lastUpdated))[2]

	for key := range spaces {
		spaces[key].Available = avail[key]
		spaces[key].Status = status[key]
		spaces[key].Updated = updated
	}

	return spaces

}

func writeData(s structure) {

	file, err := os.OpenFile("/home/dsifford/Dropbox/ParkingData/"+s.Name+".csv", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		log.Fatalln("CSV file could not be created or opened:", err)
	}
	defer file.Close()

	writer := csv.NewWriter(file)

	if stat, _ := file.Stat(); stat.Size() == 0 {
		writer.Write([]string{"Updated", "Type", "Status", "Spaces Available"})
	}

	for _, sp := range s.Spaces {
		writer.Write([]string{sp.Updated, sp.Name, sp.Status, sp.Available})
	}
	writer.Flush()

}
<commit_msg>Closes #2<commit_after>package main

import (
	"encoding/csv"
	"errors"
	"github.com/yhat/scrape"
	"golang.org/x/net/html"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"
)

type space struct {
	Name, Status, Updated, Available string
}

type structure struct {
	Name, URLCode string
	Number        int
	Spaces        []space
}

func main() {

	c := make(chan structure)
	go getStructures(c)
	for i := range c {
		writeData(i)
	}

}

func getStructures(c chan<- structure) {
	for i := 1; i < 9; i++ {
		if i == 7 || i == 4 {
			continue
		}

		s := structure{
			Name:    "Structure" + strconv.Itoa(i),
			Number:  i,
			URLCode: strconv.Itoa(i + 88),
		}

		// Attempt the request a total of 5 times
		var err error
		for j := 0; j < 5; j++ {
			s.Spaces, err = s.getSpaces()
			if err != nil {
				continue
			}
			break
		}

		c <- s
	}
	close(c)
}

func (s structure) getSpaces() ([]space, error) {

	spaces := []space{
		space{Name: "WSU Permit"},
		space{Name: "Student OneCard"},
		space{Name: "Visitor"},
	}
	re := map[string]*regexp.Regexp{
		"avail":   regexp.MustCompile(`[0-9]+`),
		"status":  regexp.MustCompile(`(OPEN|CLOSED|FULL)`),
		"updated": regexp.MustCompile(`(?P<1>^.+: )(?P<2>.+)`),
	}

	// Request
	client := &http.Client{}
	req, err := http.NewRequest("GET", "http://m.wayne.edu/parking.php?location="+s.URLCode, nil)
	if err != nil {
		return spaces, errors.New("Request failed")
	}
	req.Header.Set("User-Agent", "Apple-iPhone6C1/")

	// Response
	resp, err := client.Do(req)
	if err != nil {
		return spaces, errors.New("Response failed")
	}
	defer resp.Body.Close()

	body, err := html.Parse(resp.Body)
	if err != nil {
		return spaces, errors.New("Error parsing response body")
	}

	// Parse relevant response data
	dataString, _ := scrape.Find(body, scrape.ByClass("available"))
	lastUpdated, _ := scrape.Find(body, scrape.ByClass("last_updated"))

	avail := re["avail"].FindAllString(scrape.Text(dataString), -1)
	if len(avail) == 0 {
		avail = []string{"0", "0", "0"}
	}
	status := re["status"].FindAllString(scrape.Text(dataString), -1)
	updated := re["updated"].FindStringSubmatch(scrape.Text(lastUpdated))[2]

	for key := range spaces {
		spaces[key].Available = avail[key]
		spaces[key].Status = status[key]
		spaces[key].Updated = updated
	}

	return spaces, nil

}

func writeData(s structure) {

	file, err := os.OpenFile("/home/dsifford/Dropbox/ParkingData/"+s.Name+".csv", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		log.Fatalln("CSV file could not be created or opened:", err)
	}
	defer file.Close()

	writer := csv.NewWriter(file)

	if stat, _ := file.Stat(); stat.Size() == 0 {
		writer.Write([]string{"Updated", "Type", "Status", "Spaces Available"})
	}

	for _, sp := range s.Spaces {
		writer.Write([]string{sp.Updated, sp.Name, sp.Status, sp.Available})
	}
	writer.Flush()

}
<|endoftext|>


<commit_before>// main.go
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello World!")
}
<commit_msg>added import comment<commit_after>package main // import "gcp-rover"

import (
	"fmt"
)

func main() {
	fmt.Println("Hello World!")
}
<|endoftext|>