<commit_before>package main

import (
	"context"
	"os"
	"strings"

	"github.com/cucumber/godog"
	"github.com/cucumber/godog/colors"
	pb "github.com/ii/xds-test-harness/api/adapter"
	"github.com/ii/xds-test-harness/internal/runner"
	"github.com/kylelemons/go-gypsy/yaml"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/pflag"
)

var (
	debug          = pflag.BoolP("debug", "D", false, "sets log level to debug")
	config         = pflag.StringP("config", "C", "", "Path to optional config file. This file sets the adapter and target addresses and supported variants.")
	adapterAddress = pflag.StringP("adapter", "A", ":17000", "port of adapter on target")
	targetAddress  = pflag.StringP("target", "T", ":18000", "port of xds target to test")
	nodeID         = pflag.StringP("nodeID", "N", "test-id", "node id of target")
	variant        = pflag.StringArrayP("variant", "V", []string{"sotw non-aggregated", "sotw aggregated", "incremental non-aggregated", "incremental aggregated"}, "xDS protocol variant your server supports. Add a separate flag per each supported variant.\n Possibleariants are: sotw non-aggregated\n, sotw aggregated\n, incremental non-aggregated\n, incremental aggregated\n.")
	aggregated     = false
	incremental    = false

	godogOpts = godog.Options{
		ShowStepDefinitions: false,
		Randomize:           0,
		StopOnFailure:       false,
		Strict:              false,
		NoColors:            false,
		Tags:                "",
		Format:              "",
		Concurrency:         0,
		Paths:               []string{},
		Output:              colors.Colored(os.Stdout),
	}

	r *runner.Runner
)

func init() {
	godog.BindCommandLineFlags("godog.", &godogOpts)
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
	zerolog.SetGlobalLevel(zerolog.InfoLevel)
}

func InitializeTestSuite(sc *godog.TestSuiteContext) {

	sc.BeforeSuite(func() {
		r = runner.FreshRunner()
		r.NodeID = *nodeID
		r.Aggregated = aggregated
		if err := r.ConnectClient("target", *targetAddress); err != nil {
			log.Fatal().
				Msgf("error connecting to target: %v", err)
		}
		if err := r.ConnectClient("adapter", *adapterAddress); err != nil {
			log.Fatal().
				Msgf("error connecting to adapter: %v", err)
		}
		log.Info().
			Msgf("Connected to target at %s and adapter at %s\n", *targetAddress, *adapterAddress)

		if r.Aggregated {
			log.Info().
				Msgf("Tests will be run via ADS")
		} else {
			log.Info().
				Msgf("Tests will be run non-aggregated, via separate streams")
		}
	})
}

func InitializeScenario(ctx *godog.ScenarioContext) {
	ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
		log.Debug().Msg("Fresh Runner!")
		r = runner.FreshRunner(r)
		return ctx, nil
	})
	ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
		c := pb.NewAdapterClient(r.Adapter.Conn)
		clearRequest := &pb.ClearRequest{
			Node: r.NodeID,
		}
		clear, err := c.ClearState(context.Background(), clearRequest)
		if err != nil {
			log.Err(err).
				Msg("Couldn't clear state")
		}
		log.Debug().
			Msgf("Clearing state...%v", clear.Response)
		return ctx, nil
	})
	r.LoadSteps(ctx)
}

func parseSupportedVariants(variants []string) (err error, supported map[string]bool) {
	supported = make(map[string]bool)
	for _, variant := range variants {
		variant = strings.ToLower(strings.TrimSpace(variant))
		switch variant {
		case "sotw non-aggregated":
			supported["sotw non-aggregated"] = true
		case "sotw aggregated":
			supported["sotw aggregated"] = true
		case "incremental non-aggregated":
			supported["incremental non-aggregated"] = true
		case "incremental aggregated":
			supported["incremental aggregated"] = true
		default:
			log.Info().Msgf("Cannot recognize variant: %v\nWe support:\nsotw non-aggregated\nsotw aggregated\nincremental non-aggregated\nincremental aggregated\n", variant)
		}
	}
	return nil, supported
}

func combineTags(godogTags string, customTags []string) (tags string) {
	if godogTags != "" {
		customTags = append(customTags, godogTags)
	}
	tags = strings.Join(customTags, " && ")
	return tags
}

func valuesFromConfig(config string) (target string, adapter string, nodeID string, supportedVariants map[string]bool) {
	c, err := yaml.ReadFile(config)
	if err != nil {
		log.Fatal().Msgf("Cannot read config: %v", config)
	}
	nodeID, err = c.Get("nodeID")
	if err != nil {
		log.Fatal().Msgf("Error reading config file for Node ID: %v\n", err)
	}
	target, err = c.Get("targetAddress")
	if err != nil {
		log.Fatal().Msgf("Error reading config file for Target Address: %v\n", config, err)
	}
	adapter, err = c.Get("adapterAddress")
	if err != nil {
		log.Info().Msgf("Cannot get adapter address from config file: %v\n", err)
	}
	v, err := yaml.Child(c.Root, "variants")
	if err != nil {
		log.Fatal().Msgf("Error getting variants from config: %v\n", err)
	}
	variants := []string{}
	varsInYaml, ok := v.(yaml.List)
	if ok {
		for i := 0; i < varsInYaml.Len(); i++ {
			node := varsInYaml.Item(i)
			variant := string(node.(yaml.Scalar))
			variants = append(variants, variant)
		}
	}
	err, supportedVariants = parseSupportedVariants(variants)
	if err != nil {
		log.Fatal().Msgf("Cannot parse supported variants from config: %v", err)
	}
	return target, adapter, nodeID, supportedVariants
}

func main() {
	pflag.Parse()
	godogOpts.Paths = pflag.Args()
	godogTags := godogOpts.Tags
	supportedVariants := make(map[string]bool)

	if *debug {
		zerolog.SetGlobalLevel(zerolog.DebugLevel)
	}

	// default to using CLI Flag for settings
	err, supportedVariants := parseSupportedVariants(*variant)
	if err != nil {
		log.Fatal().Msgf("Cannot parse variants from CLI: %v\n", err)
	}

	// If config present, use it for all non-debugging values
	if *config != ""{
		*targetAddress, *adapterAddress, *nodeID, supportedVariants = valuesFromConfig(*config)
	}

	suite := godog.TestSuite{
		Name:                 "xDS Test Suite",
		ScenarioInitializer:  InitializeScenario,
		TestSuiteInitializer: InitializeTestSuite,
		Options:              &godogOpts,
	}

	if supportedVariants["sotw non-aggregated"] {
		incremental = false
		aggregated = false
		customTags := []string{"@sotw", "@non-aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		suite.Run()
	}

	if supportedVariants["sotw aggregated"] {
		incremental = false
		aggregated = true
		customTags := []string{"@sotw", "@aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		suite.Run()
	}

	if supportedVariants["incremental non-aggregated"] {
		incremental = true
		aggregated = false
		customTags := []string{"@incremental", "@non-aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		suite.Run()
	}

	if supportedVariants["incremental aggregated"] {
		incremental = true
		aggregated = true
		customTags := []string{"@incremental", "@aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		suite.Run()
	}
	os.Exit(0)
}
<commit_msg>output to json for each run of test suite<commit_after>package main

import (
	"context"
	"os"
	"strings"

	"github.com/cucumber/godog"
	"github.com/cucumber/godog/colors"
	pb "github.com/ii/xds-test-harness/api/adapter"
	"github.com/ii/xds-test-harness/internal/runner"
	"github.com/kylelemons/go-gypsy/yaml"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/spf13/pflag"
)

var (
	debug          = pflag.BoolP("debug", "D", false, "sets log level to debug")
	config         = pflag.StringP("config", "C", "", "Path to optional config file. This file sets the adapter and target addresses and supported variants.")
	adapterAddress = pflag.StringP("adapter", "A", ":17000", "port of adapter on target")
	targetAddress  = pflag.StringP("target", "T", ":18000", "port of xds target to test")
	nodeID         = pflag.StringP("nodeID", "N", "test-id", "node id of target")
	variant        = pflag.StringArrayP("variant", "V", []string{"sotw non-aggregated", "sotw aggregated", "incremental non-aggregated", "incremental aggregated"}, "xDS protocol variant your server supports. Add a separate flag per each supported variant.\n Possibleariants are: sotw non-aggregated\n, sotw aggregated\n, incremental non-aggregated\n, incremental aggregated\n.")
	aggregated     = false
	incremental    = false

	godogOpts = godog.Options{
		ShowStepDefinitions: false,
		Randomize:           0,
		StopOnFailure:       false,
		Strict:              false,
		NoColors:            false,
		Tags:                "",
		Format:              "",
		Concurrency:         0,
		Paths:               []string{},
		Output:              colors.Colored(os.Stdout),
	}

	r *runner.Runner
)

func init() {
	godog.BindCommandLineFlags("godog.", &godogOpts)
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
	zerolog.SetGlobalLevel(zerolog.InfoLevel)
}

func InitializeTestSuite(sc *godog.TestSuiteContext) {
	sc.BeforeSuite(func() {
		r = runner.FreshRunner()
		r.NodeID = *nodeID
		r.Aggregated = aggregated
		if err := r.ConnectClient("target", *targetAddress); err != nil {
			log.Fatal().
				Msgf("error connecting to target: %v", err)
		}
		if err := r.ConnectClient("adapter", *adapterAddress); err != nil {
			log.Fatal().
				Msgf("error connecting to adapter: %v", err)
		}
		log.Info().
			Msgf("Connected to target at %s and adapter at %s\n", *targetAddress, *adapterAddress)

		if r.Aggregated {
			log.Info().
				Msgf("Tests will be run via ADS")
		} else {
			log.Info().
				Msgf("Tests will be run non-aggregated, via separate streams")
		}
	})
}

func InitializeScenario(ctx *godog.ScenarioContext) {
	ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
		log.Debug().Msg("Fresh Runner!")
		r = runner.FreshRunner(r)
		return ctx, nil
	})
	ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
		c := pb.NewAdapterClient(r.Adapter.Conn)
		clearRequest := &pb.ClearRequest{
			Node: r.NodeID,
		}
		clear, err := c.ClearState(context.Background(), clearRequest)
		if err != nil {
			log.Err(err).
				Msg("Couldn't clear state")
		}
		log.Debug().
			Msgf("Clearing state...%v", clear.Response)
		return ctx, nil
	})
	r.LoadSteps(ctx)
}

func parseSupportedVariants(variants []string) (err error, supported map[string]bool) {
	supported = make(map[string]bool)
	for _, variant := range variants {
		variant = strings.ToLower(strings.TrimSpace(variant))
		switch variant {
		case "sotw non-aggregated":
			supported["sotw non-aggregated"] = true
		case "sotw aggregated":
			supported["sotw aggregated"] = true
		case "incremental non-aggregated":
			supported["incremental non-aggregated"] = true
		case "incremental aggregated":
			supported["incremental aggregated"] = true
		default:
			log.Info().Msgf("Cannot recognize variant: %v\nWe support:\nsotw non-aggregated\nsotw aggregated\nincremental non-aggregated\nincremental aggregated\n", variant)
		}
	}
	return nil, supported
}

func combineTags(godogTags string, customTags []string) (tags string) {
	if godogTags != "" {
		customTags = append(customTags, godogTags)
	}
	tags = strings.Join(customTags, " && ")
	return tags
}

func valuesFromConfig(config string) (target string, adapter string, nodeID string, supportedVariants map[string]bool) {
	c, err := yaml.ReadFile(config)
	if err != nil {
		log.Fatal().Msgf("Cannot read config: %v", config)
	}
	nodeID, err = c.Get("nodeID")
	if err != nil {
		log.Fatal().Msgf("Error reading config file for Node ID: %v\n", err)
	}
	target, err = c.Get("targetAddress")
	if err != nil {
		log.Fatal().Msgf("Error reading config file for Target Address: %v\n", config, err)
	}
	adapter, err = c.Get("adapterAddress")
	if err != nil {
		log.Info().Msgf("Cannot get adapter address from config file: %v\n", err)
	}
	v, err := yaml.Child(c.Root, "variants")
	if err != nil {
		log.Fatal().Msgf("Error getting variants from config: %v\n", err)
	}
	variants := []string{}
	varsInYaml, ok := v.(yaml.List)
	if ok {
		for i := 0; i < varsInYaml.Len(); i++ {
			node := varsInYaml.Item(i)
			variant := string(node.(yaml.Scalar))
			variants = append(variants, variant)
		}
	}
	err, supportedVariants = parseSupportedVariants(variants)
	if err != nil {
		log.Fatal().Msgf("Cannot parse supported variants from config: %v", err)
	}
	return target, adapter, nodeID, supportedVariants
}

