<commit_before>package main

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	_ "net/http/pprof"

	kitlog "github.com/go-kit/kit/log"
	"github.com/jacksontj/promxy/config"
	"github.com/jacksontj/promxy/logging"
	"github.com/jacksontj/promxy/proxystorage"
	"github.com/jessevdk/go-flags"
	"github.com/julienschmidt/httprouter"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/promlog"
	"github.com/prometheus/common/route"
	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/notifier"
	"github.com/prometheus/prometheus/promql"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/util/strutil"
	"github.com/prometheus/prometheus/web/api/v1"
	"github.com/sirupsen/logrus"
)

//http://localhost:8080/api/v1/query?query=scrape_duration_seconds%5B1m%5D&time=1507256489.103&_=1507256486365

var opts struct {
	BindAddr   string `long:"bind-addr" description:"address for promxy to listen on" default:":8082"`
	ConfigFile string `long:"config" description:"path to the config file" required:"true"`
	LogLevel   string `long:"log-level" description:"Log level" default:"info"`

	QueryTimeout        time.Duration `long:"query.timeout" description:"Maximum time a query may take before being aborted." default:"2m"`
	QueryMaxConcurrency int           `long:"query.max-concurrency" description:"Maximum number of queries executed concurrently." default:"1000"`
}

func reloadConfig(rls ...proxyconfig.Reloadable) error {
	cfg, err := proxyconfig.ConfigFromFile(opts.ConfigFile)
	if err != nil {
		return fmt.Errorf("Error loading cfg: %v", err)
	}

	failed := false
	for _, rl := range rls {
		if err := rl.ApplyConfig(cfg); err != nil {
			logrus.Errorf("Failed to apply configuration: %v", err)
			failed = true
		}
	}

	if failed {
		return fmt.Errorf("One or more errors occurred while applying new configuration")
	}
	return nil
}

func main() {
	reloadables := make([]proxyconfig.Reloadable, 0)

	parser := flags.NewParser(&opts, flags.Default)
	if _, err := parser.Parse(); err != nil {
		// If the error was from the parser, then we can simply return
		// as Parse() prints the error already
		if _, ok := err.(*flags.Error); ok {
			os.Exit(1)
		}
		logrus.Fatalf("Error parsing flags: %v", err)
	}

	// Use log level
	level := logrus.InfoLevel
	switch strings.ToLower(opts.LogLevel) {
	case "panic":
		level = logrus.PanicLevel
	case "fatal":
		level = logrus.FatalLevel
	case "error":
		level = logrus.ErrorLevel
	case "warn":
		level = logrus.WarnLevel
	case "info":
		level = logrus.InfoLevel
	case "debug":
		level = logrus.DebugLevel
	default:
		logrus.Fatalf("Unknown log level: %s", opts.LogLevel)
	}
	logrus.SetLevel(level)

	// Set the log format to have a reasonable timestamp
	formatter := &logrus.TextFormatter{
		FullTimestamp: true,
	}
	logrus.SetFormatter(formatter)

	var proxyStorage storage.Storage

	ps, err := proxystorage.NewProxyStorage()
	if err != nil {
		logrus.Fatalf("Error creating proxy: %v", err)
	}
	reloadables = append(reloadables, ps)
	proxyStorage = ps

	// TODO: config for the timeout
	engine := promql.NewEngine(nil, prometheus.DefaultRegisterer, opts.QueryMaxConcurrency, opts.QueryTimeout)
	engine.NodeReplacer = ps.NodeReplacer

	// TODO: config option
	u, err := url.Parse("http://localhost:8082")
	if err != nil {
		logrus.Fatalf("Err: %v", err)
	}

	// Alert notifier
	lvl := promlog.AllowedLevel{}
	if err := lvl.Set("info"); err != nil {
		panic(err)
	}
	logger := promlog.New(lvl)
	notifierManager := notifier.NewManager(&notifier.Options{Registerer: prometheus.DefaultRegisterer}, kitlog.With(logger, "component", "notifier"))
	ruleManager := rules.NewManager(&rules.ManagerOptions{
		Context:     context.Background(), // base context for all background tasks
		ExternalURL: u,                    // URL listed as URL for "who fired this alert"
		NotifyFunc:  sendAlerts(notifierManager, u.String()),
		Logger: logger,
	})
	go ruleManager.Run()

	reloadables = append(reloadables, proxyconfig.WrapPromReloadable(&ApplyConfigFunc{func(cfg *config.Config) error {
		// Get all rule files matching the configuration oaths.
		var files []string
		for _, pat := range cfg.RuleFiles {
			fs, err := filepath.Glob(pat)
			if err != nil {
				// The only error can be a bad pattern.
				return fmt.Errorf("error retrieving rule files for %s: %s", pat, err)
			}
			files = append(files, fs...)
		}
		return ruleManager.Update(time.Duration(cfg.GlobalConfig.EvaluationInterval), files)
	}}))

	// TODO:
	cfgFunc := func() config.Config { return config.DefaultConfig }
	// Return 503 until ready (for us there isn't much startup, so this might not need to be implemented
	readyFunc := func(f http.HandlerFunc) http.HandlerFunc { return f }

	api := v1.NewAPI(
		engine,
		proxyStorage,
		nil,
		nil,
		cfgFunc,
		nil,
		readyFunc,
		nil,
		true,
	)

	apiRouter := route.New()
	api.Register(apiRouter.WithPrefix("/api/v1"))

	// API go to their router
	// Some stuff go to me
	// rest proxy

	// Create our router
	r := httprouter.New()

	// TODO: configurable path
	r.HandlerFunc("GET", "/metrics", prometheus.Handler().ServeHTTP)

	r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Have our fallback rules
		if strings.HasPrefix(r.URL.Path, "/api/") {
			apiRouter.ServeHTTP(w, r)
		} else if strings.HasPrefix(r.URL.Path, "/debug") {
			http.DefaultServeMux.ServeHTTP(w, r)
		} else {
			// For all remainingunknown paths we'll simply proxy them to *a* prometheus host
			prometheus.InstrumentHandlerFunc("proxy", ps.ProxyHandler)(w, r)
		}
	})

	if err := reloadConfig(reloadables...); err != nil {
		logrus.Fatalf("Error loading config: %s", err)
	}

	// Set up access logger
	loggedRouter := logging.NewApacheLoggingHandler(r, os.Stdout)
	srv := &http.Server{
		Addr:    opts.BindAddr,
		Handler: loggedRouter,
	}

	go func() {
		logrus.Infof("promxy starting")
		if err := srv.ListenAndServe(); err != nil {
			log.Fatalf("Error listening: %v", err)
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())

	// Wait for reload or termination signals. Start the handler for SIGHUP as
	// early as possible, but ignore it until we are ready to handle reloading
	// our config.
	sigs := make(chan os.Signal)
	defer close(sigs)
	signal.Notify(sigs, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
	for {
		select {
		case sig := <-sigs:
			switch sig {
			case syscall.SIGHUP:
				log.Infof("Reloading config")
				if err := reloadConfig(reloadables...); err != nil {
					log.Errorf("Error reloading config: %s", err)
				}
			case syscall.SIGTERM, syscall.SIGINT:
				log.Infof("promxy exiting")
				cancel()
				srv.Shutdown(ctx)
				return
			default:
				log.Errorf("Uncaught signal: %v", sig)
			}

		}
	}
}

// sendAlerts implements the rules.NotifyFunc for a Notifier.
// It filters any non-firing alerts from the input.
func sendAlerts(n *notifier.Manager, externalURL string) rules.NotifyFunc {
	return func(ctx context.Context, expr string, alerts ...*rules.Alert) error {
		var res []*notifier.Alert

		for _, alert := range alerts {
			// Only send actually firing alerts.
			if alert.State == rules.StatePending {
				continue
			}
			a := &notifier.Alert{
				StartsAt:     alert.FiredAt,
				Labels:       alert.Labels,
				Annotations:  alert.Annotations,
				GeneratorURL: externalURL + strutil.TableLinkForExpression(expr),
			}
			if !alert.ResolvedAt.IsZero() {
				a.EndsAt = alert.ResolvedAt
			}
			res = append(res, a)
		}

		if len(alerts) > 0 {
			n.Send(res...)
		}
		return nil
	}
}

type ApplyConfigFunc struct {
	f func(*config.Config) error
}

func (a *ApplyConfigFunc) ApplyConfig(cfg *config.Config) error {
	return a.f(cfg)
}
<commit_msg>Pass missing options to ruleManager<commit_after>package main

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	_ "net/http/pprof"

	kitlog "github.com/go-kit/kit/log"
	"github.com/jacksontj/promxy/config"
	"github.com/jacksontj/promxy/logging"
	"github.com/jacksontj/promxy/proxystorage"
	"github.com/jessevdk/go-flags"
	"github.com/julienschmidt/httprouter"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/promlog"
	"github.com/prometheus/common/route"
	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/notifier"
	"github.com/prometheus/prometheus/promql"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/storage"
	"github.com/prometheus/prometheus/util/strutil"
	"github.com/prometheus/prometheus/web/api/v1"
	"github.com/sirupsen/logrus"
)

//http://localhost:8080/api/v1/query?query=scrape_duration_seconds%5B1m%5D&time=1507256489.103&_=1507256486365

var opts struct {
	BindAddr   string `long:"bind-addr" description:"address for promxy to listen on" default:":8082"`
	ConfigFile string `long:"config" description:"path to the config file" required:"true"`
	LogLevel   string `long:"log-level" description:"Log level" default:"info"`

	QueryTimeout        time.Duration `long:"query.timeout" description:"Maximum time a query may take before being aborted." default:"2m"`
	QueryMaxConcurrency int           `long:"query.max-concurrency" description:"Maximum number of queries executed concurrently." default:"1000"`
}

func reloadConfig(rls ...proxyconfig.Reloadable) error {
	cfg, err := proxyconfig.ConfigFromFile(opts.ConfigFile)
	if err != nil {
		return fmt.Errorf("Error loading cfg: %v", err)
	}

	failed := false
	for _, rl := range rls {
		if err := rl.ApplyConfig(cfg); err != nil {
			logrus.Errorf("Failed to apply configuration: %v", err)
			failed = true
		}
	}

	if failed {
		return fmt.Errorf("One or more errors occurred while applying new configuration")
	}
	return nil
}

