<commit_before>package qmp

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/digitalocean/go-qemu/qmp"

	"github.com/lxc/lxd/shared/logger"
)

var monitors = map[string]*Monitor{}
var monitorsLock sync.Mutex

// RingbufSize is the size of the agent serial ringbuffer in bytes
var RingbufSize = 16

// Monitor represents a QMP monitor.
type Monitor struct {
	path string
	qmp  *qmp.SocketMonitor

	agentReady    bool
	disconnected  bool
	chDisconnect  chan struct{}
	eventHandler  func(name string, data map[string]interface{})
	serialCharDev string
}

// start handles the background goroutines for event handling and monitoring the ringbuffer
func (m *Monitor) start() error {
	// Ringbuffer monitoring function.
	checkBuffer := func() {
		// Prepare the response.
		var resp struct {
			Return string `json:"return"`
		}

		// Read the ringbuffer.
		err := m.run("ringbuf-read", fmt.Sprintf("{'device': '%s', 'size': %d, 'format': 'utf8'}", m.serialCharDev, RingbufSize), &resp)
		if err != nil {
			return
		}

		// Extract the last entry.
		entries := strings.Split(resp.Return, "\n")
		if len(entries) > 1 {
			status := entries[len(entries)-2]

			if status == "STARTED" {
				m.agentReady = true
			} else if status == "STOPPED" {
				m.agentReady = false
			}
		}
	}

	// Start event monitoring go routine.
	chEvents, err := m.qmp.Events(context.Background())
	if err != nil {
		return err
	}

	go func() {
		// Initial read from the ringbuffer.
		go checkBuffer()

		for {
			// Wait for an event, disconnection or timeout.
			select {
			case <-m.chDisconnect:
				return
			case e, more := <-chEvents:
				// Deliver non-empty events to the event handler.
				if m.eventHandler != nil && e.Event != "" {
					go m.eventHandler(e.Event, e.Data)
				}

				// Event channel is closed, lets disconnect.
				if !more {
					m.Disconnect()
					return
				}

				if e.Event == "" {
					logger.Warnf("Unexpected empty event received from qmp event channel")
					time.Sleep(time.Second) // Don't spin if we receive a lot of these.
					continue
				}

				// Check if the ringbuffer was updated (non-blocking).
				go checkBuffer()
			case <-time.After(10 * time.Second):
				// Check if the ringbuffer was updated (non-blocking).
				go checkBuffer()

				continue
			}
		}
	}()

	return nil
}

// ping is used to validate if the QMP socket is still active.
func (m *Monitor) ping() error {
	// Check if disconnected
	if m.disconnected {
		return ErrMonitorDisconnect
	}

	// Query the capabilities to validate the monitor.
	_, err := m.qmp.Run([]byte("{'execute': 'query-version'}"))
	if err != nil {
		m.Disconnect()
		return ErrMonitorDisconnect
	}

	return nil
}

// run executes a command.
func (m *Monitor) run(cmd string, args string, resp interface{}) error {
	// Check if disconnected
	if m.disconnected {
		return ErrMonitorDisconnect
	}

	// Run the command.
	var out []byte
	var err error
	if args == "" {
		out, err = m.qmp.Run([]byte(fmt.Sprintf("{'execute': '%s'}", cmd)))
	} else {
		out, err = m.qmp.Run([]byte(fmt.Sprintf("{'execute': '%s', 'arguments': %s}", cmd, args)))
	}
	if err != nil {
		// Confirm the daemon didn't die.
		errPing := m.ping()
		if errPing != nil {
			return errPing
		}

		return err
	}

	// Decode the response if needed.
	if resp != nil {
		err = json.Unmarshal(out, &resp)
		if err != nil {
			// Confirm the daemon didn't die.
			errPing := m.ping()
			if errPing != nil {
				return errPing
			}

			return ErrMonitorBadReturn
		}
	}

	return nil
}

// Connect creates or retrieves an existing QMP monitor for the path.
func Connect(path string, serialCharDev string, eventHandler func(name string, data map[string]interface{})) (*Monitor, error) {
	monitorsLock.Lock()
	defer monitorsLock.Unlock()

	// Look for an existing monitor.
	monitor, ok := monitors[path]
	if ok {
		monitor.eventHandler = eventHandler
		return monitor, nil
	}

	// Setup the connection.
	qmpConn, err := qmp.NewSocketMonitor("unix", path, time.Second)
	if err != nil {
		return nil, err
	}

	chError := make(chan error, 1)
	go func() {
		err = qmpConn.Connect()
		chError <- err
	}()

	select {
	case err := <-chError:
		if err != nil {
			return nil, err
		}
	case <-time.After(5 * time.Second):
		qmpConn.Disconnect()
		return nil, fmt.Errorf("QMP connection timed out")
	}

	// Setup the monitor struct.
	monitor = &Monitor{}
	monitor.path = path
	monitor.qmp = qmpConn
	monitor.chDisconnect = make(chan struct{}, 1)
	monitor.eventHandler = eventHandler
	monitor.serialCharDev = serialCharDev

	// Spawn goroutines.
	err = monitor.start()
	if err != nil {
		return nil, err
	}

	// Register in global map.
	monitors[path] = monitor

	return monitor, nil
}

// AgentReady indicates whether an agent has been detected.
func (m *Monitor) AgentReady() bool {
	return m.agentReady
}

// Disconnect forces a disconnection from QEMU.
func (m *Monitor) Disconnect() {
	monitorsLock.Lock()
	defer monitorsLock.Unlock()

	// Stop all go routines and disconnect from socket.
	if !m.disconnected {
		close(m.chDisconnect)
		m.disconnected = true
		m.qmp.Disconnect()
	}

	// Remove from the map.
	delete(monitors, m.path)
}

// Wait returns a channel that will be closed on disconnection.
func (m *Monitor) Wait() (chan struct{}, error) {
	// Check if disconnected
	if m.disconnected {
		return nil, ErrMonitorDisconnect
	}

	return m.chDisconnect, nil
}
<commit_msg>lxd/instance/drivers/qmp/monitor: Update run to accept an interace{} for args and JSON encode internally<commit_after>package qmp

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/digitalocean/go-qemu/qmp"

	"github.com/lxc/lxd/shared/logger"
)

var monitors = map[string]*Monitor{}
var monitorsLock sync.Mutex

// RingbufSize is the size of the agent serial ringbuffer in bytes
var RingbufSize = 16

// Monitor represents a QMP monitor.
type Monitor struct {
	path string
	qmp  *qmp.SocketMonitor

	agentReady    bool
	disconnected  bool
	chDisconnect  chan struct{}
	eventHandler  func(name string, data map[string]interface{})
	serialCharDev string
}

// start handles the background goroutines for event handling and monitoring the ringbuffer
func (m *Monitor) start() error {
	// Ringbuffer monitoring function.
	checkBuffer := func() {
		// Prepare the response.
		var resp struct {
			Return string `json:"return"`
		}

		// Read the ringbuffer.
		err := m.run("ringbuf-read", fmt.Sprintf("{'device': '%s', 'size': %d, 'format': 'utf8'}", m.serialCharDev, RingbufSize), &resp)
		if err != nil {
			return
		}

		// Extract the last entry.
		entries := strings.Split(resp.Return, "\n")
		if len(entries) > 1 {
			status := entries[len(entries)-2]

			if status == "STARTED" {
				m.agentReady = true
			} else if status == "STOPPED" {
				m.agentReady = false
			}
		}
	}

	// Start event monitoring go routine.
	chEvents, err := m.qmp.Events(context.Background())
	if err != nil {
		return err
	}

	go func() {
		// Initial read from the ringbuffer.
		go checkBuffer()

		for {
			// Wait for an event, disconnection or timeout.
			select {
			case <-m.chDisconnect:
				return
			case e, more := <-chEvents:
				// Deliver non-empty events to the event handler.
				if m.eventHandler != nil && e.Event != "" {
					go m.eventHandler(e.Event, e.Data)
				}

				// Event channel is closed, lets disconnect.
				if !more {
					m.Disconnect()
					return
				}

				if e.Event == "" {
					logger.Warnf("Unexpected empty event received from qmp event channel")
					time.Sleep(time.Second) // Don't spin if we receive a lot of these.
					continue
				}

				// Check if the ringbuffer was updated (non-blocking).
				go checkBuffer()
			case <-time.After(10 * time.Second):
				// Check if the ringbuffer was updated (non-blocking).
				go checkBuffer()

				continue
			}
		}
	}()

	return nil
}

// ping is used to validate if the QMP socket is still active.
func (m *Monitor) ping() error {
	// Check if disconnected
	if m.disconnected {
		return ErrMonitorDisconnect
	}

	// Query the capabilities to validate the monitor.
	_, err := m.qmp.Run([]byte("{'execute': 'query-version'}"))
	if err != nil {
		m.Disconnect()
		return ErrMonitorDisconnect
	}

	return nil
}

// run executes a command.
func (m *Monitor) run(cmd string, args interface{}, resp interface{}) error {
	// Check if disconnected
	if m.disconnected {
		return ErrMonitorDisconnect
	}

	// Run the command.
	requestArgs := struct {
		Execute   string      `json:"execute"`
		Arguments interface{} `json:"arguments,omitempty"`
	}{
		Execute:   cmd,
		Arguments: args,
	}

	request, err := json.Marshal(requestArgs)
	if err != nil {
		return err
	}

	out, err := m.qmp.Run(request)

	if err != nil {
		// Confirm the daemon didn't die.
		errPing := m.ping()
		if errPing != nil {
			return errPing
		}

		return err
	}

	// Decode the response if needed.
	if resp != nil {
		err = json.Unmarshal(out, &resp)
		if err != nil {
			// Confirm the daemon didn't die.
			errPing := m.ping()
			if errPing != nil {
				return errPing
			}

			return ErrMonitorBadReturn
		}
	}

	return nil
}

// Connect creates or retrieves an existing QMP monitor for the path.
func Connect(path string, serialCharDev string, eventHandler func(name string, data map[string]interface{})) (*Monitor, error) {
	monitorsLock.Lock()
	defer monitorsLock.Unlock()

	// Look for an existing monitor.
	monitor, ok := monitors[path]
	if ok {
		monitor.eventHandler = eventHandler
		return monitor, nil
	}

	// Setup the connection.
	qmpConn, err := qmp.NewSocketMonitor("unix", path, time.Second)
	if err != nil {
		return nil, err
	}

	chError := make(chan error, 1)
	go func() {
		err = qmpConn.Connect()
		chError <- err
	}()

	select {
	case err := <-chError:
		if err != nil {
			return nil, err
		}
	case <-time.After(5 * time.Second):
		qmpConn.Disconnect()
		return nil, fmt.Errorf("QMP connection timed out")
	}

	// Setup the monitor struct.
	monitor = &Monitor{}
	monitor.path = path
	monitor.qmp = qmpConn
	monitor.chDisconnect = make(chan struct{}, 1)
	monitor.eventHandler = eventHandler
	monitor.serialCharDev = serialCharDev

	// Spawn goroutines.
	err = monitor.start()
	if err != nil {
		return nil, err
	}

	// Register in global map.
	monitors[path] = monitor

	return monitor, nil
}

// AgentReady indicates whether an agent has been detected.
func (m *Monitor) AgentReady() bool {
	return m.agentReady
}

// Disconnect forces a disconnection from QEMU.
func (m *Monitor) Disconnect() {
	monitorsLock.Lock()
	defer monitorsLock.Unlock()

	// Stop all go routines and disconnect from socket.
	if !m.disconnected {
		close(m.chDisconnect)
		m.disconnected = true
		m.qmp.Disconnect()
	}

	// Remove from the map.
	delete(monitors, m.path)
}

// Wait returns a channel that will be closed on disconnection.
func (m *Monitor) Wait() (chan struct{}, error) {
	// Check if disconnected
	if m.disconnected {
		return nil, ErrMonitorDisconnect
	}

	return m.chDisconnect, nil
}
<|endoftext|>


<commit_before>package air

import (
	"encoding/json"
	"fmt"
	"runtime"
	"sync"
	"time"
)

// logger is an active logging object that generates lines of output.
type logger struct {
	sync.Mutex

	a *Air
}

// newLogger returns a new instance of the `logger` with the a.
func newLogger(a *Air) *logger {
	return &logger{
		a: a,
	}
}

