<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"gopkg.in/fsnotify.v1"
)

var (
	filesFlag = flag.String("f", "", "files and directories to watch, split by ':'")
	sigFlag   = flag.String("s", "HUP", "signal to send on change")
)

var signals = map[string]os.Signal{
	"ABRT":   syscall.SIGABRT,
	"ALRM":   syscall.SIGALRM,
	"BUS":    syscall.SIGBUS,
	"CHLD":   syscall.SIGCHLD,
	"CONT":   syscall.SIGCONT,
	"EMT":    syscall.SIGEMT,
	"FPE":    syscall.SIGFPE,
	"HUP":    syscall.SIGHUP,
	"ILL":    syscall.SIGILL,
	"INFO":   syscall.SIGINFO,
	"INT":    syscall.SIGINT,
	"IO":     syscall.SIGIO,
	"IOT":    syscall.SIGIOT,
	"KILL":   syscall.SIGKILL,
	"PIPE":   syscall.SIGPIPE,
	"PROF":   syscall.SIGPROF,
	"QUIT":   syscall.SIGQUIT,
	"SEGV":   syscall.SIGSEGV,
	"STOP":   syscall.SIGSTOP,
	"SYS":    syscall.SIGSYS,
	"TERM":   syscall.SIGTERM,
	"TRAP":   syscall.SIGTRAP,
	"TSTP":   syscall.SIGTSTP,
	"TTIN":   syscall.SIGTTIN,
	"TTOU":   syscall.SIGTTOU,
	"URG":    syscall.SIGURG,
	"USR1":   syscall.SIGUSR1,
	"USR2":   syscall.SIGUSR2,
	"VTALRM": syscall.SIGVTALRM,
	"WINCH":  syscall.SIGWINCH,
	"XCPU":   syscall.SIGXCPU,
	"XFSZ":   syscall.SIGXFSZ,
}

func signalByName(name string) (sig os.Signal, err error) {
	var ok bool
	if sig, ok = signals[strings.ToUpper(name)]; !ok {
		err = errors.New(fmt.Sprintf("unknown signal: %s", name))
	}
	return
}

func usageAndExit(s interface{}) {
	fmt.Println(s)
	flag.Usage()
	os.Exit(1)
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: blart [flags] [command]\n")
	flag.PrintDefaults()
}

func init() {
	flag.Usage = usage
	flag.Parse()
}

func main() {
	sig, err := signalByName(*sigFlag)
	if err != nil {
		usageAndExit(err)
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		usageAndExit(err)
	}
	defer watcher.Close()

	if *filesFlag == "" {
		usageAndExit("no files to watch")
	}

	if len(flag.Args()) == 0 {
		usageAndExit("no command specified")
	}

	// start watching files for changes
	for _, file := range strings.Split(*filesFlag, ":") {
		err = watcher.Add(file)
		// if a file doesn't exist that you're trying to watch at this
		// point, it's likely a config error, and we should bail
		if err != nil {
			usageAndExit(err)
		}
	}

	cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Start()
	if err != nil {
		usageAndExit(err)
	}

	fmt.Println("==> starting child", strings.Join(flag.Args(), " "))

	done := make(chan struct{})
	go func() {
		cmd.Wait()
		done <- struct{}{}
	}()

	go func() {
		var event fsnotify.Event
		var err error
		for {
			select {
			case event = <-watcher.Events:
				log.Println("==> detected change in", event.Name)
				// TODO: add throttle/debounce behavior to prevent a ton
				// of signals being sent.
				cmd.Process.Signal(sig)
				if event.Op&fsnotify.Rename == fsnotify.Rename {
					// File was renamed, so remove the old watch,
					// and add a new one
					watcher.Remove(event.Name)
					watcher.Add(event.Name)
				}
			case err = <-watcher.Errors:
				log.Println("==> error:", err)
			}
		}
	}()

	// Listen to signals send to parent, and pass along to the child
	c := make(chan os.Signal, 1)
	signal.Notify(c)
	go func() {
		var sig os.Signal
		for {
			sig = <-c
			cmd.Process.Signal(sig)
			switch sig {
			case os.Interrupt, os.Kill, syscall.SIGTERM:
				countdown := 5 * time.Second

				fmt.Println("==> attempting to shut down cleanly")
				fmt.Printf("==> waiting up to %s for child to exit\n", countdown)

				// try and wait for the child to shut down before killing
				select {
				case <-done:
				case <-time.After(countdown):
				}

				// it hasn't shut down yet, so attempt to SIGKILL
				fmt.Println("==> attempting to now kill child")
				cmd.Process.Signal(os.Kill)

				select {
				case <-done:
				case <-time.After(time.Second):
				}

				// still hasn't exited, so killing self
				fmt.Println("==> now committing suicide")
				os.Exit(1)
			}
		}
	}()

	<-done
}
<commit_msg>kinda sorta debounce signalling to child<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"gopkg.in/fsnotify.v1"
)

var (
	filesFlag = flag.String("f", "", "files and directories to watch, split by ':'")
	sigFlag   = flag.String("s", "HUP", "signal to send on change")
	delayFlag = flag.Duration("d", 3*time.Second, "time to wait after change before signalling child")
)

var signals = map[string]os.Signal{
	"ABRT":   syscall.SIGABRT,
	"ALRM":   syscall.SIGALRM,
	"BUS":    syscall.SIGBUS,
	"CHLD":   syscall.SIGCHLD,
	"CONT":   syscall.SIGCONT,
	"EMT":    syscall.SIGEMT,
	"FPE":    syscall.SIGFPE,
	"HUP":    syscall.SIGHUP,
	"ILL":    syscall.SIGILL,
	"INFO":   syscall.SIGINFO,
	"INT":    syscall.SIGINT,
	"IO":     syscall.SIGIO,
	"IOT":    syscall.SIGIOT,
	"KILL":   syscall.SIGKILL,
	"PIPE":   syscall.SIGPIPE,
	"PROF":   syscall.SIGPROF,
	"QUIT":   syscall.SIGQUIT,
	"SEGV":   syscall.SIGSEGV,
	"STOP":   syscall.SIGSTOP,
	"SYS":    syscall.SIGSYS,
	"TERM":   syscall.SIGTERM,
	"TRAP":   syscall.SIGTRAP,
	"TSTP":   syscall.SIGTSTP,
	"TTIN":   syscall.SIGTTIN,
	"TTOU":   syscall.SIGTTOU,
	"URG":    syscall.SIGURG,
	"USR1":   syscall.SIGUSR1,
	"USR2":   syscall.SIGUSR2,
	"VTALRM": syscall.SIGVTALRM,
	"WINCH":  syscall.SIGWINCH,
	"XCPU":   syscall.SIGXCPU,
	"XFSZ":   syscall.SIGXFSZ,
}

func signalByName(name string) (sig os.Signal, err error) {
	var ok bool
	if sig, ok = signals[strings.ToUpper(name)]; !ok {
		err = errors.New(fmt.Sprintf("unknown signal: %s", name))
	}
	return
}

func signalDebounce(process *os.Process, sig os.Signal, delay time.Duration) *sync.Cond {
	var m sync.Mutex
	cond := sync.NewCond(&m)
	cond.L.Lock()

	go func() {
		// continulously wait for a Broadcast event
		// then sleep, and pass along the signal to the process
		// The sleep causes the signals to effectively be debounced.
		// Note: this isn't a true debounce. We don't want to trigger
		// immediately on the first event. We explicitly want to wait
		// _then_ trigger.
		for {
			cond.Wait()
			time.Sleep(delay)
			if process != nil {
				log.Println("==> signalling child")
				process.Signal(sig)
			}
		}
	}()

	return cond
}

func usageAndExit(s interface{}) {
	fmt.Println(s)
	flag.Usage()
	os.Exit(1)
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: blart [flags] [command]\n")
	flag.PrintDefaults()
}

func init() {
	flag.Usage = usage
	flag.Parse()
}

func main() {
	sig, err := signalByName(*sigFlag)
	if err != nil {
		usageAndExit(err)
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		usageAndExit(err)
	}
	defer watcher.Close()

	if *filesFlag == "" {
		usageAndExit("no files to watch")
	}

	if len(flag.Args()) == 0 {
		usageAndExit("no command specified")
	}

	// start watching files for changes
	for _, file := range strings.Split(*filesFlag, ":") {
		err = watcher.Add(file)
		// if a file doesn't exist that you're trying to watch at this
		// point, it's likely a config error, and we should bail
		if err != nil {
			usageAndExit(err)
		}
	}

	cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Start()
	if err != nil {
		usageAndExit(err)
	}

	fmt.Println("==> starting child", strings.Join(flag.Args(), " "))

	done := make(chan struct{})
	go func() {
		cmd.Wait()
		done <- struct{}{}
	}()

	go func() {
		var event fsnotify.Event
		var err error

		// Create wrapper to debounce the signal events
		cond := signalDebounce(cmd.Process, sig, *delayFlag)

		for {
			select {
			case event = <-watcher.Events:
				log.Println("==> detected change in", event.Name)
				// Broadcase to the sync.Cond that an event has happened.
				// magic happens inside signalDebounce
				cond.Broadcast()
				if event.Op&fsnotify.Rename == fsnotify.Rename {
					// File was renamed, so remove the old watch,
					// and add a new one
					watcher.Remove(event.Name)
					watcher.Add(event.Name)
				}
			case err = <-watcher.Errors:
				log.Println("==> error:", err)
			}
		}
	}()

	// Listen to signals send to parent, and pass along to the child
	c := make(chan os.Signal, 1)
	signal.Notify(c)
	go func() {
		var sig os.Signal
		for {
			sig = <-c
			cmd.Process.Signal(sig)
			switch sig {
			case os.Interrupt, os.Kill, syscall.SIGTERM:
				countdown := 5 * time.Second

				fmt.Println("==> attempting to shut down cleanly")
				fmt.Printf("==> waiting up to %s for child to exit\n", countdown)

				// try and wait for the child to shut down before killing
				select {
				case <-done:
				case <-time.After(countdown):
				}

				// it hasn't shut down yet, so attempt to SIGKILL
				fmt.Println("==> attempting to now kill child")
				cmd.Process.Signal(os.Kill)

				select {
				case <-done:
				case <-time.After(time.Second):
				}

				// still hasn't exited, so killing self
				fmt.Println("==> now committing suicide")
				os.Exit(1)
			}
		}
	}()

	<-done
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"unicode"

	"9fans.net/go/acme"
	"9fans.net/go/plan9"
	"9fans.net/go/plumb"
	"golang.org/x/net/websocket"
	"golang.org/x/tools/cmd/guru/serial"
)

var (
	flagAddr = flag.String("listen", ":6161", "listen address")
	flagDev  = flag.Bool("dev", false, "enable dev mode")
)

func main() {
	flag.Parse()

	goPath = strings.Split(os.Getenv("GOPATH"), ":")[0]

	webFS := FS(*flagDev)
	http.Handle("/static/", http.FileServer(webFS))
	http.Handle("/ws", websocket.Handler(WS))
	http.HandleFunc("/", Index)
	http.HandleFunc("/api/open", wrap(Open))
	http.HandleFunc("/api/command", wrap(Command))
	http.HandleFunc("/api/find", wrap(Find))
	fmt.Println("listening on", *flagAddr)
	log.Fatal(http.ListenAndServe(*flagAddr, nil))
}

func Index(w http.ResponseWriter, r *http.Request) {
	w.Write(FSMustByte(*flagDev, "/static/index.html"))
}