func main() {
	pflag.Parse()
	godogOpts.Paths = pflag.Args()
	godogTags := godogOpts.Tags
	supportedVariants := make(map[string]bool)

	if *debug {
		zerolog.SetGlobalLevel(zerolog.DebugLevel)
	}

	// default to using CLI Flag for settings
	err, supportedVariants := parseSupportedVariants(*variant)
	if err != nil {
		log.Fatal().Msgf("Cannot parse variants from CLI: %v\n", err)
	}

	// If config present, use it for all non-debugging values
	if *config != ""{
		*targetAddress, *adapterAddress, *nodeID, supportedVariants = valuesFromConfig(*config)
	}

	suite := godog.TestSuite{
		Name:                 "xDS Test Suite",
		ScenarioInitializer:  InitializeScenario,
		TestSuiteInitializer: InitializeTestSuite,
		Options:              &godogOpts,
	}

	if supportedVariants["sotw non-aggregated"] {
		incremental = false
		aggregated = false
		customTags := []string{"@sotw", "@non-aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		godogOpts.Format = "pretty,cucumber:results/sotw-non-aggregated.json"
		suite.Run()
	}

	if supportedVariants["sotw aggregated"] {
		incremental = false
		aggregated = true
		customTags := []string{"@sotw", "@aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		godogOpts.Format = "pretty,cucumber:results/sotw-aggregated.json"
		suite.Run()
	}

	if supportedVariants["incremental non-aggregated"] {
		incremental = true
		aggregated = false
		customTags := []string{"@incremental", "@non-aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		godogOpts.Format = "pretty,cucumber:results/incremental-non-aggregated.json"
		suite.Run()
	}

	if supportedVariants["incremental aggregated"] {
		incremental = true
		aggregated = true
		customTags := []string{"@incremental", "@aggregated"}
		godogOpts.Tags = combineTags(godogTags, customTags)
		godogOpts.Format = "pretty,cucumber:results/incremental-aggregated.json"
		suite.Run()
	}
	os.Exit(0)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"regexp"
	"time"

	"github.com/bo0mer/yamt/metric/riemann"
	"github.com/bo0mer/yamt/netstat"
)

var (
	host      string
	port      int
	eventHost string
	interval  int
	ignoreIfs string
)

func init() {
	flag.StringVar(&host, "h", "localhost", "Riemann host (shorthand)")
	flag.StringVar(&host, "host", "localhost", "Riemann host")
	flag.IntVar(&port, "p", 5555, "Riemann port (shorthand)")
	flag.IntVar(&port, "port", 5555, "Riemann port")
	flag.StringVar(&eventHost, "e", "", "Event hostname (shorthand)")
	flag.StringVar(&eventHost, "event-host", "", "Event hostname")
	flag.IntVar(&interval, "i", 5, "Seconds between updates (shorthand)")
	flag.IntVar(&interval, "interval", 5, "Seconds between updates")
	flag.StringVar(&ignoreIfs, "g", "lo", "Interfaces to ignore (shorthand)")
	flag.StringVar(&ignoreIfs, "ignore-interfaces", "lo", "Interfaces to ignore")
}

func main() {
	flag.Parse()

	emitter := riemann.NewEmitter(fmt.Sprintf("%s:%d", host, port),
		riemann.Host(eventHost))

	re, err := regexp.Compile(ignoreIfs)
	if err != nil {
		log.Fatalf("yamt: invalid regular expression: %s\n", err)
	}
	r := netstat.NewReporter(emitter,
		netstat.Interval(time.Second*time.Duration(interval)),
		netstat.Except(re))
	r.Start()
	select {}
	defer r.Close()
}
<commit_msg>main: respect OS signals<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"regexp"
	"syscall"
	"time"

	"github.com/bo0mer/yamt/metric/riemann"
	"github.com/bo0mer/yamt/netstat"
)

var (
	host      string
	port      int
	eventHost string
	interval  int
	ignoreIfs string
)

func init() {
	flag.StringVar(&host, "h", "localhost", "Riemann host (shorthand)")
	flag.StringVar(&host, "host", "localhost", "Riemann host")
	flag.IntVar(&port, "p", 5555, "Riemann port (shorthand)")
	flag.IntVar(&port, "port", 5555, "Riemann port")
	flag.StringVar(&eventHost, "e", "", "Event hostname (shorthand)")
	flag.StringVar(&eventHost, "event-host", "", "Event hostname")
	flag.IntVar(&interval, "i", 5, "Seconds between updates (shorthand)")
	flag.IntVar(&interval, "interval", 5, "Seconds between updates")
	flag.StringVar(&ignoreIfs, "g", "lo", "Interfaces to ignore (shorthand)")
	flag.StringVar(&ignoreIfs, "ignore-interfaces", "lo", "Interfaces to ignore")
}

func main() {
	flag.Parse()

	emitter := riemann.NewEmitter(fmt.Sprintf("%s:%d", host, port),
		riemann.Host(eventHost))

	re, err := regexp.Compile(ignoreIfs)
	if err != nil {
		log.Fatalf("yamt: invalid regular expression: %s\n", err)
	}
	r := netstat.NewReporter(emitter,
		netstat.Interval(time.Second*time.Duration(interval)),
		netstat.Except(re))
	r.Start()
	defer r.Close()
	log.Printf("yamt: started emitting metrics\n")

	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	sig := <-c
	fmt.Printf("yamt: exiting due to %s\n", sig)
}
<|endoftext|>


<commit_before>// main.go
package main

import (
	"./src"
	"encoding/json"
	"github.com/gorilla/websocket"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

var globalRC = chatblast.MakeRoomController()

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func sockhandler(w http.ResponseWriter, r *http.Request) {
	// Check to see if request is from same origin
	origin := r.Header.Get("origin")
	if strings.Contains(r.Host, origin) {
		emptyResponse := []byte{}
		w.WriteHeader(403)
		w.Write(emptyResponse)
		return
	}

	// Get user name
	var name string
	nameArr := r.URL.Query()["name"]
	if len(nameArr) == 0 || nameArr[0] == "" {
		return
	} else {
		name = nameArr[0]
	}

	// Upgrade connection to websockets
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}

	// Make user and add them to globalRoom
	self := chatblast.NewUser(name, conn)
	globalRC.AddUser(self)

	// Register some teardown
	defer func() {
		globalRC.RemoveUser(self)
		conn.Close()
	}()

	// Read incomining requests from connected client, and
	// broadcast to all other connected clients
	for {
		var err error
		_, p, err := conn.ReadMessage()
		if err != nil {
			return
		}

		// Parse out JSON request
		var incoming chatblast.Message
		err = json.Unmarshal(p, &incoming)
		incoming.UserId = self.Id
		incoming.User = self
		if err != nil {
			// TODO Return some kind of error message
			// to the client only
			log.Println(err)
			log.Println("Bad JSON")
		}
		globalRC.Broadcast(&incoming)
	}

}

func handler(w http.ResponseWriter, r *http.Request) {
	// Serve up an HTML template to bootstrap everything
	t, _ := template.ParseFiles("html/chatblast.html")
	err := t.Execute(w, r.Host)
	if err != nil {
		log.Println("uh oh!")
	}
}

func debughandler(w http.ResponseWriter, r *http.Request) {
	// Serve up an HTML template to bootstrap everything
	t, _ := template.ParseFiles("html/debug.html")
	err := t.Execute(w, r.Host)
	if err != nil {
		log.Println("uh oh!")
	}
}

func jsHandler(w http.ResponseWriter, r *http.Request) {
	// Serve some static JS files
	http.ServeFile(w, r, r.URL.Path[1:])
}

func init() {
	// Register our handlers
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatal(err)
	}
	http.HandleFunc("/", handler)
	http.HandleFunc("/debug", debughandler)
	http.Handle("/js/", http.FileServer(http.Dir(dir)))
	http.HandleFunc("/sock", sockhandler)
}

func main() {
	// Kick off just a single goroutine to
	// act as a rebroadcaster for all messages
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic("ListenAndServe: " + err.Error())
	} else {
		log.Println("Serving on")
	}
}
<commit_msg>Add 'Bad JSON' error<commit_after>// main.go
package main

import (
	"./src"
	"encoding/json"
	"github.com/gorilla/websocket"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

// Instantiate global roomcontroller
var globalRC = chatblast.MakeRoomController()

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func sockhandler(w http.ResponseWriter, r *http.Request) {
	// Check to see if request is from same origin
	origin := r.Header.Get("origin")
	if strings.Contains(r.Host, origin) {
		emptyResponse := []byte{}
		w.WriteHeader(403)
		w.Write(emptyResponse)
		return
	}

	// Get user name
	var name string
	nameArr := r.URL.Query()["name"]
	if len(nameArr) == 0 || nameArr[0] == "" {
		return
	} else {
		name = nameArr[0]
	}

	// Upgrade connection to websockets
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}

	// Make user and add them to globalRoom
	self := chatblast.NewUser(name, conn)
	globalRC.AddUser(self)

	// Register some teardown
	defer func() {
		globalRC.RemoveUser(self)
		conn.Close()
	}()

	// Read incomining requests from connected client, and
	// broadcast to roomcontroller
	for {
		var err error
		_, p, err := conn.ReadMessage()
		if err != nil {
			return
		}

		// Parse out JSON request
		var incoming chatblast.Message
		err = json.Unmarshal(p, &incoming)
		if err != nil {
			incoming.Cmd = "err"
			incoming.Text = "Bad JSON"
			self.Tell(&incoming)
		} else {
			incoming.UserId = self.Id
			incoming.User = self
			globalRC.Broadcast(&incoming)
		}
	}

}

func handler(w http.ResponseWriter, r *http.Request) {
	// Serve up an HTML template to bootstrap everything
	t, _ := template.ParseFiles("html/chatblast.html")
	err := t.Execute(w, r.Host)
	if err != nil {
		log.Println("uh oh!")
	}
}

func debughandler(w http.ResponseWriter, r *http.Request) {
	// Serve up an HTML template to bootstrap everything
	t, _ := template.ParseFiles("html/debug.html")
	err := t.Execute(w, r.Host)
	if err != nil {
		log.Println("uh oh!")
	}
}

func roomshandler(w http.ResponseWriter, r *http.Request) {
	// Return a JSON object with information about all open rooms
	w.Header().Set("Content-Type", "application/json")
	rooomsJSON, err := json.Marshal(globalRC.Rooms)
	if err != nil {
		log.Println("Error serializing rooms")
		log.Println(err)
	} else {
		w.Write(rooomsJSON)
	}
}

func usershandler(w http.ResponseWriter, r *http.Request) {
	// Return a JSON object with information about all connected users
	w.Header().Set("Content-Type", "application/json")
	usersJSON, err := json.Marshal(globalRC.Users)
	if err != nil {
		log.Println("Error serializing users")
		log.Println(err)
	} else {
		w.Write(usersJSON)
	}
}

func jsHandler(w http.ResponseWriter, r *http.Request) {
	// Serve some static JS files
	http.ServeFile(w, r, r.URL.Path[1:])
}

func init() {
	// Register our handlers
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatal(err)
	}
	http.HandleFunc("/", handler)
	http.HandleFunc("/debug", debughandler)
	http.HandleFunc("/debug/rooms", roomshandler)
	http.HandleFunc("/debug/users", usershandler)
	http.Handle("/js/", http.FileServer(http.Dir(dir)))
	http.HandleFunc("/sock", sockhandler)
}