// log logs the m at the ll with the optional es.
func (l *logger) log(ll LoggerLevel, m string, es ...map[string]interface{}) {
	if !l.a.DebugMode && ll < l.a.LoggerLowestLevel {
		return
	}

	l.Lock()
	defer l.Unlock()

	fs := map[string]interface{}{
		"app_name": l.a.AppName,
		"time":     time.Now().UnixNano(),
		"level":    ll.String(),
		"message":  m,
	}
	if l.a.DebugMode {
		_, fn, l, _ := runtime.Caller(2)
		fs["caller"] = fmt.Sprintf("%s:%d", fn, l)
	}

	for _, e := range es {
		for k, v := range e {
			fs[k] = v
		}
	}

	var (
		b   []byte
		err error
	)

	if l.a.DebugMode {
		b, err = json.MarshalIndent(fs, "", "\t")
	} else {
		b, err = json.Marshal(fs)
	}

	if err != nil {
		if l.a.DebugMode {
			b = []byte(fmt.Sprintf(
				"{\n\t\"logger_error\": \"%v\"\n}",
				err,
			))
		} else {
			b = []byte(fmt.Sprintf(`{"logger_error":"%v"}`, err))
		}
	}

	l.a.LoggerOutput.Write(append(b, '\n'))
}

// LoggerLevel is the level of the logger.
type LoggerLevel uint8

// The logger levels.
const (
	// LoggerLevelDebug defines the debug level of the logger.
	LoggerLevelDebug LoggerLevel = iota

	// LoggerLevelInfo defines the info level of the logger.
	LoggerLevelInfo

	// LoggerLevelWarn defines the warn level of the logger.
	LoggerLevelWarn

	// LoggerLevelError defines the error level of the logger.
	LoggerLevelError

	// LoggerLevelFatal defines the fatal level of the logger.
	LoggerLevelFatal

	// LoggerLevelPanic defines the panic level of the logger.
	LoggerLevelPanic

	// LoggerLevelOff defines the off level of the logger. It will turn off
	// the logger.
	LoggerLevelOff
)

// String returns the string value of the ll.
func (ll LoggerLevel) String() string {
	switch ll {
	case LoggerLevelDebug:
		return "debug"
	case LoggerLevelInfo:
		return "info"
	case LoggerLevelWarn:
		return "warn"
	case LoggerLevelError:
		return "error"
	case LoggerLevelFatal:
		return "fatal"
	case LoggerLevelPanic:
		return "panic"
	case LoggerLevelOff:
		return "off"
	}

	return ""
}
<commit_msg>refactor: refine `logger#log()`<commit_after>package air

import (
	"encoding/json"
	"fmt"
	"runtime"
	"sync"
	"time"
)

// logger is an active logging object that generates lines of output.
type logger struct {
	sync.Mutex

	a *Air
}

// newLogger returns a new instance of the `logger` with the a.
func newLogger(a *Air) *logger {
	return &logger{
		a: a,
	}
}

// log logs the m at the ll with the optional es.
func (l *logger) log(ll LoggerLevel, m string, es ...map[string]interface{}) {
	if !l.a.DebugMode && ll < l.a.LoggerLowestLevel {
		return
	}

	l.Lock()
	defer l.Unlock()

	fs := map[string]interface{}{
		"app_name": l.a.AppName,
		"time":     time.Now().UnixNano(),
		"level":    ll.String(),
		"message":  m,
	}
	if l.a.DebugMode {
		_, fn, l, _ := runtime.Caller(2)
		fs["caller"] = fmt.Sprintf("%s:%d", fn, l)
	}

	for _, e := range es {
		for k, v := range e {
			fs[k] = v
		}
	}

	var (
		b   []byte
		err error
	)

	if l.a.DebugMode {
		b, err = json.MarshalIndent(fs, "", "\t")
	} else {
		b, err = json.Marshal(fs)
	}

	if err != nil {
		s := ""
		if l.a.DebugMode {
			s = fmt.Sprintf("{\n\t\"logger_error\": %q\n}", err)
		} else {
			s = fmt.Sprintf("{\"logger_error\":%q}", err)
		}

		b = []byte(s)
	}

	l.a.LoggerOutput.Write(b)
	l.a.LoggerOutput.Write([]byte{'\n'})
}

// LoggerLevel is the level of the logger.
type LoggerLevel uint8

// The logger levels.
const (
	// LoggerLevelDebug defines the debug level of the logger.
	LoggerLevelDebug LoggerLevel = iota

	// LoggerLevelInfo defines the info level of the logger.
	LoggerLevelInfo

	// LoggerLevelWarn defines the warn level of the logger.
	LoggerLevelWarn

	// LoggerLevelError defines the error level of the logger.
	LoggerLevelError

	// LoggerLevelFatal defines the fatal level of the logger.
	LoggerLevelFatal

	// LoggerLevelPanic defines the panic level of the logger.
	LoggerLevelPanic

	// LoggerLevelOff defines the off level of the logger. It will turn off
	// the logger.
	LoggerLevelOff
)

// String returns the string value of the ll.
func (ll LoggerLevel) String() string {
	switch ll {
	case LoggerLevelDebug:
		return "debug"
	case LoggerLevelInfo:
		return "info"
	case LoggerLevelWarn:
		return "warn"
	case LoggerLevelError:
		return "error"
	case LoggerLevelFatal:
		return "fatal"
	case LoggerLevelPanic:
		return "panic"
	case LoggerLevelOff:
		return "off"
	}

	return ""
}
<|endoftext|>


<commit_before>// go-logging - Logging library for Go
//
// Copyright (c) 2014 Dmitry Prazdnichnov <dp@bambucha.org>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package logging