func wrap(f func(r *http.Request) (interface{}, error)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		res, err := f(r)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if res == nil {
			return
		}
		w.Header().Add("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(res); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func WS(ws *websocket.Conn) {
	defer ws.Close()
	wins := listWindows()
	if err := websocket.JSON.Send(ws, wins); err != nil {
		return
	}
	a, err := acme.Log()
	if err != nil {
		log.Println(err)
		return
	}
	defer a.Close()
Loop:
	for {
		r, err := a.Read()
		if err != nil {
			log.Println(err)
			return
		}
		switch r.Op {
		case "new", "del":
		default:
			continue Loop
		}
		wins := listWindows()
		if err := websocket.JSON.Send(ws, wins); err != nil {
			return
		}
	}
}

func listWindows() interface{} {
	ws, err := acme.Windows()
	if err != nil {
		log.Printf("acme list windows: %s", err)
		return nil
	}
	wins := make([]acme.WinInfo, 0, len(ws))
	for _, w := range ws {
		if !strings.HasSuffix(w.Name, ".go") {
			continue
		}
		wins = append(wins, w)
	}
	return struct {
		Windows []acme.WinInfo
	}{
		wins,
	}
}

func open(b []byte) error {
	port, err := plumb.Open("send", plan9.OWRITE)
	if err != nil {
		return err
	}
	defer port.Close()
	msg := &plumb.Message{
		Dst:  "edit",
		Type: "text",
		Data: b,
	}
	return msg.Send(port)
}

func Open(r *http.Request) (interface{}, error) {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	return nil, open(b)
}

func Command(r *http.Request) (interface{}, error) {
	id := r.FormValue("id")
	command := r.FormValue("command")
	scope := r.FormValue("scope")
	wid, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	pos, body, err := getWin(wid)
	if err != nil {
		return nil, err
	}
	modified := fmt.Sprintf("%s\n%d\n%s", pos.file, len(body), body)
	var bin string
	var args []string
	var hasLines bool
	switch command {
	case "docs":
		bin = "gogetdoc"
		args = []string{"-modified", "-pos", pos.String()}
	default:
		bin = "guru"
		args = []string{"-modified"}
		if scope != "" {
			args = append(args, "-scope", scope)
		}
		if command == "definition" {
			args = append(args, "-json")
		}
		args = append(args, command, pos.String2())
		hasLines = true
	}
	cmd := exec.Command(bin, args...)
	cmd.Stdin = strings.NewReader(modified)
	b, err := cmd.CombinedOutput()
	s := strings.TrimSpace(string(b))
	if err != nil {
		s = fmt.Sprintf("%s: %s", err, s)
	} else if !cmd.ProcessState.Success() {
		hasLines = false
	}
	if command == "definition" && cmd.ProcessState.Success() {
		var def serial.Definition
		if err := json.Unmarshal([]byte(s), &def); err != nil {
			s = fmt.Sprintf("%s: %s", err, s)
		} else {
			open([]byte(def.ObjPos))
			s = fmt.Sprintf("%s: %s", def.ObjPos, def.Desc)
		}
	}
	pre, ctx, post := getContext(body, pos.q0)
	return struct {
		Pos     string
		Name    string
		Data    string
		Pre     string
		Context string
		Post    string
		Lines   bool
	}{
		pos.String(),
		command,
		s,
		pre, ctx, post,
		hasLines,
	}, nil
}

func getContext(b []byte, at int) (pre, ctx, post string) {
	left := bytes.LastIndexByte(b[:at], '\n')
	right := bytes.IndexByte(b[at:], '\n')
	const limit = 200
	if at-left > 200 {
		left = at - 200
	}
	if right > 200 {
		right = 200
	} else if right < 0 {
		right = 0
	}
	emphl := at
	for emphl >= left && emphl < len(b) {
		if unicode.IsLetter(rune(b[emphl])) {
			emphl--
		} else {
			break
		}
	}
	emphr := at
	for emphr < at+right && emphr < len(b) {
		if unicode.IsLetter(rune(b[emphr])) {
			emphr++
		} else {
			break
		}
	}
	if emphl == emphr {
		emphr++
	}
	if emphr > at+right {
		emphr = at + right
	}
	s := string(b)
	return s[left:emphl], s[emphl:emphr], s[emphr : at+right]
}

func getWin(id int) (Pos, []byte, error) {
	var p Pos
	w, err := acme.Open(id, nil)
	if err != nil {
		return p, nil, fmt.Errorf("open: %v: %s", id, err)
	}
	defer w.CloseFiles()
	_, _, err = w.ReadAddr() // make sure address file is already open.
	if err != nil {
		return p, nil, fmt.Errorf("read addr first: %s", err)
	}
	err = w.Ctl("addr=dot")
	if err != nil {
		return p, nil, fmt.Errorf("ctl addr dot: %s", err)
	}
	q0, q1, err := w.ReadAddr()
	if err != nil {
		return p, nil, fmt.Errorf("read addr: %s", err)
	}
	b, err := w.ReadAll("body")
	if err != nil {
		return p, nil, fmt.Errorf("read body: %s", err)
	}
	tagb, err := w.ReadAll("tag")
	if err != nil {
		return p, nil, fmt.Errorf("read tag: %s", err)
	}
	ts := strings.Fields(string(tagb))
	if len(ts) < 1 {
		return p, nil, fmt.Errorf("bad tag")
	}
	qp0 := runeOffset2ByteOffset(b, q0)
	qp1 := runeOffset2ByteOffset(b, q1)
	return Pos{
		file: ts[0],
		q0:   qp0,
		q1:   qp1,
	}, b, nil
}

type Pos struct {
	file   string
	q0, q1 int
}

func (p Pos) String() string {
	return fmt.Sprintf("%s:#%d", p.file, p.q0)
}

func (p Pos) String2() string {
	return fmt.Sprintf("%s,#%d", p, p.q1)
}

func runeOffset2ByteOffset(b []byte, off int) int {
	r := 0
	for i, _ := range string(b) {
		if r == off {
			return i
		}
		r++
	}
	return len(b)
}

var (
	findLock   sync.Mutex
	findCancel context.CancelFunc
	goPath     string
)

func getFindCtx() context.Context {
	findLock.Lock()
	if findCancel != nil {
		findCancel()
	}
	var ctx context.Context
	ctx, findCancel = context.WithCancel(context.Background())
	findLock.Unlock()
	return ctx
}

func Find(r *http.Request) (interface{}, error) {
	name := strings.TrimSpace(r.FormValue("find"))
	scope := strings.TrimSpace(r.FormValue("scope"))
	scope = strings.TrimRight(scope, ".")
	path := filepath.Join(goPath, "src", scope)

	name = "*" + name + "*"
	out, err := exec.CommandContext(getFindCtx(), "find", path, "-type", "f", "-iname", name).Output()
	if err != nil {
		return nil, err
	}
	res := strings.Split(strings.TrimSpace(string(out)), "\n")
	sort.Strings(res)
	const limit = 50
	if len(res) > limit {
		res = res[:limit]
	}
	return struct {
		Input string
		Found []string
	}{
		Input: r.FormValue("find"),
		Found: res,
	}, nil
}

//go:generate browserify -t [ babelify --presets [ react ] ] static/src/site.js -o static/js/site.js
//go:generate esc -o static.go -pkg main static/index.html static/js
<commit_msg>better gopath finding<commit_after>package main

import (
	"bytes"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"unicode"

	"9fans.net/go/acme"
	"9fans.net/go/plan9"
	"9fans.net/go/plumb"
	"golang.org/x/net/websocket"
	"golang.org/x/tools/cmd/guru/serial"
)

var (
	flagAddr = flag.String("listen", ":6161", "listen address")
	flagDev  = flag.Bool("dev", false, "enable dev mode")
)

func main() {
	flag.Parse()

	out, err := exec.Command("go", "env", "GOPATH").Output()
	if err != nil {
		panic(err)
	}
	goPath = strings.TrimSpace(string(out))

	webFS := FS(*flagDev)
	http.Handle("/static/", http.FileServer(webFS))
	http.Handle("/ws", websocket.Handler(WS))
	http.HandleFunc("/", Index)
	http.HandleFunc("/api/open", wrap(Open))
	http.HandleFunc("/api/command", wrap(Command))
	http.HandleFunc("/api/find", wrap(Find))
	fmt.Println("listening on", *flagAddr)
	log.Fatal(http.ListenAndServe(*flagAddr, nil))
}

func Index(w http.ResponseWriter, r *http.Request) {
	w.Write(FSMustByte(*flagDev, "/static/index.html"))
}

func wrap(f func(r *http.Request) (interface{}, error)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		res, err := f(r)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if res == nil {
			return
		}
		w.Header().Add("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(res); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func WS(ws *websocket.Conn) {
	defer ws.Close()
	wins := listWindows()
	if err := websocket.JSON.Send(ws, wins); err != nil {
		return
	}
	a, err := acme.Log()
	if err != nil {
		log.Println(err)
		return
	}
	defer a.Close()
Loop:
	for {
		r, err := a.Read()
		if err != nil {
			log.Println(err)
			return
		}
		switch r.Op {
		case "new", "del":
		default:
			continue Loop
		}
		wins := listWindows()
		if err := websocket.JSON.Send(ws, wins); err != nil {
			return
		}
	}
}

func listWindows() interface{} {
	ws, err := acme.Windows()
	if err != nil {
		log.Printf("acme list windows: %s", err)
		return nil
	}
	wins := make([]acme.WinInfo, 0, len(ws))
	for _, w := range ws {
		if !strings.HasSuffix(w.Name, ".go") {
			continue
		}
		wins = append(wins, w)
	}
	return struct {
		Windows []acme.WinInfo
	}{
		wins,
	}
}

func open(b []byte) error {
	port, err := plumb.Open("send", plan9.OWRITE)
	if err != nil {
		return err
	}
	defer port.Close()
	msg := &plumb.Message{
		Dst:  "edit",
		Type: "text",
		Data: b,
	}
	return msg.Send(port)
}

func Open(r *http.Request) (interface{}, error) {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	return nil, open(b)
}

func Command(r *http.Request) (interface{}, error) {
	id := r.FormValue("id")
	command := r.FormValue("command")
	scope := r.FormValue("scope")
	wid, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	pos, body, err := getWin(wid)
	if err != nil {
		return nil, err
	}
	modified := fmt.Sprintf("%s\n%d\n%s", pos.file, len(body), body)
	var bin string
	var args []string
	var hasLines bool
	switch command {
	case "docs":
		bin = "gogetdoc"
		args = []string{"-modified", "-pos", pos.String()}
	default:
		bin = "guru"
		args = []string{"-modified"}
		if scope != "" {
			args = append(args, "-scope", scope)
		}
		if command == "definition" {
			args = append(args, "-json")
		}
		args = append(args, command, pos.String2())
		hasLines = true
	}
	cmd := exec.Command(bin, args...)
	cmd.Stdin = strings.NewReader(modified)
	b, err := cmd.CombinedOutput()
	s := strings.TrimSpace(string(b))
	if err != nil {
		s = fmt.Sprintf("%s: %s", err, s)
	} else if !cmd.ProcessState.Success() {
		hasLines = false
	}
	if command == "definition" && cmd.ProcessState.Success() {
		var def serial.Definition
		if err := json.Unmarshal([]byte(s), &def); err != nil {
			s = fmt.Sprintf("%s: %s", err, s)
		} else {
			open([]byte(def.ObjPos))
			s = fmt.Sprintf("%s: %s", def.ObjPos, def.Desc)
		}
	}
	pre, ctx, post := getContext(body, pos.q0)
	return struct {
		Pos     string
		Name    string
		Data    string
		Pre     string
		Context string
		Post    string
		Lines   bool
	}{
		pos.String(),
		command,
		s,
		pre, ctx, post,
		hasLines,
	}, nil
}

func getContext(b []byte, at int) (pre, ctx, post string) {
	left := bytes.LastIndexByte(b[:at], '\n')
	right := bytes.IndexByte(b[at:], '\n')
	const limit = 200
	if at-left > 200 {
		left = at - 200
	}
	if right > 200 {
		right = 200
	} else if right < 0 {
		right = 0
	}
	emphl := at
	for emphl >= left && emphl < len(b) {
		if unicode.IsLetter(rune(b[emphl])) {
			emphl--
		} else {
			break
		}
	}
	emphr := at
	for emphr < at+right && emphr < len(b) {
		if unicode.IsLetter(rune(b[emphr])) {
			emphr++
		} else {
			break
		}
	}
	if emphl == emphr {
		emphr++
	}
	if emphr > at+right {
		emphr = at + right
	}
	s := string(b)
	return s[left:emphl], s[emphl:emphr], s[emphr : at+right]
}

func getWin(id int) (Pos, []byte, error) {
	var p Pos
	w, err := acme.Open(id, nil)
	if err != nil {
		return p, nil, fmt.Errorf("open: %v: %s", id, err)
	}
	defer w.CloseFiles()
	_, _, err = w.ReadAddr() // make sure address file is already open.
	if err != nil {
		return p, nil, fmt.Errorf("read addr first: %s", err)
	}
	err = w.Ctl("addr=dot")
	if err != nil {
		return p, nil, fmt.Errorf("ctl addr dot: %s", err)
	}
	q0, q1, err := w.ReadAddr()
	if err != nil {
		return p, nil, fmt.Errorf("read addr: %s", err)
	}
	b, err := w.ReadAll("body")
	if err != nil {
		return p, nil, fmt.Errorf("read body: %s", err)
	}
	tagb, err := w.ReadAll("tag")
	if err != nil {
		return p, nil, fmt.Errorf("read tag: %s", err)
	}
	ts := strings.Fields(string(tagb))
	if len(ts) < 1 {
		return p, nil, fmt.Errorf("bad tag")
	}
	qp0 := runeOffset2ByteOffset(b, q0)
	qp1 := runeOffset2ByteOffset(b, q1)
	return Pos{
		file: ts[0],
		q0:   qp0,
		q1:   qp1,
	}, b, nil
}

type Pos struct {
	file   string
	q0, q1 int
}

func (p Pos) String() string {
	return fmt.Sprintf("%s:#%d", p.file, p.q0)
}

func (p Pos) String2() string {
	return fmt.Sprintf("%s,#%d", p, p.q1)
}

func runeOffset2ByteOffset(b []byte, off int) int {
	r := 0
	for i, _ := range string(b) {
		if r == off {
			return i
		}
		r++
	}
	return len(b)
}

var (
	findLock   sync.Mutex
	findCancel context.CancelFunc
	goPath     string
)

func getFindCtx() context.Context {
	findLock.Lock()
	if findCancel != nil {
		findCancel()
	}
	var ctx context.Context
	ctx, findCancel = context.WithCancel(context.Background())
	findLock.Unlock()
	return ctx
}

func Find(r *http.Request) (interface{}, error) {
	name := strings.TrimSpace(r.FormValue("find"))
	scope := strings.TrimSpace(r.FormValue("scope"))
	scope = strings.TrimRight(scope, ".")
	path := filepath.Join(goPath, "src", scope)

	name = "*" + name + "*"
	out, err := exec.CommandContext(getFindCtx(), "find", path, "-type", "f", "-iname", name).Output()
	if err != nil {
		return nil, err
	}
	res := strings.Split(strings.TrimSpace(string(out)), "\n")
	sort.Strings(res)
	const limit = 50
	if len(res) > limit {
		res = res[:limit]
	}
	return struct {
		Input string
		Found []string
	}{
		Input: r.FormValue("find"),
		Found: res,
	}, nil
}

//go:generate browserify -t [ babelify --presets [ react ] ] static/src/site.js -o static/js/site.js
//go:generate esc -o static.go -pkg main static/index.html static/js
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"runtime"
	"syscall"
	"time"
	"unsafe"
)

var (
	py          = syscall.NewLazyDLL("Python34.dll")
	Init        = py.NewProc("Py_Initialize")
	Finalize    = py.NewProc("Py_Finalize")
	SetPath     = py.NewProc("Py_SetPath")
	SetProgName = py.NewProc("Py_SetProgramName")
	SetArgvEx   = py.NewProc("PySys_SetArgvEx")
	SetArgv     = py.NewProc("PySys_SetArgv")
	Run         = py.NewProc("PyRun_SimpleString")
	Main        = py.NewProc("Py_Main")
	Free        = py.NewProc("PyMem_RawFree")
	RawMalloc   = py.NewProc("PyMem_RawMalloc")
	Decode      = py.NewProc("Py_DecodeLocale")
)

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // use all physical cores. for in your case of using go routines

	//prog := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("python")))
	//Call(SetProgName, prog)
	argc := len(os.Args)
	argv := make([]uintptr, argc)
	for i, v := range os.Args[1:] {
		argv[i+1] = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v)))
	}

	Call(Init)
	s := time.Now()
	if argc > 1 {
		fmt.Println("Running", os.Args[1], "...")
	}
	Call(Main, uintptr(argc), uintptr(unsafe.Pointer(&argv[0])))
	//updatepath := 0
	e := time.Since(s)
	fmt.Println("Execution time :", e)
	/* Example : inline script instead of external script execution */
	//script := syscall.StringByteSlice(`from sys import argv; print(argv)`)
	//script := syscall.StringByteSlice(`import sys; sys.argv=['idea.sh']; from imp import load_module, PY_SOURCE; f='main.py'; load_module('__main__', open(f), f, ('.py', 'r', PY_SOURCE))`)
	//Call(Run, uintptr(unsafe.Pointer(&script[0])))

	Call(Finalize)
	time.Sleep(1 * time.Microsecond)
}