func main() {
	reloadables := make([]proxyconfig.Reloadable, 0)

	parser := flags.NewParser(&opts, flags.Default)
	if _, err := parser.Parse(); err != nil {
		// If the error was from the parser, then we can simply return
		// as Parse() prints the error already
		if _, ok := err.(*flags.Error); ok {
			os.Exit(1)
		}
		logrus.Fatalf("Error parsing flags: %v", err)
	}

	// Use log level
	level := logrus.InfoLevel
	switch strings.ToLower(opts.LogLevel) {
	case "panic":
		level = logrus.PanicLevel
	case "fatal":
		level = logrus.FatalLevel
	case "error":
		level = logrus.ErrorLevel
	case "warn":
		level = logrus.WarnLevel
	case "info":
		level = logrus.InfoLevel
	case "debug":
		level = logrus.DebugLevel
	default:
		logrus.Fatalf("Unknown log level: %s", opts.LogLevel)
	}
	logrus.SetLevel(level)

	// Set the log format to have a reasonable timestamp
	formatter := &logrus.TextFormatter{
		FullTimestamp: true,
	}
	logrus.SetFormatter(formatter)

	var proxyStorage storage.Storage

	ps, err := proxystorage.NewProxyStorage()
	if err != nil {
		logrus.Fatalf("Error creating proxy: %v", err)
	}
	reloadables = append(reloadables, ps)
	proxyStorage = ps

	// TODO: config for the timeout
	engine := promql.NewEngine(nil, prometheus.DefaultRegisterer, opts.QueryMaxConcurrency, opts.QueryTimeout)
	engine.NodeReplacer = ps.NodeReplacer

	// TODO: config option
	u, err := url.Parse("http://localhost:8082")
	if err != nil {
		logrus.Fatalf("Err: %v", err)
	}

	// Alert notifier
	lvl := promlog.AllowedLevel{}
	if err := lvl.Set("info"); err != nil {
		panic(err)
	}
	logger := promlog.New(lvl)
	notifierManager := notifier.NewManager(&notifier.Options{Registerer: prometheus.DefaultRegisterer}, kitlog.With(logger, "component", "notifier"))
	ruleManager := rules.NewManager(&rules.ManagerOptions{
		Context:     context.Background(), // base context for all background tasks
		ExternalURL: u,                    // URL listed as URL for "who fired this alert"
		QueryFunc:   rules.EngineQueryFunc(engine, proxyStorage),
		NotifyFunc:  sendAlerts(notifierManager, u.String()),
		Appendable:  proxyStorage,
		Logger:      logger,
	})
	go ruleManager.Run()

	reloadables = append(reloadables, proxyconfig.WrapPromReloadable(&ApplyConfigFunc{func(cfg *config.Config) error {
		// Get all rule files matching the configuration oaths.
		var files []string
		for _, pat := range cfg.RuleFiles {
			fs, err := filepath.Glob(pat)
			if err != nil {
				// The only error can be a bad pattern.
				return fmt.Errorf("error retrieving rule files for %s: %s", pat, err)
			}
			files = append(files, fs...)
		}
		return ruleManager.Update(time.Duration(cfg.GlobalConfig.EvaluationInterval), files)
	}}))

	// TODO:
	cfgFunc := func() config.Config { return config.DefaultConfig }
	// Return 503 until ready (for us there isn't much startup, so this might not need to be implemented
	readyFunc := func(f http.HandlerFunc) http.HandlerFunc { return f }

	api := v1.NewAPI(
		engine,
		proxyStorage,
		nil,
		nil,
		cfgFunc,
		nil,
		readyFunc,
		nil,
		true,
	)

	apiRouter := route.New()
	api.Register(apiRouter.WithPrefix("/api/v1"))

	// API go to their router
	// Some stuff go to me
	// rest proxy

	// Create our router
	r := httprouter.New()

	// TODO: configurable path
	r.HandlerFunc("GET", "/metrics", prometheus.Handler().ServeHTTP)

	r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Have our fallback rules
		if strings.HasPrefix(r.URL.Path, "/api/") {
			apiRouter.ServeHTTP(w, r)
		} else if strings.HasPrefix(r.URL.Path, "/debug") {
			http.DefaultServeMux.ServeHTTP(w, r)
		} else {
			// For all remainingunknown paths we'll simply proxy them to *a* prometheus host
			prometheus.InstrumentHandlerFunc("proxy", ps.ProxyHandler)(w, r)
		}
	})

	if err := reloadConfig(reloadables...); err != nil {
		logrus.Fatalf("Error loading config: %s", err)
	}

	// Set up access logger
	loggedRouter := logging.NewApacheLoggingHandler(r, os.Stdout)
	srv := &http.Server{
		Addr:    opts.BindAddr,
		Handler: loggedRouter,
	}

	go func() {
		logrus.Infof("promxy starting")
		if err := srv.ListenAndServe(); err != nil {
			log.Fatalf("Error listening: %v", err)
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())

	// Wait for reload or termination signals. Start the handler for SIGHUP as
	// early as possible, but ignore it until we are ready to handle reloading
	// our config.
	sigs := make(chan os.Signal)
	defer close(sigs)
	signal.Notify(sigs, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
	for {
		select {
		case sig := <-sigs:
			switch sig {
			case syscall.SIGHUP:
				log.Infof("Reloading config")
				if err := reloadConfig(reloadables...); err != nil {
					log.Errorf("Error reloading config: %s", err)
				}
			case syscall.SIGTERM, syscall.SIGINT:
				log.Infof("promxy exiting")
				cancel()
				srv.Shutdown(ctx)
				return
			default:
				log.Errorf("Uncaught signal: %v", sig)
			}

		}
	}
}

// sendAlerts implements the rules.NotifyFunc for a Notifier.
// It filters any non-firing alerts from the input.
func sendAlerts(n *notifier.Manager, externalURL string) rules.NotifyFunc {
	return func(ctx context.Context, expr string, alerts ...*rules.Alert) error {
		var res []*notifier.Alert

		for _, alert := range alerts {
			// Only send actually firing alerts.
			if alert.State == rules.StatePending {
				continue
			}
			a := &notifier.Alert{
				StartsAt:     alert.FiredAt,
				Labels:       alert.Labels,
				Annotations:  alert.Annotations,
				GeneratorURL: externalURL + strutil.TableLinkForExpression(expr),
			}
			if !alert.ResolvedAt.IsZero() {
				a.EndsAt = alert.ResolvedAt
			}
			res = append(res, a)
		}

		if len(alerts) > 0 {
			n.Send(res...)
		}
		return nil
	}
}

type ApplyConfigFunc struct {
	f func(*config.Config) error
}

func (a *ApplyConfigFunc) ApplyConfig(cfg *config.Config) error {
	return a.f(cfg)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/go-kit/kit/log"
	"github.com/moov-io/ach/server"
)

/**
CreateFile
curl -d '{"id":"08B751B2","immediateDestination":"9876543210", "immediateDestinationName":"Federal Reserve Bank", "immediateOrigin":"1234567890","immediateOriginName":"My Bank Name"}' -H "Content-Type: application/json" -X POST http://localhost:8080/files/

GetFile
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/1234

GetFiles
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/

DeleteFile
curl -H "Content-Type: application/json" -X DELETE http://localhost:8080/files/1234

CreateBatch
curl -d '{"id":"54321","serviceClassCode":220,"standardEntryClassCode":"WEB","companyName":"Your Company inc","companyIdentification":"121042882","companyEntryDescription":"Online Order","ODFIIdentification":"12104288"}' -H "Content-Type: application/json" -X POST http://localhost:8080/files/08B751B2/batches/

GetBatch
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/08B751B2/batches/54321

GetBatches
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/08B751B2/batches/

DeleteBatch
curl -H "Content-Type: application/json" -X DELETE http://localhost:8080/files/08B751B2/batches/54321
**/

func main() {
	var (
		httpAddr = flag.String("http.addr", ":8080", "HTTP listen address")
	)
	flag.Parse()

	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
		logger = log.With(logger, "caller", log.DefaultCaller)
	}

	var s server.Service
	{
		s = server.NewService(server.NewRepositoryInMemory())
		s = server.LoggingMiddleware(logger)(s)
	}

	var h http.Handler
	{
		h = server.MakeHTTPHandler(s, log.With(logger, "component", "HTTP"))
	}

	// Listen for application termination.
	errs := make(chan error)
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errs <- fmt.Errorf("%s", <-c)
	}()

	go func() {
		logger.Log("transport", "HTTP", "addr", *httpAddr)
		errs <- http.ListenAndServe(*httpAddr, h)
	}()

	logger.Log("exit", <-errs)
}
<commit_msg>cmd/server: misc cleanup to better Go<commit_after>package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/go-kit/kit/log"

	"github.com/moov-io/ach/server"
)

/**
CreateFile
curl -d '{"id":"08B751B2","immediateDestination":"9876543210", "immediateDestinationName":"Federal Reserve Bank", "immediateOrigin":"1234567890","immediateOriginName":"My Bank Name"}' -H "Content-Type: application/json" -X POST http://localhost:8080/files/

GetFile
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/1234

GetFiles
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/

DeleteFile
curl -H "Content-Type: application/json" -X DELETE http://localhost:8080/files/1234

CreateBatch
curl -d '{"id":"54321","serviceClassCode":220,"standardEntryClassCode":"WEB","companyName":"Your Company inc","companyIdentification":"121042882","companyEntryDescription":"Online Order","ODFIIdentification":"12104288"}' -H "Content-Type: application/json" -X POST http://localhost:8080/files/08B751B2/batches/

GetBatch
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/08B751B2/batches/54321

GetBatches
curl -H "Content-Type: application/json" -X GET http://localhost:8080/files/08B751B2/batches/

DeleteBatch
curl -H "Content-Type: application/json" -X DELETE http://localhost:8080/files/08B751B2/batches/54321
**/

var (
	httpAddr = flag.String("http.addr", ":8080", "HTTP listen address")
	logger log.Logger

	svc server.Service
	handler http.Handler
)

func main() {
	flag.Parse()

	// Setup logging, default to stdout
	logger = log.NewLogfmtLogger(os.Stderr)
	logger = log.With(logger, "ts", log.DefaultTimestampUTC)
	logger = log.With(logger, "caller", log.DefaultCaller)

	// Setup underlying ach service
	svc = server.NewService(server.NewRepositoryInMemory())
	svc = server.LoggingMiddleware(logger)(svc)

	// Create HTTP server
	handler = server.MakeHTTPHandler(svc, log.With(logger, "component", "HTTP"))

	// Listen for application termination.
	errs := make(chan error)
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errs <- fmt.Errorf("%s", <-c)
	}()

	go func() {
		logger.Log("transport", "HTTP", "addr", *httpAddr)
		errs <- http.ListenAndServe(*httpAddr, handler)
	}()

	logger.Log("exit", <-errs)
}
<|endoftext|>


<commit_before>// Package meta implements access to FLAC metadata.
package meta

import (
	"errors"
	"io"
	"io/ioutil"
	"os"

	"github.com/mewkiz/pkg/bit"
)

// A Block contains the header and body of a metadata block.
//
// ref: https://www.xiph.org/flac/format.html#metadata_block
type Block struct {
	// Metadata block header.
	Header
	// Metadata block body of type *StreamInfo, *Application, ... etc. Body is
	// initially nil, and gets populated by a call to Block.Parse.
	Body interface{}
	// Underlying io.Reader; limited by the length of the block body.
	lr io.Reader
}

// New creates a new Block for accessing the metadata of r. It reads and parses
// a metadata block header. Call Block.Parse to parse the metadata block body,
// and call Block.Skip to ignore it.
func New(r io.Reader) (block *Block, err error) {
	block = new(Block)
	err = block.parseHeader(r)
	if err != nil {
		return nil, err
	}
	block.lr = io.LimitReader(r, block.Length)
	return block, nil
}

// Parse reads and parses the header and body of a metadata block. Use New for
// additional granularity.
func Parse(r io.Reader) (block *Block, err error) {
	block, err = New(r)
	if err != nil {
		return nil, err
	}
	err = block.Parse()
	if err != nil {
		return nil, err
	}
	return block, nil
}

// Errors returned by Parse.
var (
	ErrReservedType = errors.New("meta.Block.Parse: reserved block type")
	ErrInvalidType  = errors.New("meta.Block.Parse: invalid block type")
)

// Parse reads and parses the metadata block body.
func (block *Block) Parse() error {
	switch block.Type {
	case TypeStreamInfo:
		return block.parseStreamInfo()
	case TypePadding:
		return block.verifyPadding()
	case TypeApplication:
		return block.parseApplication()
	case TypeSeekTable:
		return block.parseSeekTable()
	case TypeVorbisComment:
		return block.parseVorbisComment()
	case TypeCueSheet:
		return block.parseCueSheet()
	case TypePicture:
		return block.parsePicture()
	}
	if block.Type >= 7 && block.Type <= 126 {
		return ErrReservedType
	}
	return ErrInvalidType
}

// Skip ignores the contents of the metadata block body.
func (block *Block) Skip() error {
	if sr, ok := block.lr.(io.Seeker); ok {
		_, err := sr.Seek(0, os.SEEK_END)
		return err
	}
	_, err := io.Copy(ioutil.Discard, block.lr)
	return err
}

// A Header contains information about the type and length of a metadata block.
//
// ref: https://www.xiph.org/flac/format.html#metadata_block_header
type Header struct {
	// Metadata block body type.
	Type Type
	// Length of body data in bytes.
	Length int64
	// IsLast specifies if the block is the last metadata block.
	IsLast bool
}