import (
	"fmt"
	"io"
	"os"
	"path"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Logger struct {
	name    string
	mutex   sync.Mutex
	level   int
	format  string
	datefmt string
}

func (logger *Logger) Log(level int, args ...interface{}) error {

	if logger.level < level {
		return nil
	}

	var (
		pc       uintptr
		pathname string
		filename string
		funcName string
		message  string
		lineno   int
		ok       bool
		writer   io.Writer
	)

	if pc, pathname, lineno, ok = runtime.Caller(3); !ok {
		pathname = "???"
		filename = "???"
		lineno = 0
	} else {
		pcfunc := runtime.FuncForPC(pc)
		if pcfunc != nil {
			funcName = pcfunc.Name()
		} else {
			funcName = "???"
		}
		filename = path.Base(pathname)
	}

	arg := fmt.Sprint(args[0])

	if len(args) == 0 {
		message = arg
	} else {
		message = fmt.Sprintf(arg, args[1:]...)
	}

	replace := strings.NewReplacer(
		"{name}", logger.name,
		"{level}", GetLevelName(level),
		"{line}", strconv.Itoa(lineno),
		"{date}", time.Now().Format(logger.datefmt),
		"{file}", filename,
		"{path}", pathname,
		"{func}", funcName,
		"{message}", message,
	)

	buf := []byte(replace.Replace(logger.format))

	logger.mutex.Lock()
	defer logger.mutex.Unlock()

	if logger.level >= WARNING {
		writer = os.Stdout
	}

	if logger.level < WARNING {
		writer = os.Stderr
	}

	_, err := writer.Write(buf)

	return err
}

func (logger *Logger) GetName() string {
	return logger.name
}

func (logger *Logger) SetLevel(level int) {
	logger.mutex.Lock()
	defer logger.mutex.Unlock()
	logger.level = level
}

func (logger *Logger) GetLevel() int {
	return logger.level
}

func (logger *Logger) SetFormat(format string) {
	logger.mutex.Lock()
	defer logger.mutex.Unlock()
	logger.format = format
}

func (logger *Logger) GetFormat() string {
	return logger.format
}

func (logger *Logger) SetDateFormat(datefmt string) {
	logger.mutex.Lock()
	defer logger.mutex.Unlock()
	logger.datefmt = datefmt
}

func (logger *Logger) GetDateFormat() string {
	return logger.datefmt
}

func (logger *Logger) Print(args ...interface{}) {
	logger.Log(NOTSET, args...)
}

func (logger *Logger) Trace(args ...interface{}) {
	logger.Log(TRACE, args...)
}

func (logger *Logger) Debug(args ...interface{}) {
	logger.Log(DEBUG, args...)
}

func (logger *Logger) Informational(args ...interface{}) {
	logger.Log(INFORMATIONAL, args...)
}

func (logger *Logger) Info(args ...interface{}) {
	logger.Log(INFO, args...)
}

func (logger *Logger) Notice(args ...interface{}) {
	logger.Log(NOTICE, args...)
}

func (logger *Logger) Warning(args ...interface{}) {
	logger.Log(WARNING, args...)
}

func (logger *Logger) Warn(args ...interface{}) {
	logger.Log(WARN, args...)
}

func (logger *Logger) Error(args ...interface{}) {
	logger.Log(ERROR, args...)
}

func (logger *Logger) Err(args ...interface{}) {
	logger.Log(ERR, args...)
}

func (logger *Logger) Critical(args ...interface{}) {
	logger.Log(CRITICAL, args...)
	os.Exit(1)
}

func (logger *Logger) Crit(args ...interface{}) {
	logger.Log(CRIT, args...)
	os.Exit(1)
}

func (logger *Logger) Fatal(args ...interface{}) {
	logger.Log(FATAL, args...)
	os.Exit(1)
}

func (logger *Logger) Alert(args ...interface{}) {
	logger.Log(ALERT, args...)
	os.Exit(1)
}

func (logger *Logger) Emergency(args ...interface{}) {
	logger.Log(EMERGENCY, args...)
	panic(fmt.Sprint(args...))
}

func (logger *Logger) Emerg(args ...interface{}) {
	logger.Log(EMERG, args...)
	panic(fmt.Sprint(args...))
}

func (logger *Logger) Panic(args ...interface{}) {
	logger.Log(PANIC, args...)
	panic(fmt.Sprint(args...))
}
<commit_msg>Refactoring<commit_after>// go-logging - Logging library for Go
//
// Copyright (c) 2014 Dmitry Prazdnichnov <dp@bambucha.org>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package logging

import (
	"fmt"
	"io"
	"os"
	"path"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Logger struct {
	name    string
	mutex   sync.Mutex
	level   int
	format  string
	datefmt string
}

func (logger *Logger) Log(level int, args ...interface{}) error {

	if logger.level < level {
		return nil
	}

	var (
		pc       uintptr
		pathname string
		filename string
		funcName string
		message  string
		lineno   int
		ok       bool
		writer   io.Writer
	)

	if pc, pathname, lineno, ok = runtime.Caller(3); !ok {
		pathname = "???"
		filename = "???"
		lineno = 0
	} else {
		pcfunc := runtime.FuncForPC(pc)
		if pcfunc != nil {
			funcName = pcfunc.Name()
		} else {
			funcName = "???"
		}
		filename = path.Base(pathname)
	}

	message = fmt.Sprint(args[0])

	if len(args) != 0 {
		message = fmt.Sprintf(message, args[1:]...)
	}

	replace := strings.NewReplacer(
		"{name}", logger.name,
		"{level}", GetLevelName(level),
		"{line}", strconv.Itoa(lineno),
		"{date}", time.Now().Format(logger.datefmt),
		"{file}", filename,
		"{path}", pathname,
		"{func}", funcName,
		"{message}", message,
	)

	buf := []byte(replace.Replace(logger.format))

	logger.mutex.Lock()
	defer logger.mutex.Unlock()

	if logger.level >= WARNING {
		writer = os.Stdout
	} else {
		writer = os.Stderr
	}

	_, err := writer.Write(buf)

	return err
}

func (logger *Logger) GetName() string {
	return logger.name
}

func (logger *Logger) SetLevel(level int) {
	logger.mutex.Lock()
	defer logger.mutex.Unlock()
	logger.level = level
}

func (logger *Logger) GetLevel() int {
	return logger.level
}

func (logger *Logger) SetFormat(format string) {
	logger.mutex.Lock()
	defer logger.mutex.Unlock()
	logger.format = format
}

func (logger *Logger) GetFormat() string {
	return logger.format
}

func (logger *Logger) SetDateFormat(datefmt string) {
	logger.mutex.Lock()
	defer logger.mutex.Unlock()
	logger.datefmt = datefmt
}

func (logger *Logger) GetDateFormat() string {
	return logger.datefmt
}

func (logger *Logger) Print(args ...interface{}) {
	logger.Log(NOTSET, args...)
}

func (logger *Logger) Trace(args ...interface{}) {
	logger.Log(TRACE, args...)
}

func (logger *Logger) Debug(args ...interface{}) {
	logger.Log(DEBUG, args...)
}

func (logger *Logger) Informational(args ...interface{}) {
	logger.Log(INFORMATIONAL, args...)
}

func (logger *Logger) Info(args ...interface{}) {
	logger.Log(INFO, args...)
}

func (logger *Logger) Notice(args ...interface{}) {
	logger.Log(NOTICE, args...)
}

func (logger *Logger) Warning(args ...interface{}) {
	logger.Log(WARNING, args...)
}

func (logger *Logger) Warn(args ...interface{}) {
	logger.Log(WARN, args...)
}

func (logger *Logger) Error(args ...interface{}) {
	logger.Log(ERROR, args...)
}

func (logger *Logger) Err(args ...interface{}) {
	logger.Log(ERR, args...)
}

func (logger *Logger) Critical(args ...interface{}) {
	logger.Log(CRITICAL, args...)
	os.Exit(1)
}

func (logger *Logger) Crit(args ...interface{}) {
	logger.Log(CRIT, args...)
	os.Exit(1)
}

func (logger *Logger) Fatal(args ...interface{}) {
	logger.Log(FATAL, args...)
	os.Exit(1)
}

func (logger *Logger) Alert(args ...interface{}) {
	logger.Log(ALERT, args...)
	os.Exit(1)
}

func (logger *Logger) Emergency(args ...interface{}) {
	logger.Log(EMERGENCY, args...)
	panic(fmt.Sprint(args...))
}

func (logger *Logger) Emerg(args ...interface{}) {
	logger.Log(EMERG, args...)
	panic(fmt.Sprint(args...))
}

func (logger *Logger) Panic(args ...interface{}) {
	logger.Log(PANIC, args...)
	panic(fmt.Sprint(args...))
}
<|endoftext|>


<commit_before>package metrics

import (
	"sync/atomic"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/uuid"
	"github.com/pachyderm/pachyderm/src/client/version"

	"go.pedge.io/lion/proto"
	kube "k8s.io/kubernetes/pkg/client/unversioned"
)

var metrics = &Metrics{}

func latestMetricsFromDB(dbClient *gorethink.Session, metric *Metric) {
	term := gorethink.DB(dbClient.Database())
	cursor, err := term.Object(
		"Repos",
		term.Table("Repos").GetAll().Count(),
		"Files",
		term.Table("Files").GetAll().Count(),
		"Jobs",
		term.Table("Jobs").GetAll().Count(),
		"Pipelines",
		term.Table("Pipelines").GetAll().Count(),
	).Run(dbClient)
	cursor.One(&metric)
}

// ReportMetrics blocks and reports metrics, if modified, to the
// given kubernetes client every 15 seconds.
func ReportMetrics(clusterID string, kubeClient *kube.Client, dbClient *gorethink.Session) {
	metrics.ID = clusterID
	metrics.PodID = uuid.NewWithoutDashes()
	metrics.Version = version.PrettyPrintVersion(version.Version)
	for {
		write := atomic.SwapInt64(&modified, 0)
		if write == 1 {
			externalMetrics(kubeClient, metrics)
			latestMetricsFromDB(dbClient, metrics)
			protolion.Info(metrics)
			reportSegment(metrics)
		}
		<-time.After(15 * time.Second)
	}
}
<commit_msg>Fix some compiler errors<commit_after>package metrics

import (
	"fmt"
	"sync/atomic"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/uuid"
	"github.com/pachyderm/pachyderm/src/client/version"

	"github.com/dancannon/gorethink"
	"go.pedge.io/lion/proto"
	kube "k8s.io/kubernetes/pkg/client/unversioned"
)

var metrics = &Metrics{}

func latestMetricsFromDB(dbClient *gorethink.Session, metrics *Metrics) {
	term := gorethink.DB(dbClient.Database())
	cursor, err := term.Object(
		"Repos",
		term.Table("Repos").GetAll().Count(),
		"Files",
		term.Table("Files").GetAll().Count(),
		"Jobs",
		term.Table("Jobs").GetAll().Count(),
		"Pipelines",
		term.Table("Pipelines").GetAll().Count(),
	).Run(dbClient)
	cursor.One(&metrics)
}

// ReportMetrics blocks and reports metrics, if modified, to the
// given kubernetes client every 15 seconds.
func ReportMetrics(clusterID string, kubeClient *kube.Client, dbClient *gorethink.Session) {
	metrics.ID = clusterID
	metrics.PodID = uuid.NewWithoutDashes()
	metrics.Version = version.PrettyPrintVersion(version.Version)
	for {
		externalMetrics(kubeClient, metrics)
		latestMetricsFromDB(dbClient, metrics)
		protolion.Info(metrics)
		fmt.Printf("Writing metrics: %v\n", metrics)
		reportSegment(metrics)
		<-time.After(15 * time.Second)
	}
}
<|endoftext|>


<commit_before>package ppsutil

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"unicode"

	"github.com/gogo/protobuf/jsonpb"
	"github.com/gogo/protobuf/proto"

	ppsclient "github.com/pachyderm/pachyderm/src/client/pps"
	"gopkg.in/pachyderm/yaml.v3"
)

type pipelineDecoder interface {
	// Decode parses the next CreatePipelineRequest in the pipelineDecoder's input
	// stream into 'v'
	Decode(v interface{}) error
}

// jsonpbDecoder implements the 'pipelineDecoder' interface to decode
// JSON-encoded pipelines using the gogoproto json decoder (which is slightly
// more forgiving than go's built-in JSON decoder)
type jsonpbDecoder struct {
	decoder *json.Decoder
}

func newJSONPBDecoder(r io.Reader) *jsonpbDecoder {
	return &jsonpbDecoder{
		decoder: json.NewDecoder(r),
	}
}

func (d *jsonpbDecoder) Decode(v interface{}) error {
	msg, ok := v.(proto.Message)
	if ok {
		return jsonpb.UnmarshalNext(d.decoder, msg)
	}
	return d.decoder.Decode(v)
}

// yamlDecoder implements the 'pipelineDecoder' interface to decode
// YAML-encoded pipelines
type yamlDecoder struct {
	yaml.Decoder
}

func newYAMLDecoder(r io.Reader) *yamlDecoder {
	return &yamlDecoder{*yaml.NewDecoder(r)}
}

// PipelineManifestReader helps with unmarshalling pipeline configs from JSON. It's used by
// 'create pipeline' and 'update pipeline'
//
// Note that the json decoder is able to parse text that gopkg.in/yaml.v3 cannot
// (multiple json documents) so we currently guess whether the document is JSON
// or not by looking at the first non-space character and seeing if it's '{' (we
// originally tried parsing the pipeline spec with both parsers, but that
// approach made it hard to return sensible errors). We may fail to parse valid
// YAML documents this way, so hopefully the yaml parser gains multi-document
// support and we can rely on it fully.
type PipelineManifestReader struct {
	decoder pipelineDecoder
}

// NewPipelineManifestReader creates a new manifest reader from a path.
func NewPipelineManifestReader(path string) (result *PipelineManifestReader, retErr error) {
	var pipelineBytes []byte
	var err error
	if path == "-" {
		fmt.Print("Reading from stdin.\n")
		pipelineBytes, err = ioutil.ReadAll(os.Stdin)
	} else if url, err := url.Parse(path); err == nil && url.Scheme != "" {
		resp, err := http.Get(url.String())
		if err != nil {
			return nil, err
		}
		defer func() {
			if err := resp.Body.Close(); err != nil && retErr == nil {
				retErr = err
			}
		}()
		pipelineBytes, err = ioutil.ReadAll(resp.Body)
	} else {
		pipelineBytes, err = ioutil.ReadFile(path)
	}
	if err != nil {
		return nil, err
	}
	idx := bytes.IndexFunc(pipelineBytes, func(r rune) bool {
		return !unicode.IsSpace(r)
	})
	if idx >= 0 && pipelineBytes[idx] == '{' {
		return &PipelineManifestReader{
			decoder: newJSONPBDecoder(bytes.NewReader(pipelineBytes)),
		}, nil
	}
	return &PipelineManifestReader{
		decoder: newYAMLDecoder(bytes.NewReader(pipelineBytes)),
	}, nil
}

// NextCreatePipelineRequest gets the next request from the manifest reader.
//
// The implementation of this is currently somewhat wasteful: it parses the
// whole request into a map, modifies the map, serializes the map back into
// JSON, and then deserializes the JSON into a CreatePipelineRequest struct.
// This is to manage embedded data (currently just TFJobs, but could later be
// spark jobs or some such), which are represented as serialized JSON in the
// CreatePipelineRequest struct, but which we parse as structured data.
//
// No effort is made to bypass this parse-serialize-parse process even in the
// common case where the pipeline contains no TFJob, because all of this happens
// only in 'pachctl', and only when a pipeline is created or updated.
func (r *PipelineManifestReader) NextCreatePipelineRequest() (*ppsclient.CreatePipelineRequest, error) {
	// Parse whole request into a semi-structured map
	holder := make(map[string]interface{})
	if err := r.decoder.Decode(&holder); err != nil {
		if err == io.EOF {
			return nil, err
		}
		return nil, fmt.Errorf("malformed pipeline spec: %s", err)
	}

	var key string
	var ok bool
	var tfjob interface{}
	if tfjob, ok = holder["TFJob"]; ok {
		key = "TFJob" // go json default munging
	} else if tfjob, ok = holder["tf_job"]; ok {
		key = "tf_job" // protobuf-generated 'json' tag
	}
	if key != "" {
		var err error
		var tfjob_text []byte
		if tfjob, ok := tfjob.(map[string]interface{}); ok {
			// tiny validation--make sure "kind" is "TFJob" (or is unset)
			if tfjob["kind"] == "" {
				tfjob["kind"] = "TFJob"
			} else if tfjob["kind"] != "TFJob" {
				return nil, errors.New("tf_job must contain a kubernetes manifest for a Kubeflow TFJob")
			}
			tfjob_text, err = json.Marshal(tfjob)
		} else {
			err = fmt.Errorf("jsonpb parses TFJob as unexpected type %T", tfjob)
		}
		if err != nil {
			return nil, fmt.Errorf("could not convert TFJob to text: %v", err)
		}
		delete(holder, key)
		holder["tf_job"] = map[string]interface{}{
			"tf_job": string(tfjob_text),
		}
	}

	// serialize 'holder' to json, then parse again into a CreatePipelineRequest
	// using jsonpb (which has special handling for timestamps and other types)
	pipelineRequestBytes, err := json.Marshal(holder)
	if err != nil {
		return nil, fmt.Errorf("serialization error while canonicalizing CreatePipelineRequest: %v", err)
	}
	decoder := json.NewDecoder(bytes.NewReader(pipelineRequestBytes))
	var result ppsclient.CreatePipelineRequest
	if err := jsonpb.UnmarshalNext(decoder, &result); err != nil {
		return nil, fmt.Errorf("parse error while canonicalizing CreatePipelineRequest: %v", err)
	}
	return &result, nil
}

// DescribeSyntaxError describes a syntax error encountered parsing json.
func DescribeSyntaxError(originalErr error, parsedBuffer bytes.Buffer) error {
	sErr, ok := originalErr.(*json.SyntaxError)
	if !ok {
		return originalErr
	}

	buffer := make([]byte, sErr.Offset)
	parsedBuffer.Read(buffer)

	lineOffset := strings.LastIndex(string(buffer[:len(buffer)-1]), "\n")
	if lineOffset == -1 {
		lineOffset = 0
	}

	lines := strings.Split(string(buffer[:len(buffer)-1]), "\n")
	lineNumber := len(lines)

	descriptiveErrorString := fmt.Sprintf("Syntax Error on line %v:\n%v\n%v^\n%v\n",
		lineNumber,
		string(buffer[lineOffset:]),
		strings.Repeat(" ", int(sErr.Offset)-2-lineOffset),
		originalErr,
	)

	return errors.New(descriptiveErrorString)
}
<commit_msg>Fix lint error<commit_after>package ppsutil

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"unicode"

	"github.com/gogo/protobuf/jsonpb"
	"github.com/gogo/protobuf/proto"

	ppsclient "github.com/pachyderm/pachyderm/src/client/pps"
	"gopkg.in/pachyderm/yaml.v3"
)