func main() {
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic("ListenAndServe: " + err.Error())
	} else {
		log.Println("Serving on")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

/*
	c	matches any literal character c
	.	matches any single character
	^	matches the beginning of the input string
	$	matches the end of the input string
	*	matches zero or more occurrences of the previous character
*/

const (
	charNil charType = iota
	charEscapeLiteral
	charLiteral
	charDot
	charBegin
	charEnd
	charStar
	charConcat
)

//go:generate stringer -type=charType
type charType int

type char struct {
	typ charType
	val byte
}

func (c char) String() string {
	return fmt.Sprintf("{%s %q}", c.typ, c.val)
}

type lexer struct {
	expression string
	pos        int
	chars      []char
}

func (l *lexer) run() {
	for {
		switch l.expression[l.pos] {
		case '\\':
			l.emit(charEscapeLiteral)
		case '.':
			l.emit(charDot)
		case '^':
			l.emit(charBegin)
		case '$':
			l.emit(charEnd)
		case '*':
			l.emit(charStar)
		default:
			l.emit(charLiteral)
		}
		if !l.next() {
			break
		}
	}
}

func (l *lexer) top() *char {
	if len(l.chars) > 0 {
		return &l.chars[len(l.chars)-1]
	}
	return nil
}

func (l *lexer) emit(t charType) {
	c := l.expression[l.pos]
	if t == charEscapeLiteral {
		if l.next() {
			c = escape(l.expression[l.pos])
		} else {
			log.Fatalln("cannot have a trailing backslash in regular expression")
		}
	}
	if t == charStar {
		top := l.top()
		if top == nil || (top.typ != charLiteral && top.typ != charDot) {
			log.Fatalln("Preceding token to star is not quantifiable")
		}
	}
	if t != charStar {
		l.chars = append(l.chars, char{charConcat, '.'})
	}
	l.chars = append(l.chars, char{t, c})
}

func escape(c byte) byte {
	switch c {
	case '0':
		return '\x00'
	case 'a':
		return '\x07'
	case 'b':
		return '\x08'
	case 't':
		return '\x09'
	case 'n':
		return '\x0A'
	case 'v':
		return '\x0B'
	case 'f':
		return '\x0C'
	case 'r':
		return '\x0D'
	case 'e':
		return '\x1B'
	case '\\':
		return '\x5C'
	default:
		return c
	}
}

func (l *lexer) next() bool {
	l.pos++
	if l.pos < len(l.expression) {
		return true
	}
	return false
}

func lex(expression string) []char {
	if len(expression) == 0 {
		return []char{}
	}
	l := &lexer{
		expression: expression,
		chars:      make([]char, 0, len(expression)),
	}
	l.run()
	return l.chars[1:]
}

func postfix(chars []char) []char {
	output := []char{}
	operator := []char{}
	pop := func() *char {
		if len(operator) > 0 {
			c := &operator[len(operator)-1]
			operator = operator[:len(operator)-1]
			return c
		}
		return nil
	}
	for _, c := range chars {
		switch c.typ {
		case charConcat:
			if t := pop(); t != nil {
				output = append(output, *t)
			}
			operator = append(operator, c)
		default:
			output = append(output, c)
		}
	}
	output = append(output, operator...)
	return output
}

//go:generate stringer -type=styp
type styp int

const (
	match styp = iota
	split
	single
)

type state struct {
	typ      styp
	c        char
	out      []*state
	lastlist int // list generation number
}

func (s state) String() string {
	return fmt.Sprintf("{typ: %s, c: %s, out: %p}", s.typ, s.c, s.out)
}

type frag struct {
	start *state
	out   []ptr
}

type ptr struct {
	s *state
	i int
}

func patch(out []ptr, start *state) {
	var p ptr
	for i := len(out) - 1; i >= 0; i-- {
		p, out = out[len(out)-1], out[:len(out)-1]
		index := p.i
		p.s.out[index] = start
	}
}

func post2nfa(postfix []char) (start *state) {
	stack := []frag{}
	push := func(f frag) {
		stack = append(stack, f)
	}
	pop := func() (f frag) {
		f, stack = stack[len(stack)-1], stack[:len(stack)-1]
		return
	}
	for _, p := range postfix {
		switch p.typ {
		default:
			s := &state{typ: single, c: p, out: []*state{nil}}
			out := []ptr{{s, 0}}
			push(frag{s, out})
		case charConcat:
			e2 := pop()
			e1 := pop()
			patch(e1.out, e2.start)
			push(frag{e1.start, e2.out})
		case charStar:
			e := pop()
			s := &state{typ: split, out: []*state{e.start, nil}}
			patch(e.out, s)
			out := []ptr{{s, 1}}
			push(frag{s, out})
		}
	}
	e := pop()
	patch(e.out, &state{typ: match})
	return e.start
}

func addstate(list *[]*state, s *state, listid int) {
	if s.lastlist == listid {
		return
	}
	s.lastlist = listid
	if s.typ == split {
		addstate(list, s.out[0], listid)
		addstate(list, s.out[1], listid)
		return
	}
	*list = append(*list, s)
}

func matchregex(start *state, source string) bool {
	listid := 1
	list := []*state{}
	addstate(&list, start, listid)

	for i := range source {
		c := source[i]
		list, listid = step(list, c, listid)
	}
	return ismatch(list)
}

func step(list []*state, c byte, listid int) ([]*state, int) {
	nlist := []*state{}
	listid++
	for _, s := range list {
		if s.typ == single && s.c.val == c {
			addstate(&nlist, s.out[0], listid)
		}
	}
	return nlist, listid
}

func ismatch(list []*state) bool {
	for _, s := range list {
		if s.typ == match {
			return true
		}
	}
	return false
}

func printnfa(s *state) {
	fmt.Println(s)
	switch s.typ {
	case single:
		printnfa(s.out[0])
	case split:
		printnfa(s.out[1])
	case match:
		return
	}
}
func main() {
	inSrc, _ := ioutil.ReadAll(os.Stdin)
	source := string(inSrc)
	flag.Parse()
	regexp := flag.Arg(0)

	chars := lex(regexp)
	chars = postfix(chars)
	nfa := post2nfa(chars)
	fmt.Println(matchregex(nfa, source))
}
<commit_msg>Added cached dfa conversion used map instead of binary tree. 	might switch in the future, 	but i'm sure that go uses a tree 	underneath to implement map<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

/*
	c	matches any literal character c
	.	matches any single character
	^	matches the beginning of the input string
	$	matches the end of the input string
	*	matches zero or more occurrences of the previous character
*/

const (
	charNil charType = iota
	charEscapeLiteral
	charLiteral
	charDot
	charBegin
	charEnd
	charStar
	charConcat
)

//go:generate stringer -type=charType
type charType int

type char struct {
	typ charType
	val byte
}

func (c char) String() string {
	return fmt.Sprintf("{%s %q}", c.typ, c.val)
}

type lexer struct {
	expression string
	pos        int
	chars      []char
}

func (l *lexer) run() {
	for {
		switch l.expression[l.pos] {
		case '\\':
			l.emit(charEscapeLiteral)
		case '.':
			l.emit(charDot)
		case '^':
			l.emit(charBegin)
		case '$':
			l.emit(charEnd)
		case '*':
			l.emit(charStar)
		default:
			l.emit(charLiteral)
		}
		if !l.next() {
			break
		}
	}
}

func (l *lexer) top() *char {
	if len(l.chars) > 0 {
		return &l.chars[len(l.chars)-1]
	}
	return nil
}

func (l *lexer) emit(t charType) {
	c := l.expression[l.pos]
	if t == charEscapeLiteral {
		if l.next() {
			c = escape(l.expression[l.pos])
		} else {
			log.Fatalln("cannot have a trailing backslash in regular expression")
		}
	}
	if t == charStar {
		top := l.top()
		if top == nil || (top.typ != charLiteral && top.typ != charDot) {
			log.Fatalln("Preceding token to star is not quantifiable")
		}
	}
	if t != charStar {
		l.chars = append(l.chars, char{charConcat, '.'})
	}
	l.chars = append(l.chars, char{t, c})
}

func escape(c byte) byte {
	switch c {
	case '0':
		return '\x00'
	case 'a':
		return '\x07'
	case 'b':
		return '\x08'
	case 't':
		return '\x09'
	case 'n':
		return '\x0A'
	case 'v':
		return '\x0B'
	case 'f':
		return '\x0C'
	case 'r':
		return '\x0D'
	case 'e':
		return '\x1B'
	case '\\':
		return '\x5C'
	default:
		return c
	}
}

func (l *lexer) next() bool {
	l.pos++
	if l.pos < len(l.expression) {
		return true
	}
	return false
}

func lex(expression string) []char {
	if len(expression) == 0 {
		return []char{}
	}
	l := &lexer{
		expression: expression,
		chars:      make([]char, 0, len(expression)),
	}
	l.run()
	return l.chars[1:]
}

func postfix(chars []char) []char {
	output := []char{}
	operator := []char{}
	pop := func() *char {
		if len(operator) > 0 {
			c := &operator[len(operator)-1]
			operator = operator[:len(operator)-1]
			return c
		}
		return nil
	}
	for _, c := range chars {
		switch c.typ {
		case charConcat:
			if t := pop(); t != nil {
				output = append(output, *t)
			}
			operator = append(operator, c)
		default:
			output = append(output, c)
		}
	}
	output = append(output, operator...)
	return output
}

//go:generate stringer -type=styp
type styp int

const (
	match styp = iota
	split
	single
)

type state struct {
	typ      styp
	c        char
	out      []*state
	lastlist int // list generation number
}

func (s state) String() string {
	return fmt.Sprintf("{typ: %s, c: %s, out: %p}", s.typ, s.c, s.out)
}

type frag struct {
	start *state
	out   []ptr
}

type ptr struct {
	s *state
	i int
}

type dfastate struct {
	list *[]*state
	next [256]*dfastate
}

var cacheddfa map[*[]*state]*dfastate

func patch(out []ptr, start *state) {
	var p ptr
	for i := len(out) - 1; i >= 0; i-- {
		p, out = out[len(out)-1], out[:len(out)-1]
		index := p.i
		p.s.out[index] = start
	}
}

func post2nfa(postfix []char) (start *state) {
	stack := []frag{}
	push := func(f frag) {
		stack = append(stack, f)
	}
	pop := func() (f frag) {
		f, stack = stack[len(stack)-1], stack[:len(stack)-1]
		return
	}
	for _, p := range postfix {
		switch p.typ {
		default:
			s := &state{typ: single, c: p, out: []*state{nil}}
			out := []ptr{{s, 0}}
			push(frag{s, out})
		case charConcat:
			e2 := pop()
			e1 := pop()
			patch(e1.out, e2.start)
			push(frag{e1.start, e2.out})
		case charStar:
			e := pop()
			s := &state{typ: split, out: []*state{e.start, nil}}
			patch(e.out, s)
			out := []ptr{{s, 1}}
			push(frag{s, out})
		}
	}
	e := pop()
	patch(e.out, &state{typ: match})
	return e.start
}

func addstate(list *[]*state, s *state, listid int) {
	if s.lastlist == listid {
		return
	}
	s.lastlist = listid
	if s.typ == split {
		addstate(list, s.out[0], listid)
		addstate(list, s.out[1], listid)
		return
	}
	*list = append(*list, s)
}

func matchregex(start *state, source string) bool {
	cacheddfa = make(map[*[]*state]*dfastate)

	listid := 1
	list := []*state{}
	addstate(&list, start, listid)
	d := getdfastate(&list)
	var next *dfastate

	for i := range source {
		c := source[i]
		next = d.next[c]
		if next == nil {
			list, listid = step(list, c, listid)
			d.next[c] = getdfastate(&list)
			next = d.next[c]
		}
		d = next
	}
	return ismatch(*d.list)
}

func step(list []*state, c byte, listid int) ([]*state, int) {
	nlist := []*state{}
	listid++
	for _, s := range list {
		if s.typ == single && s.c.val == c {
			addstate(&nlist, s.out[0], listid)
		}
	}
	return nlist, listid
}

func getdfastate(list *[]*state) *dfastate {
	d := cacheddfa[list]
	if d != nil {
		return d
	}
	d = &dfastate{list: list}
	cacheddfa[list] = d
	return d
}

func ismatch(list []*state) bool {
	for _, s := range list {
		if s.typ == match {
			return true
		}
	}
	return false
}

func printnfa(s *state) {
	fmt.Println(s)
	switch s.typ {
	case single:
		printnfa(s.out[0])
	case split:
		printnfa(s.out[1])
	case match:
		return
	}
}
func main() {
	inSrc, _ := ioutil.ReadAll(os.Stdin)
	source := string(inSrc)
	flag.Parse()
	regexp := flag.Arg(0)

	chars := lex(regexp)
	chars = postfix(chars)
	nfa := post2nfa(chars)
	fmt.Println(matchregex(nfa, source))
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ssm"
	"log"
	"os"
	"os/exec"
	"strings"
	"syscall"
)

var verbose = os.Getenv("ENV_INJECTOR_VERBOSE") == "1"

func main() {
	injectEnviron()

	args := os.Args
	if len(args) <= 1 {
		log.Fatal("missing command")
	}

	path, err := exec.LookPath(args[1])
	if err != nil {
		log.Fatal(err)
	}
	err = syscall.Exec(path, args[1:], os.Environ())
	if err != nil {
		log.Fatal(err)
	}
}

func trace(v ...interface{}) {
	if verbose {
		log.Println(v...)
	}
}

func tracef(format string, v ...interface{}) {
	if verbose {
		log.Printf(format, v...)
	}
}

func injectEnviron() {
	prefix := os.Getenv("ENV_INJECTOR_PREFIX")
	if prefix == "" {
		trace("no parameter prefix specified, skipping injection")
		return
	}
	tracef("parameter prefix: %s", prefix)
	if !strings.HasSuffix(prefix, ".") {
		prefix += "."
	}

	names := []*string{}
	for _, env := range os.Environ() {
		parts := strings.SplitN(env, "=", 2)
		if len(parts) == 2 && parts[1] == "" {
			names = append(names, aws.String(prefix+parts[0]))
		}
	}
	if len(names) == 0 {
		trace("nothing to be injected")
		return
	}

	sess, err := session.NewSessionWithOptions(session.Options{
		SharedConfigState: session.SharedConfigEnable,
	})
	if err != nil {
		trace(err)
		trace("failed to create session")
		return
	}
	if *sess.Config.Region == "" {
		trace("no explicit region configuration. So now retrieving ec2metadata...")
		region, err := ec2metadata.New(sess).Region()
		if err != nil {
			trace(err)
			trace("could not find region configuration")
			return
		}
		sess.Config.Region = aws.String(region)
	}

	var svc *ssm.SSM
	if arn := os.Getenv("ENV_INJECTOR_ASSUME_ROLE_ARN"); arn != "" {
		creds := stscreds.NewCredentials(sess, arn)
		svc = ssm.New(sess, &aws.Config{Credentials: creds})
	} else {
		svc = ssm.New(sess)
	}

	// 'GetParameters' fails entirely when any one of parameters is not permitted to get.
	// So call 'GetParameters' one by one.
	for _, n := range names {
		result, err := svc.GetParameters(&ssm.GetParametersInput{
			Names:          []*string{n},
			WithDecryption: aws.Bool(true),
		})
		if err != nil {
			tracef("failed to get: %s", *n)
			trace(err)
			continue
		}

		for _, key := range result.InvalidParameters {
			tracef("invalid parameter: %s", *key)
		}
		for _, param := range result.Parameters {
			key := strings.TrimPrefix(*param.Name, prefix)
			os.Setenv(key, *param.Value)
			tracef("env injected: %s", key)
		}
	}
}
<commit_msg>support hierarchical parameters<commit_after>package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ssm"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
)

var verbose = os.Getenv("ENV_INJECTOR_VERBOSE") == "1"

// For lazy initialization
var service *ssm.SSM