// parseHeader reads and parses the header of a metadata block.
func (block *Block) parseHeader(r io.Reader) error {
	// 1 bit: IsLast.
	br := bit.NewReader(r)
	x, err := br.Read(1)
	if err != nil {
		return err
	}
	if x != 0 {
		block.IsLast = true
	}

	// 7 bits: Type.
	x, err = br.Read(7)
	if err != nil {
		return err
	}
	block.Type = Type(x)

	// 24 bits: Length.
	x, err = br.Read(24)
	if err != nil {
		return err
	}
	block.Length = int64(x)

	return nil
}

// Type represents the type of a metadata block body.
type Type uint8

// Metadata block body types.
const (
	TypeStreamInfo Type = iota
	TypePadding
	TypeApplication
	TypeSeekTable
	TypeVorbisComment
	TypeCueSheet
	TypePicture
)
<commit_msg>meta: Return partially populated Block on errors from New and Parse.<commit_after>// Package meta implements access to FLAC metadata.
package meta

import (
	"errors"
	"io"
	"io/ioutil"
	"os"

	"github.com/mewkiz/pkg/bit"
)

// A Block contains the header and body of a metadata block.
//
// ref: https://www.xiph.org/flac/format.html#metadata_block
type Block struct {
	// Metadata block header.
	Header
	// Metadata block body of type *StreamInfo, *Application, ... etc. Body is
	// initially nil, and gets populated by a call to Block.Parse.
	Body interface{}
	// Underlying io.Reader; limited by the length of the block body.
	lr io.Reader
}

// New creates a new Block for accessing the metadata of r. It reads and parses
// a metadata block header. Call Block.Parse to parse the metadata block body,
// and call Block.Skip to ignore it.
func New(r io.Reader) (block *Block, err error) {
	block = new(Block)
	err = block.parseHeader(r)
	if err != nil {
		return block, err
	}
	block.lr = io.LimitReader(r, block.Length)
	return block, nil
}

// Parse reads and parses the header and body of a metadata block. Use New for
// additional granularity.
func Parse(r io.Reader) (block *Block, err error) {
	block, err = New(r)
	if err != nil {
		return block, err
	}
	err = block.Parse()
	if err != nil {
		return block, err
	}
	return block, nil
}

// Errors returned by Parse.
var (
	ErrReservedType = errors.New("meta.Block.Parse: reserved block type")
	ErrInvalidType  = errors.New("meta.Block.Parse: invalid block type")
)

// Parse reads and parses the metadata block body.
func (block *Block) Parse() error {
	switch block.Type {
	case TypeStreamInfo:
		return block.parseStreamInfo()
	case TypePadding:
		return block.verifyPadding()
	case TypeApplication:
		return block.parseApplication()
	case TypeSeekTable:
		return block.parseSeekTable()
	case TypeVorbisComment:
		return block.parseVorbisComment()
	case TypeCueSheet:
		return block.parseCueSheet()
	case TypePicture:
		return block.parsePicture()
	}
	if block.Type >= 7 && block.Type <= 126 {
		return ErrReservedType
	}
	return ErrInvalidType
}

// Skip ignores the contents of the metadata block body.
func (block *Block) Skip() error {
	if sr, ok := block.lr.(io.Seeker); ok {
		_, err := sr.Seek(0, os.SEEK_END)
		return err
	}
	_, err := io.Copy(ioutil.Discard, block.lr)
	return err
}

// A Header contains information about the type and length of a metadata block.
//
// ref: https://www.xiph.org/flac/format.html#metadata_block_header
type Header struct {
	// Metadata block body type.
	Type Type
	// Length of body data in bytes.
	Length int64
	// IsLast specifies if the block is the last metadata block.
	IsLast bool
}

// parseHeader reads and parses the header of a metadata block.
func (block *Block) parseHeader(r io.Reader) error {
	// 1 bit: IsLast.
	br := bit.NewReader(r)
	x, err := br.Read(1)
	if err != nil {
		return err
	}
	if x != 0 {
		block.IsLast = true
	}

	// 7 bits: Type.
	x, err = br.Read(7)
	if err != nil {
		return err
	}
	block.Type = Type(x)

	// 24 bits: Length.
	x, err = br.Read(24)
	if err != nil {
		return err
	}
	block.Length = int64(x)

	return nil
}

// Type represents the type of a metadata block body.
type Type uint8

// Metadata block body types.
const (
	TypeStreamInfo Type = iota
	TypePadding
	TypeApplication
	TypeSeekTable
	TypeVorbisComment
	TypeCueSheet
	TypePicture
)
<|endoftext|>


<commit_before>package bot

import (
	"fmt"
	"strings"
	"time"

	"strconv"

	"github.com/nlopes/slack"
)

var (
	prefix        string = "party_"
	sprefix       string = "s_" + prefix
	partyIndexKey string = sprefix + "keys"
	meuName       string = "파티 모집원 메우"
)

func rescheduleParty(bot *Meu) {
	items, err := bot.rc.Keys(prefix + "*")
	if err != nil {
		return
	}
	now := time.Now()
	for _, key := range items {
		saved_time, keyword := parseKey(key)
		if now.After(saved_time) {
			members, _ := bot.rc.SetList(key)
			for _, member := range members {
				bot.rc.SetRemove(sprefix+member, key)
			}
			bot.rc.Erase(key)
		} else {
			scheduleParty(bot, &saved_time, keyword)
			registerToIndex(bot, &saved_time, key)
		}
	}

	bot.rc.SortedSetRemoveRange(partyIndexKey, 0, now.Unix())
}

func parseKey(key string) (time.Time, string) {
	parts := strings.Split(key, "_")
	sec, _ := strconv.Atoi(parts[1])
	return time.Unix(int64(sec), 0), parts[2]
}

func registerToIndex(bot *Meu, date *time.Time, key string) {
	if _, err := bot.rc.SortedSetRank(partyIndexKey, key).Result(); err != nil {
		bot.rc.SortedSetAdd(partyIndexKey, int(date.Unix()), key)
	}
}

func scheduleParty(bot *Meu, date *time.Time, keyword string) {
	bot.cr.AddFunc(fmt.Sprintf("0 %d %d %d %d *", date.Minute(), date.Hour(), date.Day(), date.Month()), alarmFuncGenerator(bot, keyword, partyKey(date, keyword)))
}

func partyKey(date *time.Time, keyword string) string {
	return prefix + strconv.FormatInt(date.Unix(), 10) + "_" + keyword
}

func alarmFuncGenerator(bot *Meu, keyword string, key string) func() {
	return func() {
		list, err := bot.rc.SetList(key)
		bot.rc.Erase(key)
		if err == nil {
			members := make([]string, len(list))
			for i, item := range list {
				members[i] = fmt.Sprintf("<@%s>", item)
				bot.rc.SetRemove(sprefix+item, key)
			}
			bot.PostMessage("#random", fmt.Sprintf("'%s' 파티 10분 전이다 메우. %s", keyword, strings.Join(members, " ")), slack.PostMessageParameters{
				AsUser:    false,
				Username:  meuName,
				IconEmoji: ":meu:",
			})
		}
	}
}

func correctDate(matched []string) *time.Time {
	now := time.Now()
	month, err := strconv.Atoi(matched[0])
	var not_set struct {
		month bool
		day   bool
	}
	if err != nil {
		month = int(now.Month())
		not_set.month = true
	}
	day, err := strconv.Atoi(matched[1])
	if err != nil {
		day = now.Day()
		not_set.day = true
	}
	hour, err := strconv.Atoi(matched[2])
	if err != nil {
		return nil
	}
	min, err := strconv.Atoi(matched[3])
	if err != nil {
		min = 0
	}

	date := time.Date(now.Year(), time.Month(month), day, hour, min, 0, 0, now.Location())
	if date.Before(now) {
		corrected := false
		// first try after 12 hour
		if not_set.day {
			if date.Hour() < 12 {
				date = date.Add(time.Hour * 12)
				if corrected = !date.Before(now); !corrected {
					// reset
					date = date.Add(time.Hour * -12)
				}
			}
			if !corrected {
				date = date.AddDate(0, 0, 1)
			}
		} else if not_set.month {
			date = date.AddDate(0, 1, 0)
		} else {
			date = date.AddDate(1, 0, 0)
		}
		if date.Before(now) {
			return nil
		}
	}

	return &date
}

func event_key_to_slack_attach(key string) slack.Attachment {
	date, keyword := parseKey(key)
	return event_to_slack_attach(key, keyword, &date)
}

func event_to_slack_attach(key string, keyword string, date *time.Time) slack.Attachment {
	return slack.Attachment{
		Fields: []slack.AttachmentField{
			slack.AttachmentField{
				Title: "일시",
				Value: date.String(),
			},
			slack.AttachmentField{
				Title: "이름",
				Value: keyword,
			},
			slack.AttachmentField{
				Title: "파티 ID",
				Value: key,
			},
		},
	}
}

func register_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	keyword := strings.TrimSpace(matched[5])

	date := correctDate(matched[1:])
	if date == nil {
		bot.replySimple(e, "과거에 대해서 파티를 모집할 수 없다 메우")
		return
	}

	key := partyKey(date, keyword)
	inserted := bot.rc.SetAdd(key, e.User)
	registerToIndex(bot, date, key)
	responseData := slack.PostMessageParameters{
		AsUser:    false,
		IconEmoji: ":meu:",
		Username:  meuName,
		Attachments: []slack.Attachment{
			event_to_slack_attach(key, keyword, date),
		},
	}
	if inserted.Val() == 1 {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 파티 대기에 들어갔다 메우", e.User), responseData)
		cardinal := bot.rc.SetCard(key)
		bot.rc.SetAdd(sprefix+e.User, key)
		if cardinal.Val() == 1 {
			scheduleParty(bot, date, keyword)
		}
	} else {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 이미 들어가있는 파티다 메우.", e.User), responseData)
	}
}

func list_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	b_t := correctDate(matched[1:5])
	e_t := correctDate(matched[5:9])
	var (
		end   time.Time
		begin time.Time
	)
	if b_t == nil {
		keys, err := bot.rc.SetList(sprefix + e.User)
		if err != nil || len(keys) == 0 {
			bot.replySimple(e, "대기중인 파티가 없다 메우.")
		} else {
			attachments := make([]slack.Attachment, len(keys))
			for i, key := range keys {
				attachments[i] = event_key_to_slack_attach(key)
			}
			bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 지금 대기중인 파티는 다음과 같다 메우.", e.User), slack.PostMessageParameters{
				AsUser:      false,
				IconEmoji:   ":meu:",
				Username:    meuName,
				Attachments: attachments,
			})
		}
		return
	}
	begin = *b_t
	if e_t == nil {
		d, _ := time.ParseDuration("1h")
		end = begin.Add(d)
		d, _ = time.ParseDuration("-1h")
		begin = begin.Add(d)
	} else {
		end = *e_t
	}

	keys, _ := bot.rc.SortedSetRange(partyIndexKey, begin.Unix(), end.Unix())
	attachments := make([]slack.AttachmentField, len(keys))
	for i, key := range keys {
		t, k := parseKey(key)
		attachments[i] = slack.AttachmentField{
			Title: k,
			Value: t.String(),
		}
	}

	bot.PostMessage(e.Channel,
		fmt.Sprintf("%s ~ %s 사이에 있는 파티 목록은 다음과 같다 메우.", begin.String(), end.String()),
		slack.PostMessageParameters{
			AsUser:    false,
			IconEmoji: ":meu:",
			Username:  meuName,
			Attachments: []slack.Attachment{
				slack.Attachment{
					Fields: attachments,
				},
			},
		})
}