func Call(proc *syscall.LazyProc, args ...uintptr) uintptr {
	r1, r2, le := proc.Call(args...)
	//fmt.Println(r1, r2, le) // Debug purpose
	_, _ = r2, le
	return r1
}
<commit_msg>implying example<commit_after>package main

import (
	"fmt"
	"os"
	"runtime"
	"syscall"
	"time"
	"unsafe"
)

var (
	py          = syscall.NewLazyDLL("Python34.dll")
	Init        = py.NewProc("Py_Initialize")
	Finalize    = py.NewProc("Py_Finalize")
	SetPath     = py.NewProc("Py_SetPath")
	SetProgName = py.NewProc("Py_SetProgramName")
	SetArgvEx   = py.NewProc("PySys_SetArgvEx")
	SetArgv     = py.NewProc("PySys_SetArgv")
	Run         = py.NewProc("PyRun_SimpleString")
	Main        = py.NewProc("Py_Main")
	Free        = py.NewProc("PyMem_RawFree")
	RawMalloc   = py.NewProc("PyMem_RawMalloc")
	Decode      = py.NewProc("Py_DecodeLocale")
)

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // use all physical cores. for in your case of using go routines

	//prog := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("python")))
	//Call(SetProgName, prog)

	/*  Example : Imply target script and pass every argument to there */
	/*argc := len(os.Args) + 1
	argv := make([]uintptr, argc+1)
	argv[1] = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("util.py")))
	for i, v := range os.Args[1:] {
		argv[i+2] = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v)))
	}*/

	/*  Example : Simplest wrapper */
	argc := len(os.Args)
	argv := make([]uintptr, argc)
	for i, v := range os.Args[1:] {
		argv[i+1] = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v)))
	}

	Call(Init)
	s := time.Now()
	Call(Main, uintptr(argc), uintptr(unsafe.Pointer(&argv[0])))
	//updatepath := 0
	e := time.Since(s)
	fmt.Println("Execution time :", e)
	/* Example : inline script instead of external script execution */
	//script := syscall.StringByteSlice(`from sys import argv; print(argv)`)
	//script := syscall.StringByteSlice(`import sys; sys.argv=['idea.sh']; from imp import load_module, PY_SOURCE; f='main.py'; load_module('__main__', open(f), f, ('.py', 'r', PY_SOURCE))`)
	//Call(Run, uintptr(unsafe.Pointer(&script[0])))

	Call(Finalize)
	time.Sleep(1 * time.Microsecond)
}

func Call(proc *syscall.LazyProc, args ...uintptr) uintptr {
	r1, r2, le := proc.Call(args...)
	//fmt.Println(r1, r2, le) // Debug purpose
	_, _ = r2, le
	return r1
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"runtime"
	"unicode/utf8"

	"github.com/vheon/picker/terminal"
	"github.com/vheon/picker/tty"
)

func appendChar(s string, b rune) string {
	return s + string(b)
}

func backspace(s string) string {
	if l := len(s); l > 0 {
		_, size := utf8.DecodeLastRuneInString(s)
		return s[:l-size]
	}
	return s
}

func readAllCandidates(r io.Reader) []Candidate {
	scanner := bufio.NewScanner(r)
	var lines []Candidate
	for scanner.Scan() {
		lines = append(lines, NewCandidate(scanner.Text()))
	}
	return lines
}

func handle_input(picker *Picker, view *View, key rune) *View {
	switch key {
	case terminal.Ctrl_N:
		view.Down()
	case terminal.Ctrl_P:
		view.Up()
	case terminal.Backspace:
		view = picker.Answer(backspace(view.Query))
	case terminal.LF:
		view.Done = true
		view.ClearPrompt()
	case terminal.Ctrl_U, terminal.Ctrl_W:
		view = picker.Answer("")
	default:
		view = picker.Answer(appendChar(view.Query, key))
	}
	return view
}

const visibleRows = 20

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	picker := NewPicker(readAllCandidates(os.Stdin), visibleRows)

	tty := tty.New()
	tty.Stty("-echo", "-icanon")
	defer tty.Restore()

	terminal := terminal.NewTerminal(tty)

	view := picker.Answer("")
	terminal.MakeRoom(view.Height)
	view.DrawOnTerminal(terminal)
	for !view.Done {
		view = handle_input(picker, view, tty.ReadRune())
		view.DrawOnTerminal(terminal)
	}
	fmt.Println(view.Selected())
}
<commit_msg>Move creation when needed<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"runtime"
	"unicode/utf8"

	"github.com/vheon/picker/terminal"
	"github.com/vheon/picker/tty"
)

func appendChar(s string, b rune) string {
	return s + string(b)
}

func backspace(s string) string {
	if l := len(s); l > 0 {
		_, size := utf8.DecodeLastRuneInString(s)
		return s[:l-size]
	}
	return s
}

func readAllCandidates(r io.Reader) []Candidate {
	scanner := bufio.NewScanner(r)
	var lines []Candidate
	for scanner.Scan() {
		lines = append(lines, NewCandidate(scanner.Text()))
	}
	return lines
}

func handle_input(picker *Picker, view *View, key rune) *View {
	switch key {
	case terminal.Ctrl_N:
		view.Down()
	case terminal.Ctrl_P:
		view.Up()
	case terminal.Backspace:
		view = picker.Answer(backspace(view.Query))
	case terminal.LF:
		view.Done = true
		view.ClearPrompt()
	case terminal.Ctrl_U, terminal.Ctrl_W:
		view = picker.Answer("")
	default:
		view = picker.Answer(appendChar(view.Query, key))
	}
	return view
}

const visibleRows = 20

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tty := tty.New()
	tty.Stty("-echo", "-icanon")
	defer tty.Restore()

	terminal := terminal.NewTerminal(tty)

	picker := NewPicker(readAllCandidates(os.Stdin), visibleRows)
	view := picker.Answer("")
	terminal.MakeRoom(view.Height)
	view.DrawOnTerminal(terminal)
	for !view.Done {
		view = handle_input(picker, view, tty.ReadRune())
		view.DrawOnTerminal(terminal)
	}
	fmt.Println(view.Selected())
}
<|endoftext|>


<commit_before>// Package main defines a command line interface for the sqlboiler package
package main

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

	"github.com/kat-co/vala"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	cmdState  *State
	cmdConfig *Config
)

func main() {
	var err error

	viper.SetConfigName("sqlboiler")

	configHome := os.Getenv("XDG_CONFIG_HOME")
	homePath := os.Getenv("HOME")
	wd, err := os.Getwd()
	if err != nil {
		wd = "./"
	}

	configPaths := []string{wd}
	if len(configHome) > 0 {
		configPaths = append(configPaths, filepath.Join(configHome, "sqlboiler"))
	} else {
		configPaths = append(configPaths, filepath.Join(homePath, ".config/sqlboiler"))
	}

	for _, p := range configPaths {
		viper.AddConfigPath(p)
	}

	// Ignore errors here, fallback to other validation methods.
	// Users can use environment variables if a config is not found.
	_ = viper.ReadInConfig()

	// Set up the cobra root command
	var rootCmd = &cobra.Command{
		Use:   "sqlboiler [flags] <driver>",
		Short: "SQL Boiler generates an ORM tailored to your database schema.",
		Long: "SQL Boiler generates a Go ORM from template files, tailored to your database schema.\n" +
			`Complete documentation is available at http://github.com/vattle/sqlboiler`,
		Example:       `sqlboiler postgres`,
		PreRunE:       preRun,
		RunE:          run,
		PostRunE:      postRun,
		SilenceErrors: true,
		SilenceUsage:  true,
	}

	// Set up the cobra root command flags
	rootCmd.PersistentFlags().StringP("output", "o", "models", "The name of the folder to output to")
	rootCmd.PersistentFlags().StringP("pkgname", "p", "models", "The name you wish to assign to your generated package")
	rootCmd.PersistentFlags().StringSliceP("exclude", "x", nil, "Tables to be excluded from the generated package")

	viper.SetDefault("postgres.sslmode", "require")
	viper.SetDefault("postgres.port", "5432")
	viper.BindPFlags(rootCmd.PersistentFlags())
	viper.AutomaticEnv()

	if err := rootCmd.Execute(); err != nil {
		if e, ok := err.(commandFailure); ok {
			fmt.Printf("%s\n", string(e))
			return
		}

		fmt.Printf("%+v\n", err)
		os.Exit(1)
	}
}