type pipelineDecoder interface {
	// Decode parses the next CreatePipelineRequest in the pipelineDecoder's input
	// stream into 'v'
	Decode(v interface{}) error
}

// jsonpbDecoder implements the 'pipelineDecoder' interface to decode
// JSON-encoded pipelines using the gogoproto json decoder (which is slightly
// more forgiving than go's built-in JSON decoder)
type jsonpbDecoder struct {
	decoder *json.Decoder
}

func newJSONPBDecoder(r io.Reader) *jsonpbDecoder {
	return &jsonpbDecoder{
		decoder: json.NewDecoder(r),
	}
}

func (d *jsonpbDecoder) Decode(v interface{}) error {
	msg, ok := v.(proto.Message)
	if ok {
		return jsonpb.UnmarshalNext(d.decoder, msg)
	}
	return d.decoder.Decode(v)
}

// yamlDecoder implements the 'pipelineDecoder' interface to decode
// YAML-encoded pipelines
type yamlDecoder struct {
	yaml.Decoder
}

func newYAMLDecoder(r io.Reader) *yamlDecoder {
	return &yamlDecoder{*yaml.NewDecoder(r)}
}

// PipelineManifestReader helps with unmarshalling pipeline configs from JSON. It's used by
// 'create pipeline' and 'update pipeline'
//
// Note that the json decoder is able to parse text that gopkg.in/yaml.v3 cannot
// (multiple json documents) so we currently guess whether the document is JSON
// or not by looking at the first non-space character and seeing if it's '{' (we
// originally tried parsing the pipeline spec with both parsers, but that
// approach made it hard to return sensible errors). We may fail to parse valid
// YAML documents this way, so hopefully the yaml parser gains multi-document
// support and we can rely on it fully.
type PipelineManifestReader struct {
	decoder pipelineDecoder
}

// NewPipelineManifestReader creates a new manifest reader from a path.
func NewPipelineManifestReader(path string) (result *PipelineManifestReader, retErr error) {
	var pipelineBytes []byte
	var err error
	if path == "-" {
		fmt.Print("Reading from stdin.\n")
		pipelineBytes, err = ioutil.ReadAll(os.Stdin)
	} else if url, err := url.Parse(path); err == nil && url.Scheme != "" {
		resp, err := http.Get(url.String())
		if err != nil {
			return nil, err
		}
		defer func() {
			if err := resp.Body.Close(); err != nil && retErr == nil {
				retErr = err
			}
		}()
		pipelineBytes, err = ioutil.ReadAll(resp.Body)
	} else {
		pipelineBytes, err = ioutil.ReadFile(path)
	}
	if err != nil {
		return nil, err
	}
	idx := bytes.IndexFunc(pipelineBytes, func(r rune) bool {
		return !unicode.IsSpace(r)
	})
	if idx >= 0 && pipelineBytes[idx] == '{' {
		return &PipelineManifestReader{
			decoder: newJSONPBDecoder(bytes.NewReader(pipelineBytes)),
		}, nil
	}
	return &PipelineManifestReader{
		decoder: newYAMLDecoder(bytes.NewReader(pipelineBytes)),
	}, nil
}

// NextCreatePipelineRequest gets the next request from the manifest reader.
//
// The implementation of this is currently somewhat wasteful: it parses the
// whole request into a map, modifies the map, serializes the map back into
// JSON, and then deserializes the JSON into a CreatePipelineRequest struct.
// This is to manage embedded data (currently just TFJobs, but could later be
// spark jobs or some such), which are represented as serialized JSON in the
// CreatePipelineRequest struct, but which we parse as structured data.
//
// No effort is made to bypass this parse-serialize-parse process even in the
// common case where the pipeline contains no TFJob, because all of this happens
// only in 'pachctl', and only when a pipeline is created or updated.
func (r *PipelineManifestReader) NextCreatePipelineRequest() (*ppsclient.CreatePipelineRequest, error) {
	// Parse whole request into a semi-structured map
	holder := make(map[string]interface{})
	if err := r.decoder.Decode(&holder); err != nil {
		if err == io.EOF {
			return nil, err
		}
		return nil, fmt.Errorf("malformed pipeline spec: %s", err)
	}

	var key string
	var ok bool
	var tfjob interface{}
	if tfjob, ok = holder["TFJob"]; ok {
		key = "TFJob" // go json default munging
	} else if tfjob, ok = holder["tf_job"]; ok {
		key = "tf_job" // protobuf-generated 'json' tag
	}
	if key != "" {
		var err error
		var tfjobText []byte
		if tfjob, ok := tfjob.(map[string]interface{}); ok {
			// tiny validation--make sure "kind" is "TFJob" (or is unset)
			if tfjob["kind"] == "" {
				tfjob["kind"] = "TFJob"
			} else if tfjob["kind"] != "TFJob" {
				return nil, errors.New("tf_job must contain a kubernetes manifest for a Kubeflow TFJob")
			}
			tfjobText, err = json.Marshal(tfjob)
		} else {
			err = fmt.Errorf("jsonpb parses TFJob as unexpected type %T", tfjob)
		}
		if err != nil {
			return nil, fmt.Errorf("could not convert TFJob to text: %v", err)
		}
		delete(holder, key)
		holder["tf_job"] = map[string]interface{}{
			"tf_job": string(tfjobText),
		}
	}

	// serialize 'holder' to json, then parse again into a CreatePipelineRequest
	// using jsonpb (which has special handling for timestamps and other types)
	pipelineRequestBytes, err := json.Marshal(holder)
	if err != nil {
		return nil, fmt.Errorf("serialization error while canonicalizing CreatePipelineRequest: %v", err)
	}
	decoder := json.NewDecoder(bytes.NewReader(pipelineRequestBytes))
	var result ppsclient.CreatePipelineRequest
	if err := jsonpb.UnmarshalNext(decoder, &result); err != nil {
		return nil, fmt.Errorf("parse error while canonicalizing CreatePipelineRequest: %v", err)
	}
	return &result, nil
}

// DescribeSyntaxError describes a syntax error encountered parsing json.
func DescribeSyntaxError(originalErr error, parsedBuffer bytes.Buffer) error {
	sErr, ok := originalErr.(*json.SyntaxError)
	if !ok {
		return originalErr
	}

	buffer := make([]byte, sErr.Offset)
	parsedBuffer.Read(buffer)

	lineOffset := strings.LastIndex(string(buffer[:len(buffer)-1]), "\n")
	if lineOffset == -1 {
		lineOffset = 0
	}

	lines := strings.Split(string(buffer[:len(buffer)-1]), "\n")
	lineNumber := len(lines)

	descriptiveErrorString := fmt.Sprintf("Syntax Error on line %v:\n%v\n%v^\n%v\n",
		lineNumber,
		string(buffer[lineOffset:]),
		strings.Repeat(" ", int(sErr.Offset)-2-lineOffset),
		originalErr,
	)

	return errors.New(descriptiveErrorString)
}
<|endoftext|>


<commit_before>package main

import (
	"time"
)

func classifyGoods() {
	defer time.Sleep(time.Second)

	type GroupId int
	type Hash string
	type GoodId int64

	// load existing group infos
	type GroupInfo struct {
		GroupId GroupId     `db:"group_id"`
		Hashes  StringArray `db:"hashes"`
		Title   string      `db:"title"`
	}
	var groupRows []GroupInfo
	ce(db.Select(&groupRows, `SELECT * FROM groups`), "select group infos")
	groupIdToHashSet := make(map[GroupId]map[Hash]struct{})
	hashToGroupIdSet := make(map[Hash]map[GroupId]struct{})
	for _, info := range groupRows {
		for _, hash := range info.Hashes {
			hash := Hash(hash)
			if _, ok := groupIdToHashSet[info.GroupId]; !ok {
				groupIdToHashSet[info.GroupId] = make(map[Hash]struct{})
			}
			groupIdToHashSet[info.GroupId][hash] = struct{}{}
			if _, ok := hashToGroupIdSet[hash]; !ok {
				hashToGroupIdSet[hash] = make(map[GroupId]struct{})
			}
			hashToGroupIdSet[hash][info.GroupId] = struct{}{}
		}
	}
	pt("group infos loaded\n")

select_goods:
	tx := db.MustBegin()

	var goodIds Int64Array
	err := tx.Select(&goodIds, `SELECT good_id
			FROM goods
			WHERE added_at >= $1
			AND status > 0
			AND group_id IS NULL
			LIMIT 256
			`,
		time.Now().Add(-time.Hour*24*45).Format("2006-01-02"),
	)
	ce(err, "select goods")
	pt("select %d goods\n", len(goodIds))
	if len(goodIds) == 0 {
		return
	}

	//XXX debug
	//var err error
	//goodIds := Int64Array{
	//	2398924,
	//}

	var infos []struct {
		GoodId GoodId `db:"good_id"`
		Hash   string `db:"hash"`
	}
	err = tx.Select(&infos, `SELECT i.good_id, encode(sha512_16k, 'hex') AS hash
		FROM images i
		LEFT JOIN urls u ON u.url_id = i.url_id
		WHERE i.good_id = ANY($1)
		`,
		goodIds,
	)
	ce(err, "select hashes")
	goodIdToHashSet := make(map[GoodId]map[Hash]struct{})
	for _, info := range infos {
		if _, ok := goodIdToHashSet[info.GoodId]; !ok {
			goodIdToHashSet[info.GoodId] = make(map[Hash]struct{})
		}
		goodIdToHashSet[info.GoodId][Hash(info.Hash)] = struct{}{}
	}
	pt("select %d rows of infos\n", len(infos))

loop_goods:
	for _, goodId := range goodIds {
		goodId := GoodId(goodId)
		candidateGroupIdSet := make(map[GroupId]struct{})
		for hash := range goodIdToHashSet[goodId] {
			if groupIdSet, ok := hashToGroupIdSet[hash]; ok {
				for groupId := range groupIdSet {
					candidateGroupIdSet[groupId] = struct{}{}
				}
			}
		}

		for groupId := range candidateGroupIdSet {
			count := 0
			total := 0
			for hash := range groupIdToHashSet[groupId] {
				if _, ok := goodIdToHashSet[goodId][hash]; ok {
					count++
				}
				total++
			}
			if total-count <= 3 && count > 5 {
				var similarity float64
				ce(tx.Get(&similarity, `SELECT similarity(
					(SELECT title FROM groups WHERE group_id = $1),
					(SELECT title FROM goods WHERE good_id = $2)
				)`,
					groupId,
					goodId,
				), "get similarity")
				if similarity < 0.3 && count < 15 {
					// 有15个图相同的话，就不管标题了
					// 如果发现有商品的小图用量大于15,那就提高
					continue
				}
				//same group
				_, err := tx.Exec(`UPDATE goods
							SET group_id = $1
							WHERE good_id = $2
							`,
					groupId,
					goodId,
				)
				ce(err, "update goods")
				continue loop_goods
			}
		}

		// new group or ignore
		if len(goodIdToHashSet[goodId]) > 5 {
			// new group
			var groupHashes StringArray
			for hash := range goodIdToHashSet[goodId] {
				groupHashes = append(groupHashes, string(hash))
			}
			row := tx.QueryRow(`INSERT INTO groups
				(hashes, title)
				VALUES ($1, (SELECT title FROM goods WHERE good_id = $2))
				RETURNING group_id
				`,
				groupHashes,
				goodId,
			)
			ce(err, "insert new group")
			var newGroupId GroupId
			ce(row.Scan(&newGroupId), "scan")
			for hash := range goodIdToHashSet[goodId] {
				if _, ok := groupIdToHashSet[newGroupId]; !ok {
					groupIdToHashSet[newGroupId] = make(map[Hash]struct{})
				}
				groupIdToHashSet[newGroupId][hash] = struct{}{}
				if _, ok := hashToGroupIdSet[hash]; !ok {
					hashToGroupIdSet[hash] = make(map[GroupId]struct{})
				}
				hashToGroupIdSet[hash][newGroupId] = struct{}{}
			}
			_, err := tx.Exec(`UPDATE goods
				SET group_id = $1
				WHERE good_id = $2
				`,
				newGroupId,
				goodId,
			)
			ce(err, "update goods")
		} else {
			// ignore this item
			_, err := tx.Exec(`UPDATE goods
								SET group_id = 0
								WHERE good_id = $1
								`,
				goodId,
			)
			ce(err, "update goods")
		}

	}

	ce(tx.Commit(), "commit")

	goto select_goods

}
<commit_msg>update<commit_after>package main