func main() {
	injectEnviron()

	args := os.Args
	if len(args) <= 1 {
		log.Fatal("missing command")
	}

	path, err := exec.LookPath(args[1])
	if err != nil {
		log.Fatal(err)
	}
	err = syscall.Exec(path, args[1:], os.Environ())
	if err != nil {
		log.Fatal(err)
	}
}

func trace(v ...interface{}) {
	if verbose {
		log.Println(v...)
	}
}

func tracef(format string, v ...interface{}) {
	if verbose {
		log.Printf(format, v...)
	}
}

func injectEnviron() {
	injectEnvironByPath()
	injectEnvironByPrefix()
}

func injectEnvironByPath() {
	path := os.Getenv("ENV_INJECTOR_PATH")
	if path == "" {
		trace("no parameter path specified, skipping injection by path")
		return
	}
	tracef("parameter path: %s", path)

	svc := getSSMService()
	if svc == nil {
		return
	}

	result, err := svc.GetParametersByPath(&ssm.GetParametersByPathInput{
		Path:           aws.String(path),
		WithDecryption: aws.Bool(true),
	})
	if err != nil {
		tracef("failed to get by path: %s", path)
		trace(err)
	}

	for _, param := range result.Parameters {
		key, err := filepath.Rel(path, aws.StringValue(param.Name))
		if err != nil {
			trace(err)
			continue
		}
		if os.Getenv(key) == "" {
			os.Setenv(key, aws.StringValue(param.Value))
			tracef("env injected: %s", key)
		}
	}
}

func injectEnvironByPrefix() {
	prefix := os.Getenv("ENV_INJECTOR_PREFIX")

	if prefix == "" {
		trace("no parameter prefix specified, skipping injection by prefix")
		return
	}
	tracef("parameter prefix: %s", prefix)
	if !strings.HasSuffix(prefix, ".") {
		prefix += "."
	}

	names := []*string{}
	for _, env := range os.Environ() {
		parts := strings.SplitN(env, "=", 2)
		if len(parts) == 2 && parts[1] == "" {
			names = append(names, aws.String(prefix+parts[0]))
		}
	}
	if len(names) == 0 {
		trace("nothing to be injected by prefix")
		return
	}

	svc := getSSMService()
	if svc == nil {
		return
	}

	// 'GetParameters' fails entirely when any one of parameters is not permitted to get.
	// So call 'GetParameters' one by one.
	for _, n := range names {
		result, err := svc.GetParameters(&ssm.GetParametersInput{
			Names:          []*string{n},
			WithDecryption: aws.Bool(true),
		})
		if err != nil {
			tracef("failed to get: %s", *n)
			trace(err)
			continue
		}

		for _, key := range result.InvalidParameters {
			tracef("invalid parameter: %s", aws.StringValue(key))
		}
		for _, param := range result.Parameters {
			key := strings.TrimPrefix(aws.StringValue(param.Name), prefix)
			os.Setenv(key, aws.StringValue(param.Value))
			tracef("env injected: %s", key)
		}
	}
}

// Initialize SSM service lazily, and return it.
func getSSMService() *ssm.SSM {
	if service != nil {
		return service
	}

	sess, err := session.NewSessionWithOptions(session.Options{
		SharedConfigState: session.SharedConfigEnable,
	})
	if err != nil {
		trace(err)
		trace("failed to create session")
		return nil
	}
	if *sess.Config.Region == "" {
		trace("no explicit region configuration. So now retrieving ec2metadata...")
		region, err := ec2metadata.New(sess).Region()
		if err != nil {
			trace(err)
			trace("could not find region configuration")
			return nil
		}
		sess.Config.Region = aws.String(region)
	}

	if arn := os.Getenv("ENV_INJECTOR_ASSUME_ROLE_ARN"); arn != "" {
		creds := stscreds.NewCredentials(sess, arn)
		service = ssm.New(sess, &aws.Config{Credentials: creds})
	} else {
		service = ssm.New(sess)
	}
	return service
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"errors"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/asuleymanov/golos-go/client"
	"github.com/go-telegram-bot-api/telegram-bot-api"

	"github.com/Chiliec/golos-vote-bot/db"
	"github.com/Chiliec/golos-vote-bot/models"
)

var (
	database *sql.DB
	logins   map[int]string
)

const (
	rpc   = "wss://ws.golos.io"
	chain = "golos"

	addKeyButtonText    = "🗝 Добавить ключ"
	removeKeyButtonText = "❌ Удалить ключ"

	groupLink = "https://t.me/joinchat/AlKeQUQpN8-9oShtaTcY7Q"
	groupID   = -1001143551951

	requiredVotes     = 1
	initialUserRating = 10
)

var alreadyVotedError = errors.New("уже проголосовали")

func init() {
	db, err := db.InitDB("./db/database.db")
	db.SetMaxOpenConns(1)
	if err != nil {
		log.Panic(err)
	}
	database = db
	logins = map[int]string{}
}

func main() {
	token := os.Getenv("TELEGRAM_TOKEN")
	if token == "" {
		log.Panic(errors.New("Нет токена"))
	}
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		log.Panic(err)
	}

	bot.Debug = false

	log.Printf("Authorized on account %s", bot.Self.UserName)

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60

	updates, err := bot.GetUpdatesChan(u)
	if err != nil {
		log.Panic(err)
	}
	for update := range updates {
		err := processMessage(bot, update)
		if err != nil {
			log.Println(err)
		}
	}
}

func processMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update) error {
	chatID, err := getChatID(update)
	if err != nil {
		return err
	}
	userID, err := getUserID(update)
	if err != nil {
		return err
	}
	msg := tgbotapi.NewMessage(chatID, "")
	if update.Message != nil {
		regexp, err := regexp.Compile("https://golos.io/([-a-zA-Z0-9@:%_+.~#?&//=]{2,256})/@([-a-zA-Z0-9.]{2,256})/([-a-zA-Z0-9@:%_+.~#?&=]{2,256})")
		if err != nil {
			return err
		}
		switch {
		case update.Message.IsCommand():
			switch update.Message.Command() {
			case "start":
				addKeyButton := tgbotapi.NewKeyboardButton(addKeyButtonText)
				removeKeyButton := tgbotapi.NewKeyboardButton(removeKeyButtonText)
				firstButtonRow := []tgbotapi.KeyboardButton{addKeyButton, removeKeyButton}
				keyboard := tgbotapi.NewReplyKeyboard(firstButtonRow)
				msg.ReplyMarkup = keyboard
				msg.Text = fmt.Sprintf("Привет, %s! \n\n"+
					"Я — бот для коллективного кураторства в [социальной блокчейн-сети \"Голос\"](https://golos.io).\n\n"+
					"Предлагаю начать с добавления приватного постинг-ключа нажатием кнопки \""+addKeyButtonText+"\""+
					", после чего я дам ссылку на группу куда предлагать посты для поддержки.\n\n"+
					"По любым вопросам пиши моему хозяину — @babin",
					update.Message.From.FirstName)
				forgetLogin(userID)
			}
		case update.Message.Text == addKeyButtonText:
			msg.Text = "Введи логин на Голосе"
			setWaitLogin(userID)
		case update.Message.Text == removeKeyButtonText:
			credential, err := models.GetCredentialByUserID(userID, database)
			msg.Text = "Произошла ошибка при удалении ключа"
			if err == nil {
				credential.PostingKey = ""
				result, err := credential.Save(database)
				if result && err == nil {
					msg.Text = "Твой ключ успешно удалён. Я больше не буду отвечать на твои предложения по курированию постов."
				}
			}
			forgetLogin(userID)
		case regexp.MatchString(update.Message.Text):
			msg.ReplyToMessageID = update.Message.MessageID

			if update.Message.Chat.Type == "private" {
				msg.Text = "Присоединяйтесь к нашей группе: " + groupLink
				break
			}

			matched := regexp.FindStringSubmatch(update.Message.Text)
			author, permalink := matched[2], matched[3]

			percent := 5
			if update.Message.Chat.ID == groupID {
				percent = 100
			}

			voteModel := models.Vote{
				UserID:    userID,
				Author:    author,
				Permalink: permalink,
				Percent:   percent,
			}

			if voteModel.Exists(database) {
				msg.Text = "Уже голосовал за этот пост!"
				break
			}

			voteID, err := voteModel.Save(database)
			if err != nil {
				return err
			}

			msg.Text = "Голосование открыто"
			markup := getVoteMarkup(voteID, 0, 0)
			msg.ReplyMarkup = markup
		default:
			if update.Message.Chat.Type != "private" {
				return nil
			}
			msg.Text = "Не понимаю"
			if wait, login := isWaitingKey(userID); wait {
				if login == "" {
					login = strings.Trim(update.Message.Text, "@")
					setWaitKey(userID, login)
					msg.Text = "Введите приватный ключ"
				} else {
					credential := models.Credential{
						UserID:     userID,
						UserName:   login,
						PostingKey: update.Message.Text,
						Rating:     initialUserRating,
					}
					if rating, err := credential.GetRating(database); err == nil {
						credential.Rating = rating
					}

					golos := client.NewApi([]string{rpc}, chain)
					defer golos.Rpc.Close()
					if golos.Login(credential.UserName, credential.PostingKey) {
						result, err := credential.Save(database)
						if err != nil {
							log.Println(err.Error())
						}
						if result {
							msg.Text = "Логин и приватный ключ успешно сохранён! " +
								"Присоединяйтесь к нашей группе для участия в курировании: " + groupLink
						} else {
							msg.Text = "Не смог сохранить логин и приватный ключ :("
						}
					} else {
						msg.Text = "Логин и приватный ключ не совпадают :("
					}

					forgetLogin(userID)
				}
			}
		}
	} else if update.CallbackQuery != nil {
		log.Println(update.CallbackQuery.Message)
		arr := strings.Split(update.CallbackQuery.Data, "_")
		voteStringID := arr[0]
		action := arr[1]
		voteID, err := strconv.ParseInt(voteStringID, 10, 64)
		if err != nil {
			return err
		}
		isGood := action == "good"
		response := models.Response{
			UserID: userID,
			VoteID: voteID,
			Result: isGood,
		}
		var text string
		if response.Exists(database) {
			text = "Вы уже голосовали!"
		} else {
			_, err := response.Save(database)
			if err != nil {
				return err
			}
			text = "Голос принят"
			voteModel := models.GetVote(database, voteID)
			err = verifyVotes(bot, voteModel, update)
			if err != nil {
				return err
			}
		}
		config := tgbotapi.CallbackConfig{
			CallbackQueryID: update.CallbackQuery.ID,
			Text:            text,
			ShowAlert:       true,
		}
		bot.AnswerCallbackQuery(config)
		return nil
	}
	if msg.Text != "" {
		msg.ParseMode = "Markdown"
		msg.DisableWebPagePreview = true
		bot.Send(msg)
	} else {
		return errors.New("отсутствует текст сообщения")
	}
	return nil
}

func forgetLogin(userID int) {
	delete(logins, userID)
}

func setWaitLogin(userID int) {
	logins[userID] = ""
}

func setWaitKey(userID int, login string) {
	logins[userID] = login
}

func isWaitingKey(userID int) (bool, string) {
	for id, login := range logins {
		if userID == id {
			return true, login
		}
	}
	return false, ""
}

func verifyVotes(bot *tgbotapi.BotAPI, voteModel models.Vote, update tgbotapi.Update) error {
	chatID, err := getChatID(update)
	if err != nil {
		return err
	}
	userID, err := getUserID(update)
	if err != nil {
		return err
	}
	messageID, err := getMessageID(update)
	if err != nil {
		return err
	}

	responses, err := models.GetAllResponsesForVoteID(voteModel.VoteID, database)
	if err != nil {
		return err
	}

	var positives, negatives int
	for _, response := range responses {
		if response.Result {
			positives = positives + 1
		} else {
			negatives = negatives + 1
		}
	}

	markup := getVoteMarkup(voteModel.VoteID, positives, negatives)
	updateTextConfig := tgbotapi.EditMessageTextConfig{
		BaseEdit: tgbotapi.BaseEdit{
			ChatID:      chatID,
			MessageID:   messageID,
			ReplyMarkup: &markup,
		},
		Text: update.CallbackQuery.Message.Text,
	}
	bot.Send(updateTextConfig)

	credential := models.Credential{UserID: userID}

	if positives+negatives >= requiredVotes {
		msg := tgbotapi.NewEditMessageText(chatID, messageID, "")
		if positives >= negatives {
			credential.IncrementRating(database)

			successVotes := vote(voteModel)
			msg.Text = fmt.Sprintf("Проголосовал с силой %d%% c %d аккаунтов", voteModel.Percent, successVotes)
		} else {
			credential.DecrementRating(database)
			rating, err := credential.GetRating(database)
			if err != nil {
				return err
			}
			msg.Text = "Пост отклонен, рейтинг предлагающего снижен"
			if rating < 0 {
				memberConfig := tgbotapi.KickChatMemberConfig{
					ChatMemberConfig: tgbotapi.ChatMemberConfig{
						ChatID: chatID,
						UserID: userID,
					},
					UntilDate: 0,
				}
				bot.KickChatMember(memberConfig)
				msg.Text = "Пост отклонен, предлагающий исключен"
			}
		}
		_, err := bot.Send(msg)
		if err != nil {
			return err
		}
	}
	return nil
}