type commandFailure string

func (c commandFailure) Error() string {
	return string(c)
}

func preRun(cmd *cobra.Command, args []string) error {
	var err error

	if len(args) == 0 {
		return commandFailure("Must provide a driver name.")
	}

	driverName := args[0]

	cmdConfig = &Config{
		DriverName: driverName,
		OutFolder:  viper.GetString("output"),
		PkgName:    viper.GetString("pkgname"),
	}

	// BUG: https://github.com/spf13/viper/issues/200
	// Look up the value of ExcludeTables directly from PFlags in Cobra if we
	// detect a malformed value coming out of viper.
	// Once the bug is fixed we'll be able to move this into the init above
	cmdConfig.ExcludeTables = viper.GetStringSlice("exclude")
	if len(cmdConfig.ExcludeTables) == 1 && strings.HasPrefix(cmdConfig.ExcludeTables[0], "[") {
		cmdConfig.ExcludeTables, err = cmd.PersistentFlags().GetStringSlice("exclude")
		if err != nil {
			return err
		}
	}

	if viper.IsSet("postgres.dbname") {
		cmdConfig.Postgres = PostgresConfig{
			User:    viper.GetString("postgres.user"),
			Pass:    viper.GetString("postgres.pass"),
			Host:    viper.GetString("postgres.host"),
			Port:    viper.GetInt("postgres.port"),
			DBName:  viper.GetString("postgres.dbname"),
			SSLMode: viper.GetString("postgres.sslmode"),
		}

		// Set the default SSLMode value
		if cmdConfig.Postgres.SSLMode == "" {
			viper.Set("postgres.sslmode", "require")
			cmdConfig.Postgres.SSLMode = viper.GetString("postgres.sslmode")
		}

		err = vala.BeginValidation().Validate(
			vala.StringNotEmpty(cmdConfig.Postgres.User, "postgres.user"),
			vala.StringNotEmpty(cmdConfig.Postgres.Host, "postgres.host"),
			vala.Not(vala.Equals(cmdConfig.Postgres.Port, 0, "postgres.port")),
			vala.StringNotEmpty(cmdConfig.Postgres.DBName, "postgres.dbname"),
			vala.StringNotEmpty(cmdConfig.Postgres.SSLMode, "postgres.sslmode"),
		).Check()

		if err != nil {
			return commandFailure(err.Error())
		}
	} else if driverName == "postgres" {
		return errors.New("Postgres driver requires a postgres section in your config file.")
	}

	cmdState, err = New(cmdConfig)
	return err
}

func run(cmd *cobra.Command, args []string) error {
	return cmdState.Run(true)
}

func postRun(cmd *cobra.Command, args []string) error {
	return cmdState.Cleanup()
}
<commit_msg>Add debug flag<commit_after>// Package main defines a command line interface for the sqlboiler package
package main

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

	"github.com/kat-co/vala"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	cmdState  *State
	cmdConfig *Config
)

func main() {
	var err error

	viper.SetConfigName("sqlboiler")

	configHome := os.Getenv("XDG_CONFIG_HOME")
	homePath := os.Getenv("HOME")
	wd, err := os.Getwd()
	if err != nil {
		wd = "./"
	}

	configPaths := []string{wd}
	if len(configHome) > 0 {
		configPaths = append(configPaths, filepath.Join(configHome, "sqlboiler"))
	} else {
		configPaths = append(configPaths, filepath.Join(homePath, ".config/sqlboiler"))
	}

	for _, p := range configPaths {
		viper.AddConfigPath(p)
	}

	// Ignore errors here, fallback to other validation methods.
	// Users can use environment variables if a config is not found.
	_ = viper.ReadInConfig()

	// Set up the cobra root command
	var rootCmd = &cobra.Command{
		Use:   "sqlboiler [flags] <driver>",
		Short: "SQL Boiler generates an ORM tailored to your database schema.",
		Long: "SQL Boiler generates a Go ORM from template files, tailored to your database schema.\n" +
			`Complete documentation is available at http://github.com/vattle/sqlboiler`,
		Example:       `sqlboiler postgres`,
		PreRunE:       preRun,
		RunE:          run,
		PostRunE:      postRun,
		SilenceErrors: true,
		SilenceUsage:  true,
	}

	// Set up the cobra root command flags
	rootCmd.PersistentFlags().StringP("output", "o", "models", "The name of the folder to output to")
	rootCmd.PersistentFlags().StringP("pkgname", "p", "models", "The name you wish to assign to your generated package")
	rootCmd.PersistentFlags().StringSliceP("exclude", "x", nil, "Tables to be excluded from the generated package")
	rootCmd.PersistentFlags().BoolP("debug", "d", false, "Debug mode prints stack traces on error")

	viper.SetDefault("postgres.sslmode", "require")
	viper.SetDefault("postgres.port", "5432")
	viper.BindPFlags(rootCmd.PersistentFlags())
	viper.AutomaticEnv()

	if err := rootCmd.Execute(); err != nil {
		if e, ok := err.(commandFailure); ok {
			fmt.Printf("%v\n", string(e))
		} else if !viper.GetBool("debug") {
			fmt.Printf("%v\n", err)
		} else {
			fmt.Printf("%+v\n", err)
		}

		os.Exit(1)
	}
}

type commandFailure string

func (c commandFailure) Error() string {
	return string(c)
}

func preRun(cmd *cobra.Command, args []string) error {
	var err error

	if len(args) == 0 {
		return commandFailure("Must provide a driver name.")
	}

	driverName := args[0]

	cmdConfig = &Config{
		DriverName: driverName,
		OutFolder:  viper.GetString("output"),
		PkgName:    viper.GetString("pkgname"),
	}

	// BUG: https://github.com/spf13/viper/issues/200
	// Look up the value of ExcludeTables directly from PFlags in Cobra if we
	// detect a malformed value coming out of viper.
	// Once the bug is fixed we'll be able to move this into the init above
	cmdConfig.ExcludeTables = viper.GetStringSlice("exclude")
	if len(cmdConfig.ExcludeTables) == 1 && strings.HasPrefix(cmdConfig.ExcludeTables[0], "[") {
		cmdConfig.ExcludeTables, err = cmd.PersistentFlags().GetStringSlice("exclude")
		if err != nil {
			return err
		}
	}

	if viper.IsSet("postgres.dbname") {
		cmdConfig.Postgres = PostgresConfig{
			User:    viper.GetString("postgres.user"),
			Pass:    viper.GetString("postgres.pass"),
			Host:    viper.GetString("postgres.host"),
			Port:    viper.GetInt("postgres.port"),
			DBName:  viper.GetString("postgres.dbname"),
			SSLMode: viper.GetString("postgres.sslmode"),
		}

		// Set the default SSLMode value
		if cmdConfig.Postgres.SSLMode == "" {
			viper.Set("postgres.sslmode", "require")
			cmdConfig.Postgres.SSLMode = viper.GetString("postgres.sslmode")
		}

		err = vala.BeginValidation().Validate(
			vala.StringNotEmpty(cmdConfig.Postgres.User, "postgres.user"),
			vala.StringNotEmpty(cmdConfig.Postgres.Host, "postgres.host"),
			vala.Not(vala.Equals(cmdConfig.Postgres.Port, 0, "postgres.port")),
			vala.StringNotEmpty(cmdConfig.Postgres.DBName, "postgres.dbname"),
			vala.StringNotEmpty(cmdConfig.Postgres.SSLMode, "postgres.sslmode"),
		).Check()

		if err != nil {
			return commandFailure(err.Error())
		}
	} else if driverName == "postgres" {
		return errors.New("Postgres driver requires a postgres section in your config file.")
	}

	cmdState, err = New(cmdConfig)
	return err
}

func run(cmd *cobra.Command, args []string) error {
	return cmdState.Run(true)
}

func postRun(cmd *cobra.Command, args []string) error {
	return cmdState.Cleanup()
}
<|endoftext|>


<commit_before>// Copyright 2017 The Scribble Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
Package tcp provides a TCP transport implementation.

Typical usage of a TCP connection:

	cfg := NewConnection("127.0.0.1", "6060")
	s := cfg.Accept() // Server accepting connection from client.
	defer func(s *Conn){
		if err := s.Close(); err != nil {
			// handle errors
		}(s)
	}
	...
	c := cfg.Connect() // Client connecting to server.
	defer func(c *Conn){
		if err := c.Close(); err != nil {
			// handle errors
		}(c)
	}

Client c and Server s can then be used as I/O streams implementing
io.Reader and io.Writer.

*/
package tcp

import (
	"bufio"
	"encoding/gob"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"time"

	"github.com/nickng/scribble-go/runtime/transport"
)

var (
	ErrCloseUnfinishedConn = errors.New("transport/tcp: closing connection with unread data")
)

// SerialisationError is the kind of error where a value
// cannot be sent due to serialisation failure.
type SerialisationError struct {
	cause error
}

func (e SerialisationError) Error() string {
	return fmt.Sprintf("transport/tcp send: serialisation failed: %v", e.cause)
}

// DeserialisationError is the kind of error where a value
// cannot be received due to deserialisation failure.
type DeserialisationError struct {
	cause error
}

func (e DeserialisationError) Error() string {
	return fmt.Sprintf("transport/tcp recv: deserisalisation failed: %v", e.cause)
}

// ConnCfg is a connection configuration, contains
// the details required to establish a connection.
type ConnCfg struct {
	Host string
	Port string

	// DelimMeth specifies delimiter implementation.
	DelimMeth DelimitMethod

	// retryWait specifies the time to wait before retrying connection.
	retryWait time.Duration
}

// NewConnection is a convenient wrapper for a TCP connection
// and can be used as either server-side or client-side.
func NewConnection(host, port string) ConnCfg {
	return ConnCfg{Host: host, Port: port}
}

func NewConnectionWithRetry(host, port string, retryWait time.Duration) ConnCfg {
	return ConnCfg{Host: host, Port: port, retryWait: retryWait}
}

// Accept listens for and accepts connection from a TCP socket using
// details from cfg, and returns the TCP stream as a ReadWriteCloser.
//
// Accept blocks while waiting for connection to be accepted.
func (cfg ConnCfg) Accept() transport.Channel {
	ln, err := net.Listen("tcp", fmt.Sprintf(":%s", cfg.Port))
	if err != nil {
		log.Fatalf("cannot listen at :%s: %v", cfg.Port, err)
	}
	conn, err := ln.Accept()
	if err != nil {
		log.Fatalf("cannot accept connection at :%s: %v", cfg.Port, err)
	}
	return cfg.newConn(conn)
}

// Connect establishes a connection with a TCP socket using details
// from cfg, and returns the TCP stream as a ReadWriteCloser.
func (cfg ConnCfg) Connect() transport.Channel {
	addr := net.JoinHostPort(cfg.Host, cfg.Port)
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		if cfg.retryWait > 0 {
			time.Sleep(cfg.retryWait)
			cfg.retryWait = 0
			return cfg.Connect()
		}
		log.Fatalf("cannot connect to %s: %v", addr, err)
	}
	return cfg.newConn(conn)
}

func (cfg ConnCfg) newConn(rwc net.Conn) *Conn {
	c := &Conn{
		rwc: rwc,
	}
	c.bufr = newReader(c.rwc)
	c.bufw = newWriter(c.rwc)

	c.enc = gob.NewEncoder(NewDelimWriter(c, cfg.DelimMeth))
	c.dec = gob.NewDecoder(NewDelimReader(c, cfg.DelimMeth))
	return c
}

// Conn is a connected TCP stream/connection, and wraps a net.Conn created
// by either Accept or Connect.
//
// Conn implements ReadWriteCloser and can be used as is, more fine-grained
// message formatting control (such as delimited multi messages) should use
// NewSizedReader/SizedWriter (message with size prefix) or
// NewDelimReader/DelimWriter (delimited message).
type Conn struct {
	// rwc is the real TCP connection.
	rwc net.Conn

	// bufr is a buffered stream to the TCP connection.
	bufr *bufio.Reader

	// dec is a deserialisation decoder for messages from the TCP connection.
	dec *gob.Decoder

	// bufw is a buffered stream to the TCP connection.
	bufw *bufio.Writer

	// enc is a serialisation encoder for messages to the TCP connection.
	enc *gob.Encoder
}