func exit_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	key := strings.TrimSpace(matched[1])
	if bot.rc.SetRemove(key, e.User).Val() == 1 {
		bot.replySimple(e, "성공적으로 파티 대기에서 빠졌다 메우")
		bot.rc.SetRemove(sprefix+e.User, key)
		if bot.rc.SetCard(key).Val() == 0 {
			bot.rc.Erase(key)
			bot.rc.SortedSetRemove(partyIndexKey, key)
		}
	} else {
		bot.replySimple(e, "잘못된 파티 이름이거나 대기중이 아닌 파티이다 메우")
	}
}
<commit_msg>Shorten items<commit_after>package bot

import (
	"fmt"
	"strings"
	"time"

	"strconv"

	"github.com/nlopes/slack"
)

var (
	prefix        string = "party_"
	sprefix       string = "s_" + prefix
	partyIndexKey string = sprefix + "keys"
	meuName       string = "파티 모집원 메우"
)

func rescheduleParty(bot *Meu) {
	items, err := bot.rc.Keys(prefix + "*")
	if err != nil {
		return
	}
	now := time.Now()
	for _, key := range items {
		saved_time, keyword := parseKey(key)
		if now.After(saved_time) {
			members, _ := bot.rc.SetList(key)
			for _, member := range members {
				bot.rc.SetRemove(sprefix+member, key)
			}
			bot.rc.Erase(key)
		} else {
			scheduleParty(bot, &saved_time, keyword)
			registerToIndex(bot, &saved_time, key)
		}
	}

	bot.rc.SortedSetRemoveRange(partyIndexKey, 0, now.Unix())
}

func parseKey(key string) (time.Time, string) {
	parts := strings.Split(key, "_")
	sec, _ := strconv.Atoi(parts[1])
	return time.Unix(int64(sec), 0), parts[2]
}

func registerToIndex(bot *Meu, date *time.Time, key string) {
	if _, err := bot.rc.SortedSetRank(partyIndexKey, key).Result(); err != nil {
		bot.rc.SortedSetAdd(partyIndexKey, int(date.Unix()), key)
	}
}

func scheduleParty(bot *Meu, date *time.Time, keyword string) {
	bot.cr.AddFunc(fmt.Sprintf("0 %d %d %d %d *", date.Minute(), date.Hour(), date.Day(), date.Month()), alarmFuncGenerator(bot, keyword, partyKey(date, keyword)))
}

func partyKey(date *time.Time, keyword string) string {
	return prefix + strconv.FormatInt(date.Unix(), 10) + "_" + keyword
}

func alarmFuncGenerator(bot *Meu, keyword string, key string) func() {
	return func() {
		list, err := bot.rc.SetList(key)
		bot.rc.Erase(key)
		if err == nil {
			members := make([]string, len(list))
			for i, item := range list {
				members[i] = fmt.Sprintf("<@%s>", item)
				bot.rc.SetRemove(sprefix+item, key)
			}
			bot.PostMessage("#random", fmt.Sprintf("'%s' 파티 10분 전이다 메우. %s", keyword, strings.Join(members, " ")), slack.PostMessageParameters{
				AsUser:    false,
				Username:  meuName,
				IconEmoji: ":meu:",
			})
		}
	}
}

func correctDate(matched []string) *time.Time {
	now := time.Now()
	month, err := strconv.Atoi(matched[0])
	var not_set struct {
		month bool
		day   bool
	}
	if err != nil {
		month = int(now.Month())
		not_set.month = true
	}
	day, err := strconv.Atoi(matched[1])
	if err != nil {
		day = now.Day()
		not_set.day = true
	}
	hour, err := strconv.Atoi(matched[2])
	if err != nil {
		return nil
	}
	min, err := strconv.Atoi(matched[3])
	if err != nil {
		min = 0
	}

	date := time.Date(now.Year(), time.Month(month), day, hour, min, 0, 0, now.Location())
	if date.Before(now) {
		corrected := false
		// first try after 12 hour
		if not_set.day {
			if date.Hour() < 12 {
				date = date.Add(time.Hour * 12)
				if corrected = !date.Before(now); !corrected {
					// reset
					date = date.Add(time.Hour * -12)
				}
			}
			if !corrected {
				date = date.AddDate(0, 0, 1)
			}
		} else if not_set.month {
			date = date.AddDate(0, 1, 0)
		} else {
			date = date.AddDate(1, 0, 0)
		}
		if date.Before(now) {
			return nil
		}
	}

	return &date
}

func event_key_to_slack_attach(key string) slack.Attachment {
	date, keyword := parseKey(key)
	return event_to_slack_attach(key, keyword, &date)
}

func event_to_slack_attach(key string, keyword string, date *time.Time) slack.Attachment {
	return slack.Attachment{
		Fields: []slack.AttachmentField{
			slack.AttachmentField{
				Title: "일시",
				Value: date.String(),
			},
			slack.AttachmentField{
				Title: "이름",
				Value: keyword,
				Short: true,
			},
			slack.AttachmentField{
				Title: "파티 ID",
				Value: key,
				Short: true,
			},
		},
	}
}

func register_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	keyword := strings.TrimSpace(matched[5])

	date := correctDate(matched[1:])
	if date == nil {
		bot.replySimple(e, "과거에 대해서 파티를 모집할 수 없다 메우")
		return
	}

	key := partyKey(date, keyword)
	inserted := bot.rc.SetAdd(key, e.User)
	registerToIndex(bot, date, key)
	responseData := slack.PostMessageParameters{
		AsUser:    false,
		IconEmoji: ":meu:",
		Username:  meuName,
		Attachments: []slack.Attachment{
			event_to_slack_attach(key, keyword, date),
		},
	}
	if inserted.Val() == 1 {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 파티 대기에 들어갔다 메우", e.User), responseData)
		cardinal := bot.rc.SetCard(key)
		bot.rc.SetAdd(sprefix+e.User, key)
		if cardinal.Val() == 1 {
			scheduleParty(bot, date, keyword)
		}
	} else {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 이미 들어가있는 파티다 메우.", e.User), responseData)
	}
}

func list_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	b_t := correctDate(matched[1:5])
	e_t := correctDate(matched[5:9])
	var (
		end   time.Time
		begin time.Time
	)
	if b_t == nil {
		keys, err := bot.rc.SetList(sprefix + e.User)
		if err != nil || len(keys) == 0 {
			bot.replySimple(e, "대기중인 파티가 없다 메우.")
		} else {
			attachments := make([]slack.Attachment, len(keys))
			for i, key := range keys {
				attachments[i] = event_key_to_slack_attach(key)
			}
			bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 지금 대기중인 파티는 다음과 같다 메우.", e.User), slack.PostMessageParameters{
				AsUser:      false,
				IconEmoji:   ":meu:",
				Username:    meuName,
				Attachments: attachments,
			})
		}
		return
	}
	begin = *b_t
	if e_t == nil {
		d, _ := time.ParseDuration("1h")
		end = begin.Add(d)
		d, _ = time.ParseDuration("-1h")
		begin = begin.Add(d)
	} else {
		end = *e_t
	}

	keys, _ := bot.rc.SortedSetRange(partyIndexKey, begin.Unix(), end.Unix())
	attachments := make([]slack.AttachmentField, len(keys))
	for i, key := range keys {
		t, k := parseKey(key)
		attachments[i] = slack.AttachmentField{
			Title: k,
			Value: t.String(),
			Short: true,
		}
	}

	bot.PostMessage(e.Channel,
		fmt.Sprintf("%s ~ %s 사이에 있는 파티 목록은 다음과 같다 메우.", begin.String(), end.String()),
		slack.PostMessageParameters{
			AsUser:    false,
			IconEmoji: ":meu:",
			Username:  meuName,
			Attachments: []slack.Attachment{
				slack.Attachment{
					Fields: attachments,
				},
			},
		})
}

func exit_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	key := strings.TrimSpace(matched[1])
	if bot.rc.SetRemove(key, e.User).Val() == 1 {
		bot.replySimple(e, "성공적으로 파티 대기에서 빠졌다 메우")
		bot.rc.SetRemove(sprefix+e.User, key)
		if bot.rc.SetCard(key).Val() == 0 {
			bot.rc.Erase(key)
			bot.rc.SortedSetRemove(partyIndexKey, key)
		}
	} else {
		bot.replySimple(e, "잘못된 파티 이름이거나 대기중이 아닌 파티이다 메우")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"github.com/nexes/goIRC/pkg/irc"
)

func main() {
	var currentRoom string
	client := irc.NewClient("hilljoh", "", "irc.freenode.net")

	client.HandleEventFunc(irc.EventConnect, func(event irc.EventType) {
		fmt.Printf("connection = codeName: %d message: %s\n", event.Code, event.Message)
	})

	client.HandleEventFunc(irc.EventDisconnect, func(event irc.EventType) {
		fmt.Println("disconnect event")
	})

	client.HandleEventFunc(irc.EventPing, func(event irc.EventType) {
		fmt.Println("ping event: ", event.Message)
	})

	client.HandleEventFunc(irc.EventError, func(event irc.EventType) {
		fmt.Printf("Error event code %d: msg: %s\n", event.Code, event.Err.Error())
	})

	client.HandleEventFunc(irc.EventMOTD, func(event irc.EventType) {
		fmt.Printf("%s\n", event.Message)
	})

	client.HandleEventFunc(irc.EventMessage, func(event irc.EventType) {
		fmt.Printf("[%s]: %s - %s\n", event.Room, event.Nick, event.Message)
	})

	client.HandleEventFunc(irc.EventChannelMessage, func(event irc.EventType) {
		switch event.Code {
		case irc.RPL_FORWARDJOIN:
			// sometimes the room will forward to a different named room, e.g #programming -> ##programming
			fmt.Printf("Room forwared to %s. message: %s\n", event.Room, event.Message)
			currentRoom = event.Room
		case irc.RPL_LIST:
			fmt.Printf("List: Room = %s. Topic =  %s\n", event.Room, event.Message)
		case irc.RPL_NAMREPLY:
			fmt.Printf("Name for %s: %s\n", event.Room, event.Message)
		}
	})

	client.HandleEventFunc(irc.EventRoomMessage, func(event irc.EventType) {
		switch event.Code {
		case irc.RPL_ROOMJOIN:
			fmt.Printf("\t%s Joined %s\n", event.Nick, event.Room)
		case irc.RPL_ROOMPART:
			fmt.Printf("\t%s Parted %s\n", event.Nick, event.Room)
		case irc.RPL_ROOMQUIT:
			fmt.Printf("\t%s Quit %s\n", event.Nick, event.Room)
		case irc.RPL_TOPIC:
			fmt.Printf("TOPIC: %s\n\n", event.Message)
		}
	})

	// capture user input
	go func() {
	loop:
		for {
			stdin := bufio.NewReader(os.Stdin)
			input, err := stdin.ReadString('\n')
			if err != nil {
				fmt.Println("error reading stdio ", err)
			}

			args := strings.Split(strings.TrimSpace(input), " ")

			switch strings.ToLower(args[0]) {
			case "/join":
				client.Command(irc.Command{
					Action: "join",
					Args:   args[1:],
				})
			case "/list":
				client.Command(irc.Command{
					Action: "list",
					Args:   args[1:],
				})
			case "/names":
				client.Command(irc.Command{
					Action: "names",
					Args:   args[1:],
				})
			case "/part":
				client.Command(irc.Command{
					Action: "part",
					Args:   args[1:],
				})
			case "/kick":
				client.Command(irc.Command{
					Action: "kick",
					Args:   args[1:],
				})
			case "/quit":
				client.StopConnection()
				break loop

			default:
				message := strings.Join(args, " ")
				client.WriteToTarget(currentRoom, message)
			}
		}
	}()

	client.StartConnection()
}
<commit_msg>small note on example program<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"github.com/nexes/goIRC/pkg/irc"
)