func vote(vote models.Vote) int {
	credentials, err := models.GetAllCredentials(database)
	if err != nil {
		log.Println("Не смогли извлечь ключи из базы")
		return 0
	}
	for _, credential := range credentials {
		client.Key_List[credential.UserName] = client.Keys{PKey: credential.PostingKey}
	}
	log.Printf("Загружено %d аккаунтов", len(credentials))

	var errors []error
	var wg sync.WaitGroup
	wg.Add(len(credentials))
	for _, credential := range credentials {
		client.Key_List[credential.UserName] = client.Keys{PKey: credential.PostingKey}
		go func(credential models.Credential) {
			defer wg.Done()
			weight := vote.Percent * 100
			golos := client.NewApi([]string{rpc}, chain)
			defer golos.Rpc.Close()
			err := golos.Vote(credential.UserName, vote.Author, vote.Permalink, weight)
			if err != nil {
				log.Println("Ошибка при голосовании: " + err.Error())
				errors = append(errors, err)
			}
		}(credential)
	}
	wg.Wait()
	return len(credentials) - len(errors)
}

func getVoteMarkup(voteID int64, positives int, negatives int) tgbotapi.InlineKeyboardMarkup {
	stringVoteID := strconv.FormatInt(voteID, 10)
	goodButton := tgbotapi.NewInlineKeyboardButtonData("👍 Хороший пост ("+strconv.Itoa(positives)+")", stringVoteID+"_good")
	badButton := tgbotapi.NewInlineKeyboardButtonData("👎 Плохой пост ("+strconv.Itoa(negatives)+")", stringVoteID+"_bad")
	buttons := []tgbotapi.InlineKeyboardButton{}
	buttons = append(buttons, goodButton)
	row := []tgbotapi.InlineKeyboardButton{goodButton, badButton}
	markup := tgbotapi.InlineKeyboardMarkup{}
	markup.InlineKeyboard = append(markup.InlineKeyboard, row)
	return markup
}

func getChatID(update tgbotapi.Update) (int64, error) {
	if update.Message != nil {
		return update.Message.Chat.ID, nil
	} else if update.CallbackQuery != nil {
		return update.CallbackQuery.Message.Chat.ID, nil
	} else {
		return 0, errors.New("Не получили ID чата")
	}
}

func getUserID(update tgbotapi.Update) (int, error) {
	if update.Message != nil {
		return update.Message.From.ID, nil
	} else if update.CallbackQuery != nil {
		return update.CallbackQuery.Message.From.ID, nil
	} else {
		return 0, errors.New("Не получили ID пользователя")
	}
}

func getMessageID(update tgbotapi.Update) (int, error) {
	if update.Message != nil {
		return update.Message.MessageID, nil
	} else if update.CallbackQuery != nil {
		return update.CallbackQuery.Message.MessageID, nil
	} else {
		return 0, errors.New("Не получили ID сообщения")
	}
}
<commit_msg>Only users who added key can suggest posts<commit_after>package main

import (
	"database/sql"
	"errors"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/asuleymanov/golos-go/client"
	"github.com/go-telegram-bot-api/telegram-bot-api"

	"github.com/Chiliec/golos-vote-bot/db"
	"github.com/Chiliec/golos-vote-bot/models"
)

var (
	database *sql.DB
	logins   map[int]string
)

const (
	rpc   = "wss://ws.golos.io"
	chain = "golos"

	addKeyButtonText    = "🗝 Добавить ключ"
	removeKeyButtonText = "❌ Удалить ключ"

	groupLink = "https://t.me/joinchat/AlKeQUQpN8-9oShtaTcY7Q"
	groupID   = -1001143551951

	requiredVotes     = 1
	initialUserRating = 10
)

var alreadyVotedError = errors.New("уже проголосовали")

func init() {
	db, err := db.InitDB("./db/database.db")
	db.SetMaxOpenConns(1)
	if err != nil {
		log.Panic(err)
	}
	database = db
	logins = map[int]string{}
}

func main() {
	token := os.Getenv("TELEGRAM_TOKEN")
	if token == "" {
		log.Panic(errors.New("Нет токена"))
	}
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		log.Panic(err)
	}

	bot.Debug = false

	log.Printf("Authorized on account %s", bot.Self.UserName)

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60

	updates, err := bot.GetUpdatesChan(u)
	if err != nil {
		log.Panic(err)
	}
	for update := range updates {
		err := processMessage(bot, update)
		if err != nil {
			log.Println(err)
		}
	}
}

func processMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update) error {
	chatID, err := getChatID(update)
	if err != nil {
		return err
	}
	userID, err := getUserID(update)
	if err != nil {
		return err
	}
	msg := tgbotapi.NewMessage(chatID, "")
	if update.Message != nil {
		regexp, err := regexp.Compile("https://golos.io/([-a-zA-Z0-9@:%_+.~#?&//=]{2,256})/@([-a-zA-Z0-9.]{2,256})/([-a-zA-Z0-9@:%_+.~#?&=]{2,256})")
		if err != nil {
			return err
		}
		switch {
		case update.Message.IsCommand():
			switch update.Message.Command() {
			case "start":
				addKeyButton := tgbotapi.NewKeyboardButton(addKeyButtonText)
				removeKeyButton := tgbotapi.NewKeyboardButton(removeKeyButtonText)
				firstButtonRow := []tgbotapi.KeyboardButton{addKeyButton, removeKeyButton}
				keyboard := tgbotapi.NewReplyKeyboard(firstButtonRow)
				msg.ReplyMarkup = keyboard
				msg.Text = fmt.Sprintf("Привет, %s! \n\n"+
					"Я — бот для коллективного кураторства в [социальной блокчейн-сети \"Голос\"](https://golos.io).\n\n"+
					"Предлагаю начать с добавления приватного постинг-ключа нажатием кнопки \""+addKeyButtonText+"\""+
					", после чего я дам ссылку на группу куда предлагать посты для поддержки.\n\n"+
					"По любым вопросам пиши моему хозяину — @babin",
					update.Message.From.FirstName)
				forgetLogin(userID)
			}
		case update.Message.Text == addKeyButtonText:
			msg.Text = "Введи логин на Голосе"
			setWaitLogin(userID)
		case update.Message.Text == removeKeyButtonText:
			credential, err := models.GetCredentialByUserID(userID, database)
			msg.Text = "Произошла ошибка при удалении ключа"
			if err == nil {
				credential.PostingKey = ""
				result, err := credential.Save(database)
				if result && err == nil {
					msg.Text = "Твой ключ успешно удалён. Я больше не буду отвечать на твои предложения по курированию постов."
				}
			}
			forgetLogin(userID)
		case regexp.MatchString(update.Message.Text):
			msg.ReplyToMessageID = update.Message.MessageID

			if update.Message.Chat.Type == "private" {
				msg.Text = "Присоединяйтесь к нашей группе: " + groupLink
				break
			}

			credential, err := models.GetCredentialByUserID(userID, database)
			if err != nil || len(credential.PostingKey) == 0 {
				msg.Text = "Не могу допустить тебя к кураторству, меня ещё нет твоего постинг-ключа. Напиши мне в личку, обсудим этот вопрос"
				break
			}

			matched := regexp.FindStringSubmatch(update.Message.Text)
			author, permalink := matched[2], matched[3]

			percent := 5
			if chatID == groupID {
				percent = 100
			}

			voteModel := models.Vote{
				UserID:    userID,
				Author:    author,
				Permalink: permalink,
				Percent:   percent,
			}

			if voteModel.Exists(database) {
				msg.Text = "Уже голосовал за этот пост!"
				break
			}

			voteID, err := voteModel.Save(database)
			if err != nil {
				return err
			}

			msg.Text = "Голосование открыто"
			markup := getVoteMarkup(voteID, 0, 0)
			msg.ReplyMarkup = markup
		default:
			if update.Message.Chat.Type != "private" {
				return nil
			}
			msg.Text = "Не понимаю"
			if wait, login := isWaitingKey(userID); wait {
				if login == "" {
					login = strings.Trim(update.Message.Text, "@")
					setWaitKey(userID, login)
					msg.Text = "Введите приватный ключ"
				} else {
					credential := models.Credential{
						UserID:     userID,
						UserName:   login,
						PostingKey: update.Message.Text,
						Rating:     initialUserRating,
					}
					if rating, err := credential.GetRating(database); err == nil {
						credential.Rating = rating
					}

					golos := client.NewApi([]string{rpc}, chain)
					defer golos.Rpc.Close()
					if golos.Login(credential.UserName, credential.PostingKey) {
						result, err := credential.Save(database)
						if err != nil {
							log.Println(err.Error())
						}
						if result {
							msg.Text = "Логин и приватный ключ успешно сохранён! " +
								"Присоединяйтесь к нашей группе для участия в курировании: " + groupLink
						} else {
							msg.Text = "Не смог сохранить логин и приватный ключ :("
						}
					} else {
						msg.Text = "Логин и приватный ключ не совпадают :("
					}

					forgetLogin(userID)
				}
			}
		}
	} else if update.CallbackQuery != nil {
		log.Println(update.CallbackQuery.Message)
		arr := strings.Split(update.CallbackQuery.Data, "_")
		voteStringID := arr[0]
		action := arr[1]
		voteID, err := strconv.ParseInt(voteStringID, 10, 64)
		if err != nil {
			return err
		}
		isGood := action == "good"
		response := models.Response{
			UserID: userID,
			VoteID: voteID,
			Result: isGood,
		}
		var text string
		if response.Exists(database) {
			text = "Вы уже голосовали!"
		} else {
			_, err := response.Save(database)
			if err != nil {
				return err
			}
			text = "Голос принят"
			voteModel := models.GetVote(database, voteID)
			err = verifyVotes(bot, voteModel, update)
			if err != nil {
				return err
			}
		}
		config := tgbotapi.CallbackConfig{
			CallbackQueryID: update.CallbackQuery.ID,
			Text:            text,
			ShowAlert:       true,
		}
		bot.AnswerCallbackQuery(config)
		return nil
	}
	if msg.Text != "" {
		msg.ParseMode = "Markdown"
		msg.DisableWebPagePreview = true
		bot.Send(msg)
	} else {
		return errors.New("отсутствует текст сообщения")
	}
	return nil
}

func forgetLogin(userID int) {
	delete(logins, userID)
}

func setWaitLogin(userID int) {
	logins[userID] = ""
}

func setWaitKey(userID int, login string) {
	logins[userID] = login
}

func isWaitingKey(userID int) (bool, string) {
	for id, login := range logins {
		if userID == id {
			return true, login
		}
	}
	return false, ""
}

func verifyVotes(bot *tgbotapi.BotAPI, voteModel models.Vote, update tgbotapi.Update) error {
	chatID, err := getChatID(update)
	if err != nil {
		return err
	}
	userID, err := getUserID(update)
	if err != nil {
		return err
	}
	messageID, err := getMessageID(update)
	if err != nil {
		return err
	}

	responses, err := models.GetAllResponsesForVoteID(voteModel.VoteID, database)
	if err != nil {
		return err
	}

	var positives, negatives int
	for _, response := range responses {
		if response.Result {
			positives = positives + 1
		} else {
			negatives = negatives + 1
		}
	}

	markup := getVoteMarkup(voteModel.VoteID, positives, negatives)
	updateTextConfig := tgbotapi.EditMessageTextConfig{
		BaseEdit: tgbotapi.BaseEdit{
			ChatID:      chatID,
			MessageID:   messageID,
			ReplyMarkup: &markup,
		},
		Text: update.CallbackQuery.Message.Text,
	}
	bot.Send(updateTextConfig)

	credential := models.Credential{UserID: userID}

	if positives+negatives >= requiredVotes {
		msg := tgbotapi.NewEditMessageText(chatID, messageID, "")
		if positives >= negatives {
			credential.IncrementRating(database)

			successVotes := vote(voteModel)
			msg.Text = fmt.Sprintf("Проголосовал с силой %d%% c %d аккаунтов", voteModel.Percent, successVotes)
		} else {
			credential.DecrementRating(database)
			rating, err := credential.GetRating(database)
			if err != nil {
				return err
			}
			msg.Text = "Пост отклонен, рейтинг предлагающего снижен"
			if rating < 0 {
				memberConfig := tgbotapi.KickChatMemberConfig{
					ChatMemberConfig: tgbotapi.ChatMemberConfig{
						ChatID: chatID,
						UserID: userID,
					},
					UntilDate: 0,
				}
				bot.KickChatMember(memberConfig)
				msg.Text = "Пост отклонен, предлагающий исключен"
			}
		}
		_, err := bot.Send(msg)
		if err != nil {
			return err
		}
	}
	return nil
}

func vote(vote models.Vote) int {
	credentials, err := models.GetAllCredentials(database)
	if err != nil {
		log.Println("Не смогли извлечь ключи из базы")
		return 0
	}
	for _, credential := range credentials {
		client.Key_List[credential.UserName] = client.Keys{PKey: credential.PostingKey}
	}
	log.Printf("Загружено %d аккаунтов", len(credentials))

	var errors []error
	var wg sync.WaitGroup
	wg.Add(len(credentials))
	for _, credential := range credentials {
		client.Key_List[credential.UserName] = client.Keys{PKey: credential.PostingKey}
		go func(credential models.Credential) {
			defer wg.Done()
			weight := vote.Percent * 100
			golos := client.NewApi([]string{rpc}, chain)
			defer golos.Rpc.Close()
			err := golos.Vote(credential.UserName, vote.Author, vote.Permalink, weight)
			if err != nil {
				log.Println("Ошибка при голосовании: " + err.Error())
				errors = append(errors, err)
			}
		}(credential)
	}
	wg.Wait()
	return len(credentials) - len(errors)
}