// newReader returns a fresh buffered Reader.
func newReader(r io.Reader) *bufio.Reader {
	// TODO(nickng): use sync.Pool to reduce allocation per new connection.
	return bufio.NewReader(r)
}

// newWriter returns a fresh buffered Writer.
func newWriter(w io.Writer) *bufio.Writer {
	// TODO(nickng): use sync.Pool to reduce allocation per new connection.
	return bufio.NewWriter(w)
}

// Read reads data into p. It returns the number of bytes read into p. The
// bytes are taken from at most one Read on the underlying Reader, hence n
// may be less than len(p). At EOF, the count will be zero and err will be
// io.EOF.
//
// The underlying implementation is a *bufio.Reader.
func (c *Conn) Read(p []byte) (n int, err error) {
	return c.bufr.Read(p)
}

// Writer writes the content of p into the underlying stream. It returns
// the number of bytes written. If n < len(p), it also returns an error
// explaining why the write is short.
//
// The underlying implementation is a *bufio.Writer, and data will be
// flushed whenever Write is called.
func (c *Conn) Write(p []byte) (n int, err error) {
	n, err = c.bufw.Write(p)
	c.bufw.Flush()
	return n, err
}

// Close closes the underlying TCP connection.
func (c *Conn) Close() error {
	if c.bufw.Available() > 0 {
		c.bufw.Flush()
	}
	if c.bufr.Buffered() > 0 {
		c.rwc.Close()
		return ErrCloseUnfinishedConn
	}
	return c.rwc.Close()
}

// Send serialises values val then sends the serialised
// values to the underlying stream of connection c.
func (c *Conn) Send(val interface{}) error {
	if err := c.enc.Encode(val); err != nil {
		return SerialisationError{cause: err}
	}
	return nil
}

// Recv receives values from the underlying stream then deserialises and
// writes the deserialised values to the pointer addresses specified by ptr.
func (c *Conn) Recv(ptr interface{}) error {
	if err := c.dec.Decode(ptr); err != nil {
		return DeserialisationError{cause: err}
	}
	return nil
}
<commit_msg>transport/tcp: Add mutex for read/write stream.<commit_after>// Copyright 2017 The Scribble Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
Package tcp provides a TCP transport implementation.

Typical usage of a TCP connection:

	cfg := NewConnection("127.0.0.1", "6060")
	s := cfg.Accept() // Server accepting connection from client.
	defer func(s *Conn){
		if err := s.Close(); err != nil {
			// handle errors
		}(s)
	}
	...
	c := cfg.Connect() // Client connecting to server.
	defer func(c *Conn){
		if err := c.Close(); err != nil {
			// handle errors
		}(c)
	}

Client c and Server s can then be used as I/O streams implementing
io.Reader and io.Writer.

*/
package tcp

import (
	"bufio"
	"encoding/gob"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"time"

	"github.com/nickng/scribble-go/runtime/transport"
)

var (
	ErrCloseUnfinishedConn = errors.New("transport/tcp: closing connection with unread data")
)

// SerialisationError is the kind of error where a value
// cannot be sent due to serialisation failure.
type SerialisationError struct {
	cause error
}

func (e SerialisationError) Error() string {
	return fmt.Sprintf("transport/tcp send: serialisation failed: %v", e.cause)
}

// DeserialisationError is the kind of error where a value
// cannot be received due to deserialisation failure.
type DeserialisationError struct {
	cause error
}

func (e DeserialisationError) Error() string {
	return fmt.Sprintf("transport/tcp recv: deserisalisation failed: %v", e.cause)
}

// ConnCfg is a connection configuration, contains
// the details required to establish a connection.
type ConnCfg struct {
	Host string
	Port string

	// DelimMeth specifies delimiter implementation.
	DelimMeth DelimitMethod

	// retryWait specifies the time to wait before retrying connection.
	retryWait time.Duration
}

// NewConnection is a convenient wrapper for a TCP connection
// and can be used as either server-side or client-side.
func NewConnection(host, port string) ConnCfg {
	return ConnCfg{Host: host, Port: port}
}

func NewConnectionWithRetry(host, port string, retryWait time.Duration) ConnCfg {
	return ConnCfg{Host: host, Port: port, retryWait: retryWait}
}

// Accept listens for and accepts connection from a TCP socket using
// details from cfg, and returns the TCP stream as a ReadWriteCloser.
//
// Accept blocks while waiting for connection to be accepted.
func (cfg ConnCfg) Accept() transport.Channel {
	ln, err := net.Listen("tcp", fmt.Sprintf(":%s", cfg.Port))
	if err != nil {
		log.Fatalf("cannot listen at :%s: %v", cfg.Port, err)
	}
	conn, err := ln.Accept()
	if err != nil {
		log.Fatalf("cannot accept connection at :%s: %v", cfg.Port, err)
	}
	return cfg.newConn(conn)
}

// Connect establishes a connection with a TCP socket using details
// from cfg, and returns the TCP stream as a ReadWriteCloser.
func (cfg ConnCfg) Connect() transport.Channel {
	addr := net.JoinHostPort(cfg.Host, cfg.Port)
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		if cfg.retryWait > 0 {
			time.Sleep(cfg.retryWait)
			cfg.retryWait = 0
			return cfg.Connect()
		}
		log.Fatalf("cannot connect to %s: %v", addr, err)
	}
	return cfg.newConn(conn)
}

func (cfg ConnCfg) newConn(rwc net.Conn) *Conn {
	c := &Conn{
		rwc: rwc,
	}
	c.rdMu.Lock()
	c.bufr = newReader(c.rwc)
	c.dec = gob.NewDecoder(NewDelimReader(c, cfg.DelimMeth))
	c.rdMu.Unlock()

	c.wtMu.Lock()
	c.bufw = newWriter(c.rwc)
	c.enc = gob.NewEncoder(NewDelimWriter(c, cfg.DelimMeth))
	c.wtMu.Unlock()
	return c
}

// Conn is a connected TCP stream/connection, and wraps a net.Conn created
// by either Accept or Connect.
//
// Conn implements ReadWriteCloser and can be used as is, more fine-grained
// message formatting control (such as delimited multi messages) should use
// NewSizedReader/SizedWriter (message with size prefix) or
// NewDelimReader/DelimWriter (delimited message).
type Conn struct {
	// rwc is the real TCP connection.
	rwc net.Conn

	// guards the read buffer and the decoder
	rdMu sync.Mutex

	bufr *bufio.Reader // bufr is a buffered stream to the TCP connection.
	dec  *gob.Decoder  // dec is a serialisation decoder for messages from rwc.

	// guards the write buffer and the encoder
	wtMu sync.Mutex

	bufw *bufio.Writer // bufw is a buffered stream to the TCP connection.
	enc  *gob.Encoder  // enc is a serialisation encoder for messages to rwc.
}

// newReader returns a fresh buffered Reader.
func newReader(r io.Reader) *bufio.Reader {
	// TODO(nickng): use sync.Pool to reduce allocation per new connection.
	return bufio.NewReader(r)
}

// newWriter returns a fresh buffered Writer.
func newWriter(w io.Writer) *bufio.Writer {
	// TODO(nickng): use sync.Pool to reduce allocation per new connection.
	return bufio.NewWriter(w)
}

// Read reads data into p. It returns the number of bytes read into p. The
// bytes are taken from at most one Read on the underlying Reader, hence n
// may be less than len(p). At EOF, the count will be zero and err will be
// io.EOF.
//
// The underlying implementation is a *bufio.Reader.
func (c *Conn) Read(p []byte) (n int, err error) {
	return c.bufr.Read(p)
}

// Writer writes the content of p into the underlying stream. It returns
// the number of bytes written. If n < len(p), it also returns an error
// explaining why the write is short.
//
// The underlying implementation is a *bufio.Writer, and data will be
// flushed whenever Write is called.
func (c *Conn) Write(p []byte) (n int, err error) {
	n, err = c.bufw.Write(p)
	c.bufw.Flush()
	return n, err
}

// Close closes the underlying TCP connection.
func (c *Conn) Close() error {
	if c.bufw.Available() > 0 {
		c.bufw.Flush()
	}
	if c.bufr.Buffered() > 0 {
		c.rwc.Close()
		return ErrCloseUnfinishedConn
	}
	return c.rwc.Close()
}

// Send serialises values val then sends the serialised
// values to the underlying stream of connection c.
func (c *Conn) Send(val interface{}) error {
	c.wtMu.Lock()
	defer c.wtMu.Unlock()
	if err := c.enc.Encode(val); err != nil {
		return SerialisationError{cause: err}
	}
	return nil
}