func main() {
	var currentRoom string
	client := irc.NewClient("hilljoh", "", "irc.freenode.net")

	client.HandleEventFunc(irc.EventConnect, func(event irc.EventType) {
		fmt.Printf("connection = codeName: %d message: %s\n", event.Code, event.Message)
	})

	client.HandleEventFunc(irc.EventDisconnect, func(event irc.EventType) {
		fmt.Println("disconnect event")
	})

	client.HandleEventFunc(irc.EventPing, func(event irc.EventType) {
		fmt.Println("ping event: ", event.Message)
	})

	client.HandleEventFunc(irc.EventError, func(event irc.EventType) {
		fmt.Printf("Error event code %d: msg: %s\n", event.Code, event.Err.Error())
	})

	client.HandleEventFunc(irc.EventMOTD, func(event irc.EventType) {
		fmt.Printf("%s\n", event.Message)
	})

	// this event is called when privmsg is received
	client.HandleEventFunc(irc.EventMessage, func(event irc.EventType) {
		fmt.Printf("[%s]: %s - %s\n", event.Room, event.Nick, event.Message)
	})

	client.HandleEventFunc(irc.EventChannelMessage, func(event irc.EventType) {
		switch event.Code {
		case irc.RPL_FORWARDJOIN:
			// sometimes the room will forward to a different named room, e.g #programming -> ##programming
			fmt.Printf("Room forwared to %s. message: %s\n", event.Room, event.Message)
			currentRoom = event.Room
		case irc.RPL_LIST:
			fmt.Printf("List: Room = %s. Topic =  %s\n", event.Room, event.Message)
		case irc.RPL_NAMREPLY:
			fmt.Printf("Name for %s: %s\n", event.Room, event.Message)
		}
	})

	client.HandleEventFunc(irc.EventRoomMessage, func(event irc.EventType) {
		switch event.Code {
		case irc.RPL_ROOMJOIN:
			fmt.Printf("\t%s Joined %s\n", event.Nick, event.Room)
		case irc.RPL_ROOMPART:
			fmt.Printf("\t%s Parted %s\n", event.Nick, event.Room)
		case irc.RPL_ROOMQUIT:
			fmt.Printf("\t%s Quit %s\n", event.Nick, event.Room)
		case irc.RPL_TOPIC:
			fmt.Printf("TOPIC: %s\n\n", event.Message)
		}
	})

	// capture user input
	go func() {
	loop:
		for {
			stdin := bufio.NewReader(os.Stdin)
			input, err := stdin.ReadString('\n')
			if err != nil {
				fmt.Println("error reading stdio ", err)
			}

			args := strings.Split(strings.TrimSpace(input), " ")

			switch strings.ToLower(args[0]) {
			case "/join":
				client.Command(irc.Command{
					Action: "join",
					Args:   args[1:],
				})
			case "/list":
				client.Command(irc.Command{
					Action: "list",
					Args:   args[1:],
				})
			case "/names":
				client.Command(irc.Command{
					Action: "names",
					Args:   args[1:],
				})
			case "/part":
				client.Command(irc.Command{
					Action: "part",
					Args:   args[1:],
				})
			case "/kick":
				client.Command(irc.Command{
					Action: "kick",
					Args:   args[1:],
				})
			case "/quit":
				client.StopConnection()
				break loop

			default:
				message := strings.Join(args, " ")
				client.WriteToTarget(currentRoom, message)
			}
		}
	}()

	client.StartConnection()
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/containers/image/copy"
	"github.com/containers/image/docker/reference"
	"github.com/containers/image/manifest"
	"github.com/containers/image/transports"
	"github.com/containers/image/transports/alltransports"
	"github.com/containers/image/types"
	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/urfave/cli"
)

// contextsFromGlobalOptions returns source and destionation types.SystemContext depending on c.
func contextsFromGlobalOptions(c *cli.Context) (*types.SystemContext, *types.SystemContext, error) {
	sourceCtx, err := contextFromGlobalOptions(c, "src-")
	if err != nil {
		return nil, nil, err
	}

	destinationCtx, err := contextFromGlobalOptions(c, "dest-")
	if err != nil {
		return nil, nil, err
	}

	return sourceCtx, destinationCtx, nil
}

func copyHandler(c *cli.Context) error {
	if len(c.Args()) != 2 {
		return errors.New("Usage: copy source destination")
	}

	policyContext, err := getPolicyContext(c)
	if err != nil {
		return fmt.Errorf("Error loading trust policy: %v", err)
	}
	defer policyContext.Destroy()

	srcRef, err := alltransports.ParseImageName(c.Args()[0])
	if err != nil {
		return fmt.Errorf("Invalid source name %s: %v", c.Args()[0], err)
	}
	destRef, err := alltransports.ParseImageName(c.Args()[1])
	if err != nil {
		return fmt.Errorf("Invalid destination name %s: %v", c.Args()[1], err)
	}
	signBy := c.String("sign-by")
	removeSignatures := c.Bool("remove-signatures")

	sourceCtx, destinationCtx, err := contextsFromGlobalOptions(c)
	if err != nil {
		return err
	}

	var manifestType string
	if c.IsSet("format") {
		switch c.String("format") {
		case "oci":
			manifestType = imgspecv1.MediaTypeImageManifest
		case "v2s1":
			manifestType = manifest.DockerV2Schema1SignedMediaType
		case "v2s2":
			manifestType = manifest.DockerV2Schema2MediaType
		default:
			return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format"))
		}
	}

	if c.IsSet("additional-tag") {
		for _, image := range c.StringSlice("additional-tag") {
			ref, err := reference.ParseNormalizedNamed(image)
			if err != nil {
				return fmt.Errorf("error parsing additional-tag '%s': %v", image, err)
			}
			namedTagged, isNamedTagged := ref.(reference.NamedTagged)
			if !isNamedTagged {
				return fmt.Errorf("additional-tag '%s' must be a tagged reference", image)
			}
			destinationCtx.DockerArchiveAdditionalTags = append(destinationCtx.DockerArchiveAdditionalTags, namedTagged)
		}
	}

	return copy.Image(context.Background(), policyContext, destRef, srcRef, &copy.Options{
		RemoveSignatures:      removeSignatures,
		SignBy:                signBy,
		ReportWriter:          os.Stdout,
		SourceCtx:             sourceCtx,
		DestinationCtx:        destinationCtx,
		ForceManifestMIMEType: manifestType,
	})
}

var copyCmd = cli.Command{
	Name:  "copy",
	Usage: "Copy an IMAGE-NAME from one location to another",
	Description: fmt.Sprintf(`

	Container "IMAGE-NAME" uses a "transport":"details" format.

	Supported transports:
	%s

	See skopeo(1) section "IMAGE NAMES" for the expected format
	`, strings.Join(transports.ListNames(), ", ")),
	ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE",
	Action:    copyHandler,
	// FIXME: Do we need to namespace the GPG aspect?
	Flags: []cli.Flag{
		cli.StringSliceFlag{
			Name:  "additional-tag",
			Usage: "additional tags (supports docker-archive)",
		},
		cli.StringFlag{
			Name:  "authfile",
			Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
		},
		cli.BoolFlag{
			Name:  "remove-signatures",
			Usage: "Do not copy signatures from SOURCE-IMAGE",
		},
		cli.StringFlag{
			Name:  "sign-by",
			Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`",
		},
		cli.StringFlag{
			Name:  "src-creds, screds",
			Value: "",
			Usage: "Use `USERNAME[:PASSWORD]` for accessing the source registry",
		},
		cli.StringFlag{
			Name:  "dest-creds, dcreds",
			Value: "",
			Usage: "Use `USERNAME[:PASSWORD]` for accessing the destination registry",
		},
		cli.StringFlag{
			Name:  "src-cert-dir",
			Value: "",
			Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry or daemon",
		},
		cli.BoolTFlag{
			Name:  "src-tls-verify",
			Usage: "require HTTPS and verify certificates when talking to the container source registry or daemon (defaults to true)",
		},
		cli.StringFlag{
			Name:  "dest-cert-dir",
			Value: "",
			Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry or daemon",
		},
		cli.BoolTFlag{
			Name:  "dest-tls-verify",
			Usage: "require HTTPS and verify certificates when talking to the container destination registry or daemon (defaults to true)",
		},
		cli.StringFlag{
			Name:  "dest-ostree-tmp-dir",
			Value: "",
			Usage: "`DIRECTORY` to use for OSTree temporary files",
		},
		cli.StringFlag{
			Name:  "src-shared-blob-dir",
			Value: "",
			Usage: "`DIRECTORY` to use to fetch retrieved blobs (OCI layout sources only)",
		},
		cli.StringFlag{
			Name:  "dest-shared-blob-dir",
			Value: "",
			Usage: "`DIRECTORY` to use to store retrieved blobs (OCI layout destinations only)",
		},
		cli.StringFlag{
			Name:  "format, f",
			Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)",
		},
		cli.BoolFlag{
			Name:  "dest-compress",
			Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)",
		},
		cli.StringFlag{
			Name:  "src-daemon-host",
			Value: "",
			Usage: "use docker daemon host at `HOST` (docker-daemon sources only)",
		},
		cli.StringFlag{
			Name:  "dest-daemon-host",
			Value: "",
			Usage: "use docker daemon host at `HOST` (docker-daemon destinations only)",
		},
	},
}
<commit_msg>cmd/skopeo: show full help on 'copy' with no args<commit_after>package main

import (
	"context"
	"fmt"
	"os"
	"strings"

	"github.com/containers/image/copy"
	"github.com/containers/image/docker/reference"
	"github.com/containers/image/manifest"
	"github.com/containers/image/transports"
	"github.com/containers/image/transports/alltransports"
	"github.com/containers/image/types"
	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/urfave/cli"
)

// contextsFromGlobalOptions returns source and destionation types.SystemContext depending on c.
func contextsFromGlobalOptions(c *cli.Context) (*types.SystemContext, *types.SystemContext, error) {
	sourceCtx, err := contextFromGlobalOptions(c, "src-")
	if err != nil {
		return nil, nil, err
	}

	destinationCtx, err := contextFromGlobalOptions(c, "dest-")
	if err != nil {
		return nil, nil, err
	}

	return sourceCtx, destinationCtx, nil
}

func copyHandler(c *cli.Context) error {
	if len(c.Args()) != 2 {
		return cli.ShowCommandHelp(c, "copy")
	}

	policyContext, err := getPolicyContext(c)
	if err != nil {
		return fmt.Errorf("Error loading trust policy: %v", err)
	}
	defer policyContext.Destroy()

	srcRef, err := alltransports.ParseImageName(c.Args()[0])
	if err != nil {
		return fmt.Errorf("Invalid source name %s: %v", c.Args()[0], err)
	}
	destRef, err := alltransports.ParseImageName(c.Args()[1])
	if err != nil {
		return fmt.Errorf("Invalid destination name %s: %v", c.Args()[1], err)
	}
	signBy := c.String("sign-by")
	removeSignatures := c.Bool("remove-signatures")

	sourceCtx, destinationCtx, err := contextsFromGlobalOptions(c)
	if err != nil {
		return err
	}

	var manifestType string
	if c.IsSet("format") {
		switch c.String("format") {
		case "oci":
			manifestType = imgspecv1.MediaTypeImageManifest
		case "v2s1":
			manifestType = manifest.DockerV2Schema1SignedMediaType
		case "v2s2":
			manifestType = manifest.DockerV2Schema2MediaType
		default:
			return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format"))
		}
	}

	if c.IsSet("additional-tag") {
		for _, image := range c.StringSlice("additional-tag") {
			ref, err := reference.ParseNormalizedNamed(image)
			if err != nil {
				return fmt.Errorf("error parsing additional-tag '%s': %v", image, err)
			}
			namedTagged, isNamedTagged := ref.(reference.NamedTagged)
			if !isNamedTagged {
				return fmt.Errorf("additional-tag '%s' must be a tagged reference", image)
			}
			destinationCtx.DockerArchiveAdditionalTags = append(destinationCtx.DockerArchiveAdditionalTags, namedTagged)
		}
	}

	return copy.Image(context.Background(), policyContext, destRef, srcRef, &copy.Options{
		RemoveSignatures:      removeSignatures,
		SignBy:                signBy,
		ReportWriter:          os.Stdout,
		SourceCtx:             sourceCtx,
		DestinationCtx:        destinationCtx,
		ForceManifestMIMEType: manifestType,
	})
}

var copyCmd = cli.Command{
	Name:  "copy",
	Usage: "Copy an IMAGE-NAME from one location to another",
	Description: fmt.Sprintf(`

	Container "IMAGE-NAME" uses a "transport":"details" format.

	Supported transports:
	%s

	See skopeo(1) section "IMAGE NAMES" for the expected format
	`, strings.Join(transports.ListNames(), ", ")),
	ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE",
	Action:    copyHandler,
	// FIXME: Do we need to namespace the GPG aspect?
	Flags: []cli.Flag{
		cli.StringSliceFlag{
			Name:  "additional-tag",
			Usage: "additional tags (supports docker-archive)",
		},
		cli.StringFlag{
			Name:  "authfile",
			Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
		},
		cli.BoolFlag{
			Name:  "remove-signatures",
			Usage: "Do not copy signatures from SOURCE-IMAGE",
		},
		cli.StringFlag{
			Name:  "sign-by",
			Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`",
		},
		cli.StringFlag{
			Name:  "src-creds, screds",
			Value: "",
			Usage: "Use `USERNAME[:PASSWORD]` for accessing the source registry",
		},
		cli.StringFlag{
			Name:  "dest-creds, dcreds",
			Value: "",
			Usage: "Use `USERNAME[:PASSWORD]` for accessing the destination registry",
		},
		cli.StringFlag{
			Name:  "src-cert-dir",
			Value: "",
			Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry or daemon",
		},
		cli.BoolTFlag{
			Name:  "src-tls-verify",
			Usage: "require HTTPS and verify certificates when talking to the container source registry or daemon (defaults to true)",
		},
		cli.StringFlag{
			Name:  "dest-cert-dir",
			Value: "",
			Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry or daemon",
		},
		cli.BoolTFlag{
			Name:  "dest-tls-verify",
			Usage: "require HTTPS and verify certificates when talking to the container destination registry or daemon (defaults to true)",
		},
		cli.StringFlag{
			Name:  "dest-ostree-tmp-dir",
			Value: "",
			Usage: "`DIRECTORY` to use for OSTree temporary files",
		},
		cli.StringFlag{
			Name:  "src-shared-blob-dir",
			Value: "",
			Usage: "`DIRECTORY` to use to fetch retrieved blobs (OCI layout sources only)",
		},
		cli.StringFlag{
			Name:  "dest-shared-blob-dir",
			Value: "",
			Usage: "`DIRECTORY` to use to store retrieved blobs (OCI layout destinations only)",
		},
		cli.StringFlag{
			Name:  "format, f",
			Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)",
		},
		cli.BoolFlag{
			Name:  "dest-compress",
			Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)",
		},
		cli.StringFlag{
			Name:  "src-daemon-host",
			Value: "",
			Usage: "use docker daemon host at `HOST` (docker-daemon sources only)",
		},
		cli.StringFlag{
			Name:  "dest-daemon-host",
			Value: "",
			Usage: "use docker daemon host at `HOST` (docker-daemon destinations only)",
		},
	},
}
<|endoftext|>