func getVoteMarkup(voteID int64, positives int, negatives int) tgbotapi.InlineKeyboardMarkup {
	stringVoteID := strconv.FormatInt(voteID, 10)
	goodButton := tgbotapi.NewInlineKeyboardButtonData("👍 Хороший пост ("+strconv.Itoa(positives)+")", stringVoteID+"_good")
	badButton := tgbotapi.NewInlineKeyboardButtonData("👎 Плохой пост ("+strconv.Itoa(negatives)+")", stringVoteID+"_bad")
	buttons := []tgbotapi.InlineKeyboardButton{}
	buttons = append(buttons, goodButton)
	row := []tgbotapi.InlineKeyboardButton{goodButton, badButton}
	markup := tgbotapi.InlineKeyboardMarkup{}
	markup.InlineKeyboard = append(markup.InlineKeyboard, row)
	return markup
}

func getChatID(update tgbotapi.Update) (int64, error) {
	if update.Message != nil {
		return update.Message.Chat.ID, nil
	} else if update.CallbackQuery != nil {
		return update.CallbackQuery.Message.Chat.ID, nil
	} else {
		return 0, errors.New("Не получили ID чата")
	}
}

func getUserID(update tgbotapi.Update) (int, error) {
	if update.Message != nil {
		return update.Message.From.ID, nil
	} else if update.CallbackQuery != nil {
		return update.CallbackQuery.Message.From.ID, nil
	} else {
		return 0, errors.New("Не получили ID пользователя")
	}
}

func getMessageID(update tgbotapi.Update) (int, error) {
	if update.Message != nil {
		return update.Message.MessageID, nil
	} else if update.CallbackQuery != nil {
		return update.CallbackQuery.Message.MessageID, nil
	} else {
		return 0, errors.New("Не получили ID сообщения")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/Felamande/jsvm"

	"github.com/codegangsta/cli"
	"github.com/peterh/liner"

	_ "github.com/Felamande/god/modules/go"
	"github.com/Felamande/god/modules/god"
	"github.com/Felamande/god/modules/hotkey"
	_ "github.com/Felamande/god/modules/log"
	_ "github.com/Felamande/jsvm/module/os"
	_ "github.com/Felamande/jsvm/module/path"
)

var version = "2.0.0-beta"

func init() {
	err := jsvm.Run("god.js")
	if err != nil {
		fmt.Println(err)
	}

}
func main() {

	app := cli.NewApp()
	app.Version = version
	app.Name = filepath.Base(os.Args[0])
	app.HelpName = app.Name
	app.Usage = "An automation tool for Go project."
	app.Action = enterAction
	app.Commands = append(app.Commands, initCmd, watchCmd, reloadCmd, exitCmd)

	for name, CbFunc := range god.SubCmd {
		app.Commands = append(app.Commands, newSubCmd(name, CbFunc))
	}

	go hotkey.ApplyAll()

	god.Init.Call()
	app.Run(os.Args)
}

func enterAction(c *cli.Context) {
	// fmt.Println("enter")
	line := liner.NewLiner()
	defer line.Close()
	line.SetCtrlCAborts(false)

	historyFile, err := os.OpenFile(filepath.Join(os.TempDir(), ".god_hostory"), os.O_CREATE|os.O_RDWR, 0777)
	if err == nil {
		line.ReadHistory(historyFile)
	}

	for {
		argStr, err := line.Prompt(`(god) `)
		argStr = strings.TrimSpace(argStr)
		if len(argStr) == 0 {
			continue
		}
		line.AppendHistory(argStr)
		if err == liner.ErrPromptAborted {
			os.Exit(0)
		} else if err != nil {
			fmt.Println(err)
			continue
		}
		splits := strings.Split(argStr, " ")
		var args = append([]string{}, "god")
		for _, s := range splits {
			if s != "" {
				args = append(args, s)
			}
		}
		if len(args) == 0 {
			continue
		}
		if c.App.Command(args[1]) == nil {
			continue
		}

		c.App.Run(args)

	}

}

func newSubCmd(name string, CbFunc jsvm.Func) cli.Command {
	return cli.Command{
		Name: name,
		Action: func(c *cli.Context) {
			arg := c.Args()
			flags, nargs, err := parseArgs(arg, "-")
			if err != nil {
				fmt.Println(name+": ", err)
				return
			}
			CbFunc.Call(nargs, flags)
		},
		Usage: "user defined command " + name,

		SkipFlagParsing: true,
	}
}
<commit_msg>call god init after enter action.<commit_after>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/Felamande/jsvm"

	"github.com/codegangsta/cli"
	"github.com/peterh/liner"

	_ "github.com/Felamande/god/modules/go"
	"github.com/Felamande/god/modules/god"
	"github.com/Felamande/god/modules/hotkey"
	_ "github.com/Felamande/god/modules/log"
	_ "github.com/Felamande/jsvm/module/os"
	_ "github.com/Felamande/jsvm/module/path"
)

var version = "2.0.0-beta"

func init() {
	err := jsvm.Run("god.js")
	if err != nil {
		fmt.Println(err)
	}

}
func main() {

	app := cli.NewApp()
	app.Version = version
	app.Name = filepath.Base(os.Args[0])
	app.HelpName = app.Name
	app.Usage = "An automation tool for Go project."
	app.Action = enterAction
	app.Commands = append(app.Commands, initCmd, watchCmd, reloadCmd, exitCmd)

	for name, CbFunc := range god.SubCmd {
		app.Commands = append(app.Commands, newSubCmd(name, CbFunc))
	}

	go hotkey.ApplyAll()
	app.Run(os.Args)
}

func enterAction(c *cli.Context) {
	god.Init.Call()
	line := liner.NewLiner()
	defer line.Close()
	line.SetCtrlCAborts(false)

	historyFile, err := os.OpenFile(filepath.Join(os.TempDir(), ".god_hostory"), os.O_CREATE|os.O_RDWR, 0777)
	if err == nil {
		line.ReadHistory(historyFile)
	}

	for {
		argStr, err := line.Prompt(`(god) `)
		argStr = strings.TrimSpace(argStr)
		if len(argStr) == 0 {
			continue
		}
		line.AppendHistory(argStr)
		if err == liner.ErrPromptAborted {
			os.Exit(0)
		} else if err != nil {
			fmt.Println(err)
			continue
		}
		splits := strings.Split(argStr, " ")
		var args = append([]string{}, "god")
		for _, s := range splits {
			if s != "" {
				args = append(args, s)
			}
		}
		if len(args) == 0 {
			continue
		}
		if c.App.Command(args[1]) == nil {
			continue
		}

		c.App.Run(args)

	}

}

func newSubCmd(name string, CbFunc jsvm.Func) cli.Command {
	return cli.Command{
		Name: name,
		Action: func(c *cli.Context) {
			arg := c.Args()
			flags, nargs, err := parseArgs(arg, "-")
			if err != nil {
				fmt.Println(name+": ", err)
				return
			}
			CbFunc.Call(nargs, flags)
		},
		Usage: "user defined command " + name,

		SkipFlagParsing: true,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"

	"github.com/mattn/go-runewidth"
)

func usage() {
	os.Stderr.WriteString(`
Usage: ali [OPTION]... [FILE]...
Align FILE(s), or standard input.

Options:
  -r, --regexp             PATTERN is a regular expression
  -s, --separator=PATTERN  use PATTERN to separate line (default: /\s+/)
  --help                   show this help message
  --version                print the version
`[1:])
}

func version() {
	os.Stderr.WriteString(`
v0.1.0
`[1:])
}

var SPACES = regexp.MustCompile(`\s+`)

type Separator struct {
	re        *regexp.Regexp
	UseRegexp bool
}

func NewSeparator() *Separator {
	return &Separator{}
}

func (s *Separator) String() string {
	return fmt.Sprint(*s)
}

func (s *Separator) Set(expr string) error {
	if !s.UseRegexp {
		expr = regexp.QuoteMeta(expr)
	}
	re, err := regexp.Compile(expr)
	if err != nil {
		return err
	}
	s.re = re
	return nil
}

func (s *Separator) Split(t string) []string {
	if s.re == nil {
		return SPACES.Split(t, -1)
	}

	matches := s.re.FindAllStringIndex(t, -1)
	if len(matches) == 0 {
		return []string{t}
	}

	sls := make([]string, 0, len(matches)*2+1)
	beg, end := 0, 0
	for _, match := range matches {
		end = match[0]
		sls = append(sls, t[beg:end])
		beg, end = match[0], match[1]
		sls = append(sls, t[beg:end])
		beg = match[1]
	}
	sls = append(sls, t[beg:])
	for i := 0; i < len(sls); i++ {
		sls[i] = strings.TrimSpace(sls[i])
	}
	return sls
}

type Aligner struct {
	w     io.Writer
	Sep   *Separator
	lines [][]string
	width []int
}

func NewAligner(w io.Writer) *Aligner {
	return &Aligner{
		w:   w,
		Sep: NewSeparator(),
	}
}

func (a *Aligner) appendLine(s string) {
	sp := a.Sep.Split(s)
	a.lines = append(a.lines, sp)
	if len(sp) == 1 {
		return
	}
	for i, cell := range sp {
		if i == len(a.width) {
			a.width = append(a.width, 0)
		}

		w := runewidth.StringWidth(cell)
		if w > a.width[i] {
			a.width[i] = w
		}
	}
}

func (a *Aligner) ReadAll(r io.Reader) error {
	s := bufio.NewScanner(r)
	for s.Scan() {
		a.appendLine(s.Text())
	}
	return s.Err()
}

func (a *Aligner) format(l []string) string {
	if len(l) == 1 {
		return l[0]
	}
	for i := 0; i < len(l); i++ {
		l[i] = l[i] + strings.Repeat(" ", a.width[i]-runewidth.StringWidth(l[i]))
	}
	return strings.TrimSpace(strings.Join(l, " "))
}

func (a *Aligner) Flush() error {
	for _, l := range a.lines {
		_, err := fmt.Fprintln(a.w, a.format(l))
		if err != nil {
			return err
		}
	}
	return nil
}

func do(a *Aligner, r io.Reader) error {
	if err := a.ReadAll(r); err != nil {
		return err
	}
	return a.Flush()
}

func _main() error {
	isHelp := flag.Bool("help", false, "")
	isVersion := flag.Bool("version", false, "")

	a := NewAligner(os.Stdout)
	flag.Var(a.Sep, "s", "")
	flag.Var(a.Sep, "separator", "")
	flag.BoolVar(&a.Sep.UseRegexp, "r", false, "")
	flag.BoolVar(&a.Sep.UseRegexp, "regexp", false, "")

	flag.Usage = usage
	flag.Parse()
	switch {
	case *isHelp:
		usage()
		return nil
	case *isVersion:
		version()
		return nil
	}

	if flag.NArg() < 1 {
		return do(a, os.Stdin)
	}

	var input []io.Reader
	for _, fname := range flag.Args() {
		f, err := os.Open(fname)
		if err != nil {
			return err
		}
		defer f.Close()
		input = append(input, f)
	}
	return do(a, io.MultiReader(input...))
}

func main() {
	if err := _main(); err != nil {
		fmt.Fprintln(os.Stderr, "ali:", err)
		os.Exit(1)
	}
}
<commit_msg>Enable specify margin<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/mattn/go-runewidth"
)

func usage() {
	os.Stderr.WriteString(`
Usage: ali [OPTION]... [FILE]...
Align FILE(s), or standard input.

Options:
  -m, --margin=FORMAT      join line by FORMAT (default: 1:1)
  -r, --regexp             PATTERN is a regular expression
  -s, --separator=PATTERN  use PATTERN to separate line (default: /\s+/)
  --help                   show this help message
  --version                print the version
`[1:])
}

func version() {
	os.Stderr.WriteString(`
v0.1.0
`[1:])
}

var (
	DIGIT_ONLY             = regexp.MustCompile(`^\d+$`)
	COLON_SEPARATED_DIGITS = regexp.MustCompile(`^(\d+):(\d+)$`)
)

type Margin struct {
	left  int
	right int
}

func NewMargin() *Margin {
	return &Margin{
		left:  1,
		right: 1,
	}
}

func (m *Margin) String() string {
	return fmt.Sprint(*m)
}

func (m *Margin) Set(format string) error {
	switch {
	case DIGIT_ONLY.MatchString(format):
		n, err := strconv.Atoi(format)
		if err != nil {
			return err
		}
		m.left, m.right = n, n
	case COLON_SEPARATED_DIGITS.MatchString(format):
		a := COLON_SEPARATED_DIGITS.FindAllStringSubmatch(format, -1)
		left, err := strconv.Atoi(a[0][1])
		if err != nil {
			return err
		}
		right, err := strconv.Atoi(a[0][2])
		if err != nil {
			return err
		}
		m.left, m.right = left, right
	default:
		return fmt.Errorf("margin:", "invalid format:", format)
	}
	return nil
}