// Recv receives values from the underlying stream then deserialises and
// writes the deserialised values to the pointer addresses specified by ptr.
func (c *Conn) Recv(ptr interface{}) error {
	c.rdMu.Lock()
	defer c.rdMu.Unlock()
	if err := c.dec.Decode(ptr); err != nil {
		return DeserialisationError{cause: err}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/codegangsta/cli"
)

type CodeFile struct {
	filename  string
	extension string
	name      string
	ftype     string
}

func newCodeFile(filename string) CodeFile {
	var f CodeFile

	f.filename = filename
	f.extension = filepath.Ext(filename)
	f.name = filename[0 : len(filename)-len(f.extension)]

	if f.extension != "" {
		f.ftype = f.extension[1:]
	}
}

func (f *CodeFile) show() {
	fmt.Printf("[Filename]: %s\n[Name]: %s\n[Extension]: %s\n[Type]: %s\n",
		f.filename, f.name, f.extension, f.ftype)
}

func (f *CodeFile) Compile() error {
	// (TODO): Support different filetype
	// (TODO): Check operating system

	cmd := exec.Command("gcc", "-o", f.name, f.filename)
	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func (f *CodeFile) Run() error {
	var cmd *exec.Cmd

	switch f.ftype {
	case "":
		cmd = exec.Command("./", f.name)
	case "c":
		f.Compile()
		cmd = exec.Command("./" + f.name)
	case "go":
		cmd = exec.Command("go", "run", f.filename)
	case "py":
		// (TODO): different python versions
		cmd = exec.Command("python", f.filename)
	case "lua":
		cmd = exec.Command("lua", f.filename)
	case "ruby":
		cmd = exec.Command("ruby", f.filename)

	default:
		return errors.New("Not a valid filetype!\n[Supported] bin, C",
			+"Go, Python, Lua, Ruby")
	}
	cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func compile(c *cli.Context) {
	file := newCodeFile(c.Args().First())

	if err := file.Compile(); err != nil {
		fmt.Errorf("%v\n", err)
	}
}

func run(c *cli.Context) {
	file := newCodeFile(c.Args().First())

	if err := file.Run(); err != nil {
		fmt.Errorf("%v\n", err)
	}
}

func main() {
	app := cli.NewApp()
	app.Usage = "Compile and/or run your code files"
	app.Commands = []cli.Command{
		{
			Name:      "compile",
			ShortName: "c",
			Usage:     "compiles a file",
			Action, compile,
		},
		{
			Name:   "run",
			Usage:  "Run a file/script, it compiles it if needed",
			Action: run,
		},
	}
	app.RunAndExitOnError()
}
<commit_msg>fixed inital buggs<commit_after>package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/codegangsta/cli"
)

type CodeFile struct {
	filename  string
	extension string
	name      string
	ftype     string
}

func newCodeFile(filename string) CodeFile {
	var f CodeFile

	f.filename = filename
	f.extension = filepath.Ext(filename)
	f.name = filename[0 : len(filename)-len(f.extension)]

	if f.extension != "" {
		f.ftype = f.extension[1:]
	}
	return f
}

func (f *CodeFile) show() {
	fmt.Printf("[Filename]: %s\n[Name]: %s\n[Extension]: %s\n[Type]: %s\n",
		f.filename, f.name, f.extension, f.ftype)
}

func (f *CodeFile) Compile() error {
	// (TODO): Support different filetype
	// (TODO): Check operating system

	cmd := exec.Command("gcc", "-o", f.name, f.filename)
	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func (f *CodeFile) Run() error {
	var cmd *exec.Cmd

	switch f.ftype {
	case "":
		cmd = exec.Command("./", f.name)
	case "c":
		f.Compile()
		cmd = exec.Command("./" + f.name)
	case "go":
		cmd = exec.Command("go", "run", f.filename)
	case "py":
		// (TODO): different python versions
		cmd = exec.Command("python", f.filename)
	case "lua":
		cmd = exec.Command("lua", f.filename)
	case "ruby":
		cmd = exec.Command("ruby", f.filename)

	default:
		return errors.New("Not a valid filetype!\n[Supported] bin, C" +
			"Go, Python, Lua, Ruby")
	}
	cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func compile(c *cli.Context) {
	file := newCodeFile(c.Args().First())

	if err := file.Compile(); err != nil {
		fmt.Errorf("%v\n", err)
	}
}

func run(c *cli.Context) {
	file := newCodeFile(c.Args().First())

	if err := file.Run(); err != nil {
		fmt.Errorf("%v\n", err)
	}
}

func main() {
	app := cli.NewApp()
	app.Usage = "Compile and/or run your code files"
	app.Commands = []cli.Command{
		{
			Name:      "compile",
			ShortName: "c",
			Usage:     "compiles a file",
			Action:    compile,
		},
		{
			Name:   "run",
			Usage:  "Run a file/script, it compiles it if needed",
			Action: run,
		},
	}
	app.RunAndExitOnError()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/nlopes/slack"

	"github.com/kamaln7/karmabot/database"
	"github.com/kamaln7/karmabot/webui"
)

var (
	regexps = map[string]*regexp.Regexp{
		"motivate":    regexp.MustCompile(`^(?:\?|!)m\s+@?([^\s]+?)(?:\:\s)?$`),
		"karma":       regexp.MustCompile(`^@?([^\s]+?)(?:\:\s)?([\+]{2,}|[\-]{2,})((?: for)? (.*))?$`),
		"leaderboard": regexp.MustCompile(`^karma(?:bot)? (?:leaderboard|top|highscores) ?([0-9]+)?$`),
		"url":         regexp.MustCompile(`^karma(?:bot)? (?:url|web|link)?$`),
		"slackUser":   regexp.MustCompile(`^<@([A-Za-z0-9]+)>$`),
	}

	debug                       bool
	hasWebUI                    bool
	webUIURL                    string
	maxPoints, leaderboardLimit int
	bot                         *slack.Client
	rtm                         *slack.RTM
	ll                          *log.Logger
)

func main() {
	ll = log.New(os.Stdout, "", log.Lshortfile|log.LstdFlags)

	var (
		flagToken            = flag.String("token", "", "slack RTM token")
		flagDBPath           = flag.String("db", "./db.sqlite3", "path to sqlite database")
		flagMaxPoints        = flag.Int("maxpoints", 6, "the maximum amount of points that users can give/take at once")
		flagLeaderboardLimit = flag.Int("leaderboardlimit", 10, "the default amount of users to list in the leaderboard")
		flagDebug            = flag.Bool("debug", false, "set debug mode")
		flagTOTP             = flag.String("totp", "", "totp key")
		flagWebUIPath        = flag.String("webuipath", "", "path to web UI files")
		flagListenAddr       = flag.String("listenaddr", "", "address to listen and serve the web ui on")
		flagWebUIURL         = flag.String("webuiurl", "", "url address for accessing the web ui")
	)

	flag.Parse()
	maxPoints = *flagMaxPoints
	leaderboardLimit = *flagLeaderboardLimit
	debug = *flagDebug
	hasWebUI = *flagWebUIPath != "" && *flagListenAddr != ""
	if hasWebUI {
		if *flagWebUIURL != "" {
			webUIURL = *flagWebUIURL
		} else {
			webUIURL = fmt.Sprintf("http://%s/", *flagListenAddr)
		}
	}

	if *flagToken == "" {
		ll.Fatalln("please pass the slack RTM token using the -token option")
	}

	database.Init(ll, *flagDBPath)

	bot = slack.New(*flagToken)
	slack.SetLogger(ll)
	bot.SetDebug(debug)

	rtm = bot.NewRTM()
	go rtm.ManageConnection()

	if hasWebUI {
		webUIConfig := &webui.Config{
			Logger:           ll,
			TOTPKey:          *flagTOTP,
			ListenAddr:       *flagListenAddr,
			FilesPath:        *flagWebUIPath,
			LeaderboardLimit: leaderboardLimit,
			Debug:            debug,
		}

		webui.Init(webUIConfig)
		go webui.Listen()
	}

	for {
		select {
		case msg := <-rtm.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.MessageEvent:
				go handleMessage(msg)
			case *slack.ConnectedEvent:
				ll.Println("connected!")
				if debug {
					ll.Println("infos:", ev.Info)
					ll.Println("connection counter:", ev.ConnectionCount)
				}
			case *slack.RTMError:
				ll.Printf("Slack RTM error: %s\n", ev.Error())
			case *slack.InvalidAuthEvent:
				ll.Fatalln("invalid slack token")
			default:
			}

		}
	}
}

func handleMessage(msg slack.RTMEvent) {
	ev := msg.Data.(*slack.MessageEvent)

	if ev.Type != "message" {
		return
	}

	// convert motivates into karmabot syntax
	if match := regexps["motivate"].FindStringSubmatch(ev.Text); len(match) > 0 {
		ev.Text = match[1] + "++ for doing good work"
	}

	switch {
	case regexps["url"].MatchString(ev.Text):
		printURL(ev)

	case regexps["karma"].MatchString(ev.Text):
		givePoints(ev)

	case regexps["leaderboard"].MatchString(ev.Text):
		printLeaderboard(ev)
	}
}

func printURL(ev *slack.MessageEvent) {
	if !hasWebUI {
		rtm.SendMessage(rtm.NewOutgoingMessage("webui not enabled. please pass the `-webuipath`, `-webuiurl`, and `-listenaddr` options in order to enable the web ui", ev.Channel))
		return
	}

	token, err := webui.GetToken()
	if handleError(err, ev.Channel) {
		return
	}

	rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("%s?token=%s", webUIURL, token), ev.Channel))
}

func givePoints(ev *slack.MessageEvent) {
	match := regexps["karma"].FindStringSubmatch(ev.Text)
	if len(match) == 0 {
		return
	}

	from, err := getUserNameByID(ev.User)
	if handleError(err, ev.Channel) {
		return
	}
	to, err := parseUser(match[1])
	if handleError(err, ev.Channel) {
		return
	}
	to = strings.ToLower(to)

	points := min(len(match[2])-1, maxPoints)
	if match[2][0] == '-' {
		points *= -1
	}
	reason := match[4]

	record := database.Points{
		From:   from,
		To:     to,
		Points: points,
		Reason: reason,
	}

	err = database.InsertPoints(record)
	if handleError(err, ev.Channel) {
		return
	}

	userPoints, err := database.GetPoints(to)
	if handleError(err, ev.Channel) {
		return
	}

	text := fmt.Sprintf("%s == %d (", to, userPoints)

	if points > 0 {
		text += "+"
	}
	text += strconv.Itoa(points)

	if reason != "" {
		text += fmt.Sprintf(" for %s", reason)
	}
	text += ")"

	rtm.SendMessage(rtm.NewOutgoingMessage(text, ev.Channel))
}

func printLeaderboard(ev *slack.MessageEvent) {
	match := regexps["leaderboard"].FindStringSubmatch(ev.Text)
	if len(match) == 0 {
		return
	}

	limit := leaderboardLimit
	if match[1] != "" {
		var err error
		limit, err = strconv.Atoi(match[1])
		if handleError(err, ev.Channel) {
			return
		}
	}

	text := fmt.Sprintf("*top %d leaderboard*\n", limit)

	if hasWebUI {
		token, err := webui.GetToken()
		if err == nil {
			text += fmt.Sprintf("%sleaderboard/%d?token=%s\n", webUIURL, limit, token)
		}
	}

	leaderboard, err := database.GetLeaderboard(limit)
	if handleError(err, ev.Channel) {
		return
	}

	for i, user := range leaderboard {
		text += fmt.Sprintf("%d. %s == %d\n", i+1, munge(user.User), user.Points)
	}

	rtm.SendMessage(rtm.NewOutgoingMessage(text, ev.Channel))
}

func handleError(err error, to string) bool {
	if err != nil {
		ll.Printf("Slack RTM error: %s\n", err.Error())
		rtm.SendMessage(rtm.NewOutgoingMessage("error: "+err.Error(), to))
		return true
	}

	return false
}

func parseUser(user string) (string, error) {
	if match := regexps["slackUser"].FindStringSubmatch(user); len(match) > 0 {
		return getUserNameByID(match[1])
	}

	return user, nil
}

func getUserNameByID(id string) (string, error) {
	userInfo, err := bot.GetUserInfo(id)
	if err != nil {
		return "", err
	}

	return userInfo.Name, nil
}
<commit_msg>Switch map of regexps to anonymous struct of regexps. (#3)<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/nlopes/slack"

	"github.com/kamaln7/karmabot/database"
	"github.com/kamaln7/karmabot/webui"
)

var (
	regexps = struct {
		Motivate, Karma, Leaderboard, URL, SlackUser *regexp.Regexp
	}{
		Motivate:    regexp.MustCompile(`^(?:\?|!)m\s+@?([^\s]+?)(?:\:\s)?$`),
		Karma:       regexp.MustCompile(`^@?([^\s]+?)(?:\:\s)?([\+]{2,}|[\-]{2,})((?: for)? (.*))?$`),
		Leaderboard: regexp.MustCompile(`^karma(?:bot)? (?:leaderboard|top|highscores) ?([0-9]+)?$`),
		URL:         regexp.MustCompile(`^karma(?:bot)? (?:url|web|link)?$`),
		SlackUser:   regexp.MustCompile(`^<@([A-Za-z0-9]+)>$`),
	}

	debug                       bool
	hasWebUI                    bool
	webUIURL                    string
	maxPoints, leaderboardLimit int
	bot                         *slack.Client
	rtm                         *slack.RTM
	ll                          *log.Logger
)

func main() {
	ll = log.New(os.Stdout, "", log.Lshortfile|log.LstdFlags)

	var (
		flagToken            = flag.String("token", "", "slack RTM token")
		flagDBPath           = flag.String("db", "./db.sqlite3", "path to sqlite database")
		flagMaxPoints        = flag.Int("maxpoints", 6, "the maximum amount of points that users can give/take at once")
		flagLeaderboardLimit = flag.Int("leaderboardlimit", 10, "the default amount of users to list in the leaderboard")
		flagDebug            = flag.Bool("debug", false, "set debug mode")
		flagTOTP             = flag.String("totp", "", "totp key")
		flagWebUIPath        = flag.String("webuipath", "", "path to web UI files")
		flagListenAddr       = flag.String("listenaddr", "", "address to listen and serve the web ui on")
		flagWebUIURL         = flag.String("webuiurl", "", "url address for accessing the web ui")
	)

	flag.Parse()
	maxPoints = *flagMaxPoints
	leaderboardLimit = *flagLeaderboardLimit
	debug = *flagDebug
	hasWebUI = *flagWebUIPath != "" && *flagListenAddr != ""
	if hasWebUI {
		if *flagWebUIURL != "" {
			webUIURL = *flagWebUIURL
		} else {
			webUIURL = fmt.Sprintf("http://%s/", *flagListenAddr)
		}
	}

	if *flagToken == "" {
		ll.Fatalln("please pass the slack RTM token using the -token option")
	}

	database.Init(ll, *flagDBPath)

	bot = slack.New(*flagToken)
	slack.SetLogger(ll)
	bot.SetDebug(debug)

	rtm = bot.NewRTM()
	go rtm.ManageConnection()

	if hasWebUI {
		webUIConfig := &webui.Config{
			Logger:           ll,
			TOTPKey:          *flagTOTP,
			ListenAddr:       *flagListenAddr,
			FilesPath:        *flagWebUIPath,
			LeaderboardLimit: leaderboardLimit,
			Debug:            debug,
		}

		webui.Init(webUIConfig)
		go webui.Listen()
	}

	for {
		select {
		case msg := <-rtm.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.MessageEvent:
				go handleMessage(msg)
			case *slack.ConnectedEvent:
				ll.Println("connected!")
				if debug {
					ll.Println("infos:", ev.Info)
					ll.Println("connection counter:", ev.ConnectionCount)
				}
			case *slack.RTMError:
				ll.Printf("Slack RTM error: %s\n", ev.Error())
			case *slack.InvalidAuthEvent:
				ll.Fatalln("invalid slack token")
			default:
			}

		}
	}
}