<commit_before>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command worker runs the vuln worker server.
// It can also be used to perform actions from the command line
// by providing a sub-command.
package main

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"
	"time"

	"golang.org/x/exp/event"
	"golang.org/x/vuln/internal/gitrepo"
	"golang.org/x/vuln/internal/worker"
	"golang.org/x/vuln/internal/worker/log"
	"golang.org/x/vuln/internal/worker/store"
)

var (
	// Flags only for the command-line tool.
	localRepoPath = flag.String("local-cve-repo", "", "path to local repo, instead of cloning remote")
	force         = flag.Bool("force", false, "force an update to happen")
	limit         = flag.Int("limit", 0,
		"limit on number of things to list or issues to create (0 means unlimited)")
	githubTokenFile = flag.String("ghtokenfile", "",
		"path to file containing GitHub access token (for creating issues)")
	knownModuleFile = flag.String("known-module-file", "", "file with list of all known modules")
)

// Config for both the server and the command-line tool.
var cfg worker.Config

func init() {
	flag.StringVar(&cfg.Project, "project", os.Getenv("GOOGLE_CLOUD_PROJECT"), "project ID (required)")
	flag.StringVar(&cfg.Namespace, "namespace", os.Getenv("VULN_WORKER_NAMESPACE"), "Firestore namespace (required)")
	flag.BoolVar(&cfg.UseErrorReporting, "report-errors", os.Getenv("VULN_WORKER_REPORT_ERRORS") == "true",
		"use the error reporting API")
	flag.StringVar(&cfg.IssueRepo, "issue-repo", os.Getenv("VULN_WORKER_ISSUE_REPO"), "repo to create issues in")
}

const pkgsiteURL = "https://pkg.go.dev"

func main() {
	flag.Usage = func() {
		out := flag.CommandLine.Output()
		fmt.Fprintln(out, "usage:")
		fmt.Fprintln(out, "worker FLAGS")
		fmt.Fprintln(out, "  run as a server, listening at the PORT env var")
		fmt.Fprintln(out, "worker FLAGS SUBCOMMAND ...")
		fmt.Fprintln(out, "  run as a command-line tool, executing SUBCOMMAND")
		fmt.Fprintln(out, "  subcommands:")
		fmt.Fprintln(out, "    update COMMIT: perform an update operation")
		fmt.Fprintln(out, "    list-updates: display info about update operations")
		fmt.Fprintln(out, "    list-cves TRIAGE_STATE: display info about CVE records")
		fmt.Fprintln(out, "    create-issues: create issues for CVEs that need them")
		fmt.Fprintln(out, "    show ID1 ID2 ...: display CVE records")
		fmt.Fprintln(out, "flags:")
		flag.PrintDefaults()
	}

	flag.Parse()
	if *githubTokenFile != "" {
		data, err := ioutil.ReadFile(*githubTokenFile)
		if err != nil {
			die("%v", err)
		}
		cfg.GitHubAccessToken = strings.TrimSpace(string(data))
	} else {
		cfg.GitHubAccessToken = os.Getenv("VULN_GITHUB_ACCESS_TOKEN")
	}
	if err := cfg.Validate(); err != nil {
		dieWithUsage("%v", err)
	}

	ctx := log.WithLineLogger(context.Background())
	log.Info(ctx, "config",
		event.String("Project", cfg.Project),
		event.String("Namespace", cfg.Namespace),
		event.String("IssueRepo", cfg.IssueRepo))

	var err error
	cfg.Store, err = store.NewFireStore(ctx, cfg.Project, cfg.Namespace)
	if err != nil {
		die("firestore: %v", err)
	}
	if flag.NArg() > 0 {
		err = runCommandLine(ctx)
	} else {
		err = runServer(ctx)
	}
	if err != nil {
		dieWithUsage("%v", err)
	}
}

func runServer(ctx context.Context) error {
	if os.Getenv("PORT") == "" {
		return errors.New("need PORT")
	}
	if _, err := worker.NewServer(ctx, cfg); err != nil {
		return err
	}
	addr := ":" + os.Getenv("PORT")
	log.Infof(ctx, "Listening on addr %s", addr)
	return fmt.Errorf("listening: %v", http.ListenAndServe(addr, nil))
}

const timeFormat = "2006/01/02 15:04:05"

func runCommandLine(ctx context.Context) error {
	switch flag.Arg(0) {
	case "list-updates":
		return listUpdatesCommand(ctx)
	case "list-cves":
		return listCVEsCommand(ctx, flag.Arg(1))
	case "update":
		if flag.NArg() != 2 {
			return errors.New("usage: update COMMIT")
		}
		return updateCommand(ctx, flag.Arg(1))
	case "create-issues":
		return createIssuesCommand(ctx)
	case "show":
		return showCommand(ctx, flag.Args()[1:])
	default:
		return fmt.Errorf("unknown command: %q", flag.Arg(1))
	}
}

func listUpdatesCommand(ctx context.Context) error {
	recs, err := cfg.Store.ListCommitUpdateRecords(ctx, 0)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "Start\tEnd\tCommit\tID\tCVEs Processed\n")
	for i, r := range recs {
		if *limit > 0 && i >= *limit {
			break
		}
		endTime := "unfinished"
		if !r.EndedAt.IsZero() {
			endTime = r.EndedAt.In(time.Local).Format(timeFormat)
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%d/%d (added %d, modified %d)\n",
			r.StartedAt.In(time.Local).Format(timeFormat),
			endTime,
			r.CommitHash,
			r.ID,
			r.NumProcessed, r.NumTotal, r.NumAdded, r.NumModified)
	}
	return tw.Flush()
}

func listCVEsCommand(ctx context.Context, triageState string) error {
	ts := store.TriageState(triageState)
	if err := ts.Validate(); err != nil {
		return err
	}
	crs, err := cfg.Store.ListCVERecordsWithTriageState(ctx, ts)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "ID\tCVEState\tCommit\tReason\tModule\tIssue\tIssue Created\n")
	for i, r := range crs {
		if *limit > 0 && i >= *limit {
			break
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
			r.ID, r.CVEState, r.CommitHash, r.TriageStateReason, r.Module, r.IssueReference, worker.FormatTime(r.IssueCreatedAt))
	}
	return tw.Flush()
}

func updateCommand(ctx context.Context, commitHash string) error {
	repoPath := gitrepo.CVEListRepoURL
	if *localRepoPath != "" {
		repoPath = *localRepoPath
	}
	if *knownModuleFile != "" {
		if err := populateKnownModules(*knownModuleFile); err != nil {
			return err
		}
	}
	err := worker.UpdateCommit(ctx, repoPath, commitHash, cfg.Store, pkgsiteURL, *force)
	if cerr := new(worker.CheckUpdateError); errors.As(err, &cerr) {
		return fmt.Errorf("%w; use -force to override", cerr)
	}
	return err
}

func populateKnownModules(filename string) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	var mods []string
	scan := bufio.NewScanner(f)
	for scan.Scan() {
		line := strings.TrimSpace(scan.Text())
		if line == "" || line[0] == '#' {
			continue
		}
		mods = append(mods, line)
	}
	if err := scan.Err(); err != nil {
		return err
	}
	worker.SetKnownModules(mods)
	fmt.Printf("set %d known modules\n", len(mods))
	return nil
}

func createIssuesCommand(ctx context.Context) error {
	owner, repoName, err := worker.ParseGithubRepo(cfg.IssueRepo)
	if err != nil {
		return err
	}
	client := worker.NewGithubIssueClient(owner, repoName, cfg.GitHubAccessToken)
	return worker.CreateIssues(ctx, cfg.Store, client, *limit)
}

func showCommand(ctx context.Context, ids []string) error {
	for _, id := range ids {
		r, err := cfg.Store.GetCVERecord(ctx, id)
		if err != nil {
			return err
		}
		if r == nil {
			fmt.Printf("%s not found\n", id)
		} else {
			// Display as JSON because it's an easy way to get nice formatting.
			j, err := json.MarshalIndent(r, "", "\t")
			if err != nil {
				return err
			}
			fmt.Printf("%s\n", j)
		}
	}
	return nil
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	os.Exit(1)
}

func dieWithUsage(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	flag.Usage()
	os.Exit(1)
}
<commit_msg>cmd/worker: create-issues: check for flags<commit_after>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command worker runs the vuln worker server.
// It can also be used to perform actions from the command line
// by providing a sub-command.
package main

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"
	"time"

	"golang.org/x/exp/event"
	"golang.org/x/vuln/internal/gitrepo"
	"golang.org/x/vuln/internal/worker"
	"golang.org/x/vuln/internal/worker/log"
	"golang.org/x/vuln/internal/worker/store"
)