import (
	//"github.com/jmoiron/sqlx"
	//_ "github.com/lib/pq"
	"time"
)

func classifyGoods() {
	defer time.Sleep(time.Second)

	type GroupId int
	type Hash string
	type GoodId int64

	// load existing group infos
	type GroupInfo struct {
		GroupId GroupId     `db:"group_id"`
		Hashes  StringArray `db:"hashes"`
		Title   string      `db:"title"`
	}
	var groupRows []GroupInfo
	ce(db.Select(&groupRows, `SELECT
		group_id, hashes, title
		FROM groups`), "select group infos")
	groupIdToHashSet := make(map[GroupId]map[Hash]struct{})
	hashToGroupIdSet := make(map[Hash]map[GroupId]struct{})
	for _, info := range groupRows {
		for _, hash := range info.Hashes {
			hash := Hash(hash)
			if _, ok := groupIdToHashSet[info.GroupId]; !ok {
				groupIdToHashSet[info.GroupId] = make(map[Hash]struct{})
			}
			groupIdToHashSet[info.GroupId][hash] = struct{}{}
			if _, ok := hashToGroupIdSet[hash]; !ok {
				hashToGroupIdSet[hash] = make(map[GroupId]struct{})
			}
			hashToGroupIdSet[hash][info.GroupId] = struct{}{}
		}
	}
	pt("group infos loaded\n")

select_goods:
	tx := db.MustBegin()

	var goodIds Int64Array
	err := tx.Select(&goodIds, `SELECT good_id
			FROM goods
			WHERE added_at >= $1
			--AND status > 0
			AND group_id IS NULL
			ORDER BY good_id DESC
			LIMIT 256
			`,
		time.Now().Add(-time.Hour*24*120).Format("2006-01-02"),
	)
	ce(err, "select goods")
	pt("select %d goods\n", len(goodIds))
	if len(goodIds) == 0 {
		return
	}

	//XXX debug
	//var err error
	//goodIds := Int64Array{
	//	2398924,
	//}

	//XXX from vvic db
	//lalaDB, err := sqlx.Connect("postgres", "host=reus.mobi user=reus dbname=lala connect_timeout=60")
	//ce(err, "connect to lala db")
	//var goodIdsFromLala Int64Array
	//ce(lalaDB.Select(&goodIdsFromLala, `SELECT vvic_id FROM items`), "select good ids from lala db")
	//var goodIds Int64Array
	//ce(db.Select(&goodIds, `SELECT good_id FROM goods
	//	WHERE good_id = ANY($1)
	//	AND group_id IS NULL
	//	`,
	//	goodIdsFromLala,
	//), "select good ids")
	//pt("%d good ids\n", len(goodIds))
	//if len(goodIds) == 0 {
	//	return
	//}

	var infos []struct {
		GoodId GoodId `db:"good_id"`
		Hash   string `db:"hash"`
	}
	err = tx.Select(&infos, `SELECT i.good_id, encode(sha512_16k, 'hex') AS hash
		FROM images i
		LEFT JOIN urls u ON u.url_id = i.url_id
		WHERE i.good_id = ANY($1)
		AND sha512_16k IS NOT NULL
		`,
		goodIds,
	)
	ce(err, "select hashes")
	goodIdToHashSet := make(map[GoodId]map[Hash]struct{})
	for _, info := range infos {
		if _, ok := goodIdToHashSet[info.GoodId]; !ok {
			goodIdToHashSet[info.GoodId] = make(map[Hash]struct{})
		}
		goodIdToHashSet[info.GoodId][Hash(info.Hash)] = struct{}{}
	}
	pt("select %d rows of infos\n", len(infos))

loop_goods:
	for _, goodId := range goodIds {
		goodId := GoodId(goodId)
		candidateGroupIdSet := make(map[GroupId]struct{})
		for hash := range goodIdToHashSet[goodId] {
			if groupIdSet, ok := hashToGroupIdSet[hash]; ok {
				for groupId := range groupIdSet {
					candidateGroupIdSet[groupId] = struct{}{}
				}
			}
		}

		for groupId := range candidateGroupIdSet {
			count := 0
			total := 0
			for hash := range groupIdToHashSet[groupId] {
				if _, ok := goodIdToHashSet[goodId][hash]; ok {
					count++
				}
				total++
			}
			if total-count <= 3 && count > 5 {
				var similarity float64
				ce(tx.Get(&similarity, `SELECT similarity(
					(SELECT title FROM groups WHERE group_id = $1),
					(SELECT title FROM goods WHERE good_id = $2)
				)`,
					groupId,
					goodId,
				), "get similarity")
				if similarity < 0.3 && count < 15 {
					// 有15个图相同的话，就不管标题了
					// 如果发现有商品的小图用量大于15,那就提高
					continue
				}
				//same group
				_, err := tx.Exec(`UPDATE goods
							SET group_id = $1
							WHERE good_id = $2
							`,
					groupId,
					goodId,
				)
				ce(err, "update goods")
				continue loop_goods
			}
		}

		// new group
		var groupHashes StringArray
		for hash := range goodIdToHashSet[goodId] {
			groupHashes = append(groupHashes, string(hash))
		}
		row := tx.QueryRow(`INSERT INTO groups
				(hashes, title)
				VALUES ($1, (SELECT title FROM goods WHERE good_id = $2))
				RETURNING group_id
				`,
			groupHashes,
			goodId,
		)
		ce(err, "insert new group")
		var newGroupId GroupId
		ce(row.Scan(&newGroupId), "scan")
		for hash := range goodIdToHashSet[goodId] {
			if _, ok := groupIdToHashSet[newGroupId]; !ok {
				groupIdToHashSet[newGroupId] = make(map[Hash]struct{})
			}
			groupIdToHashSet[newGroupId][hash] = struct{}{}
			if _, ok := hashToGroupIdSet[hash]; !ok {
				hashToGroupIdSet[hash] = make(map[GroupId]struct{})
			}
			hashToGroupIdSet[hash][newGroupId] = struct{}{}
		}
		_, err := tx.Exec(`UPDATE goods
				SET group_id = $1
				WHERE good_id = $2
				`,
			newGroupId,
			goodId,
		)
		ce(err, "update goods")

	}

	ce(tx.Commit(), "commit")

	goto select_goods

}
<|endoftext|>


<commit_before>package figlet4go

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type font struct {
	hardblank string
	height    int
	fontSlice []string
}

type fontManager struct {
	// font library
	fontLib map[string]*font

	// font name to path
	fontList map[string]string
}

func newFontManager() *fontManager {
	this := &fontManager{}

	this.fontLib = make(map[string]*font)
	this.fontList = make(map[string]string)
	this.loadBuildInFont()

	return this
}

// walk through the path, load all the *.flf font file
func (this *fontManager) loadFont(fontPath string) error {

	return filepath.Walk(fontPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() || !strings.HasSuffix(info.Name(), ".flf") {
			return nil
		}

		fontName := strings.TrimSuffix(info.Name(), ".flf")
		this.fontList[fontName] = path
		return nil
	})
}

func (this *fontManager) loadBuildInFont() error {
	font, err := this.parseFontContent(builtInFont)
	if err != nil {
		return err
	}
	this.fontLib["default"] = font
	return nil
}

func (this *fontManager) loadDiskFont(fontName string) error {

	fontFilePath, ok := this.fontList[fontName]
	if !ok {
		return errors.New("FontName Not Found.")
	}

	// read full file content
	fileBuf, err := ioutil.ReadFile(fontFilePath)
	if err != nil {
		return err
	}

	font, err := this.parseFontContent(string(fileBuf))
	if err != nil {
		return err
	}

	this.fontLib[fontName] = font
	return nil
}

func (this *fontManager) parseFontContent(cont string) (*font, error) {
	lines := strings.Split(cont, "\n")
	if len(lines) < 1 {
		return nil, errors.New("font content error")
	}

	// flf2a$ 7 5 16 -1 12
	// Fender by Scooter 8/94 (jkratten@law.georgetown.edu)
	//
	// Explanation of first line:
	// flf2 - "magic number" for file identification
	// a    - should always be `a', for now
	// $    - the "hardblank" -- prints as a blank, but can't be smushed
	// 7    - height of a character
	// 5    - height of a character, not including descenders
	// 10   - max line length (excluding comment lines) + a fudge factor
	// -1   - default smushmode for this font (like "-m 15" on command line)
	// 12   - number of comment lines

	header := strings.Split(lines[0], " ")

	font := &font{}
	font.hardblank = header[0][len(header)-1:]
	font.height, _ = strconv.Atoi(header[1])

	commentEndLine, _ := strconv.Atoi(header[5])
	font.fontSlice = lines[commentEndLine+1:]

	return font, nil
}

func (this *fontManager) getFont(fontName string) (*font, error) {
	font, ok := this.fontLib[fontName]
	if !ok {
		err := this.loadDiskFont(fontName)
		if err != nil {
			font, _ := this.fontLib["default"]
			return font, nil
		}
	}
	font, _ = this.fontLib[fontName]
	return font, nil
}
<commit_msg>fix bug<commit_after>package figlet4go

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type font struct {
	hardblank string
	height    int
	fontSlice []string
}

type fontManager struct {
	// font library
	fontLib map[string]*font

	// font name to path
	fontList map[string]string
}

func newFontManager() *fontManager {
	this := &fontManager{}

	this.fontLib = make(map[string]*font)
	this.fontList = make(map[string]string)
	this.loadBuildInFont()

	return this
}

// walk through the path, load all the *.flf font file
func (this *fontManager) loadFont(fontPath string) error {

	return filepath.Walk(fontPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() || !strings.HasSuffix(info.Name(), ".flf") {
			return nil
		}

		fontName := strings.TrimSuffix(info.Name(), ".flf")
		this.fontList[fontName] = path
		return nil
	})
}

func (this *fontManager) loadBuildInFont() error {
	font, err := this.parseFontContent(builtInFont)
	if err != nil {
		return err
	}
	this.fontLib["default"] = font
	return nil
}

func (this *fontManager) loadDiskFont(fontName string) error {

	fontFilePath, ok := this.fontList[fontName]
	if !ok {
		return errors.New("FontName Not Found.")
	}

	// read full file content
	fileBuf, err := ioutil.ReadFile(fontFilePath)
	if err != nil {
		return err
	}

	font, err := this.parseFontContent(string(fileBuf))
	if err != nil {
		return err
	}

	this.fontLib[fontName] = font
	return nil
}

func (this *fontManager) parseFontContent(cont string) (*font, error) {
	lines := strings.Split(cont, "\n")
	if len(lines) < 1 {
		return nil, errors.New("font content error")
	}

	// flf2a$ 7 5 16 -1 12
	// Fender by Scooter 8/94 (jkratten@law.georgetown.edu)
	//
	// Explanation of first line:
	// flf2 - "magic number" for file identification
	// a    - should always be `a', for now
	// $    - the "hardblank" -- prints as a blank, but can't be smushed
	// 7    - height of a character
	// 5    - height of a character, not including descenders
	// 10   - max line length (excluding comment lines) + a fudge factor
	// -1   - default smushmode for this font (like "-m 15" on command line)
	// 12   - number of comment lines

	header := strings.Split(lines[0], " ")

	font := &font{}
	font.hardblank = header[0][len(header[0])-1:]
	font.height, _ = strconv.Atoi(header[1])

	commentEndLine, _ := strconv.Atoi(header[5])
	font.fontSlice = lines[commentEndLine+1:]

	return font, nil
}

func (this *fontManager) getFont(fontName string) (*font, error) {
	font, ok := this.fontLib[fontName]
	if !ok {
		err := this.loadDiskFont(fontName)
		if err != nil {
			font, _ := this.fontLib["default"]
			return font, nil
		}
	}
	font, _ = this.fontLib[fontName]
	return font, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"io/ioutil"
	"log"
	"os"

	"github.com/SYNQfm/SYNQ-Golang/synq"
)

func handleError(err error) {
	if err != nil {
		log.Printf("Error : %s\n", err.Error())
		os.Exit(1)
	}
}