func handleMessage(msg slack.RTMEvent) {
	ev := msg.Data.(*slack.MessageEvent)

	if ev.Type != "message" {
		return
	}

	// convert motivates into karmabot syntax
	if match := regexps.Motivate.FindStringSubmatch(ev.Text); len(match) > 0 {
		ev.Text = match[1] + "++ for doing good work"
	}

	switch {
	case regexps.URL.MatchString(ev.Text):
		printURL(ev)

	case regexps.Karma.MatchString(ev.Text):
		givePoints(ev)

	case regexps.Leaderboard.MatchString(ev.Text):
		printLeaderboard(ev)
	}
}

func printURL(ev *slack.MessageEvent) {
	if !hasWebUI {
		rtm.SendMessage(rtm.NewOutgoingMessage("webui not enabled. please pass the `-webuipath`, `-webuiurl`, and `-listenaddr` options in order to enable the web ui", ev.Channel))
		return
	}

	token, err := webui.GetToken()
	if handleError(err, ev.Channel) {
		return
	}

	rtm.SendMessage(rtm.NewOutgoingMessage(fmt.Sprintf("%s?token=%s", webUIURL, token), ev.Channel))
}

func givePoints(ev *slack.MessageEvent) {
	match := regexps.Karma.FindStringSubmatch(ev.Text)
	if len(match) == 0 {
		return
	}

	from, err := getUserNameByID(ev.User)
	if handleError(err, ev.Channel) {
		return
	}
	to, err := parseUser(match[1])
	if handleError(err, ev.Channel) {
		return
	}
	to = strings.ToLower(to)

	points := min(len(match[2])-1, maxPoints)
	if match[2][0] == '-' {
		points *= -1
	}
	reason := match[4]

	record := database.Points{
		From:   from,
		To:     to,
		Points: points,
		Reason: reason,
	}

	err = database.InsertPoints(record)
	if handleError(err, ev.Channel) {
		return
	}

	userPoints, err := database.GetPoints(to)
	if handleError(err, ev.Channel) {
		return
	}

	text := fmt.Sprintf("%s == %d (", to, userPoints)

	if points > 0 {
		text += "+"
	}
	text += strconv.Itoa(points)

	if reason != "" {
		text += fmt.Sprintf(" for %s", reason)
	}
	text += ")"

	rtm.SendMessage(rtm.NewOutgoingMessage(text, ev.Channel))
}

func printLeaderboard(ev *slack.MessageEvent) {
	match := regexps.Leaderboard.FindStringSubmatch(ev.Text)
	if len(match) == 0 {
		return
	}

	limit := leaderboardLimit
	if match[1] != "" {
		var err error
		limit, err = strconv.Atoi(match[1])
		if handleError(err, ev.Channel) {
			return
		}
	}

	text := fmt.Sprintf("*top %d leaderboard*\n", limit)

	if hasWebUI {
		token, err := webui.GetToken()
		if err == nil {
			text += fmt.Sprintf("%sleaderboard/%d?token=%s\n", webUIURL, limit, token)
		}
	}

	leaderboard, err := database.GetLeaderboard(limit)
	if handleError(err, ev.Channel) {
		return
	}

	for i, user := range leaderboard {
		text += fmt.Sprintf("%d. %s == %d\n", i+1, munge(user.User), user.Points)
	}

	rtm.SendMessage(rtm.NewOutgoingMessage(text, ev.Channel))
}

func handleError(err error, to string) bool {
	if err != nil {
		ll.Printf("Slack RTM error: %s\n", err.Error())
		rtm.SendMessage(rtm.NewOutgoingMessage("error: "+err.Error(), to))
		return true
	}

	return false
}

func parseUser(user string) (string, error) {
	if match := regexps.SlackUser.FindStringSubmatch(user); len(match) > 0 {
		return getUserNameByID(match[1])
	}

	return user, nil
}

func getUserNameByID(id string) (string, error) {
	userInfo, err := bot.GetUserInfo(id)
	if err != nil {
		return "", err
	}

	return userInfo.Name, nil
}
<|endoftext|>


<commit_before>// 23 june 2014
package main

import (
	"fmt"
	"os"
	"encoding/json"
	"io"
	"bytes"
	"sort"
	"strings"
)

type indenter struct {
	w	io.Writer
}
func (i *indenter) Write(p []byte) (n int, err error) {
	b := new(bytes.Buffer)
	err = json.Indent(b, p, "", " ")//"\t")
	if err != nil { return 0, err }
	return i.w.Write(b.Bytes())
}

func jsonout(w io.Writer, data interface{}) {
	e := json.NewEncoder(w)
	err := e.Encode(data)
	if err != nil { panic(err) }
}

func main() {
	if len(os.Args) != 4 { panic("usage: " + os.Args[0] + " repo ver {json|jsoni}") }
	ns, err := ReadNamespace(os.Args[1], os.Args[2])
	if err != nil { panic(err) }
	switch os.Args[3] {
	case "json":
		jsonout(os.Stdout, ns)
	case "jsoni":
		jsonout(&indenter{os.Stdout}, ns)
	case "innerobj":
		objs := make([]ObjectInfo, len(ns.Objects))
		copy(objs, ns.Objects)
		sort.Sort(sort.Reverse(sort.IntSlice(ns.TopLevelObjects)))		// TODO should we do this ourselves? (minus the reversing)
		for _, i := range ns.TopLevelObjects {
			objs = append(objs[:i], objs[i + 1:]...)
		}
		jsonout(&indenter{os.Stdout}, objs)
	case "allargs":
		for i, _ := range ns.Args {
			fmt.Println(ns.ArgToGo(i))
		}
	case "allcallbacks":
		for i, _ := range ns.Callbacks {
			fmt.Println(ns.CallbackToGo(i))
		}
	case "allfunctions":
		for i, _ := range ns.Functions {
			fmt.Println(ns.FunctionToGo(i))
		}
	case "allsignals":
		for i, _ := range ns.Signals {
			fmt.Println(ns.SignalToGo(i))
		}
	case "allvfuncs":
		for i, _ := range ns.VFuncs {
			fmt.Println(ns.VFuncToGo(i))
		}
	case "allconsts":
		for i, _ := range ns.Constants {
			fmt.Println(ns.ConstantToGo(i))
		}
	case "allfields":
		for i, _ := range ns.Fields {
			fmt.Println(ns.FieldToGo(i))
		}
	// TODO properties
	case "allenums":
		for i, _ := range ns.Enums {
			fmt.Println(ns.EnumToGo(i))
		}
	case "allinterfaces":
		for i, _ := range ns.Interfaces {
			fmt.Println(ns.InterfaceToGo(i))
		}
	default:
		os.Args = os.Args[:1]		// quick hack
		main()
	}
}

func (ns Namespace) ArgToGo(n int) string {
	arg := ns.Args[n]
	return fmt.Sprintf("%s %s", arg.Name, ns.TypeToGo(arg.Type))
}

func (ns Namespace) CallbackToGo(n int) string {
	return ns.Callbacks[n].CallableToGo(ns)
}

func (ns Namespace) FunctionToGo(n int) string {
	return ns.Functions[n].CallableToGo(ns)
}

func (ns Namespace) SignalToGo(n int) string {
	return ns.Signals[n].CallableToGo(ns)
}

func (ns Namespace) VFuncToGo(n int) string {
	return ns.VFuncs[n].CallableToGo(ns)
}

func (cb CallableInfo) CallableToGo(ns Namespace) string {
	if cb.Namespace != ns.Name {
		return "// " + cb.Name + " external; skip"
	}
	s := "func "
	if cb.IsMethod {
		s += "() "
	}
	s += cb.Name + "("
	for _, i := range cb.Args {
		s += ns.ArgToGo(i) + ", "
	}
	s += ")"
	ret := ns.TypeToGo(cb.ReturnType)
	if ret != "" {
		s += " (ret " + ret + ")"
	}
	// TODO return args and errors
	return s
}

func (ns Namespace) ConstantToGo(n int) string {
	c := ns.Constants[n]
	if c.Namespace != ns.Name {
		return "// " + c.Name + " external; skip"
	}
	s := "const " + c.Name + " " + ns.TypeToGo(c.Type) + " = "
	s += "C." + strings.ToUpper(c.Namespace) + "_" + c.Name
	return s
}

func (ns Namespace) FieldToGo(n int) string {
	f := ns.Fields[n]
	if f.Namespace != ns.Name {
		return "\t// " + f.Name + " external; skip"
	}
	s := "\t" + f.Name + " " + ns.TypeToGo(f.Type)
	return s
}

func (ns Namespace) EnumToGo(n int) string {
	e := ns.Enums[n]
	if e.Namespace != ns.Name {
		return "// " + e.Name + " external; skip"
	}
	// TODO use e.Name or e.RTName?
	s := "type " + e.Name + " " + e.StorageType.BasicString() + "\n"
	s += "const (\n"
	for _, i := range e.Values {
		v := ns.Values[i]
		s += "\t" + v.Name + " " + e.Name + " = "
		s += "C." + strings.ToUpper(v.Namespace) + "_" + v.Name + "\n"
	}
	s += ")"
	return s
}

func (ns Namespace) InterfaceToGo(n int) string {
	i := ns.Interfaces[n]
	if i.Namespace != ns.Name {
		return "// " + i.Name + " external; skip"
	}
	s := "type " + i.Name + " interface {\n"
	for _, p := range i.Prerequisites {
		s += "\t" + strings.ToLower(p.Namespace) + "." + p.Name + "\n"
	}
	// TODO properties
	s += "\t// methods\n"
	for _, n := range i.Methods {
		s += "\t" + ns.FunctionToGo(n) + "\n"
	}
	s += "\t// signals\n"
	for _, n := range i.Signals {
		s += "\t" + ns.SignalToGo(n) + "\n"
	}
	s += "\t// vfuncs\n"
	for _, n := range i.VFuncs {
		s += "\t" + ns.VFuncToGo(n) + "\n"
	}
	// TODO Struct
	s += "}"
	for _, n := range i.Constants {
		s += ns.ConstantToGo(n) + "\n"
	}
	// TODO Struct
	return s
}

func (ns Namespace) TypeToGo(n int) string {
	t := ns.Types[n]
	s := ""
	if t.IsPointer {
		switch t.Tag {
		case TagUTF8String, TagFilename, TagArray, TagGList, TagGSList, TagGHashTable:
			// don't add a pointer to these C types
		default:
			s += "*"
		}
	}
	// don't add t.Namespace; that'll produce weird things for cross-included data like gobject.string
	switch t.Tag {
	case TagVoid:
		if t.IsPointer {
			s = "interface{}"
		}
		// otherwise it's a function return; do nothing
	case TagBoolean:
		s += "bool"
	case TagInt8:
		s += "int8"
	case TagUint8:
		s += "uint8"
	case TagInt16:
		s += "int16"
	case TagUint16:
		s += "uint16"
	case TagInt32:
		s += "int32"
	case TagUint32:
		s += "uint32"
	case TagInt64:
		s += "int64"
	case TagUint64:
		s += "uint64"
	case TagFloat:
		s += "float32"
	case TagDouble:
		s += "float64"
	case TagGType:
		s += "GType"
	case TagUTF8String:
		s += "string"
	case TagFilename:
		s += "string"
	case TagArray:
		switch t.ArrayType {
		case CArray, GArray:
			s += "[]"
			s += ns.TypeToGo(t.ParamTypes[0])
		case GPtrArray:
			s += "[]*"
			s += ns.TypeToGo(t.ParamTypes[0])
		case GByteArray:
			s += "[]byte"
		default:
			panic(fmt.Errorf("unknown array type %d", t.ArrayType))
		}
	case TagInterface:
		if t.Interface.Namespace != ns.Name {
			s += strings.ToLower(t.Interface.Namespace) + "."
		}
		s += t.Interface.Name
	case TagGList:
		s += "[]"
		if ns.Types[t.ParamTypes[0]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[0])
	case TagGSList:
		s += "[]"
		if ns.Types[t.ParamTypes[0]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[0])
	case TagGHashTable:
		s += "map["
		if ns.Types[t.ParamTypes[0]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[0])
		s += "]"
		if ns.Types[t.ParamTypes[1]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[1])
	case TagGError:
		s += "error"
	case TagUnichar:
		s += "rune"
	default:
		panic(fmt.Errorf("unknown tag type %d", t.Tag))
	}
	return s
}