func (m *Margin) Join(a []string) string {
	if len(a) == 0 {
		return ""
	}
	if len(a) == 1 {
		return a[0]
	}
	n := (m.left + m.right) * (len(a) / 2)
	for i := 0; i < len(a); i++ {
		n += len(a[i])
	}
	lm, rm := strings.Repeat(" ", m.left), strings.Repeat(" ", m.right)

	b := make([]byte, n)
	bp := copy(b, a[0])
	for i := 2; i <= len(a); i += 2 {
		bp += copy(b[bp:], lm)
		bp += copy(b[bp:], a[i-1])
		if i != len(a) {
			bp += copy(b[bp:], rm)
			bp += copy(b[bp:], a[i])
		}
	}
	return string(b)
}

var SPACES = regexp.MustCompile(`\s+`)

type Separator struct {
	re        *regexp.Regexp
	UseRegexp bool
}

func NewSeparator() *Separator {
	return &Separator{}
}

func (s *Separator) String() string {
	return fmt.Sprint(*s)
}

func (s *Separator) Set(expr string) error {
	if !s.UseRegexp {
		expr = regexp.QuoteMeta(expr)
	}
	re, err := regexp.Compile(expr)
	if err != nil {
		return err
	}
	s.re = re
	return nil
}

func (s *Separator) Split(t string) []string {
	if s.re == nil {
		return SPACES.Split(t, -1)
	}

	matches := s.re.FindAllStringIndex(t, -1)
	if len(matches) == 0 {
		return []string{t}
	}

	sls := make([]string, 0, len(matches)*2+1)
	beg, end := 0, 0
	for _, match := range matches {
		end = match[0]
		sls = append(sls, t[beg:end])
		beg, end = match[0], match[1]
		sls = append(sls, t[beg:end])
		beg = match[1]
	}
	sls = append(sls, t[beg:])
	for i := 0; i < len(sls); i++ {
		sls[i] = strings.TrimSpace(sls[i])
	}
	return sls
}

type Aligner struct {
	w      io.Writer
	Margin *Margin
	Sep    *Separator
	lines  [][]string
	width  []int
}

func NewAligner(w io.Writer) *Aligner {
	return &Aligner{
		w:      w,
		Margin: NewMargin(),
		Sep:    NewSeparator(),
	}
}

func (a *Aligner) appendLine(s string) {
	sp := a.Sep.Split(s)
	a.lines = append(a.lines, sp)
	if len(sp) == 1 {
		return
	}
	for i, cell := range sp {
		if i == len(a.width) {
			a.width = append(a.width, 0)
		}

		w := runewidth.StringWidth(cell)
		if w > a.width[i] {
			a.width[i] = w
		}
	}
}

func (a *Aligner) ReadAll(r io.Reader) error {
	s := bufio.NewScanner(r)
	for s.Scan() {
		a.appendLine(s.Text())
	}
	return s.Err()
}

func (a *Aligner) format(l []string) string {
	if len(l) == 1 {
		return l[0]
	}
	for i := 0; i < len(l); i++ {
		l[i] = l[i] + strings.Repeat(" ", a.width[i]-runewidth.StringWidth(l[i]))
	}
	return strings.TrimSpace(a.Margin.Join(l))
}

func (a *Aligner) Flush() error {
	for _, l := range a.lines {
		_, err := fmt.Fprintln(a.w, a.format(l))
		if err != nil {
			return err
		}
	}
	return nil
}

func do(a *Aligner, r io.Reader) error {
	if err := a.ReadAll(r); err != nil {
		return err
	}
	return a.Flush()
}

func _main() error {
	isHelp := flag.Bool("help", false, "")
	isVersion := flag.Bool("version", false, "")

	a := NewAligner(os.Stdout)
	flag.Var(a.Margin, "m", "")
	flag.Var(a.Margin, "margin", "")
	flag.Var(a.Sep, "s", "")
	flag.Var(a.Sep, "separator", "")
	flag.BoolVar(&a.Sep.UseRegexp, "r", false, "")
	flag.BoolVar(&a.Sep.UseRegexp, "regexp", false, "")

	flag.Usage = usage
	flag.Parse()
	switch {
	case *isHelp:
		usage()
		return nil
	case *isVersion:
		version()
		return nil
	}

	if flag.NArg() < 1 {
		return do(a, os.Stdin)
	}

	var input []io.Reader
	for _, fname := range flag.Args() {
		f, err := os.Open(fname)
		if err != nil {
			return err
		}
		defer f.Close()
		input = append(input, f)
	}
	return do(a, io.MultiReader(input...))
}

func main() {
	if err := _main(); err != nil {
		fmt.Fprintln(os.Stderr, "ali:", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path"
	"strings"
	"time"
)

var (
	TIMELOG_FILE = path.Join(os.Getenv("HOME"), ".timelog.txt")
)

const (
	AVG_LINE_LENGTH  int64 = 50
	MAX_LOGS_PER_DAY int64 = 20
)

func main() {
	var report = flag.String("report", "none", "Type of report you want to generate, options are full, projects ..")
	var days = flag.Int("d", -1, "Prints the logs for the last n days")
	var lineCount = flag.Int64("n", -1, "Prints the last n logs, prints 10 lines by default")
	flag.Parse()
	//if report is none it means we are logging a task
	switch {
	case len(os.Args) == 1 || *lineCount != -1:
		printLatestLogs(*lineCount)
	case *days != -1:
		printLogsForDays(*days)
	case *report == "none":
		logTask()
	default:
		generateReport(*report)
	}
}

func logTask() {
	logFile, err := os.OpenFile(TIMELOG_FILE, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
	if err != nil {
		log.Fatal("Unable to open timelog file:", err)
	}
	defer logFile.Close()

	msg := strings.Join(os.Args[1:], " ")
	//this is to stay consistent with the gtimelog format
	msg = strings.Replace(msg, "::", "**", 1)
	op := time.Now().Format("2006-01-02 15:04: ") + msg + "\n"
	_, err = logFile.WriteString(op)
	if err != nil {
		log.Fatal("Failed to write:", err)
	}
}

func generateReport(reportType string) {
	fmt.Println("Generating report:", reportType)
}

func filterLogs(logs []*Log, predicate func(*Log) bool) []*Log {
	oplogs := make([]*Log, 0, len(logs))
	for _, l := range logs {
		if predicate(l) {
			oplogs = append(oplogs, l)
		}
	}
	return oplogs
}
func printLogsForDays(days int) {
	tillDate := time.Now().AddDate(0, 0, -1*days)
	tillDate = roundOffToDate(tillDate)
	logs := readLatestLogs(int64(days) * MAX_LOGS_PER_DAY)
	logs = filterLogs(logs, func(l *Log) bool { return l.End.After(tillDate) })
	printSummary(logs)
}

func printLatestLogs(n int64) {

	//this is when no option is given
	if n == -1 {
		n = 10
	}
	logs := readLatestLogs(n)
	lindex := int64(len(logs)) - n
	//if we have less lines than the lineCount, show all the lines
	if lindex < 0 {
		lindex = 0
	}
	printSummary(logs[lindex:])
}

func printSummary(logs []*Log) {
	for _, log := range logs {
		fmt.Println(log.String())
	}
}

func roundOffToDate(t time.Time) time.Time {
	dateString := "20060102"
	t, _ = time.Parse(dateString, t.Format(dateString))
	return t
}
<commit_msg>Refactor roundoff date<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path"
	"strings"
	"time"
)

var (
	TIMELOG_FILE = path.Join(os.Getenv("HOME"), ".timelog.txt")
)

const (
	AVG_LINE_LENGTH  int64 = 50
	MAX_LOGS_PER_DAY int64 = 20
)

func main() {
	var report = flag.String("report", "none", "Type of report you want to generate, options are full, projects ..")
	var days = flag.Int("d", -1, "Prints the logs for the last n days")
	var lineCount = flag.Int64("n", -1, "Prints the last n logs, prints 10 lines by default")
	flag.Parse()
	//if report is none it means we are logging a task
	switch {
	case len(os.Args) == 1 || *lineCount != -1:
		printLatestLogs(*lineCount)
	case *days != -1:
		printLogsForDays(*days)
	case *report == "none":
		logTask()
	default:
		generateReport(*report)
	}
}

func logTask() {
	logFile, err := os.OpenFile(TIMELOG_FILE, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
	if err != nil {
		log.Fatal("Unable to open timelog file:", err)
	}
	defer logFile.Close()

	msg := strings.Join(os.Args[1:], " ")
	//this is to stay consistent with the gtimelog format
	msg = strings.Replace(msg, "::", "**", 1)
	op := time.Now().Format("2006-01-02 15:04: ") + msg + "\n"
	_, err = logFile.WriteString(op)
	if err != nil {
		log.Fatal("Failed to write:", err)
	}
}

func generateReport(reportType string) {
	fmt.Println("Generating report:", reportType)
}

func filterLogs(logs []*Log, predicate func(*Log) bool) []*Log {
	oplogs := make([]*Log, 0, len(logs))
	for _, l := range logs {
		if predicate(l) {
			oplogs = append(oplogs, l)
		}
	}
	return oplogs
}
func printLogsForDays(days int) {
	tillDate := time.Now().AddDate(0, 0, -1*days)
	tillDate = roundOffToDate(tillDate)
	logs := readLatestLogs(int64(days) * MAX_LOGS_PER_DAY)
	logs = filterLogs(logs, func(l *Log) bool { return l.End.After(tillDate) })
	printSummary(logs)
}

func printLatestLogs(n int64) {

	//this is when no option is given
	if n == -1 {
		n = 10
	}
	logs := readLatestLogs(n)
	lindex := int64(len(logs)) - n
	//if we have less lines than the lineCount, show all the lines
	if lindex < 0 {
		lindex = 0
	}
	printSummary(logs[lindex:])
}

func printSummary(logs []*Log) {
	for _, log := range logs {
		fmt.Println(log.String())
	}
}

func roundOffToDate(t time.Time) time.Time {
	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/nbari/violetear"
	"log"
	"net/http"
)

var version, githash string

func catchAll(w http.ResponseWriter, r *http.Request) {
	html := `

		<meta http-equiv="refresh" content="3" />

2+2 = ?

	%s`
	fmt.Fprintf(w, html, version)
}

func main() {
	router := violetear.New()
	router.LogRequests = true

	router.HandleFunc("*", catchAll)

	log.Fatal(http.ListenAndServe(":8000", router))
}
<commit_msg>	modified:   main.go<commit_after>package main

import (
	"fmt"
	"github.com/nbari/violetear"
	"log"
	"net/http"
)

var version, githash string

func catchAll(w http.ResponseWriter, r *http.Request) {
	html := `

		<meta http-equiv="refresh" content="3" />

change me

	%s`
	fmt.Fprintf(w, html, version)
}

func main() {
	router := violetear.New()
	router.LogRequests = true

	router.HandleFunc("*", catchAll)

	log.Fatal(http.ListenAndServe(":8000", router))
}
<|endoftext|>


<commit_before>package main

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

func HelloWorldHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Version 1.1.3")
}

func main() {
    http.HandleFunc("/import", ImportHandler)
    http.HandleFunc("/", HelloWorldHandler)

    log.Println("Connected to mongodb, wait for connections on :8080")
    http.ListenAndServe(":8080", nil)
}
<commit_msg>Add hostname<commit_after>package main

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

func HelloWorldHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Version 1.1.3")
    fmt.Fprintf(w, os.Getenv("HOSTNAME"))
}

func main() {
    http.HandleFunc("/import", ImportHandler)
    http.HandleFunc("/", HelloWorldHandler)

    log.Println("Connected to mongodb, wait for connections on :8080")
    http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	_ "net/http/pprof"
	"os"

	"github.com/Financial-Times/base-ft-rw-app-go"
	"github.com/Financial-Times/go-fthealth/v1a"
	"github.com/Financial-Times/neo-utils-go"
	"github.com/Financial-Times/subjects-rw-neo4j/subjects"
	log "github.com/Sirupsen/logrus"
	"github.com/jawher/mow.cli"
	"github.com/jmcvetta/neoism"
)

func init() {
	log.SetFormatter(new(log.JSONFormatter))
}

func main() {
	app := cli.App("subjetcs-rw-neo4j", "A RESTful API for managing Subjects in neo4j")
	neoURL := app.String(cli.StringOpt{
		Name:   "neo-url",
		Value:  "http://localhost:7474/db/data",
		Desc:   "neo4j endpoint URL",
		EnvVar: "NEO_URL",
	})
	graphiteTCPAddress := app.String(cli.StringOpt{
		Name:   "graphite-tcp-address",
		Value:  "",
		Desc:   "Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally",
		EnvVar: "GRAPHITE_TCP_ADDRESS",
	})
	graphitePrefix := app.String(cli.StringOpt{
		Name:   "graphite-prefix",
		Value:  "",
		Desc:   "Prefix to use. Should start with content, include the environment, and the host name. e.g. coco.pre-prod.subjects-rw-neo4j.1",
		EnvVar: "GRAPHITE_PREFIX",
	})
	port := app.Int(cli.IntOpt{
		Name:   "port",
		Value:  8080,
		Desc:   "Port to listen on",
		EnvVar: "PORT",
	})
	batchSize := app.Int(cli.IntOpt{
		Name:   "batch-size",
		Value:  1024,
		Desc:   "Maximum number of statements to execute per batch",
		EnvVar: "BATCH_SIZE",
	})
	logMetrics := app.Bool(cli.BoolOpt{
		Name:   "log-metrics",
		Value:  false,
		Desc:   "Whether to log metrics. Set to true if running locally and you want metrics output",
		EnvVar: "LOG_METRICS",
	})

	app.Action = func() {
		db, err := neoism.Connect(*neoURL)
		if err != nil {
			log.Errorf("Could not connect to neo4j, error=[%s]\n", err)
		}

		batchRunner := neoutils.NewBatchCypherRunner(neoutils.StringerDb{db}, *batchSize)
		subjectsDriver := subjects.NewCypherSubjectsService(batchRunner, db)

		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)

		engs := map[string]baseftrwapp.Service{
			"subjects": subjectsDriver,
		}

		var checks []v1a.Check
		for _, e := range engs {
			checks = append(checks, makeCheck(e, batchRunner))
		}

		baseftrwapp.RunServer(engs,
			v1a.Handler("ft-subjects_rw_neo4j ServiceModule", "Writes 'subjects' to Neo4j, usually as part of a bulk upload done on a schedule", checks...),
			*port, "subjects-rw-neo4j", "local")
	}

	app.Run(os.Args)
}