func main() {
	var err error
	var video synq.Video
	var (
		c = flag.String("command", "", "one of: details, upload_info, upload, create, uploader_info, uploader, query or create_and_then_multipart_upload")
		a = flag.String("api_key", "", "pass the synq api key")
		v = flag.String("video_id", "", "pass in the video id to get data about")
		f = flag.String("file", "", "path to file you want to upload or userdata")
		q = flag.String("query", "", "query string to use")
	)
	flag.Parse()
	cmd := *c
	vid := *v
	api_key := *a
	file := *f
	if api_key == "" {
		log.Println("missing 'api_key'")
		os.Exit(1)
	}
	api := synq.New(api_key)
	log.Printf("using %s API\n")
	switch cmd {
	case "details":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		log.Printf("getting video %s\n", vid)
		video, err = api.GetVideo(vid)
	case "upload_info":
		log.Printf("Getting upload info for %s\n", vid)
		video.Api = &api
		video.Id = vid
		err = video.GetUploadInfo()
	case "query":
		videos, err := api.Query(*q)
		handleError(err)
		log.Printf("Found %d videos\n", len(videos))
		for _, video := range videos {
			log.Printf(video.Display())
		}
		os.Exit(0)
	case "upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		log.Printf("uploading file '%s'\n", file)
		video.Api = &api
		video.Id = vid
		err = video.UploadFile(file)
	case "create":
		log.Printf("Creating new video")
		if file != "" {
			log.Printf("loading userdata file from %s\n", file)
			bytes, err := ioutil.ReadFile(file)
			if err == nil {
				userdata := make(map[string]interface{})
				json.Unmarshal(bytes, &userdata)
				video, err = api.Create(userdata)
			}
		} else {
			video, err = api.Create()
		}
	case "uploader_info":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid
		err = video.GetUploaderInfo()
		if err != nil {
			log.Println(err)
			os.Exit(1)
		}
		log.Println("uploader_url:", video.UploaderInfo["uploader_url"])
		os.Exit(0)
	case "uploader":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	case "create_and_then_multipart_upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}

		log.Printf("Creating new video")
		video, err = api.Create()
		handleError(err)

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	default:
		err = errors.New("unknown command '" + cmd + "'")
	}
	handleError(err)
	log.Printf(video.Display())
}
<commit_msg>fix cli errors (temp)<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"io/ioutil"
	"log"
	"os"

	"github.com/SYNQfm/SYNQ-Golang/synq"
)

func handleError(err error) {
	if err != nil {
		log.Printf("Error : %s\n", err.Error())
		os.Exit(1)
	}
}

func main() {
	var err error
	var video synq.Video
	var (
		c = flag.String("command", "", "one of: details, upload_info, upload, create, uploader_info, uploader, query or create_and_then_multipart_upload")
		a = flag.String("api_key", "", "pass the synq api key")
		v = flag.String("video_id", "", "pass in the video id to get data about")
		f = flag.String("file", "", "path to file you want to upload or userdata")
		q = flag.String("query", "", "query string to use")
	)
	flag.Parse()
	cmd := *c
	vid := *v
	api_key := *a
	file := *f
	if api_key == "" {
		log.Println("missing 'api_key'")
		os.Exit(1)
	}
	api := synq.NewV1(api_key)
	log.Printf("using %s API\n")
	switch cmd {
	case "details":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		log.Printf("getting video %s\n", vid)
		video, err = api.GetVideo(vid)
	case "upload_info":
		log.Printf("Getting upload info for %s\n", vid)
		video.Api = &api
		video.Id = vid
		err = video.GetUploadInfo()
	case "query":
		videos, err := api.Query(*q)
		handleError(err)
		log.Printf("Found %d videos\n", len(videos))
		for _, video := range videos {
			log.Printf(video.Display())
		}
		os.Exit(0)
	case "upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		log.Printf("uploading file '%s'\n", file)
		video.Api = &api
		video.Id = vid
		err = video.UploadFile(file)
	case "create":
		log.Printf("Creating new video")
		if file != "" {
			log.Printf("loading userdata file from %s\n", file)
			bytes, err := ioutil.ReadFile(file)
			if err == nil {
				userdata := make(map[string]interface{})
				json.Unmarshal(bytes, &userdata)
				video, err = api.Create(userdata)
			}
		} else {
			video, err = api.Create()
		}
	case "uploader_info":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid
		err = video.GetUploaderInfo()
		if err != nil {
			log.Println(err)
			os.Exit(1)
		}
		log.Println("uploader_url:", video.UploaderInfo["uploader_url"])
		os.Exit(0)
	case "uploader":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	case "create_and_then_multipart_upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}

		log.Printf("Creating new video")
		video, err = api.Create()
		handleError(err)

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	default:
		err = errors.New("unknown command '" + cmd + "'")
	}
	handleError(err)
	log.Printf(video.Display())
}
<|endoftext|>


<commit_before>/*

cli is a simple package to allow a game to render to the screen and be
interacted with. It's intended primarily as a tool to diagnose and play around
with a game while its moves and logic are being defined.

*/
package cli

import (
	"encoding/json"
	"fmt"
	"github.com/jkomoros/boardgame"
	"github.com/jroimartin/gocui"
)

type renderType int

const (
	renderJSON renderType = iota
	renderRender
	renderChest
)

//Controller is the primary type of the package.
type Controller struct {
	game     *boardgame.Game
	gui      *gocui.Gui
	renderer RendererFunc
	render   renderType
	mode     inputMode
}

//RenderrerFunc takes a state and outputs a list of strings that should be
//printed to screen to depict it.
type RendererFunc func(boardgame.StatePayload) string

func NewController(game *boardgame.Game, renderer RendererFunc) *Controller {
	return &Controller{
		game:     game,
		renderer: renderer,
	}
}

//Implement the gocui.Manager interface
func (c *Controller) Layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if v, err := g.SetView("main", 0, 0, maxX-1, maxY-2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Title = "JSON"
		v.Frame = true
	}

	if v, err := g.SetView("status", 0, maxY-2, maxX-1, maxY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.FgColor = gocui.ColorBlack
		v.BgColor = gocui.ColorWhite
		v.Frame = false
	}

	switch c.mode {
	case modeProposingMove:
		if v, err := g.SetView("move", 0, maxY-30, maxX-1, maxY-2); err != nil {
			if err != gocui.ErrUnknownView {
				return err
			}
			v.Frame = true
			v.Title = "Proposing Move"

			fmt.Fprint(v, "This is where move stuff will show up.")

			g.SetViewOnTop("move")
		}
	case modeNormal:
		//Delete the view, if it exists
		if err := g.DeleteView("move"); err != nil {
			//It's OK if it's ErrUnknownView because that just means it wasn't
			//in there and was a no op.
			if err != gocui.ErrUnknownView {
				return err
			}
		}
	default:
		//Presumably a nil mode. Meh.
	}

	//Update the json field of view

	if view, err := g.View("main"); err == nil {
		view.Clear()
		switch c.render {
		case renderRender:
			//Print renderered view
			fmt.Fprint(view, c.renderRendered())
			view.Title = "Rendered"
		case renderJSON:
			//Print JSON view
			fmt.Fprint(view, c.renderJSON())
			view.Title = "JSON"
		case renderChest:
			fmt.Fprint(view, c.renderChest())
			view.Title = "Chest"
		}

	}

	if view, err := g.View("status"); err == nil {
		view.Clear()
		fmt.Fprint(view, c.mode.statusLine())
	}

	return nil
}

func (c *Controller) renderRendered() string {
	return c.renderer(c.game.State.Payload)
}

func (c *Controller) renderJSON() string {
	return string(boardgame.Serialize(c.game.State.JSON()))
}

func (c *Controller) renderChest() string {

	deck := make(map[string][]interface{})

	for _, name := range c.game.Chest().DeckNames() {

		components := c.game.Chest().Deck(name).Components()

		values := make([]interface{}, len(components))

		for i, component := range components {
			values[i] = struct {
				Index  int
				Values interface{}
			}{
				i,
				component.Values,
			}
		}

		deck[name] = values
	}

	json, err := json.MarshalIndent(deck, "", "  ")

	if err != nil {
		panic(err)
	}

	return string(json)

}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}

func (c *Controller) ScrollUp() {
	var view *gocui.View
	var err error
	if view, err = c.gui.View("main"); err != nil {
		return
	}
	x, y := view.Origin()
	view.SetOrigin(x, y-1)
}

func (c *Controller) ScrollDown() {
	var view *gocui.View
	var err error
	if view, err = c.gui.View("main"); err != nil {
		return
	}
	x, y := view.Origin()
	view.SetOrigin(x, y+1)
}

func (c *Controller) ToggleRender() {
	c.render++
	if c.render > renderChest {
		c.render = 0
	}
}

func (c *Controller) StartProposingMove() {
	c.EnterMode(modeProposingMove)
}

//Cancels any mode that we're in by going back to normal mode.
func (c *Controller) CancelMode() {
	c.EnterMode(modeNormal)
}

func (c *Controller) EnterMode(m inputMode) {
	g := c.gui

	//Clear out all keybindings; mode. enterMode will reestablish them.
	g.DeleteKeybindings("")
	for _, view := range g.Views() {
		g.DeleteKeybindings(view.Name())
	}

	m.enterMode(c)
	c.mode = m
}

//Once the controller is set up, call Start. It will block until it is time
//to exit.
func (c *Controller) Start() {

	g, err := gocui.NewGui(gocui.OutputNormal)

	if err != nil {
		panic("Couldn't create gui:" + err.Error())
	}

	defer g.Close()

	c.gui = g

	g.InputEsc = true

	//manager has to be set before setting keybindings, because it clears all
	//keybindings when set.
	g.SetManager(c)

	c.EnterMode(modeNormal)

	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
		panic(err)
	}

}
<commit_msg>Render list of moves for the game (or none if there aren't any). Part of #26.<commit_after>/*

cli is a simple package to allow a game to render to the screen and be
interacted with. It's intended primarily as a tool to diagnose and play around
with a game while its moves and logic are being defined.

*/
package cli

import (
	"encoding/json"
	"fmt"
	"github.com/jkomoros/boardgame"
	"github.com/jroimartin/gocui"
	"strings"
)

type renderType int

const (
	renderJSON renderType = iota
	renderRender
	renderChest
)

//Controller is the primary type of the package.
type Controller struct {
	game     *boardgame.Game
	gui      *gocui.Gui
	renderer RendererFunc
	render   renderType
	mode     inputMode
}

//RenderrerFunc takes a state and outputs a list of strings that should be
//printed to screen to depict it.
type RendererFunc func(boardgame.StatePayload) string

func NewController(game *boardgame.Game, renderer RendererFunc) *Controller {
	return &Controller{
		game:     game,
		renderer: renderer,
	}
}

//Implement the gocui.Manager interface
func (c *Controller) Layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	if v, err := g.SetView("main", 0, 0, maxX-1, maxY-2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Title = "JSON"
		v.Frame = true
	}

	if v, err := g.SetView("status", 0, maxY-2, maxX-1, maxY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.FgColor = gocui.ColorBlack
		v.BgColor = gocui.ColorWhite
		v.Frame = false
	}

	switch c.mode {
	case modeProposingMove:
		if v, err := g.SetView("move", 0, maxY-30, maxX-1, maxY-2); err != nil {
			if err != gocui.ErrUnknownView {
				return err
			}
			v.Frame = true
			v.Title = "Proposing Move"

			fmt.Fprint(v, c.renderMoves())

			g.SetViewOnTop("move")
		}
	case modeNormal:
		//Delete the view, if it exists
		if err := g.DeleteView("move"); err != nil {
			//It's OK if it's ErrUnknownView because that just means it wasn't
			//in there and was a no op.
			if err != gocui.ErrUnknownView {
				return err
			}
		}
	default:
		//Presumably a nil mode. Meh.
	}

	//Update the json field of view

	if view, err := g.View("main"); err == nil {
		view.Clear()
		switch c.render {
		case renderRender:
			//Print renderered view
			fmt.Fprint(view, c.renderRendered())
			view.Title = "Rendered"
		case renderJSON:
			//Print JSON view
			fmt.Fprint(view, c.renderJSON())
			view.Title = "JSON"
		case renderChest:
			fmt.Fprint(view, c.renderChest())
			view.Title = "Chest"
		}

	}

	if view, err := g.View("status"); err == nil {
		view.Clear()
		fmt.Fprint(view, c.mode.statusLine())
	}

	return nil
}