// for GList, GSList, and GHashTable, whether the stored type is a pointer is not stored; use this function to find out
func (t TypeTag) GContainerStorePointer() bool {
	return t == TagInterface
}

func (t TypeTag) BasicString() string {
	s := ""
	switch t {
	case TagBoolean:
		s += "bool"
	case TagInt8:
		s += "int8"
	case TagUint8:
		s += "uint8"
	case TagInt16:
		s += "int16"
	case TagUint16:
		s += "uint16"
	case TagInt32:
		s += "int32"
	case TagUint32:
		s += "uint32"
	case TagInt64:
		s += "int64"
	case TagUint64:
		s += "uint64"
	case TagFloat:
		s += "float32"
	case TagDouble:
		s += "float64"
	case TagGType:
		s += "GType"
	case TagUTF8String:
		s += "string"
	case TagFilename:
		s += "string"
	case TagUnichar:
		s += "rune"
	default:
		panic(fmt.Errorf("unknown or non-basic tag type %d", t))
	}
	return s
}
<commit_msg>Added enumerating objects.<commit_after>// 23 june 2014
package main

import (
	"fmt"
	"os"
	"encoding/json"
	"io"
	"bytes"
	"sort"
	"strings"
)

type indenter struct {
	w	io.Writer
}
func (i *indenter) Write(p []byte) (n int, err error) {
	b := new(bytes.Buffer)
	err = json.Indent(b, p, "", " ")//"\t")
	if err != nil { return 0, err }
	return i.w.Write(b.Bytes())
}

func jsonout(w io.Writer, data interface{}) {
	e := json.NewEncoder(w)
	err := e.Encode(data)
	if err != nil { panic(err) }
}

func main() {
	if len(os.Args) != 4 { panic("usage: " + os.Args[0] + " repo ver {json|jsoni}") }
	ns, err := ReadNamespace(os.Args[1], os.Args[2])
	if err != nil { panic(err) }
	switch os.Args[3] {
	case "json":
		jsonout(os.Stdout, ns)
	case "jsoni":
		jsonout(&indenter{os.Stdout}, ns)
	case "innerobj":
		objs := make([]ObjectInfo, len(ns.Objects))
		copy(objs, ns.Objects)
		sort.Sort(sort.Reverse(sort.IntSlice(ns.TopLevelObjects)))		// TODO should we do this ourselves? (minus the reversing)
		for _, i := range ns.TopLevelObjects {
			objs = append(objs[:i], objs[i + 1:]...)
		}
		jsonout(&indenter{os.Stdout}, objs)
	case "allargs":
		for i, _ := range ns.Args {
			fmt.Println(ns.ArgToGo(i))
		}
	case "allcallbacks":
		for i, _ := range ns.Callbacks {
			fmt.Println(ns.CallbackToGo(i))
		}
	case "allfunctions":
		for i, _ := range ns.Functions {
			fmt.Println(ns.FunctionToGo(i))
		}
	case "allsignals":
		for i, _ := range ns.Signals {
			fmt.Println(ns.SignalToGo(i))
		}
	case "allvfuncs":
		for i, _ := range ns.VFuncs {
			fmt.Println(ns.VFuncToGo(i))
		}
	case "allconsts":
		for i, _ := range ns.Constants {
			fmt.Println(ns.ConstantToGo(i))
		}
	case "allfields":
		for i, _ := range ns.Fields {
			fmt.Println(ns.FieldToGo(i))
		}
	// TODO properties
	case "allenums":
		for i, _ := range ns.Enums {
			fmt.Println(ns.EnumToGo(i))
		}
	case "allinterfaces":
		for i, _ := range ns.Interfaces {
			fmt.Println(ns.InterfaceToGo(i))
		}
	case "allobjects":
		for i, _ := range ns.Objects {
			fmt.Println(ns.ObjectToGo(i))
		}
	default:
		os.Args = os.Args[:1]		// quick hack
		main()
	}
}

func (ns Namespace) ArgToGo(n int) string {
	arg := ns.Args[n]
	return fmt.Sprintf("%s %s", arg.Name, ns.TypeToGo(arg.Type))
}

func (ns Namespace) CallbackToGo(n int) string {
	return ns.Callbacks[n].CallableToGo(ns)
}

func (ns Namespace) FunctionToGo(n int) string {
	return ns.Functions[n].CallableToGo(ns)
}

func (ns Namespace) SignalToGo(n int) string {
	return ns.Signals[n].CallableToGo(ns)
}

func (ns Namespace) VFuncToGo(n int) string {
	return ns.VFuncs[n].CallableToGo(ns)
}

func (cb CallableInfo) CallableToGo(ns Namespace) string {
	if cb.Namespace != ns.Name {
		return "// " + cb.Name + " external; skip"
	}
	s := "func "
	if cb.IsMethod {
		s += "() "
	}
	s += cb.Name + "("
	for _, i := range cb.Args {
		s += ns.ArgToGo(i) + ", "
	}
	s += ")"
	ret := ns.TypeToGo(cb.ReturnType)
	if ret != "" {
		s += " (ret " + ret + ")"
	}
	// TODO return args and errors
	return s
}

func (ns Namespace) ConstantToGo(n int) string {
	c := ns.Constants[n]
	if c.Namespace != ns.Name {
		return "// " + c.Name + " external; skip"
	}
	s := "const " + c.Name + " " + ns.TypeToGo(c.Type) + " = "
	s += "C." + strings.ToUpper(c.Namespace) + "_" + c.Name
	return s
}

func (ns Namespace) FieldToGo(n int) string {
	f := ns.Fields[n]
	if f.Namespace != ns.Name {
		return "\t// " + f.Name + " external; skip"
	}
	s := "\t" + f.Name + " " + ns.TypeToGo(f.Type)
	return s
}

func (ns Namespace) EnumToGo(n int) string {
	e := ns.Enums[n]
	if e.Namespace != ns.Name {
		return "// " + e.Name + " external; skip"
	}
	// TODO use e.Name or e.RTName?
	s := "type " + e.Name + " " + e.StorageType.BasicString() + "\n"
	s += "const (\n"
	for _, i := range e.Values {
		v := ns.Values[i]
		s += "\t" + v.Name + " " + e.Name + " = "
		s += "C." + strings.ToUpper(v.Namespace) + "_" + v.Name + "\n"
	}
	s += ")"
	return s
}

func (ns Namespace) InterfaceToGo(n int) string {
	i := ns.Interfaces[n]
	if i.Namespace != ns.Name {
		return "// " + i.Name + " external; skip"
	}
	s := "type " + i.Name + " interface {\n"
	for _, p := range i.Prerequisites {
		s += "\t" + strings.ToLower(p.Namespace) + "." + p.Name + "\n"
	}
	// TODO properties
	s += "\t// methods\n"
	for _, n := range i.Methods {
		s += "\t" + ns.FunctionToGo(n) + "\n"
	}
	s += "\t// signals\n"
	for _, n := range i.Signals {
		s += "\t" + ns.SignalToGo(n) + "\n"
	}
	s += "\t// vfuncs\n"
	for _, n := range i.VFuncs {
		s += "\t" + ns.VFuncToGo(n) + "\n"
	}
	// TODO Struct
	s += "}"		// TODO newline
	for _, n := range i.Constants {
		s += ns.ConstantToGo(n) + "\n"
	}
	// TODO Struct
	return s
}

func (ns Namespace) ObjectToGo(n int) string {
	o := ns.Objects[n]
	if o.Namespace != ns.Name {
		return "// " + o.Name + " external; skip"
	}
	s := "type " + o.Name + " struct {\n"
	if o.Parent != -1 {
		oo := ns.Objects[o.Parent]
		s += "\t" + strings.ToLower(oo.Namespace) + "." + oo.Name + "\n"
	}
	s += "\t// interfaces\n"
	for _, n := range o.Interfaces {
		i := ns.Interfaces[n]
		s += "\t" + strings.ToLower(i.Namespace) + "." + i.Name + "\n"
	}
	s += "\t//fields\n"
	for _, n := range o.Fields {
		s += ns.FieldToGo(n) + "\n"
	}
	s += "}\n"
	s += "// methods\n"
	for _, n := range o.Methods {
		s += ns.FunctionToGo(n) + "\n"
	}
	// TODO properties
	s += "// signals\n"
	for _, n := range o.Signals {
		s += ns.SignalToGo(n) + "\n"
	}
	s += "// vfuncs\n"
	for _, n := range o.VFuncs {
		s += ns.VFuncToGo(n) + "\n"
	}
	// TODO Struct
	for _, n := range o.Constants {
		s += ns.ConstantToGo(n) + "\n"
	}
	// TODO the four functions
	return s
}

func (ns Namespace) TypeToGo(n int) string {
	t := ns.Types[n]
	s := ""
	if t.IsPointer {
		switch t.Tag {
		case TagUTF8String, TagFilename, TagArray, TagGList, TagGSList, TagGHashTable:
			// don't add a pointer to these C types
		default:
			s += "*"
		}
	}
	// don't add t.Namespace; that'll produce weird things for cross-included data like gobject.string
	switch t.Tag {
	case TagVoid:
		if t.IsPointer {
			s = "interface{}"
		}
		// otherwise it's a function return; do nothing
	case TagBoolean:
		s += "bool"
	case TagInt8:
		s += "int8"
	case TagUint8:
		s += "uint8"
	case TagInt16:
		s += "int16"
	case TagUint16:
		s += "uint16"
	case TagInt32:
		s += "int32"
	case TagUint32:
		s += "uint32"
	case TagInt64:
		s += "int64"
	case TagUint64:
		s += "uint64"
	case TagFloat:
		s += "float32"
	case TagDouble:
		s += "float64"
	case TagGType:
		s += "GType"
	case TagUTF8String:
		s += "string"
	case TagFilename:
		s += "string"
	case TagArray:
		switch t.ArrayType {
		case CArray, GArray:
			s += "[]"
			s += ns.TypeToGo(t.ParamTypes[0])
		case GPtrArray:
			s += "[]*"
			s += ns.TypeToGo(t.ParamTypes[0])
		case GByteArray:
			s += "[]byte"
		default:
			panic(fmt.Errorf("unknown array type %d", t.ArrayType))
		}
	case TagInterface:
		if t.Interface.Namespace != ns.Name {
			s += strings.ToLower(t.Interface.Namespace) + "."
		}
		s += t.Interface.Name
	case TagGList:
		s += "[]"
		if ns.Types[t.ParamTypes[0]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[0])
	case TagGSList:
		s += "[]"
		if ns.Types[t.ParamTypes[0]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[0])
	case TagGHashTable:
		s += "map["
		if ns.Types[t.ParamTypes[0]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[0])
		s += "]"
		if ns.Types[t.ParamTypes[1]].Tag.GContainerStorePointer() {
			s += "*"
		}
		s += ns.TypeToGo(t.ParamTypes[1])
	case TagGError:
		s += "error"
	case TagUnichar:
		s += "rune"
	default:
		panic(fmt.Errorf("unknown tag type %d", t.Tag))
	}
	return s
}

// for GList, GSList, and GHashTable, whether the stored type is a pointer is not stored; use this function to find out
func (t TypeTag) GContainerStorePointer() bool {
	return t == TagInterface
}

func (t TypeTag) BasicString() string {
	s := ""
	switch t {
	case TagBoolean:
		s += "bool"
	case TagInt8:
		s += "int8"
	case TagUint8:
		s += "uint8"
	case TagInt16:
		s += "int16"
	case TagUint16:
		s += "uint16"
	case TagInt32:
		s += "int32"
	case TagUint32:
		s += "uint32"
	case TagInt64:
		s += "int64"
	case TagUint64:
		s += "uint64"
	case TagFloat:
		s += "float32"
	case TagDouble:
		s += "float64"
	case TagGType:
		s += "GType"
	case TagUTF8String:
		s += "string"
	case TagFilename:
		s += "string"
	case TagUnichar:
		s += "rune"
	default:
		panic(fmt.Errorf("unknown or non-basic tag type %d", t))
	}
	return s
}
<|endoftext|>