var (
	// Flags only for the command-line tool.
	localRepoPath = flag.String("local-cve-repo", "", "path to local repo, instead of cloning remote")
	force         = flag.Bool("force", false, "force an update to happen")
	limit         = flag.Int("limit", 0,
		"limit on number of things to list or issues to create (0 means unlimited)")
	githubTokenFile = flag.String("ghtokenfile", "",
		"path to file containing GitHub access token (for creating issues)")
	knownModuleFile = flag.String("known-module-file", "", "file with list of all known modules")
)

// Config for both the server and the command-line tool.
var cfg worker.Config

func init() {
	flag.StringVar(&cfg.Project, "project", os.Getenv("GOOGLE_CLOUD_PROJECT"), "project ID (required)")
	flag.StringVar(&cfg.Namespace, "namespace", os.Getenv("VULN_WORKER_NAMESPACE"), "Firestore namespace (required)")
	flag.BoolVar(&cfg.UseErrorReporting, "report-errors", os.Getenv("VULN_WORKER_REPORT_ERRORS") == "true",
		"use the error reporting API")
	flag.StringVar(&cfg.IssueRepo, "issue-repo", os.Getenv("VULN_WORKER_ISSUE_REPO"), "repo to create issues in")
}

const pkgsiteURL = "https://pkg.go.dev"

func main() {
	flag.Usage = func() {
		out := flag.CommandLine.Output()
		fmt.Fprintln(out, "usage:")
		fmt.Fprintln(out, "worker FLAGS")
		fmt.Fprintln(out, "  run as a server, listening at the PORT env var")
		fmt.Fprintln(out, "worker FLAGS SUBCOMMAND ...")
		fmt.Fprintln(out, "  run as a command-line tool, executing SUBCOMMAND")
		fmt.Fprintln(out, "  subcommands:")
		fmt.Fprintln(out, "    update COMMIT: perform an update operation")
		fmt.Fprintln(out, "    list-updates: display info about update operations")
		fmt.Fprintln(out, "    list-cves TRIAGE_STATE: display info about CVE records")
		fmt.Fprintln(out, "    create-issues: create issues for CVEs that need them")
		fmt.Fprintln(out, "    show ID1 ID2 ...: display CVE records")
		fmt.Fprintln(out, "flags:")
		flag.PrintDefaults()
	}

	flag.Parse()
	if *githubTokenFile != "" {
		data, err := ioutil.ReadFile(*githubTokenFile)
		if err != nil {
			die("%v", err)
		}
		cfg.GitHubAccessToken = strings.TrimSpace(string(data))
	} else {
		cfg.GitHubAccessToken = os.Getenv("VULN_GITHUB_ACCESS_TOKEN")
	}
	if err := cfg.Validate(); err != nil {
		dieWithUsage("%v", err)
	}

	ctx := log.WithLineLogger(context.Background())
	log.Info(ctx, "config",
		event.String("Project", cfg.Project),
		event.String("Namespace", cfg.Namespace),
		event.String("IssueRepo", cfg.IssueRepo))

	var err error
	cfg.Store, err = store.NewFireStore(ctx, cfg.Project, cfg.Namespace)
	if err != nil {
		die("firestore: %v", err)
	}
	if flag.NArg() > 0 {
		err = runCommandLine(ctx)
	} else {
		err = runServer(ctx)
	}
	if err != nil {
		dieWithUsage("%v", err)
	}
}

func runServer(ctx context.Context) error {
	if os.Getenv("PORT") == "" {
		return errors.New("need PORT")
	}
	if _, err := worker.NewServer(ctx, cfg); err != nil {
		return err
	}
	addr := ":" + os.Getenv("PORT")
	log.Infof(ctx, "Listening on addr %s", addr)
	return fmt.Errorf("listening: %v", http.ListenAndServe(addr, nil))
}

const timeFormat = "2006/01/02 15:04:05"

func runCommandLine(ctx context.Context) error {
	switch flag.Arg(0) {
	case "list-updates":
		return listUpdatesCommand(ctx)
	case "list-cves":
		return listCVEsCommand(ctx, flag.Arg(1))
	case "update":
		if flag.NArg() != 2 {
			return errors.New("usage: update COMMIT")
		}
		return updateCommand(ctx, flag.Arg(1))
	case "create-issues":
		return createIssuesCommand(ctx)
	case "show":
		return showCommand(ctx, flag.Args()[1:])
	default:
		return fmt.Errorf("unknown command: %q", flag.Arg(1))
	}
}

func listUpdatesCommand(ctx context.Context) error {
	recs, err := cfg.Store.ListCommitUpdateRecords(ctx, 0)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "Start\tEnd\tCommit\tID\tCVEs Processed\n")
	for i, r := range recs {
		if *limit > 0 && i >= *limit {
			break
		}
		endTime := "unfinished"
		if !r.EndedAt.IsZero() {
			endTime = r.EndedAt.In(time.Local).Format(timeFormat)
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%d/%d (added %d, modified %d)\n",
			r.StartedAt.In(time.Local).Format(timeFormat),
			endTime,
			r.CommitHash,
			r.ID,
			r.NumProcessed, r.NumTotal, r.NumAdded, r.NumModified)
	}
	return tw.Flush()
}

func listCVEsCommand(ctx context.Context, triageState string) error {
	ts := store.TriageState(triageState)
	if err := ts.Validate(); err != nil {
		return err
	}
	crs, err := cfg.Store.ListCVERecordsWithTriageState(ctx, ts)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "ID\tCVEState\tCommit\tReason\tModule\tIssue\tIssue Created\n")
	for i, r := range crs {
		if *limit > 0 && i >= *limit {
			break
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
			r.ID, r.CVEState, r.CommitHash, r.TriageStateReason, r.Module, r.IssueReference, worker.FormatTime(r.IssueCreatedAt))
	}
	return tw.Flush()
}

func updateCommand(ctx context.Context, commitHash string) error {
	repoPath := gitrepo.CVEListRepoURL
	if *localRepoPath != "" {
		repoPath = *localRepoPath
	}
	if *knownModuleFile != "" {
		if err := populateKnownModules(*knownModuleFile); err != nil {
			return err
		}
	}
	err := worker.UpdateCommit(ctx, repoPath, commitHash, cfg.Store, pkgsiteURL, *force)
	if cerr := new(worker.CheckUpdateError); errors.As(err, &cerr) {
		return fmt.Errorf("%w; use -force to override", cerr)
	}
	return err
}

func populateKnownModules(filename string) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	var mods []string
	scan := bufio.NewScanner(f)
	for scan.Scan() {
		line := strings.TrimSpace(scan.Text())
		if line == "" || line[0] == '#' {
			continue
		}
		mods = append(mods, line)
	}
	if err := scan.Err(); err != nil {
		return err
	}
	worker.SetKnownModules(mods)
	fmt.Printf("set %d known modules\n", len(mods))
	return nil
}

func createIssuesCommand(ctx context.Context) error {
	if cfg.IssueRepo == "" {
		return errors.New("need -issue-repo")
	}
	if cfg.GitHubAccessToken == "" {
		return errors.New("need -ghtokenfile")
	}
	owner, repoName, err := worker.ParseGithubRepo(cfg.IssueRepo)
	if err != nil {
		return err
	}
	client := worker.NewGithubIssueClient(owner, repoName, cfg.GitHubAccessToken)
	return worker.CreateIssues(ctx, cfg.Store, client, *limit)
}

func showCommand(ctx context.Context, ids []string) error {
	for _, id := range ids {
		r, err := cfg.Store.GetCVERecord(ctx, id)
		if err != nil {
			return err
		}
		if r == nil {
			fmt.Printf("%s not found\n", id)
		} else {
			// Display as JSON because it's an easy way to get nice formatting.
			j, err := json.MarshalIndent(r, "", "\t")
			if err != nil {
				return err
			}
			fmt.Printf("%s\n", j)
		}
	}
	return nil
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	os.Exit(1)
}

func dieWithUsage(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	flag.Usage()
	os.Exit(1)
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Niklas Wolber
// This file is licensed under the MIT license.
// See the LICENSE file for more information.

package main

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

	"github.com/nwolber/xCUTEr/job"

	_ "net/http/pprof"
)

func main() {
	// jobDir, sshTTL, file, logFile, perf, once, quiet := config()
	_, sshTTL, file, _, perf, _, _ := config()

	if perf != "" {
		go func() {
			log.Println(http.ListenAndServe(perf, nil))
		}()
	}

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, os.Kill)

	// x, err := xCUTEr.New(jobDir, sshTTL, file, logFile, once, quiet)
	// if err != nil {
	// 	log.Fatalln(err)
	// }
	// x.Start()
	job.InitializeSSHClientStore(sshTTL)

	c, _ := job.ReadConfig(file)
	info, _ := job.Instrument(c)

	f, updates := info.GetFlunc()

	go f(context.WithValue(context.Background(), "output", os.Stdout))

	tree := ""
loop:
	for {
		select {
		// case <-x.Done:
		case text, ok := <-updates:
			if !ok {
				break loop
			}

			// fmt.Println(text)
			tree = text
		case s := <-signals:
			fmt.Println("Got signal:", s)
			// x.Stop()
			// x.Cancel()
			break loop
		}
	}
	log.Println("fin")

	fmt.Println(tree)
}
<commit_msg>Restore xCUTEr/main.go<commit_after>// Copyright (c) 2016 Niklas Wolber
// This file is licensed under the MIT license.
// See the LICENSE file for more information.

package main

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

	_ "net/http/pprof"

	"github.com/nwolber/xCUTEr"
)

func main() {
	jobDir, sshTTL, file, logFile, perf, once, quiet := config()

	if perf != "" {
		go func() {
			log.Println(http.ListenAndServe(perf, nil))
		}()
	}

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, os.Kill)

	x, err := xCUTEr.New(jobDir, sshTTL, file, logFile, once, quiet)
	if err != nil {
		log.Fatalln(err)
	}
	x.Start()

	select {
	case <-x.Done:
	case s := <-signals:
		fmt.Println("Got signal:", s)
		x.Stop()
		x.Cancel()
	}

	log.Println("fin")
}
<|endoftext|>


<commit_before>package completion

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"unicode"

	"../conio"
	"../lua"
)

type CompletionList struct {
	AllLine string
	List    []string
	RawWord string // have quotation
	Word    string
	Pos     int
}

func listUpComplete(this *conio.Buffer) (*CompletionList, error) {
	var err error
	rv := CompletionList{}

	// environment completion.
	rv.AllLine = this.String()
	rv.List, rv.Pos, err = listUpEnv(rv.AllLine)
	if len(rv.List) > 0 && rv.Pos >= 0 && err == nil {
		rv.RawWord = rv.AllLine[rv.Pos:]
		rv.Word = rv.RawWord
		return &rv, nil
	}

	// filename or commandname completion
	rv.RawWord, rv.Pos = this.CurrentWord()
	rv.Word = strings.Replace(rv.RawWord, "\"", "", -1)
	if rv.Pos > 0 {
		rv.List, err = listUpFiles(rv.Word)
	} else {
		rv.List, err = listUpCommands(rv.Word)
	}
	L, Lok := this.Session.Tag.(*lua.Lua)
	if !Lok {
		panic("conio.LineEditor.Tag is not *lua.Lua")
	}
	L.GetGlobal("nyagos")
	L.GetField(-1, "completion_hook")
	L.Remove(-2) // remove nyagos-table
	if L.IsFunction(-1) {
		L.NewTable()
		L.PushString(rv.RawWord)
		L.SetField(-2, "rawword")
		L.Push(rv.Pos + 1)
		L.SetField(-2, "pos")
		L.PushString(rv.AllLine)
		L.SetField(-2, "text")
		L.PushString(rv.Word)
		L.SetField(-2, "word")
		L.NewTable()
		for key, val := range rv.List {
			L.Push(key)
			L.PushString(val)
			L.SetTable(-3)
		}
		L.SetField(-2, "list")
		if err := L.Call(1, 1); err != nil {
			fmt.Println(err)
		}
		if L.IsTable(-1) {
			list := make([]string, 0, len(rv.List)+32)
			wordUpr := strings.ToUpper(rv.Word)
			for i := 1; true; i++ {
				L.Push(i)
				L.GetTable(-2)
				str, strErr := L.ToString(-1)
				L.Pop(1)
				if strErr != nil || str == "" {
					break
				}
				strUpr := strings.ToUpper(str)
				if strings.HasPrefix(strUpr, wordUpr) {
					list = append(list, str)
				}
			}
			if len(list) > 0 {
				rv.List = list
			}
		}
	}
	L.Pop(1) // remove something not function or result-table
	return &rv, err
}