func (c *Controller) renderMoves() string {

	var result []string

	for _, move := range c.game.Moves() {
		result = append(result, move.Name()+" : "+move.Description())
	}

	if len(result) == 0 {
		result = []string{"No moves configured for this game."}
	}

	return strings.Join(result, "\n")

}

func (c *Controller) renderRendered() string {
	return c.renderer(c.game.State.Payload)
}

func (c *Controller) renderJSON() string {
	return string(boardgame.Serialize(c.game.State.JSON()))
}

func (c *Controller) renderChest() string {

	deck := make(map[string][]interface{})

	for _, name := range c.game.Chest().DeckNames() {

		components := c.game.Chest().Deck(name).Components()

		values := make([]interface{}, len(components))

		for i, component := range components {
			values[i] = struct {
				Index  int
				Values interface{}
			}{
				i,
				component.Values,
			}
		}

		deck[name] = values
	}

	json, err := json.MarshalIndent(deck, "", "  ")

	if err != nil {
		panic(err)
	}

	return string(json)

}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}

func (c *Controller) ScrollUp() {
	var view *gocui.View
	var err error
	if view, err = c.gui.View("main"); err != nil {
		return
	}
	x, y := view.Origin()
	view.SetOrigin(x, y-1)
}

func (c *Controller) ScrollDown() {
	var view *gocui.View
	var err error
	if view, err = c.gui.View("main"); err != nil {
		return
	}
	x, y := view.Origin()
	view.SetOrigin(x, y+1)
}

func (c *Controller) ToggleRender() {
	c.render++
	if c.render > renderChest {
		c.render = 0
	}
}

func (c *Controller) StartProposingMove() {
	c.EnterMode(modeProposingMove)
}

//Cancels any mode that we're in by going back to normal mode.
func (c *Controller) CancelMode() {
	c.EnterMode(modeNormal)
}

func (c *Controller) EnterMode(m inputMode) {
	g := c.gui

	//Clear out all keybindings; mode. enterMode will reestablish them.
	g.DeleteKeybindings("")
	for _, view := range g.Views() {
		g.DeleteKeybindings(view.Name())
	}

	m.enterMode(c)
	c.mode = m
}

//Once the controller is set up, call Start. It will block until it is time
//to exit.
func (c *Controller) Start() {

	g, err := gocui.NewGui(gocui.OutputNormal)

	if err != nil {
		panic("Couldn't create gui:" + err.Error())
	}

	defer g.Close()

	c.gui = g

	g.InputEsc = true

	//manager has to be set before setting keybindings, because it clears all
	//keybindings when set.
	g.SetManager(c)

	c.EnterMode(modeNormal)

	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
		panic(err)
	}

}
<|endoftext|>


<commit_before>package sshego

import (
	"fmt"
	"testing"

	cv "github.com/glycerine/goconvey/convey"
)

func Test201ClientDirectSSH(t *testing.T) {

	cv.Convey("Used as a library, sshego should allow a client to establish a tcp forwarded TCP connection throught the SSHd without opening a listening port (that is exposed to other users' processes) on the local host", t, func() {

		// what is the Go client interface to an TCP connection?
		// the net.Conn that is returned by conn, err := net.Dial("tcp", "localhost:tcpSrvPort")

		// start a simple TCP server  that is the target of the forward through the sshd,
		// so we can confirm the client has made the connection.

		// generate a random payload for the client to send to the server.
		payloadByteCount := 50
		confirmationPayload := RandomString(payloadByteCount)
		confirmationReply := RandomString(payloadByteCount)

		serverDone := make(chan bool)

		tcpSrvLsn, tcpSrvPort := GetAvailPort()

		StartBackgroundTestTcpServer(
			serverDone,
			payloadByteCount,
			confirmationPayload,
			confirmationReply,
			tcpSrvLsn)

		s := MakeTestSshClientAndServer(true)
		defer TempDirCleanup(s.SrvCfg.Origdir, s.SrvCfg.Tempdir)

		dest := fmt.Sprintf("127.0.0.1:%v", tcpSrvPort)

		// below over SSH should be equivalent of the following
		// non-encrypted ping/pong.

		if false {
			UnencPingPong(dest, confirmationPayload, confirmationReply, payloadByteCount)
		}
		if true {
			dc := DialConfig{
				ClientKnownHostsPath: s.CliCfg.ClientKnownHostsPath,
				Mylogin:              s.Mylogin,
				RsaPath:              s.RsaPath,
				TotpUrl:              s.Totp,
				Pw:                   s.Pw,
				Sshdhost:             s.SrvCfg.EmbeddedSSHd.Host,
				Sshdport:             s.SrvCfg.EmbeddedSSHd.Port,
				DownstreamHostPort:   dest,
				TofuAddIfNotKnown:    true,
			}

			// first time we add the server key
			channelToTcpServer, _, err := dc.Dial()
			fmt.Printf("after dc.Dial() in cli_test.go: err = '%v'", err)
			cv.So(err.Error(), cv.ShouldContainSubstring, "Re-run without -new")

			// second time we connect based on that server key
			dc.TofuAddIfNotKnown = false
			channelToTcpServer, _, err = dc.Dial()
			cv.So(err, cv.ShouldBeNil)

			VerifyClientServerExchangeAcrossSshd(channelToTcpServer, confirmationPayload, confirmationReply, payloadByteCount)
			channelToTcpServer.Close()
		}
		// tcp-server should have exited because it got the expected
		// message and replied with the agreed upon reply and then exited.
		<-serverDone

		// done with testing, cleanup
		s.SrvCfg.Esshd.Stop()
		<-s.SrvCfg.Esshd.Halt.Done.Chan
		cv.So(true, cv.ShouldEqual, true) // we should get here.
	})
}
<commit_msg>atg. robust to simple connection errors in test 201<commit_after>package sshego

import (
	"fmt"
	"net"
	"strings"
	"testing"

	cv "github.com/glycerine/goconvey/convey"
)

func Test201ClientDirectSSH(t *testing.T) {

	cv.Convey("Used as a library, sshego should allow a client to establish a tcp forwarded TCP connection throught the SSHd without opening a listening port (that is exposed to other users' processes) on the local host", t, func() {

		// what is the Go client interface to an TCP connection?
		// the net.Conn that is returned by conn, err := net.Dial("tcp", "localhost:tcpSrvPort")

		// start a simple TCP server  that is the target of the forward through the sshd,
		// so we can confirm the client has made the connection.

		// generate a random payload for the client to send to the server.
		payloadByteCount := 50
		confirmationPayload := RandomString(payloadByteCount)
		confirmationReply := RandomString(payloadByteCount)

		serverDone := make(chan bool)

		tcpSrvLsn, tcpSrvPort := GetAvailPort()

		StartBackgroundTestTcpServer(
			serverDone,
			payloadByteCount,
			confirmationPayload,
			confirmationReply,
			tcpSrvLsn)

		s := MakeTestSshClientAndServer(true)
		defer TempDirCleanup(s.SrvCfg.Origdir, s.SrvCfg.Tempdir)

		dest := fmt.Sprintf("127.0.0.1:%v", tcpSrvPort)

		// below over SSH should be equivalent of the following
		// non-encrypted ping/pong.

		if false {
			UnencPingPong(dest, confirmationPayload, confirmationReply, payloadByteCount)
		}
		if true {
			dc := DialConfig{
				ClientKnownHostsPath: s.CliCfg.ClientKnownHostsPath,
				Mylogin:              s.Mylogin,
				RsaPath:              s.RsaPath,
				TotpUrl:              s.Totp,
				Pw:                   s.Pw,
				Sshdhost:             s.SrvCfg.EmbeddedSSHd.Host,
				Sshdport:             s.SrvCfg.EmbeddedSSHd.Port,
				DownstreamHostPort:   dest,
				TofuAddIfNotKnown:    true,
			}

			tries := 0
			var channelToTcpServer net.Conn
			var err error

			for ; tries < 3; tries++ {
				// first time we add the server key
				channelToTcpServer, _, err = dc.Dial()
				fmt.Printf("after dc.Dial() in cli_test.go: err = '%v'", err)
				errs := err.Error()
				case1 := strings.Contains(errs, "Re-run without -new")
				case2 := strings.Contains(errs, "getsockopt: connection refused")
				ok := case1 || case2
				cv.So(ok, cv.ShouldBeTrue)
				if case1 {
					break
				}
			}
			if tries == 3 {
				panic("could not get 'Re-run without -new' after 3 tries")
			}

			// second time we connect based on that server key
			dc.TofuAddIfNotKnown = false
			channelToTcpServer, _, err = dc.Dial()
			cv.So(err, cv.ShouldBeNil)

			VerifyClientServerExchangeAcrossSshd(channelToTcpServer, confirmationPayload, confirmationReply, payloadByteCount)
			channelToTcpServer.Close()
		}
		// tcp-server should have exited because it got the expected
		// message and replied with the agreed upon reply and then exited.
		<-serverDone

		// done with testing, cleanup
		s.SrvCfg.Esshd.Stop()
		<-s.SrvCfg.Esshd.Halt.Done.Chan
		cv.So(true, cv.ShouldEqual, true) // we should get here.
	})
}
<|endoftext|>


<commit_before>/*
Author: Gianluca Fiore <forod.g@gmail.com> © 2013-2015
*/

package main

import (
	"database/sql"
	"flag"
	"fmt"
	_ "github.com/mattn/go-sqlite3"
	"log"
	"os"
	"path/filepath"
	"time"
)

var usageString = `
kitemmuort-count (-c|-s) [-d <date>]

Arguments:
	-count|-c
		Show the kitemmuort count for a date (default is today)
	-set|-s
		Set the amount of kitemmuort for a date (default is today)
	-date|-d
		Operate (count/set) on a specific date instead than today
		Use YYYY-MM-DD (example: 2012-10-01)

`
var countArg bool	// show the kitemmuort number or not
var dateArg string	// the date string argument
var setArg int		// the kitemmuort count argument to be set for today

// parse flags
func flagsInit() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, usageString)
	}

	const (
		fDate	= ""
		fCount = false
		fSet	= 0
	)

	flag.StringVar(&dateArg, "date", fDate, "")
	flag.StringVar(&dateArg, "d", fDate, "")
	flag.BoolVar(&countArg, "count", fCount, "")
	flag.BoolVar(&countArg, "c", fCount, "")
	flag.IntVar(&setArg, "set", fSet, "")
	flag.IntVar(&setArg, "s", fSet, "")

	flag.Parse()

	if countArg != false && setArg != 0 {
		fmt.Fprintf(os.Stderr, "Either use -count or -set, not both\n")
	}
	if countArg == false && setArg == 0 {
		// no argument given, default to show kitemmuort count for today
		countArg = true
	}
}

// checkDbExist checks that the db exists and that the default table has been created
func checkDbExist(db *sql.DB) bool {
	rows, err := db.Query("SELECT * FROM kitemmuorts")
	if err != nil {
		return false
	}
	defer rows.Close()
	if rows != nil {
		return true
	}

	return false
}

// createTable creates the default table to host the application's data
func createTable(db *sql.DB) {
	cStmt, err := db.Prepare("CREATE TABLE kitemmuorts(date text, count int, UNIQUE (date));")
	if err != nil {
		log.Fatal(err)
	}

	exResult, err := cStmt.Exec()
	// check that table doesn't already exist
	if err != nil {
		log.Println(err)
		fmt.Println(exResult)
	}
}

// returnHomeDir returns the path of the current user's home directory
func returnHomeDir() string {
	if homedir := os.Getenv("HOMEPATH"); homedir != "" {
		return homedir
	} else if homedir := os.Getenv("HOME"); homedir != "" {
		return homedir
	} else {
		return ""
	}
}

// formatDateString formats the dateString flag to the appropriate format for
// the SQLite database
func formatDateString(d string) string {
	const dateLayout = "2006-01-02" // the date format layout, to match
	// how date is stored in the sqlite db

	if d == "" {
		// empty date, use today's
		t := time.Now()
		return t.Format(dateLayout)
	}
	t, err := time.Parse(dateLayout, d)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error parsing the date, is it in YYYY-MM-DD format?\n")
		log.Fatal(err)
	}
	return t.Format(dateLayout)
}