func makeCheck(service baseftrwapp.Service, cr neoutils.CypherRunner) v1a.Check {
	return v1a.Check{
		BusinessImpact:   "Cannot read/write subjects via this writer",
		Name:             "Check connectivity to Neo4j - neoUrl is a parameter in hieradata for this service",
		PanicGuide:       "TODO - write panic guide",
		Severity:         1,
		TechnicalSummary: fmt.Sprintf("Cannot connect to Neo4j instance %s with at least one subject loaded in it", cr),
		Checker:          func() (string, error) { return "", service.Check() },
	}
}
<commit_msg>Code review changes<commit_after>package main

import (
	"fmt"
	_ "net/http/pprof"
	"os"

	"github.com/Financial-Times/base-ft-rw-app-go"
	"github.com/Financial-Times/go-fthealth/v1a"
	"github.com/Financial-Times/neo-utils-go"
	"github.com/Financial-Times/subjects-rw-neo4j/subjects"
	log "github.com/Sirupsen/logrus"
	"github.com/jawher/mow.cli"
	"github.com/jmcvetta/neoism"
)

func init() {
	log.SetFormatter(new(log.JSONFormatter))
}

func main() {
	app := cli.App("subjetcs-rw-neo4j", "A RESTful API for managing Subjects in neo4j")
	neoURL := app.String(cli.StringOpt{
		Name:   "neo-url",
		Value:  "http://localhost:7474/db/data",
		Desc:   "neo4j endpoint URL",
		EnvVar: "NEO_URL",
	})
	graphiteTCPAddress := app.String(cli.StringOpt{
		Name:   "graphite-tcp-address",
		Value:  "",
		Desc:   "Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally",
		EnvVar: "GRAPHITE_TCP_ADDRESS",
	})
	graphitePrefix := app.String(cli.StringOpt{
		Name:   "graphite-prefix",
		Value:  "",
		Desc:   "Prefix to use. Should start with content, include the environment, and the host name. e.g. coco.pre-prod.subjects-rw-neo4j.1",
		EnvVar: "GRAPHITE_PREFIX",
	})
	port := app.Int(cli.IntOpt{
		Name:   "port",
		Value:  8080,
		Desc:   "Port to listen on",
		EnvVar: "PORT",
	})
	batchSize := app.Int(cli.IntOpt{
		Name:   "batch-size",
		Value:  1024,
		Desc:   "Maximum number of statements to execute per batch",
		EnvVar: "BATCH_SIZE",
	})
	logMetrics := app.Bool(cli.BoolOpt{
		Name:   "log-metrics",
		Value:  false,
		Desc:   "Whether to log metrics. Set to true if running locally and you want metrics output",
		EnvVar: "LOG_METRICS",
	})

	app.Action = func() {
		db, err := neoism.Connect(*neoURL)
		if err != nil {
			log.Errorf("Could not connect to neo4j, error=[%s]\n", err)
		}

		batchRunner := neoutils.NewBatchCypherRunner(neoutils.StringerDb{db}, *batchSize)
		subjectsDriver := subjects.NewCypherSubjectsService(batchRunner, db)

		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)

		endpoints := map[string]baseftrwapp.Service{
			"subjects": subjectsDriver,
		}

		var checks []v1a.Check
		for _, e := range endpoints {
			checks = append(checks, makeCheck(e, batchRunner))
		}

		baseftrwapp.RunServer(endpoints,
			v1a.Handler("ft-subjects_rw_neo4j ServiceModule", "Writes 'subjects' to Neo4j, usually as part of a bulk upload done on a schedule", checks...),
			*port, "subjects-rw-neo4j", "local")
	}

	app.Run(os.Args)
}

func makeCheck(service baseftrwapp.Service, cr neoutils.CypherRunner) v1a.Check {
	return v1a.Check{
		BusinessImpact:   "Cannot read/write subjects via this writer",
		Name:             "Check connectivity to Neo4j - neoUrl is a parameter in hieradata for this service",
		PanicGuide:       "TODO - write panic guide",
		Severity:         1,
		TechnicalSummary: fmt.Sprintf("Cannot connect to Neo4j instance %s with at least one subject loaded in it", cr),
		Checker:          func() (string, error) { return "", service.Check() },
	}
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	_ "github.com/lib/pq"
)

const (
	migrationsTableExists = `SELECT EXISTS (SELECT 1 FROM information_schema.tables
		WHERE table_name = 'pgmigrate')`
	createMigrationsTable = `CREATE TABLE pgmigrate(id SERIAL, migration TEXT,
		created_at TIMESTAMP DEFAULT now());`
	latestMigration = `SELECT migration FROM pgmigrate ORDER BY migration DESC`
	addMigration    = `INSERT INTO pgmigrate(migration) VALUES($1)`
	removeMigration = `DELETE FROM pgmigrate WHERE migration=$1`
)

const (
	upCommand   = "up"
	downCommand = "down"
)

func main() {
	var dsn string
	var dir string
	flag.StringVar(&dsn, "dsn",
		"postgres://postgres:@localhost/postgres?sslmode=disable",
		"The DSN to use to connect to the database")
	flag.StringVar(&dir, "dir", "sql", "The path to the directory of migration files")
	flag.Parse()

	if len(flag.Args()) != 1 {
		fmt.Fprintln(os.Stderr, "usage: pgmigrate [OPTIONS] COMMAND")
		flag.PrintDefaults()
		os.Exit(1)
	}

	cmd := flag.Args()[0]
	if cmd != upCommand && cmd != downCommand {
		fmt.Fprintf(os.Stderr, "valid commands are '%s' and '%s'\n", upCommand, downCommand)
		os.Exit(1)
	}

	if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
		fmt.Fprintf(os.Stderr, "%s does not exist or isn't a directory: %s\n", dir, err)
		os.Exit(1)
	}

	db, err := sql.Open("postgres", dsn)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not connect to database: %s\n", err)
		os.Exit(1)
	}
	defer db.Close()
	err = db.Ping()
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not ping database: %s\n", err)
		os.Exit(1)
	}

	var exists bool
	err = db.QueryRow(migrationsTableExists).Scan(&exists)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}

	if !exists {
		_, err = db.Exec(createMigrationsTable)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not create migrations table: %s\n", err)
			os.Exit(1)
		}
	}

	var lastMigration string
	err = db.QueryRow(latestMigration).Scan(&lastMigration)
	if err != nil && err != sql.ErrNoRows {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}

	if cmd == downCommand {
		down(db, dir, lastMigration)
		os.Exit(0)
	}

	up(db, dir, lastMigration)
	os.Exit(0)
}

func up(db *sql.DB, dir, lastMigration string) {
	migrations := make([]string, 0)
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error reading the migrations: %s\n", err)
		os.Exit(1)
	}
	for _, f := range files {
		n := f.Name()
		if strings.Contains(n, "up.sql") {
			if strings.TrimSuffix(n, ".up.sql") > lastMigration {
				migrations = append(migrations, n)
			}
		}
	}
	for _, m := range migrations {
		fq, err := ioutil.ReadFile(filepath.Join(dir, m))
		if err != nil {
			fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
			os.Exit(1)
		}
		_, err = db.Exec(string(fq))
		if err != nil {
			fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
			os.Exit(1)
		}
		mn := strings.TrimSuffix(m, ".up.sql")
		_, err = db.Exec(string(addMigration), mn)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error saving migration metadata: %s\n", err)
			fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
			os.Exit(1)
		}
	}
	fmt.Fprintf(os.Stdout, "Applied %d migrations\n", len(migrations))
}

func down(db *sql.DB, dir, lastMigration string) {
	if lastMigration == "" {
		os.Exit(0)
	}
	fn := filepath.Join(dir, lastMigration+".down.sql")
	if _, err := os.Stat(fn); err != nil {
		fmt.Fprintf(os.Stderr, "%s does not exist: %s\n", fn, err)
		os.Exit(1)
	}
	fq, err := ioutil.ReadFile(fn)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
		os.Exit(1)
	}
	_, err = db.Exec(string(fq))
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}
	_, err = db.Exec(string(removeMigration), lastMigration)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
		os.Exit(1)
	}
	fmt.Fprintf(os.Stdout, "Migration %s successfully removed\n", lastMigration)
}
<commit_msg>Extract methods<commit_after>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	_ "github.com/lib/pq"
)

const (
	migrationsTableExists = `SELECT EXISTS (SELECT 1 FROM information_schema.tables
		WHERE table_name = 'pgmigrate')`
	createMigrationsTable = `CREATE TABLE pgmigrate(id SERIAL, migration TEXT,
		created_at TIMESTAMP DEFAULT now());`
	latestMigration = `SELECT migration FROM pgmigrate ORDER BY migration DESC`
	addMigration    = `INSERT INTO pgmigrate(migration) VALUES($1)`
	removeMigration = `DELETE FROM pgmigrate WHERE migration=$1`
)

const (
	upCommand   = "up"
	downCommand = "down"
)

var (
	dsn string
	dir string
	cmd string
)

func main() {
	validateInput()

	db, err := sql.Open("postgres", dsn)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not connect to database: %s\n", err)
		os.Exit(1)
	}
	defer db.Close()

	validateDB(db)

	lastMigration := getLastMigration(db)

	if cmd == downCommand {
		down(db, dir, lastMigration)
		os.Exit(0)
	}

	up(db, dir, lastMigration)
	os.Exit(0)
}

func validateInput() {
	flag.StringVar(&dsn, "dsn",
		"postgres://postgres:@localhost/postgres?sslmode=disable",
		"The DSN to use to connect to the database")
	flag.StringVar(&dir, "dir", "sql", "The path to the directory of migration files")
	flag.Parse()

	if len(flag.Args()) != 1 {
		fmt.Fprintln(os.Stderr, "usage: pgmigrate [OPTIONS] COMMAND")
		flag.PrintDefaults()
		os.Exit(1)
	}

	cmd = flag.Args()[0]
	if cmd != upCommand && cmd != downCommand {
		fmt.Fprintf(os.Stderr, "valid commands are '%s' and '%s'\n", upCommand, downCommand)
		os.Exit(1)
	}

	if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
		fmt.Fprintf(os.Stderr, "%s does not exist or isn't a directory: %s\n", dir, err)
		os.Exit(1)
	}
}

func validateDB(db *sql.DB) {
	err := db.Ping()
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not ping database: %s\n", err)
		os.Exit(1)
	}

	var exists bool
	err = db.QueryRow(migrationsTableExists).Scan(&exists)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}

	if !exists {
		_, err = db.Exec(createMigrationsTable)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not create migrations table: %s\n", err)
			os.Exit(1)
		}
	}
}

func getLastMigration(db *sql.DB) string {
	var lastMigration string
	err := db.QueryRow(latestMigration).Scan(&lastMigration)
	if err != nil && err != sql.ErrNoRows {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}
	return lastMigration
}

func up(db *sql.DB, dir, lastMigration string) {
	migrations := make([]string, 0)
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error reading the migrations: %s\n", err)
		os.Exit(1)
	}
	for _, f := range files {
		n := f.Name()
		if strings.Contains(n, "up.sql") {
			if strings.TrimSuffix(n, ".up.sql") > lastMigration {
				migrations = append(migrations, n)
			}
		}
	}
	for _, m := range migrations {
		fq, err := ioutil.ReadFile(filepath.Join(dir, m))
		if err != nil {
			fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
			os.Exit(1)
		}
		_, err = db.Exec(string(fq))
		if err != nil {
			fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
			os.Exit(1)
		}
		mn := strings.TrimSuffix(m, ".up.sql")
		_, err = db.Exec(string(addMigration), mn)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error saving migration metadata: %s\n", err)
			fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
			os.Exit(1)
		}
	}
	fmt.Fprintf(os.Stdout, "Applied %d migrations\n", len(migrations))
}

func down(db *sql.DB, dir, lastMigration string) {
	if lastMigration == "" {
		os.Exit(0)
	}
	fn := filepath.Join(dir, lastMigration+".down.sql")
	if _, err := os.Stat(fn); err != nil {
		fmt.Fprintf(os.Stderr, "%s does not exist: %s\n", fn, err)
		os.Exit(1)
	}
	fq, err := ioutil.ReadFile(fn)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
		os.Exit(1)
	}
	_, err = db.Exec(string(fq))
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}
	_, err = db.Exec(string(removeMigration), lastMigration)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
		os.Exit(1)
	}
	fmt.Fprintf(os.Stdout, "Migration %s successfully removed\n", lastMigration)
}
<|endoftext|>