func KeyFuncCompletionList(this *conio.Buffer) conio.Result {
	comp, err := listUpComplete(this)
	if err != nil {
		return conio.CONTINUE
	}
	fmt.Print("\n")
	conio.BoxPrint(comp.List, os.Stdout)
	this.RepaintAll()
	return conio.CONTINUE
}

func CommonPrefix(list []string) string {
	if len(list) < 1 {
		return ""
	}
	common := list[0]
	for _, f := range list[1:] {
		cr := strings.NewReader(common)
		fr := strings.NewReader(f)
		i := 0
		var buffer bytes.Buffer
		for {
			ch, _, cerr := cr.ReadRune()
			fh, _, ferr := fr.ReadRune()
			if cerr != nil || ferr != nil || unicode.ToUpper(ch) != unicode.ToUpper(fh) {
				break
			}
			buffer.WriteRune(ch)
			i++
		}
		common = buffer.String()
	}
	return common
}

func endWithRoot(path string) bool {
	return strings.HasSuffix(path, "\\") || strings.HasSuffix(path, "/")
}

func KeyFuncCompletion(this *conio.Buffer) conio.Result {
	comp, err := listUpComplete(this)
	if err != nil || comp.List == nil || len(comp.List) <= 0 {
		return conio.CONTINUE
	}

	slashToBackSlash := true
	firstFoundSlashPos := strings.IndexRune(comp.Word, '/')
	firstFoundBackSlashPos := strings.IndexRune(comp.Word, '\\')
	if firstFoundSlashPos >= 0 && firstFoundBackSlashPos >= 0 && firstFoundSlashPos < firstFoundBackSlashPos {
		slashToBackSlash = false
	}

	commonStr := CommonPrefix(comp.List)
	needQuote := strings.ContainsRune(comp.Word, '"')
	if !needQuote {
		for _, node := range comp.List {
			if strings.ContainsAny(node, " &") {
				needQuote = true
				break
			}
		}
	}
	if needQuote {
		buffer := make([]byte, 0, 100)
		buffer = append(buffer, '"')
		buffer = append(buffer, commonStr...)
		if len(comp.List) == 1 && !endWithRoot(comp.List[0]) {
			buffer = append(buffer, '"')
		}
		commonStr = string(buffer)
	}
	if len(comp.List) == 1 && !endWithRoot(commonStr) {
		commonStr += " "
	}
	if slashToBackSlash {
		commonStr = strings.Replace(commonStr, "/", "\\", -1)
	}
	if comp.RawWord == commonStr {
		fmt.Print("\n")
		conio.BoxPrint(comp.List, os.Stdout)
		this.RepaintAll()
		return conio.CONTINUE
	}
	this.ReplaceAndRepaint(comp.Pos, commonStr)
	return conio.CONTINUE
}
<commit_msg>Fix #53 Start index on the table given nyagos.completion_hook was wrong<commit_after>package completion

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"unicode"

	"../conio"
	"../lua"
)

type CompletionList struct {
	AllLine string
	List    []string
	RawWord string // have quotation
	Word    string
	Pos     int
}

func listUpComplete(this *conio.Buffer) (*CompletionList, error) {
	var err error
	rv := CompletionList{}

	// environment completion.
	rv.AllLine = this.String()
	rv.List, rv.Pos, err = listUpEnv(rv.AllLine)
	if len(rv.List) > 0 && rv.Pos >= 0 && err == nil {
		rv.RawWord = rv.AllLine[rv.Pos:]
		rv.Word = rv.RawWord
		return &rv, nil
	}

	// filename or commandname completion
	rv.RawWord, rv.Pos = this.CurrentWord()
	rv.Word = strings.Replace(rv.RawWord, "\"", "", -1)
	if rv.Pos > 0 {
		rv.List, err = listUpFiles(rv.Word)
	} else {
		rv.List, err = listUpCommands(rv.Word)
	}
	L, Lok := this.Session.Tag.(*lua.Lua)
	if !Lok {
		panic("conio.LineEditor.Tag is not *lua.Lua")
	}
	L.GetGlobal("nyagos")
	L.GetField(-1, "completion_hook")
	L.Remove(-2) // remove nyagos-table
	if L.IsFunction(-1) {
		L.NewTable()
		L.PushString(rv.RawWord)
		L.SetField(-2, "rawword")
		L.Push(rv.Pos + 1)
		L.SetField(-2, "pos")
		L.PushString(rv.AllLine)
		L.SetField(-2, "text")
		L.PushString(rv.Word)
		L.SetField(-2, "word")
		L.NewTable()
		for key, val := range rv.List {
			L.Push(1+key)
			L.PushString(val)
			L.SetTable(-3)
		}
		L.SetField(-2, "list")
		if err := L.Call(1, 1); err != nil {
			fmt.Println(err)
		}
		if L.IsTable(-1) {
			list := make([]string, 0, len(rv.List)+32)
			wordUpr := strings.ToUpper(rv.Word)
			for i := 1; true; i++ {
				L.Push(i)
				L.GetTable(-2)
				str, strErr := L.ToString(-1)
				L.Pop(1)
				if strErr != nil || str == "" {
					break
				}
				strUpr := strings.ToUpper(str)
				if strings.HasPrefix(strUpr, wordUpr) {
					list = append(list, str)
				}
			}
			if len(list) > 0 {
				rv.List = list
			}
		}
	}
	L.Pop(1) // remove something not function or result-table
	return &rv, err
}

func KeyFuncCompletionList(this *conio.Buffer) conio.Result {
	comp, err := listUpComplete(this)
	if err != nil {
		return conio.CONTINUE
	}
	fmt.Print("\n")
	conio.BoxPrint(comp.List, os.Stdout)
	this.RepaintAll()
	return conio.CONTINUE
}

func CommonPrefix(list []string) string {
	if len(list) < 1 {
		return ""
	}
	common := list[0]
	for _, f := range list[1:] {
		cr := strings.NewReader(common)
		fr := strings.NewReader(f)
		i := 0
		var buffer bytes.Buffer
		for {
			ch, _, cerr := cr.ReadRune()
			fh, _, ferr := fr.ReadRune()
			if cerr != nil || ferr != nil || unicode.ToUpper(ch) != unicode.ToUpper(fh) {
				break
			}
			buffer.WriteRune(ch)
			i++
		}
		common = buffer.String()
	}
	return common
}

func endWithRoot(path string) bool {
	return strings.HasSuffix(path, "\\") || strings.HasSuffix(path, "/")
}

func KeyFuncCompletion(this *conio.Buffer) conio.Result {
	comp, err := listUpComplete(this)
	if err != nil || comp.List == nil || len(comp.List) <= 0 {
		return conio.CONTINUE
	}

	slashToBackSlash := true
	firstFoundSlashPos := strings.IndexRune(comp.Word, '/')
	firstFoundBackSlashPos := strings.IndexRune(comp.Word, '\\')
	if firstFoundSlashPos >= 0 && firstFoundBackSlashPos >= 0 && firstFoundSlashPos < firstFoundBackSlashPos {
		slashToBackSlash = false
	}

	commonStr := CommonPrefix(comp.List)
	needQuote := strings.ContainsRune(comp.Word, '"')
	if !needQuote {
		for _, node := range comp.List {
			if strings.ContainsAny(node, " &") {
				needQuote = true
				break
			}
		}
	}
	if needQuote {
		buffer := make([]byte, 0, 100)
		buffer = append(buffer, '"')
		buffer = append(buffer, commonStr...)
		if len(comp.List) == 1 && !endWithRoot(comp.List[0]) {
			buffer = append(buffer, '"')
		}
		commonStr = string(buffer)
	}
	if len(comp.List) == 1 && !endWithRoot(commonStr) {
		commonStr += " "
	}
	if slashToBackSlash {
		commonStr = strings.Replace(commonStr, "/", "\\", -1)
	}
	if comp.RawWord == commonStr {
		fmt.Print("\n")
		conio.BoxPrint(comp.List, os.Stdout)
		this.RepaintAll()
		return conio.CONTINUE
	}
	this.ReplaceAndRepaint(comp.Pos, commonStr)
	return conio.CONTINUE
}
<|endoftext|>


<commit_before>package fileupdater

import "github.com/webx-top/com"

type Options struct {
	TableName  string       // 数据表名称
	FieldName  string       // 数据表字段名
	SameFields []string     // 数据表类似字段名
	Embedded   bool         // 是否为嵌入图片
	Seperator  string       // 文件字段中多个文件路径之间的分隔符，空字符串代表为单个文件
	Callback   CallbackFunc `json:"-" xml:"-"`
	FieldValue FieldValue   `json:"-" xml:"-"`
}

type OptionSetter func(o *Options)

func OptTableName(tableName string) OptionSetter {
	return func(o *Options) {
		o.TableName = tableName
	}
}

func OptFieldName(fieldName string) OptionSetter {
	return func(o *Options) {
		o.FieldName = fieldName
	}
}

func OptSameFields(sameFields ...string) OptionSetter {
	return func(o *Options) {
		o.SameFields = sameFields
	}
}

func OptEmbedded(embedded bool) OptionSetter {
	return func(o *Options) {
		o.Embedded = embedded
	}
}

func OptSeperator(seperator string) OptionSetter {
	return func(o *Options) {
		o.Seperator = seperator
	}
}

func OptCallback(callbackFunc CallbackFunc) OptionSetter {
	return func(o *Options) {
		o.Callback = callbackFunc
	}
}

func OptFieldValue(field string, value ValueFunc) OptionSetter {
	return func(o *Options) {
		if o.FieldValue == nil {
			o.FieldValue = FieldValueWith(field, value)
			return
		}
		o.FieldValue.Set(field, value)
		if !com.InSlice(field, o.SameFields) {
			o.SameFields = append(o.SameFields, field)
		}
	}
}
<commit_msg>improved<commit_after>package fileupdater

import "github.com/webx-top/com"

type Options struct {
	TableName  string       // 数据表名称
	FieldName  string       // 数据表字段名
	SameFields []string     // 数据表类似字段名
	Embedded   bool         // 是否为嵌入图片
	Seperator  string       // 文件字段中多个文件路径之间的分隔符，空字符串代表为单个文件
	Callback   CallbackFunc `json:"-" xml:"-"`
	FieldValue FieldValue   `json:"-" xml:"-"`
}

type OptionSetter func(o *Options)

func OptTableName(tableName string) OptionSetter {
	return func(o *Options) {
		o.TableName = tableName
	}
}

func OptFieldName(fieldName string) OptionSetter {
	return func(o *Options) {
		o.FieldName = fieldName
	}
}

func OptSameFields(sameFields ...string) OptionSetter {
	return func(o *Options) {
		o.SameFields = sameFields
	}
}

func OptEmbedded(embedded bool) OptionSetter {
	return func(o *Options) {
		o.Embedded = embedded
	}
}

func OptSeperator(seperator string) OptionSetter {
	return func(o *Options) {
		o.Seperator = seperator
	}
}

func OptCallback(callbackFunc CallbackFunc) OptionSetter {
	return func(o *Options) {
		o.Callback = callbackFunc
	}
}

func OptFieldValue(field string, value ValueFunc) OptionSetter {
	return func(o *Options) {
		if o.FieldValue == nil {
			o.FieldValue = FieldValueWith(field, value)
		} else {
			o.FieldValue.Set(field, value)
		}
		if !com.InSlice(field, o.SameFields) {
			o.SameFields = append(o.SameFields, field)
		}
	}
}
<|endoftext|>