func main() {
	var dbReady bool	// is the db ready?
	var dbFile string	// the database filename

	flagsInit()

	homedir := returnHomeDir()

	dbFile = filepath.Join(homedir, ".kitemmuort.db")

	// check the db file exists
	if _, err := os.Stat(dbFile); err != nil {
		if os.IsNotExist(err) {
			_, err := os.Create(dbFile)
			if err != nil {
				log.Fatal(err)
			}
		} else {
			// file exists but perhaps it can't be accessed?
			log.Fatal(err)
		}
	}
	// open connection to the db
	db, err := sql.Open("sqlite3", dbFile)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	dbReady = checkDbExist(db)
	if dbReady != true {
		createTable(db)
	}

	// set the date to query on
	t := formatDateString(dateArg)

	if countArg {
		var date string
		var count int
		err := db.QueryRow("SELECT date, count FROM kitemmuorts WHERE date = ?", t).Scan(&date, &count)
		if count == 0 {
			// nothing has been set, yet, for date, exit
			fmt.Fprintf(os.Stdout, "\nNo kitemmuorts set for %s yet. Want to add some?\n", t)
			os.Exit(0)
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Fprintf(os.Stdout, "\nKitemmuort count for %s is %d\n", date, count)
	}

	if setArg != 0 {
		stmt, err := db.Prepare("INSERT OR REPLACE INTO kitemmuorts(date, count) VALUES(?, ?)")
		if err != nil {
			log.Fatal(err)
		}
		_, execErr := stmt.Exec(t, setArg)
		if err != nil {
			log.Fatal(execErr)
		}
		fmt.Fprintf(os.Stdout, "\n%d kitemmuorts set for %s\n", setArg, t)
	}
}
<commit_msg>update copyright year<commit_after>/*
Author: Gianluca Fiore <forod.g@gmail.com> © 2013-2018
*/

package main

import (
	"database/sql"
	"flag"
	"fmt"
	_ "github.com/mattn/go-sqlite3"
	"log"
	"os"
	"path/filepath"
	"time"
)

var usageString = `
kitemmuort-count (-c|-s) [-d <date>]

Arguments:
	-count|-c
		Show the kitemmuort count for a date (default is today)
	-set|-s
		Set the amount of kitemmuort for a date (default is today)
	-date|-d
		Operate (count/set) on a specific date instead than today
		Use YYYY-MM-DD (example: 2012-10-01)

`
var countArg bool	// show the kitemmuort number or not
var dateArg string	// the date string argument
var setArg int		// the kitemmuort count argument to be set for today

// parse flags
func flagsInit() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, usageString)
	}

	const (
		fDate	= ""
		fCount = false
		fSet	= 0
	)

	flag.StringVar(&dateArg, "date", fDate, "")
	flag.StringVar(&dateArg, "d", fDate, "")
	flag.BoolVar(&countArg, "count", fCount, "")
	flag.BoolVar(&countArg, "c", fCount, "")
	flag.IntVar(&setArg, "set", fSet, "")
	flag.IntVar(&setArg, "s", fSet, "")

	flag.Parse()

	if countArg != false && setArg != 0 {
		fmt.Fprintf(os.Stderr, "Either use -count or -set, not both\n")
	}
	if countArg == false && setArg == 0 {
		// no argument given, default to show kitemmuort count for today
		countArg = true
	}
}

// checkDbExist checks that the db exists and that the default table has been created
func checkDbExist(db *sql.DB) bool {
	rows, err := db.Query("SELECT * FROM kitemmuorts")
	if err != nil {
		return false
	}
	defer rows.Close()
	if rows != nil {
		return true
	}

	return false
}

// createTable creates the default table to host the application's data
func createTable(db *sql.DB) {
	cStmt, err := db.Prepare("CREATE TABLE kitemmuorts(date text, count int, UNIQUE (date));")
	if err != nil {
		log.Fatal(err)
	}

	exResult, err := cStmt.Exec()
	// check that table doesn't already exist
	if err != nil {
		log.Println(err)
		fmt.Println(exResult)
	}
}

// returnHomeDir returns the path of the current user's home directory
func returnHomeDir() string {
	if homedir := os.Getenv("HOMEPATH"); homedir != "" {
		return homedir
	} else if homedir := os.Getenv("HOME"); homedir != "" {
		return homedir
	} else {
		return ""
	}
}

// formatDateString formats the dateString flag to the appropriate format for
// the SQLite database
func formatDateString(d string) string {
	const dateLayout = "2006-01-02" // the date format layout, to match
	// how date is stored in the sqlite db

	if d == "" {
		// empty date, use today's
		t := time.Now()
		return t.Format(dateLayout)
	}
	t, err := time.Parse(dateLayout, d)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error parsing the date, is it in YYYY-MM-DD format?\n")
		log.Fatal(err)
	}
	return t.Format(dateLayout)
}

func main() {
	var dbReady bool	// is the db ready?
	var dbFile string	// the database filename

	flagsInit()

	homedir := returnHomeDir()

	dbFile = filepath.Join(homedir, ".kitemmuort.db")

	// check the db file exists
	if _, err := os.Stat(dbFile); err != nil {
		if os.IsNotExist(err) {
			_, err := os.Create(dbFile)
			if err != nil {
				log.Fatal(err)
			}
		} else {
			// file exists but perhaps it can't be accessed?
			log.Fatal(err)
		}
	}
	// open connection to the db
	db, err := sql.Open("sqlite3", dbFile)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	dbReady = checkDbExist(db)
	if dbReady != true {
		createTable(db)
	}

	// set the date to query on
	t := formatDateString(dateArg)

	if countArg {
		var date string
		var count int
		err := db.QueryRow("SELECT date, count FROM kitemmuorts WHERE date = ?", t).Scan(&date, &count)
		if count == 0 {
			// nothing has been set, yet, for date, exit
			fmt.Fprintf(os.Stdout, "\nNo kitemmuorts set for %s yet. Want to add some?\n", t)
			os.Exit(0)
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Fprintf(os.Stdout, "\nKitemmuort count for %s is %d\n", date, count)
	}

	if setArg != 0 {
		stmt, err := db.Prepare("INSERT OR REPLACE INTO kitemmuorts(date, count) VALUES(?, ?)")
		if err != nil {
			log.Fatal(err)
		}
		_, execErr := stmt.Exec(t, setArg)
		if err != nil {
			log.Fatal(execErr)
		}
		fmt.Fprintf(os.Stdout, "\n%d kitemmuorts set for %s\n", setArg, t)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/jessevdk/go-flags"
	"github.com/kardianos/osext"
	"strings"
)

type helpCommand struct{}

func (cmd *helpCommand) Execute(args []string) error {
	termKey := strings.Join(args, " ")
	showGeneralHelp := commandParser.Active == nil || commandParser.Active == commandParser.Command || len(args) < 1 && commandParser.Active.Name == "help"
	if !showGeneralHelp && len(args) > 0 {
		c := commandParser.Find(args[0])
		help := helpIndex[termKey]
		var p *flags.Command
		if c != nil && len(args) > 1 {
			p = c
			c = c.Find(args[1])
		}

		if help == nil && c == nil {
			fmt.Printf("no help for: \"%s\"\n", termKey)
			return nil
		} else if help != nil {
			c = p
			p = nil
		}

		command := termKey
		var subs []*flags.Command
		if c != nil {
			subs = c.Commands()
			if len(subs) > 0 {
				command += " <command>"
			}
		}

		fmt.Printf("\n%s\n\n", help.long)
		fmt.Printf("Usage:\n    %s %s %s\n\n", commandParser.Command.Name, command, help.usage)

		if subs != nil && len(subs) > 0 {
			fmt.Printf("where <command> is one of:\n")
			rowCount := 0
			for i, c := range subs {
				if rowCount == 0 {
					fmt.Print("    ")
				}

				fmt.Print(c.Name)
				if (i + 1) < len(subs) {
					fmt.Print(", ")
				}

				rowCount++
				if rowCount >= 6 {
					rowCount = 0
					fmt.Print("\n")
				}
			}

			if len(subs) < 6 {
				fmt.Print("\n")
			}

			fmt.Printf("\nUse \"%s help %s <cmd>\" to display help about <cmd>\n\n", commandParser.Command.Name, c.Name)
		}
	} else {
		showGeneralHelp = true
	}

	if showGeneralHelp {
		_ = runInContext(func(current *executionContext) error {
			binaryPath, _ := osext.ExecutableFolder()
			fmt.Printf("\nUsage:\n    %s <command> [options]\n\n", commandParser.Command.Name)

			fmt.Print("where <command> is one of:\n")
			rowCount := 0
			for i, c := range commandParser.Commands() {
				if rowCount == 0 {
					fmt.Print("    ")
				}

				fmt.Printf(c.Name)
				if (i + 1) < len(commandParser.Commands()) {
					fmt.Print(", ")
				}

				rowCount++
				if rowCount >= 6 {
					rowCount = 0
					fmt.Print("\n")
				}
			}

			fmt.Printf("\n\nUse \"%s help <cmd>\" to display help about <cmd>\n\n", commandParser.Command.Name)
			fmt.Printf("Config:\n    %s\n", current.config.filePath)
			fmt.Printf("Home:\n    %s\n", current.homePath)
			fmt.Printf("\n%s@%s %s\n", commandParser.Command.Name, buildVersion, binaryPath)
			return nil
		})
	}

	return nil
}

func init() {
	commandParser.AddCommand("help", "", "", &helpCommand{})
}
<commit_msg>don't compile old help command<commit_after>// -build

package main

import (
	"fmt"
	"github.com/jessevdk/go-flags"
	"github.com/kardianos/osext"
	"strings"
)

type helpCommand struct{}

func (cmd *helpCommand) Execute(args []string) error {
	termKey := strings.Join(args, " ")
	showGeneralHelp := commandParser.Active == nil || commandParser.Active == commandParser.Command || len(args) < 1 && commandParser.Active.Name == "help"
	if !showGeneralHelp && len(args) > 0 {
		c := commandParser.Find(args[0])
		help := helpIndex[termKey]
		var p *flags.Command
		if c != nil && len(args) > 1 {
			p = c
			c = c.Find(args[1])
		}

		if help == nil && c == nil {
			fmt.Printf("no help for: \"%s\"\n", termKey)
			return nil
		} else if help != nil {
			c = p
			p = nil
		}

		command := termKey
		var subs []*flags.Command
		if c != nil {
			subs = c.Commands()
			if len(subs) > 0 {
				command += " <command>"
			}
		}

		fmt.Printf("\n%s\n\n", help.long)
		fmt.Printf("Usage:\n    %s %s %s\n\n", commandParser.Command.Name, command, help.usage)

		if subs != nil && len(subs) > 0 {
			fmt.Printf("where <command> is one of:\n")
			rowCount := 0
			for i, c := range subs {
				if rowCount == 0 {
					fmt.Print("    ")
				}

				fmt.Print(c.Name)
				if (i + 1) < len(subs) {
					fmt.Print(", ")
				}

				rowCount++
				if rowCount >= 6 {
					rowCount = 0
					fmt.Print("\n")
				}
			}

			if len(subs) < 6 {
				fmt.Print("\n")
			}

			fmt.Printf("\nUse \"%s help %s <cmd>\" to display help about <cmd>\n\n", commandParser.Command.Name, c.Name)
		}
	} else {
		showGeneralHelp = true
	}

	if showGeneralHelp {
		_ = runInContext(func(current *executionContext) error {
			binaryPath, _ := osext.ExecutableFolder()
			fmt.Printf("\nUsage:\n    %s <command> [options]\n\n", commandParser.Command.Name)

			fmt.Print("where <command> is one of:\n")
			rowCount := 0
			for i, c := range commandParser.Commands() {
				if rowCount == 0 {
					fmt.Print("    ")
				}

				fmt.Printf(c.Name)
				if (i + 1) < len(commandParser.Commands()) {
					fmt.Print(", ")
				}

				rowCount++
				if rowCount >= 6 {
					rowCount = 0
					fmt.Print("\n")
				}
			}

			fmt.Printf("\n\nUse \"%s help <cmd>\" to display help about <cmd>\n\n", commandParser.Command.Name)
			fmt.Printf("Config:\n    %s\n", current.config.filePath)
			fmt.Printf("Home:\n    %s\n", current.homePath)
			fmt.Printf("\n%s@%s %s\n", commandParser.Command.Name, buildVersion, binaryPath)
			return nil
		})
	}

	return nil
}

func init() {
	commandParser.AddCommand("help", "", "", &helpCommand{})
}
<|endoftext|>