<commit_before>package early

import "github.com/achilleasa/gopher-os/kernel/hal"

var (
	errMissingArg   = []byte("(MISSING)")
	errWrongArgType = []byte("%!(WRONGTYPE)")
	errNoVerb       = []byte("%!(NOVERB)")
	errExtraArg     = []byte("%!(EXTRA)")
	padding         = []byte{' '}
	trueValue       = []byte("true")
	falseValue      = []byte("false")
)

// Printf provides a minimal Printf implementation that can be used before the
// Go runtime has been properly initialized. This version of printf does not
// allocate any memory and uses hal.ActiveTerminal for its output.
//
// Similar to fmt.Printf, this version of printf supports the following subset
// of formatting verbs:
//
// Strings:
//		%s the uninterpreted bytes of the string or byte slice
//
// Integers:
//              %o base 8
//              %d base 10
//              %x base 16, with lower-case letters for a-f
//
// Booleans:
//              %t "true" or "false"
//
// Width is specified by an optional decimal number immediately preceding the verb.
// If absent, the width is whatever is necessary to represent the value.
//
// String values with length less than the specified width will be left-padded with
// spaces. Integer values formatted as base-10 will also be left-padded with spaces.
// Finally, integer values formatted as base-16 will be left-padded with zeroes.
//
// Printf supports all built-in string and integer types but assumes that the
// Go itables have not been initialized yet so it will not check whether its
// arguments support io.Stringer if they don't match one of the supported tupes.
//
// This function does not provide support for printing pointers (%p) as this
// requires importing the reflect package. By importing reflect, the go compiler
// starts generating calls to runtime.convT2E (which calls runtime.newobject)
// when assembling the argument slice which obviously will crash the kernel since
// memory management is not yet available.
func Printf(format string, args ...interface{}) {
	var (
		nextCh                       byte
		nextArgIndex                 int
		blockStart, blockEnd, padLen int
		fmtLen                       = len(format)
	)

	for blockEnd < fmtLen {
		nextCh = format[blockEnd]
		if nextCh != '%' {
			blockEnd++
			continue
		}

		if blockStart < blockEnd {
			hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd]))
		}

		// Scan til we hit the format character
		padLen = 0
		blockEnd++
	parseFmt:
		for ; blockEnd < fmtLen; blockEnd++ {
			nextCh = format[blockEnd]
			switch {
			case nextCh == '%':
				hal.ActiveTerminal.Write([]byte{'%'})
				break parseFmt
			case nextCh >= '0' && nextCh <= '9':
				padLen = (padLen * 10) + int(nextCh-'0')
				continue
			case nextCh == 'd' || nextCh == 'x' || nextCh == 'o' || nextCh == 's' || nextCh == 't':
				// Run out of args to print
				if nextArgIndex >= len(args) {
					hal.ActiveTerminal.Write(errMissingArg)
					break parseFmt
				}

				switch nextCh {
				case 'o':
					fmtInt(args[nextArgIndex], 8, padLen)
				case 'd':
					fmtInt(args[nextArgIndex], 10, padLen)
				case 'x':
					fmtInt(args[nextArgIndex], 16, padLen)
				case 's':
					fmtString(args[nextArgIndex], padLen)
				case 't':
					fmtBool(args[nextArgIndex])
				}

				nextArgIndex++
				break parseFmt
			}

			// reached end of formatting string without finding a verb
			hal.ActiveTerminal.Write(errNoVerb)
		}
		blockStart, blockEnd = blockEnd+1, blockEnd+1
	}

	if blockStart != blockEnd {
		hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd]))
	}

	// Check for unused args
	for ; nextArgIndex < len(args); nextArgIndex++ {
		hal.ActiveTerminal.Write(errExtraArg)
	}
}

// fmtBool prints a formatted version of boolean value v using hal.ActiveTerminal
// for its output.
func fmtBool(v interface{}) {
	switch bVal := v.(type) {
	case bool:
		switch bVal {
		case true:
			hal.ActiveTerminal.Write(trueValue)
		case false:
			hal.ActiveTerminal.Write(falseValue)
		}
	default:
		hal.ActiveTerminal.Write(errWrongArgType)
		return
	}
}

// fmtString prints a formatted version of string or []byte value v, applying the
// padding specified by padLen. This function uses hal.ActiveTerminal for its
// output.
func fmtString(v interface{}, padLen int) {
	var sval []byte

	switch castedVal := v.(type) {
	case string:
		sval = []byte(castedVal)
	case []byte:
		sval = castedVal
	default:
		hal.ActiveTerminal.Write(errWrongArgType)
		return
	}

	for pad := padLen - len(sval); pad > 0; pad-- {
		hal.ActiveTerminal.Write(padding)
	}

	hal.ActiveTerminal.Write(sval)
}

// fmtInt prints out a formatted version of v in the requested base, applying the
// padding specified by padLen. This function uses hal.ActiveTerminal for its
// output, supports all built-in signed and unsigned integer types and supports
// base 8, 10 and 16 output.
func fmtInt(v interface{}, base, padLen int) {
	var (
		sval             int64
		uval             uint64
		divider          uint64
		remainder        uint64
		buf              [20]byte
		padCh            byte
		left, right, end int
	)

	switch base {
	case 8:
		divider = 8
		padCh = '0'
	case 10:
		divider = 10
		padCh = ' '
	case 16:
		divider = 16
		padCh = '0'
	}

	switch v.(type) {
	case uint8:
		uval = uint64(v.(uint8))
	case uint16:
		uval = uint64(v.(uint16))
	case uint32:
		uval = uint64(v.(uint32))
	case uint64:
		uval = v.(uint64)
	case uintptr:
		uval = uint64(v.(uintptr))
	case int8:
		sval = int64(v.(int8))
	case int16:
		sval = int64(v.(int16))
	case int32:
		sval = int64(v.(int32))
	case int64:
		sval = v.(int64)
	case int:
		sval = int64(v.(int))
	default:
		hal.ActiveTerminal.Write(errWrongArgType)
		return
	}

	// Handle signs
	if sval < 0 {
		uval = uint64(-sval)
	} else if sval > 0 {
		uval = uint64(sval)
	}

	for {
		remainder = uval % divider
		if remainder < 10 {
			buf[right] = byte(remainder) + '0'
		} else {
			// map values from 10 to 15 -> a-f
			buf[right] = byte(remainder-10) + 'a'
		}

		right++

		uval /= divider
		if uval == 0 {
			break
		}
	}

	// Apply padding if required
	for ; right-left < padLen; right++ {
		buf[right] = padCh
	}

	// Apply hex prefix
	if base == 16 {
		buf[right] = 'x'
		buf[right+1] = '0'
		right += 2
	}

	// Apply negative sign to the rightmost blank character (if using enough padding);
	// otherwise append the sign as a new char
	if sval < 0 {
		for end = right - 1; buf[end] == ' '; end-- {
		}

		if end == right-1 {
			right++
		}

		buf[end+1] = '-'
	}

	// Reverse in place
	end = right
	for right = right - 1; left < right; left, right = left+1, right-1 {
		buf[left], buf[right] = buf[right], buf[left]
	}

	hal.ActiveTerminal.Write(buf[0:end])
}
<commit_msg>Prevent early_fmt code from triggering Go's allocator<commit_after>package early

import "github.com/achilleasa/gopher-os/kernel/hal"

var (
	errMissingArg   = []byte("(MISSING)")
	errWrongArgType = []byte("%!(WRONGTYPE)")
	errNoVerb       = []byte("%!(NOVERB)")
	errExtraArg     = []byte("%!(EXTRA)")
	padding         = byte(' ')
	trueValue       = []byte("true")
	falseValue      = []byte("false")
)

// Printf provides a minimal Printf implementation that can be used before the
// Go runtime has been properly initialized. This version of printf does not
// allocate any memory and uses hal.ActiveTerminal for its output.
//
// Similar to fmt.Printf, this version of printf supports the following subset
// of formatting verbs:
//
// Strings:
//		%s the uninterpreted bytes of the string or byte slice
//
// Integers:
//              %o base 8
//              %d base 10
//              %x base 16, with lower-case letters for a-f
//
// Booleans:
//              %t "true" or "false"
//
// Width is specified by an optional decimal number immediately preceding the verb.
// If absent, the width is whatever is necessary to represent the value.
//
// String values with length less than the specified width will be left-padded with
// spaces. Integer values formatted as base-10 will also be left-padded with spaces.
// Finally, integer values formatted as base-16 will be left-padded with zeroes.
//
// Printf supports all built-in string and integer types but assumes that the
// Go itables have not been initialized yet so it will not check whether its
// arguments support io.Stringer if they don't match one of the supported tupes.
//
// This function does not provide support for printing pointers (%p) as this
// requires importing the reflect package. By importing reflect, the go compiler
// starts generating calls to runtime.convT2E (which calls runtime.newobject)
// when assembling the argument slice which obviously will crash the kernel since
// memory management is not yet available.
func Printf(format string, args ...interface{}) {
	var (
		nextCh                       byte
		nextArgIndex                 int
		blockStart, blockEnd, padLen int
		fmtLen                       = len(format)
	)

	for blockEnd < fmtLen {
		nextCh = format[blockEnd]
		if nextCh != '%' {
			blockEnd++
			continue
		}

		if blockStart < blockEnd {
			for i := blockStart; i < blockEnd; i++ {
				hal.ActiveTerminal.WriteByte(format[i])
			}
		}

		// Scan til we hit the format character
		padLen = 0
		blockEnd++
	parseFmt:
		for ; blockEnd < fmtLen; blockEnd++ {
			nextCh = format[blockEnd]
			switch {
			case nextCh == '%':
				hal.ActiveTerminal.Write([]byte{'%'})
				break parseFmt
			case nextCh >= '0' && nextCh <= '9':
				padLen = (padLen * 10) + int(nextCh-'0')
				continue
			case nextCh == 'd' || nextCh == 'x' || nextCh == 'o' || nextCh == 's' || nextCh == 't':
				// Run out of args to print
				if nextArgIndex >= len(args) {
					hal.ActiveTerminal.Write(errMissingArg)
					break parseFmt
				}

				switch nextCh {
				case 'o':
					fmtInt(args[nextArgIndex], 8, padLen)
				case 'd':
					fmtInt(args[nextArgIndex], 10, padLen)
				case 'x':
					fmtInt(args[nextArgIndex], 16, padLen)
				case 's':
					fmtString(args[nextArgIndex], padLen)
				case 't':
					fmtBool(args[nextArgIndex])
				}

				nextArgIndex++
				break parseFmt
			}

			// reached end of formatting string without finding a verb
			hal.ActiveTerminal.Write(errNoVerb)
		}
		blockStart, blockEnd = blockEnd+1, blockEnd+1
	}

	if blockStart != blockEnd {
		for i := blockStart; i < blockEnd; i++ {
			hal.ActiveTerminal.WriteByte(format[i])
		}
	}

	// Check for unused args
	for ; nextArgIndex < len(args); nextArgIndex++ {
		hal.ActiveTerminal.Write(errExtraArg)
	}
}

// fmtBool prints a formatted version of boolean value v using hal.ActiveTerminal
// for its output.
func fmtBool(v interface{}) {
	switch bVal := v.(type) {
	case bool:
		switch bVal {
		case true:
			hal.ActiveTerminal.Write(trueValue)
		case false:
			hal.ActiveTerminal.Write(falseValue)
		}
	default:
		hal.ActiveTerminal.Write(errWrongArgType)
		return
	}
}

// fmtString prints a formatted version of string or []byte value v, applying the
// padding specified by padLen. This function uses hal.ActiveTerminal for its
// output.
func fmtString(v interface{}, padLen int) {
	switch castedVal := v.(type) {
	case string:
		fmtRepeat(padding, padLen-len(castedVal))
		for i := 0; i < len(castedVal); i++ {
			hal.ActiveTerminal.WriteByte(castedVal[i])
		}
	case []byte:
		fmtRepeat(padding, padLen-len(castedVal))
		hal.ActiveTerminal.Write(castedVal)
	default:
		hal.ActiveTerminal.Write(errWrongArgType)
	}
}

// fmtRepeat writes count bytes with value ch to the hal.ActiveTerminal.
func fmtRepeat(ch byte, count int) {
	for i := 0; i < count; i++ {
		hal.ActiveTerminal.WriteByte(ch)
	}
}

// fmtInt prints out a formatted version of v in the requested base, applying the
// padding specified by padLen. This function uses hal.ActiveTerminal for its
// output, supports all built-in signed and unsigned integer types and supports
// base 8, 10 and 16 output.
func fmtInt(v interface{}, base, padLen int) {
	var (
		sval             int64
		uval             uint64
		divider          uint64
		remainder        uint64
		buf              [20]byte
		padCh            byte
		left, right, end int
	)

	switch base {
	case 8:
		divider = 8
		padCh = '0'
	case 10:
		divider = 10
		padCh = ' '
	case 16:
		divider = 16
		padCh = '0'
	}

	switch v.(type) {
	case uint8:
		uval = uint64(v.(uint8))
	case uint16:
		uval = uint64(v.(uint16))
	case uint32:
		uval = uint64(v.(uint32))
	case uint64:
		uval = v.(uint64)
	case uintptr:
		uval = uint64(v.(uintptr))
	case int8:
		sval = int64(v.(int8))
	case int16:
		sval = int64(v.(int16))
	case int32:
		sval = int64(v.(int32))
	case int64:
		sval = v.(int64)
	case int:
		sval = int64(v.(int))
	default:
		hal.ActiveTerminal.Write(errWrongArgType)
		return
	}

	// Handle signs
	if sval < 0 {
		uval = uint64(-sval)
	} else if sval > 0 {
		uval = uint64(sval)
	}

	for {
		remainder = uval % divider
		if remainder < 10 {
			buf[right] = byte(remainder) + '0'
		} else {
			// map values from 10 to 15 -> a-f
			buf[right] = byte(remainder-10) + 'a'
		}

		right++

		uval /= divider
		if uval == 0 {
			break
		}
	}

	// Apply padding if required
	for ; right-left < padLen; right++ {
		buf[right] = padCh
	}

	// Apply hex prefix
	if base == 16 {
		buf[right] = 'x'
		buf[right+1] = '0'
		right += 2
	}

	// Apply negative sign to the rightmost blank character (if using enough padding);
	// otherwise append the sign as a new char
	if sval < 0 {
		for end = right - 1; buf[end] == ' '; end-- {
		}

		if end == right-1 {
			right++
		}

		buf[end+1] = '-'
	}

	// Reverse in place
	end = right
	for right = right - 1; left < right; left, right = left+1, right-1 {
		buf[left], buf[right] = buf[right], buf[left]
	}

	hal.ActiveTerminal.Write(buf[0:end])
}
<|endoftext|>


<commit_before>package harness

// This file handles the app code introspection.
// It catalogs the controllers, their methods, and their arguments.

import (
	"go/ast"
	"go/parser"
	"go/scanner"
	"go/token"
	"log"
	"os"
	"github.com/robfig/revel"
	"strings"
	"unicode"
)

type ControllerSpec struct {
	PackageName string
	StructName  string
	ImportPath  string
	MethodSpecs []*MethodSpec

	// Used internally to identify controllers that indirectly embed *rev.Controller.
	embeddedTypes []*embeddedTypeName
}

// This is a description of a call to c.Render(..)
// It documents the argument names used, in order to propagate them to RenderArgs.
type renderCall struct {
	Line  int
	Names []string
}

type MethodSpec struct {
	Name        string        // Name of the method, e.g. "Index"
	Args        []*MethodArg  // Argument descriptors
	RenderCalls []*renderCall // Descriptions of Render() invocations from this Method.
}

type MethodArg struct {
	Name       string // Name of the argument.
	TypeName   string // The name of the type, e.g. "int", "*pkg.UserType"
	ImportPath string // If the arg is of an imported type, this is the import path.
}

type embeddedTypeName struct {
	PackageName, StructName string
}

// Maps a controller simple name (e.g. "Login") to the methods for which it is a
// receiver.
type methodMap map[string][]*MethodSpec

// Parse the app directory and return a list of the controller types found.
// Returns a CompileError if the parsing fails.
func ScanControllers(path string) (specs []*ControllerSpec, compileError *rev.Error) {
	// Parse files within the path.
	var pkgs map[string]*ast.Package
	fset := token.NewFileSet()
	pkgs, err := parser.ParseDir(fset, path, func(f os.FileInfo) bool {
		return !f.IsDir() && !strings.HasPrefix(f.Name(), ".")
	}, 0)
	if err != nil {
		if errList, ok := err.(scanner.ErrorList); ok {
			var pos token.Position = errList[0].Pos
			return nil, &rev.Error{
				SourceType:  ".go source",
				Title:       "Go Compilation Error",
				Path:        pos.Filename,
				Description: errList[0].Msg,
				Line:        pos.Line,
				Column:      pos.Column,
				SourceLines: rev.MustReadLines(pos.Filename),
			}
		}
		ast.Print(nil, err)
		log.Fatalf("Failed to parse dir: %s", err)
	}

	// For each package... (often only "controllers")
	for _, pkg := range pkgs {
		var structSpecs []*ControllerSpec
		methodSpecs := make(methodMap)

		// For each source file in the package...
		for _, file := range pkg.Files {

			// Imports maps the package key to the full import path.
			// e.g. import "sample/app/models" => "models": "sample/app/models"
			imports := map[string]string{}

			// For each declaration in the source file...
			for _, decl := range file.Decls {

				// Match and add both structs and methods
				addImports(imports, decl)
				structSpecs = appendStruct(structSpecs, pkg, decl)
				appendMethod(fset, methodSpecs, decl, pkg.Name, imports)
			}
		}

		// Filter the struct specs to just the ones that embed rev.Controller.
		structSpecs = filterControllers(structSpecs)

		// Add the method specs to them.
		for _, spec := range structSpecs {
			spec.MethodSpecs = methodSpecs[spec.StructName]
		}

		// Add the prepared ControllerSpecs to the list.
		specs = append(specs, structSpecs...)
	}
	return
}

func addImports(imports map[string]string, decl ast.Decl) {
	genDecl, ok := decl.(*ast.GenDecl)
	if !ok {
		return
	}

	if genDecl.Tok != token.IMPORT {
		return
	}

	for _, spec := range genDecl.Specs {
		importSpec := spec.(*ast.ImportSpec)
		quotedPath := importSpec.Path.Value           // e.g. "\"sample/app/models\""
		fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
		key := fullPath
		if lastSlash := strings.LastIndex(fullPath, "/"); lastSlash != -1 {
			key = fullPath[lastSlash+1:]
		}
		imports[key] = fullPath
	}
}

// If this Decl is a struct type definition, it is summarized and added to specs.
// Else, specs is returned unchanged.
func appendStruct(specs []*ControllerSpec, pkg *ast.Package, decl ast.Decl) []*ControllerSpec {
	// Filter out non-Struct type declarations.
	genDecl, ok := decl.(*ast.GenDecl)
	if !ok {
		return specs
	}

	if genDecl.Tok != token.TYPE {
		return specs
	}

	if len(genDecl.Specs) != 1 {
		rev.LOG.Printf("Surprising: Decl does not have 1 Spec: %v", genDecl)
		return specs
	}

	spec := genDecl.Specs[0].(*ast.TypeSpec)
	structType, ok := spec.Type.(*ast.StructType)
	if !ok {
		return specs
	}

	// At this point we know it's a type declaration for a struct.
	// Fill in the rest of the info by diving into the fields.
	// Add it provisionally to the Controller list -- it's later filtered using field info.
	controllerSpec := &ControllerSpec{
		PackageName: pkg.Name,
		StructName:  spec.Name.Name,
		ImportPath:  rev.ImportPath + "/app/" + pkg.Name,
	}

	for _, field := range structType.Fields.List {
		// If field.Names is set, it's not an embedded type.
		if field.Names != nil {
			continue
		}

		// A direct "sub-type" has an ast.Field as either:
		//   Ident { "AppController" }
		//   SelectorExpr { "rev", "Controller" }
		// Additionally, that can be wrapped by StarExprs.
		fieldType := field.Type
		pkgName, typeName := func() (string, string) {
			// Drill through any StarExprs.
			for {
				if starExpr, ok := fieldType.(*ast.StarExpr); ok {
					fieldType = starExpr.X
					continue
				}
				break
			}

			// If the embedded type is in the same package, it's an Ident.
			if ident, ok := fieldType.(*ast.Ident); ok {
				return pkg.Name, ident.Name
			}

			if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
				if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
					return pkgIdent.Name, selectorExpr.Sel.Name
				}
			}

			return "", ""
		}()

		// If a typename wasn't found, skip it.
		if typeName == "" {
			continue
		}

		controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
			PackageName: pkgName,
			StructName:  typeName,
		})
	}

	return append(specs, controllerSpec)
}

// If decl is a Method declaration, it is summarized and added to the array
// underneath its receiver type.
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
func appendMethod(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgName string, imports map[string]string) {
	// Func declaration?
	funcDecl, ok := decl.(*ast.FuncDecl)
	if !ok {
		return
	}

	// Have a receiver?
	if funcDecl.Recv == nil {
		return
	}

	// Is it public?
	if !unicode.IsUpper([]rune(funcDecl.Name.Name)[0]) {
		return
	}

	// Does it return a rev.Result?
	if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
		return
	}
	selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
	if !ok {
		return
	}
	if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || pkgIdent.Name != "rev" {
		return
	}
	if selExpr.Sel.Name != "Result" {
		return
	}

	// Get the receiver type, "dereferencing" it if necessary
	var recvTypeName string
	var recvType ast.Expr = funcDecl.Recv.List[0].Type
	if recvStarType, ok := recvType.(*ast.StarExpr); ok {
		recvTypeName = recvStarType.X.(*ast.Ident).Name
	} else {
		recvTypeName = recvType.(*ast.Ident).Name
	}

	method := &MethodSpec{
		Name: funcDecl.Name.Name,
	}

	// Add a description of the arguments to the method.
	for _, field := range funcDecl.Type.Params.List {
		for _, name := range field.Names {
			typeName := ExprName(field.Type)
			importPath := ""
			dotIndex := strings.Index(typeName, ".")
			isExported := unicode.IsUpper([]rune(typeName)[0])
			if dotIndex == -1 && isExported {
				typeName = pkgName + "." + typeName
			} else if dotIndex != -1 {
				// The type comes from may come from an imported package.
				argPkgName := typeName[:dotIndex]
				if importPath, ok = imports[argPkgName]; !ok {
					log.Println("Failed to find import for arg of type:", typeName)
				}
			}

			method.Args = append(method.Args, &MethodArg{
				Name:       name.Name,
				TypeName:   typeName,
				ImportPath: importPath,
			})
		}
	}

	// Add a description of the calls to Render from the method.
	// Inspect every node (e.g. always return true).
	method.RenderCalls = []*renderCall{}
	ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
		// Is it a function call?
		callExpr, ok := node.(*ast.CallExpr)
		if !ok {
			return true
		}

		// Is it calling (*Controller).Render?
		selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
		if !ok {
			return true
		}

		// The type of the receiver is not easily available, so just store every
		// call to any method called Render.
		if selExpr.Sel.Name != "Render" {
			return true
		}

		// Add this call's args to the renderArgs.
		pos := fset.Position(callExpr.Rparen)
		renderCall := &renderCall{
			Line:  pos.Line,
			Names: []string{},
		}
		for _, arg := range callExpr.Args {
			argIdent, ok := arg.(*ast.Ident)
			if !ok {
				log.Println("Unnamed argument to Render call:", pos)
				continue
			}
			renderCall.Names = append(renderCall.Names, argIdent.Name)
		}
		method.RenderCalls = append(method.RenderCalls, renderCall)
		return true
	})

	mm[recvTypeName] = append(mm[recvTypeName], method)
}

func (s *ControllerSpec) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

func (s *embeddedTypeName) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

// Remove any types that do not (directly or indirectly) embed *rev.Controller.
func filterControllers(specs []*ControllerSpec) (filtered []*ControllerSpec) {
	// Do a search in the "embedded type graph", starting with rev.Controller.
	nodeQueue := []string{"rev.Controller"}
	for len(nodeQueue) > 0 {
		controllerSimpleName := nodeQueue[0]
		nodeQueue = nodeQueue[1:]
		for _, spec := range specs {
			if rev.ContainsString(nodeQueue, spec.SimpleName()) {
				continue // Already added
			}

			// Look through the embedded types to see if the current type is among them.
			for _, embeddedType := range spec.embeddedTypes {

				// If so, add this type's simple name to the nodeQueue, and its spec to
				// the filtered list.
				if controllerSimpleName == embeddedType.SimpleName() {
					nodeQueue = append(nodeQueue, spec.SimpleName())
					filtered = append(filtered, spec)
					break
				}
			}
		}
	}
	return
}

// This returns the syntactic expression for referencing this type in Go.
// One complexity is that package-local types have to be fully-qualified.
// For example, if the type is "Hello", then it really means "pkg.Hello".
func ExprName(expr ast.Expr) string {
	switch t := expr.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.SelectorExpr:
		return ExprName(t.X) + "." + ExprName(t.Sel)
	case *ast.StarExpr:
		return "*" + ExprName(t.X)
	default:
		ast.Print(nil, expr)
		panic("Failed to generate name for field.")
	}
	return ""
}
<commit_msg>App source processing: Bug fix in finding the import path for pointer types.<commit_after>package harness

// This file handles the app code introspection.
// It catalogs the controllers, their methods, and their arguments.

import (
	"github.com/robfig/revel"
	"go/ast"
	"go/parser"
	"go/scanner"
	"go/token"
	"log"
	"os"
	"strings"
	"unicode"
)

type ControllerSpec struct {
	PackageName string
	StructName  string
	ImportPath  string
	MethodSpecs []*MethodSpec

	// Used internally to identify controllers that indirectly embed *rev.Controller.
	embeddedTypes []*embeddedTypeName
}

// This is a description of a call to c.Render(..)
// It documents the argument names used, in order to propagate them to RenderArgs.
type renderCall struct {
	Line  int
	Names []string
}

type MethodSpec struct {
	Name        string        // Name of the method, e.g. "Index"
	Args        []*MethodArg  // Argument descriptors
	RenderCalls []*renderCall // Descriptions of Render() invocations from this Method.
}

type MethodArg struct {
	Name       string // Name of the argument.
	TypeName   string // The name of the type, e.g. "int", "*pkg.UserType"
	ImportPath string // If the arg is of an imported type, this is the import path.
}

type embeddedTypeName struct {
	PackageName, StructName string
}

// Maps a controller simple name (e.g. "Login") to the methods for which it is a
// receiver.
type methodMap map[string][]*MethodSpec

// Parse the app directory and return a list of the controller types found.
// Returns a CompileError if the parsing fails.
func ScanControllers(path string) (specs []*ControllerSpec, compileError *rev.Error) {
	// Parse files within the path.
	var pkgs map[string]*ast.Package
	fset := token.NewFileSet()
	pkgs, err := parser.ParseDir(fset, path, func(f os.FileInfo) bool {
		return !f.IsDir() && !strings.HasPrefix(f.Name(), ".")
	}, 0)
	if err != nil {
		if errList, ok := err.(scanner.ErrorList); ok {
			var pos token.Position = errList[0].Pos
			return nil, &rev.Error{
				SourceType:  ".go source",
				Title:       "Go Compilation Error",
				Path:        pos.Filename,
				Description: errList[0].Msg,
				Line:        pos.Line,
				Column:      pos.Column,
				SourceLines: rev.MustReadLines(pos.Filename),
			}
		}
		ast.Print(nil, err)
		log.Fatalf("Failed to parse dir: %s", err)
	}

	// For each package... (often only "controllers")
	for _, pkg := range pkgs {
		var structSpecs []*ControllerSpec
		methodSpecs := make(methodMap)

		// For each source file in the package...
		for _, file := range pkg.Files {

			// Imports maps the package key to the full import path.
			// e.g. import "sample/app/models" => "models": "sample/app/models"
			imports := map[string]string{}

			// For each declaration in the source file...
			for _, decl := range file.Decls {

				// Match and add both structs and methods
				addImports(imports, decl)
				structSpecs = appendStruct(structSpecs, pkg, decl)
				appendMethod(fset, methodSpecs, decl, pkg.Name, imports)
			}
		}

		// Filter the struct specs to just the ones that embed rev.Controller.
		structSpecs = filterControllers(structSpecs)

		// Add the method specs to them.
		for _, spec := range structSpecs {
			spec.MethodSpecs = methodSpecs[spec.StructName]
		}

		// Add the prepared ControllerSpecs to the list.
		specs = append(specs, structSpecs...)
	}
	return
}

func addImports(imports map[string]string, decl ast.Decl) {
	genDecl, ok := decl.(*ast.GenDecl)
	if !ok {
		return
	}

	if genDecl.Tok != token.IMPORT {
		return
	}

	for _, spec := range genDecl.Specs {
		importSpec := spec.(*ast.ImportSpec)
		quotedPath := importSpec.Path.Value           // e.g. "\"sample/app/models\""
		fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
		key := fullPath
		if lastSlash := strings.LastIndex(fullPath, "/"); lastSlash != -1 {
			key = fullPath[lastSlash+1:]
		}
		imports[key] = fullPath
	}
}

// If this Decl is a struct type definition, it is summarized and added to specs.
// Else, specs is returned unchanged.
func appendStruct(specs []*ControllerSpec, pkg *ast.Package, decl ast.Decl) []*ControllerSpec {
	// Filter out non-Struct type declarations.
	genDecl, ok := decl.(*ast.GenDecl)
	if !ok {
		return specs
	}

	if genDecl.Tok != token.TYPE {
		return specs
	}

	if len(genDecl.Specs) != 1 {
		rev.LOG.Printf("Surprising: Decl does not have 1 Spec: %v", genDecl)
		return specs
	}

	spec := genDecl.Specs[0].(*ast.TypeSpec)
	structType, ok := spec.Type.(*ast.StructType)
	if !ok {
		return specs
	}

	// At this point we know it's a type declaration for a struct.
	// Fill in the rest of the info by diving into the fields.
	// Add it provisionally to the Controller list -- it's later filtered using field info.
	controllerSpec := &ControllerSpec{
		PackageName: pkg.Name,
		StructName:  spec.Name.Name,
		ImportPath:  rev.ImportPath + "/app/" + pkg.Name,
	}

	for _, field := range structType.Fields.List {
		// If field.Names is set, it's not an embedded type.
		if field.Names != nil {
			continue
		}

		// A direct "sub-type" has an ast.Field as either:
		//   Ident { "AppController" }
		//   SelectorExpr { "rev", "Controller" }
		// Additionally, that can be wrapped by StarExprs.
		fieldType := field.Type
		pkgName, typeName := func() (string, string) {
			// Drill through any StarExprs.
			for {
				if starExpr, ok := fieldType.(*ast.StarExpr); ok {
					fieldType = starExpr.X
					continue
				}
				break
			}

			// If the embedded type is in the same package, it's an Ident.
			if ident, ok := fieldType.(*ast.Ident); ok {
				return pkg.Name, ident.Name
			}

			if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
				if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
					return pkgIdent.Name, selectorExpr.Sel.Name
				}
			}

			return "", ""
		}()

		// If a typename wasn't found, skip it.
		if typeName == "" {
			continue
		}

		controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
			PackageName: pkgName,
			StructName:  typeName,
		})
	}

	return append(specs, controllerSpec)
}

// If decl is a Method declaration, it is summarized and added to the array
// underneath its receiver type.
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
func appendMethod(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgName string, imports map[string]string) {
	// Func declaration?
	funcDecl, ok := decl.(*ast.FuncDecl)
	if !ok {
		return
	}

	// Have a receiver?
	if funcDecl.Recv == nil {
		return
	}

	// Is it public?
	if !unicode.IsUpper([]rune(funcDecl.Name.Name)[0]) {
		return
	}

	// Does it return a rev.Result?
	if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
		return
	}
	selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
	if !ok {
		return
	}
	if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || pkgIdent.Name != "rev" {
		return
	}
	if selExpr.Sel.Name != "Result" {
		return
	}

	// Get the receiver type, "dereferencing" it if necessary
	var recvTypeName string
	var recvType ast.Expr = funcDecl.Recv.List[0].Type
	if recvStarType, ok := recvType.(*ast.StarExpr); ok {
		recvTypeName = recvStarType.X.(*ast.Ident).Name
	} else {
		recvTypeName = recvType.(*ast.Ident).Name
	}

	method := &MethodSpec{
		Name: funcDecl.Name.Name,
	}

	// Add a description of the arguments to the method.
	for _, field := range funcDecl.Type.Params.List {
		for _, name := range field.Names {
			typeName := ExprName(field.Type)

			// Figure out the Import Path for this field, if any.
			importPath := ""
			baseTypeName := strings.TrimLeft(typeName, "*")
			dotIndex := strings.Index(baseTypeName, ".")
			isExported := unicode.IsUpper([]rune(baseTypeName)[0])
			if dotIndex == -1 && isExported {
				// Fully-qualify types defined in that package.
				// (Need to add back the stars that we trimmed, too)
				typeName = pkgName + "." + baseTypeName
			} else if dotIndex != -1 {
				// The type comes from an imported package.
				argPkgName := baseTypeName[:dotIndex]
				if importPath, ok = imports[argPkgName]; !ok {
					log.Println("Failed to find import for arg of type:", typeName)
				}
			}

			method.Args = append(method.Args, &MethodArg{
				Name:       name.Name,
				TypeName:   typeName,
				ImportPath: importPath,
			})
		}
	}

	// Add a description of the calls to Render from the method.
	// Inspect every node (e.g. always return true).
	method.RenderCalls = []*renderCall{}
	ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
		// Is it a function call?
		callExpr, ok := node.(*ast.CallExpr)
		if !ok {
			return true
		}

		// Is it calling (*Controller).Render?
		selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
		if !ok {
			return true
		}

		// The type of the receiver is not easily available, so just store every
		// call to any method called Render.
		if selExpr.Sel.Name != "Render" {
			return true
		}

		// Add this call's args to the renderArgs.
		pos := fset.Position(callExpr.Rparen)
		renderCall := &renderCall{
			Line:  pos.Line,
			Names: []string{},
		}
		for _, arg := range callExpr.Args {
			argIdent, ok := arg.(*ast.Ident)
			if !ok {
				log.Println("Unnamed argument to Render call:", pos)
				continue
			}
			renderCall.Names = append(renderCall.Names, argIdent.Name)
		}
		method.RenderCalls = append(method.RenderCalls, renderCall)
		return true
	})

	mm[recvTypeName] = append(mm[recvTypeName], method)
}

func (s *ControllerSpec) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

func (s *embeddedTypeName) SimpleName() string {
	return s.PackageName + "." + s.StructName
}

// Remove any types that do not (directly or indirectly) embed *rev.Controller.
func filterControllers(specs []*ControllerSpec) (filtered []*ControllerSpec) {
	// Do a search in the "embedded type graph", starting with rev.Controller.
	nodeQueue := []string{"rev.Controller"}
	for len(nodeQueue) > 0 {
		controllerSimpleName := nodeQueue[0]
		nodeQueue = nodeQueue[1:]
		for _, spec := range specs {
			if rev.ContainsString(nodeQueue, spec.SimpleName()) {
				continue // Already added
			}

			// Look through the embedded types to see if the current type is among them.
			for _, embeddedType := range spec.embeddedTypes {

				// If so, add this type's simple name to the nodeQueue, and its spec to
				// the filtered list.
				if controllerSimpleName == embeddedType.SimpleName() {
					nodeQueue = append(nodeQueue, spec.SimpleName())
					filtered = append(filtered, spec)
					break
				}
			}
		}
	}
	return
}

// This returns the syntactic expression for referencing this type in Go.
// One complexity is that package-local types have to be fully-qualified.
// For example, if the type is "Hello", then it really means "pkg.Hello".
func ExprName(expr ast.Expr) string {
	switch t := expr.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.SelectorExpr:
		return ExprName(t.X) + "." + ExprName(t.Sel)
	case *ast.StarExpr:
		return "*" + ExprName(t.X)
	default:
		ast.Print(nil, expr)
		panic("Failed to generate name for field.")
	}
	return ""
}
<|endoftext|>


<commit_before>package vm

import (
	"fmt"
	"github.com/goby-lang/goby/compiler/bytecode"
	"github.com/goby-lang/goby/vm/classes"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// Version stores current Goby version
const Version = "0.1.3"

// These are the enums for marking parser's mode, which decides whether it should pop unused values.
const (
	NormalMode int = iota
	REPLMode
	TestMode
)

type isIndexTable struct {
	Data map[string]int
}

func newISIndexTable() *isIndexTable {
	return &isIndexTable{Data: make(map[string]int)}
}

type isTable map[string][]*instructionSet

type filename = string

var standardLibraries = map[string]func(*VM){
	"net/http":           initHTTPClass,
	"net/simple_server":  initSimpleServerClass,
	"uri":                initURIClass,
	"db":                 initDBClass,
	"plugin":             initPluginClass,
	"json":               initJSONClass,
	"concurrent/array":   initConcurrentArrayClass,
	"concurrent/hash":    initConcurrentHashClass,
	"concurrent/rw_lock": initConcurrentRWLockClass,
}

// VM represents a stack based virtual machine.
type VM struct {
	mainObj     *RObject
	mainThread  *thread
	objectClass *RClass
	// a map holds different types of instruction set tables
	isTables map[setType]isTable
	// method instruction set table
	methodISIndexTables map[filename]*isIndexTable
	// class instruction set table
	classISIndexTables map[filename]*isIndexTable
	// block instruction set table
	blockTables map[filename]map[string]*instructionSet
	// fileDir indicates executed file's directory
	fileDir string
	// args are command line arguments
	args []string
	// projectRoot is goby root's absolute path, which is $GOROOT/src/github.com/goby-lang/goby
	projectRoot string

	channelObjectMap *objectMap

	mode int

	libFiles []string
}

// New initializes a vm to initialize state and returns it.
func New(fileDir string, args []string) (vm *VM, e error) {
	vm = &VM{args: args}
	vm.mainThread = vm.newThread()

	vm.methodISIndexTables = map[filename]*isIndexTable{
		fileDir: newISIndexTable(),
	}
	vm.classISIndexTables = map[filename]*isIndexTable{
		fileDir: newISIndexTable(),
	}
	vm.blockTables = make(map[filename]map[string]*instructionSet)
	vm.isTables = map[setType]isTable{
		bytecode.MethodDef: make(isTable),
		bytecode.ClassDef:  make(isTable),
	}
	vm.fileDir = fileDir

	gobyRoot := os.Getenv("GOBY_ROOT")

	if len(gobyRoot) == 0 {
		vm.projectRoot = fmt.Sprintf("/usr/local/Cellar/goby/%s", Version)

		_, err := os.Stat(vm.projectRoot)

		if err != nil {
			path, _ := filepath.Abs("$GOPATH/src/github.com/goby-lang/goby")
			_, err = os.Stat(path)

			if err != nil {
				e = fmt.Errorf("You haven't set $GOBY_ROOT properly")
				return nil, e
			}

			vm.projectRoot = path
		}
	} else {
		vm.projectRoot = gobyRoot
	}

	vm.initConstants()
	vm.mainObj = vm.initMainObj()
	vm.channelObjectMap = &objectMap{store: &sync.Map{}}

	for _, fn := range vm.libFiles {
		vm.mainThread.execGobyLib(fn)
	}

	return
}

func (vm *VM) newThread() *thread {
	s := &stack{RWMutex: new(sync.RWMutex)}
	cfs := &callFrameStack{callFrames: []callFrame{}}
	t := &thread{stack: s, callFrameStack: cfs, sp: 0, cfp: 0}
	s.thread = t
	cfs.thread = t
	t.vm = vm
	return t
}

// ExecInstructions accepts a sequence of bytecodes and use vm to evaluate them.
func (vm *VM) ExecInstructions(sets []*bytecode.InstructionSet, fn string) {
	translator := newInstructionTranslator(fn)
	translator.vm = vm
	translator.transferInstructionSets(sets)

	// Keep instruction set table updated after parsed new files.
	// TODO: Find more efficient way to do this.
	for setType, table := range translator.setTable {
		for name, is := range table {
			vm.isTables[setType][name] = is
		}
	}

	vm.blockTables[translator.filename] = translator.blockTable
	vm.SetClassISIndexTable(translator.filename)
	vm.SetMethodISIndexTable(translator.filename)

	cf := newNormalCallFrame(translator.program, translator.filename, 1)
	cf.self = vm.mainObj
	vm.mainThread.callFrameStack.push(cf)

	defer func() {
		err, ok := recover().(*Error)

		if ok && vm.mode == NormalMode {
			fmt.Println(err.Message())
		}
	}()

	vm.mainThread.startFromTopFrame()
}

// SetClassISIndexTable adds new instruction set's index table to vm.classISIndexTables
func (vm *VM) SetClassISIndexTable(fn filename) {
	vm.classISIndexTables[fn] = newISIndexTable()
}

// SetMethodISIndexTable adds new instruction set's index table to vm.methodISIndexTables
func (vm *VM) SetMethodISIndexTable(fn filename) {
	vm.methodISIndexTables[fn] = newISIndexTable()
}

// main object singleton methods -----------------------------------------------------
func builtinMainObjSingletonMethods() []*BuiltinMethodObject {
	return []*BuiltinMethodObject{
		{
			Name: "to_s",
			Fn: func(receiver Object, sourceLine int) builtinMethodBody {
				return func(thread *thread, objects []Object, frame *normalCallFrame) Object {
					return thread.vm.initStringObject("main")
				}
			},
		},
	}
}

func (vm *VM) initMainObj() *RObject {
	obj := vm.objectClass.initializeInstance()
	singletonClass := vm.initializeClass(fmt.Sprintf("#<Class:%s>", obj.toString()), false)
	singletonClass.Methods.set("include", vm.topLevelClass(classes.ClassClass).lookupMethod("include"))
	singletonClass.setBuiltinMethods(builtinMainObjSingletonMethods(), false)
	obj.singletonClass = singletonClass

	return obj
}

func (vm *VM) initConstants() {
	// Init Class and Object
	cClass := initClassClass()
	vm.objectClass = initObjectClass(cClass)
	vm.topLevelClass(classes.ObjectClass).setClassConstant(cClass)

	// Init builtin classes
	builtinClasses := []*RClass{
		vm.initIntegerClass(),
		vm.initFloatClass(),
		vm.initStringClass(),
		vm.initBoolClass(),
		vm.initNullClass(),
		vm.initArrayClass(),
		vm.initHashClass(),
		vm.initRangeClass(),
		vm.initMethodClass(),
		vm.initChannelClass(),
		vm.initGoClass(),
		vm.initFileClass(),
		vm.initRegexpClass(),
		vm.initMatchDataClass(),
		vm.initGoMapClass(),
		vm.initDecimalClass(),
	}

	// Init error classes
	vm.initErrorClasses()

	for _, c := range builtinClasses {
		vm.objectClass.setClassConstant(c)
	}

	// Init ARGV
	args := []Object{}

	for _, arg := range vm.args {
		args = append(args, vm.initStringObject(arg))
	}

	vm.objectClass.constants["ARGV"] = &Pointer{Target: vm.initArrayObject(args)}

	// Init ENV
	envs := map[string]Object{}

	for _, e := range os.Environ() {
		pair := strings.Split(e, "=")
		envs[pair[0]] = vm.initStringObject(pair[1])
	}

	vm.objectClass.constants["ENV"] = &Pointer{Target: vm.initHashObject(envs)}
	vm.objectClass.constants["STDOUT"] = &Pointer{Target: vm.initFileObject(os.Stdout)}
	vm.objectClass.constants["STDERR"] = &Pointer{Target: vm.initFileObject(os.Stderr)}
	vm.objectClass.constants["STDIN"] = &Pointer{Target: vm.initFileObject(os.Stdin)}
}

func (vm *VM) topLevelClass(cn string) *RClass {
	objClass := vm.objectClass

	if cn == classes.ObjectClass {
		return objClass
	}

	return objClass.constants[cn].Target.(*RClass)
}

func (vm *VM) currentFilePath() string {
	frame := vm.mainThread.callFrameStack.top()
	return frame.FileName()
}

// loadConstant makes sure we don't create a class twice.
func (vm *VM) loadConstant(name string, isModule bool) *RClass {
	var c *RClass
	var ptr *Pointer

	ptr = vm.objectClass.constants[name]

	if ptr == nil {
		c = vm.initializeClass(name, isModule)
		vm.objectClass.setClassConstant(c)
	} else {
		c = ptr.Target.(*RClass)
	}

	return c
}

func (vm *VM) lookupConstant(cf callFrame, constName string) (constant *Pointer) {
	var namespace *RClass
	var hasNamespace bool

	top := vm.mainThread.stack.top()

	if top == nil {
		hasNamespace = false
	} else {
		namespace, hasNamespace = top.Target.(*RClass)
	}

	if hasNamespace {
		constant = namespace.lookupConstantInAllScope(constName)

		if constant != nil {
			return
		}
	}

	constant = cf.lookupConstant(constName)

	if constant == nil {
		constant = vm.objectClass.constants[constName]
	}

	if constName == classes.ObjectClass {
		constant = &Pointer{Target: vm.objectClass}
	}

	return
}
<commit_msg>Bump to v0.1.6<commit_after>package vm

import (
	"fmt"
	"github.com/goby-lang/goby/compiler/bytecode"
	"github.com/goby-lang/goby/vm/classes"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// Version stores current Goby version
const Version = "0.1.6"

// These are the enums for marking parser's mode, which decides whether it should pop unused values.
const (
	NormalMode int = iota
	REPLMode
	TestMode
)

type isIndexTable struct {
	Data map[string]int
}

func newISIndexTable() *isIndexTable {
	return &isIndexTable{Data: make(map[string]int)}
}

type isTable map[string][]*instructionSet

type filename = string

var standardLibraries = map[string]func(*VM){
	"net/http":           initHTTPClass,
	"net/simple_server":  initSimpleServerClass,
	"uri":                initURIClass,
	"db":                 initDBClass,
	"plugin":             initPluginClass,
	"json":               initJSONClass,
	"concurrent/array":   initConcurrentArrayClass,
	"concurrent/hash":    initConcurrentHashClass,
	"concurrent/rw_lock": initConcurrentRWLockClass,
}

// VM represents a stack based virtual machine.
type VM struct {
	mainObj     *RObject
	mainThread  *thread
	objectClass *RClass
	// a map holds different types of instruction set tables
	isTables map[setType]isTable
	// method instruction set table
	methodISIndexTables map[filename]*isIndexTable
	// class instruction set table
	classISIndexTables map[filename]*isIndexTable
	// block instruction set table
	blockTables map[filename]map[string]*instructionSet
	// fileDir indicates executed file's directory
	fileDir string
	// args are command line arguments
	args []string
	// projectRoot is goby root's absolute path, which is $GOROOT/src/github.com/goby-lang/goby
	projectRoot string

	channelObjectMap *objectMap

	mode int

	libFiles []string
}

// New initializes a vm to initialize state and returns it.
func New(fileDir string, args []string) (vm *VM, e error) {
	vm = &VM{args: args}
	vm.mainThread = vm.newThread()

	vm.methodISIndexTables = map[filename]*isIndexTable{
		fileDir: newISIndexTable(),
	}
	vm.classISIndexTables = map[filename]*isIndexTable{
		fileDir: newISIndexTable(),
	}
	vm.blockTables = make(map[filename]map[string]*instructionSet)
	vm.isTables = map[setType]isTable{
		bytecode.MethodDef: make(isTable),
		bytecode.ClassDef:  make(isTable),
	}
	vm.fileDir = fileDir

	gobyRoot := os.Getenv("GOBY_ROOT")

	if len(gobyRoot) == 0 {
		vm.projectRoot = fmt.Sprintf("/usr/local/Cellar/goby/%s", Version)

		_, err := os.Stat(vm.projectRoot)

		if err != nil {
			path, _ := filepath.Abs("$GOPATH/src/github.com/goby-lang/goby")
			_, err = os.Stat(path)

			if err != nil {
				e = fmt.Errorf("You haven't set $GOBY_ROOT properly")
				return nil, e
			}

			vm.projectRoot = path
		}
	} else {
		vm.projectRoot = gobyRoot
	}

	vm.initConstants()
	vm.mainObj = vm.initMainObj()
	vm.channelObjectMap = &objectMap{store: &sync.Map{}}

	for _, fn := range vm.libFiles {
		vm.mainThread.execGobyLib(fn)
	}

	return
}

func (vm *VM) newThread() *thread {
	s := &stack{RWMutex: new(sync.RWMutex)}
	cfs := &callFrameStack{callFrames: []callFrame{}}
	t := &thread{stack: s, callFrameStack: cfs, sp: 0, cfp: 0}
	s.thread = t
	cfs.thread = t
	t.vm = vm
	return t
}

// ExecInstructions accepts a sequence of bytecodes and use vm to evaluate them.
func (vm *VM) ExecInstructions(sets []*bytecode.InstructionSet, fn string) {
	translator := newInstructionTranslator(fn)
	translator.vm = vm
	translator.transferInstructionSets(sets)

	// Keep instruction set table updated after parsed new files.
	// TODO: Find more efficient way to do this.
	for setType, table := range translator.setTable {
		for name, is := range table {
			vm.isTables[setType][name] = is
		}
	}

	vm.blockTables[translator.filename] = translator.blockTable
	vm.SetClassISIndexTable(translator.filename)
	vm.SetMethodISIndexTable(translator.filename)

	cf := newNormalCallFrame(translator.program, translator.filename, 1)
	cf.self = vm.mainObj
	vm.mainThread.callFrameStack.push(cf)

	defer func() {
		err, ok := recover().(*Error)

		if ok && vm.mode == NormalMode {
			fmt.Println(err.Message())
		}
	}()

	vm.mainThread.startFromTopFrame()
}

// SetClassISIndexTable adds new instruction set's index table to vm.classISIndexTables
func (vm *VM) SetClassISIndexTable(fn filename) {
	vm.classISIndexTables[fn] = newISIndexTable()
}

// SetMethodISIndexTable adds new instruction set's index table to vm.methodISIndexTables
func (vm *VM) SetMethodISIndexTable(fn filename) {
	vm.methodISIndexTables[fn] = newISIndexTable()
}

// main object singleton methods -----------------------------------------------------
func builtinMainObjSingletonMethods() []*BuiltinMethodObject {
	return []*BuiltinMethodObject{
		{
			Name: "to_s",
			Fn: func(receiver Object, sourceLine int) builtinMethodBody {
				return func(thread *thread, objects []Object, frame *normalCallFrame) Object {
					return thread.vm.initStringObject("main")
				}
			},
		},
	}
}

func (vm *VM) initMainObj() *RObject {
	obj := vm.objectClass.initializeInstance()
	singletonClass := vm.initializeClass(fmt.Sprintf("#<Class:%s>", obj.toString()), false)
	singletonClass.Methods.set("include", vm.topLevelClass(classes.ClassClass).lookupMethod("include"))
	singletonClass.setBuiltinMethods(builtinMainObjSingletonMethods(), false)
	obj.singletonClass = singletonClass

	return obj
}

func (vm *VM) initConstants() {
	// Init Class and Object
	cClass := initClassClass()
	vm.objectClass = initObjectClass(cClass)
	vm.topLevelClass(classes.ObjectClass).setClassConstant(cClass)

	// Init builtin classes
	builtinClasses := []*RClass{
		vm.initIntegerClass(),
		vm.initFloatClass(),
		vm.initStringClass(),
		vm.initBoolClass(),
		vm.initNullClass(),
		vm.initArrayClass(),
		vm.initHashClass(),
		vm.initRangeClass(),
		vm.initMethodClass(),
		vm.initChannelClass(),
		vm.initGoClass(),
		vm.initFileClass(),
		vm.initRegexpClass(),
		vm.initMatchDataClass(),
		vm.initGoMapClass(),
		vm.initDecimalClass(),
	}

	// Init error classes
	vm.initErrorClasses()

	for _, c := range builtinClasses {
		vm.objectClass.setClassConstant(c)
	}

	// Init ARGV
	args := []Object{}

	for _, arg := range vm.args {
		args = append(args, vm.initStringObject(arg))
	}

	vm.objectClass.constants["ARGV"] = &Pointer{Target: vm.initArrayObject(args)}

	// Init ENV
	envs := map[string]Object{}

	for _, e := range os.Environ() {
		pair := strings.Split(e, "=")
		envs[pair[0]] = vm.initStringObject(pair[1])
	}

	vm.objectClass.constants["ENV"] = &Pointer{Target: vm.initHashObject(envs)}
	vm.objectClass.constants["STDOUT"] = &Pointer{Target: vm.initFileObject(os.Stdout)}
	vm.objectClass.constants["STDERR"] = &Pointer{Target: vm.initFileObject(os.Stderr)}
	vm.objectClass.constants["STDIN"] = &Pointer{Target: vm.initFileObject(os.Stdin)}
}

func (vm *VM) topLevelClass(cn string) *RClass {
	objClass := vm.objectClass

	if cn == classes.ObjectClass {
		return objClass
	}

	return objClass.constants[cn].Target.(*RClass)
}

func (vm *VM) currentFilePath() string {
	frame := vm.mainThread.callFrameStack.top()
	return frame.FileName()
}

// loadConstant makes sure we don't create a class twice.
func (vm *VM) loadConstant(name string, isModule bool) *RClass {
	var c *RClass
	var ptr *Pointer

	ptr = vm.objectClass.constants[name]

	if ptr == nil {
		c = vm.initializeClass(name, isModule)
		vm.objectClass.setClassConstant(c)
	} else {
		c = ptr.Target.(*RClass)
	}

	return c
}

func (vm *VM) lookupConstant(cf callFrame, constName string) (constant *Pointer) {
	var namespace *RClass
	var hasNamespace bool

	top := vm.mainThread.stack.top()

	if top == nil {
		hasNamespace = false
	} else {
		namespace, hasNamespace = top.Target.(*RClass)
	}

	if hasNamespace {
		constant = namespace.lookupConstantInAllScope(constName)

		if constant != nil {
			return
		}
	}

	constant = cf.lookupConstant(constName)

	if constant == nil {
		constant = vm.objectClass.constants[constName]
	}

	if constName == classes.ObjectClass {
		constant = &Pointer{Target: vm.objectClass}
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"

	"github.com/asuleymanov/golos-go"
)

var (
	voter = ""
	key   = ""
)

func main() {
	cls, err := client.NewClient([]string{"wss://api.golos.cf", "wss://ws.golos.io"}, "work")
	if err != nil {
		log.Fatalln("Error:", err)
	}

	defer cls.Close()

	cls.SetKeys(&client.Keys{PKey: []string{key}})

	if err := run(cls); err != nil {
		log.Fatalln("Error:", err)
	}
}

func run(cls *client.Client) (err error) {
	flag.Parse()
	// Process args.
	args := flag.Args()

	if len(args) != 2 {
		return errors.New("2 arguments required")
	}
	author, permlink := args[0], args[1]

	fmt.Println(cls.Vote(voter, author, permlink, 10000))

	return nil
}
<commit_msg>Update main.go<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"

	"github.com/asuleymanov/golos-go"
)

var (
	voter = ""
	key   = ""
)

func main() {
	cls, err := client.NewClient([]string{"wss://api.golos.cf", "wss://ws.golos.io"})
	if err != nil {
		log.Fatalln("Error:", err)
	}

	defer cls.Close()

	cls.SetKeys(&client.Keys{PKey: []string{key}})

	if err := run(cls); err != nil {
		log.Fatalln("Error:", err)
	}
}

func run(cls *client.Client) (err error) {
	flag.Parse()
	// Process args.
	args := flag.Args()

	if len(args) != 2 {
		return errors.New("2 arguments required")
	}
	author, permlink := args[0], args[1]

	fmt.Println(cls.Vote(voter, author, permlink, 10000))

	return nil
}
<|endoftext|>


<commit_before>package store

import (
	raftstore "github.com/Dataman-Cloud/swan/src/manager/raft/store"
	"github.com/Dataman-Cloud/swan/src/manager/raft/types"

	"github.com/boltdb/bolt"
	"golang.org/x/net/context"
)

// To update an application version we need the follow steps in one transaction
// 1. find the old app info from database.
// 2. set new version's pervious versionId to the old version's id
// 3. push thie old version to version history
// 4. store the new version in app data
// 5. put all actions in one storeActions to propose data.
func (s *FrameworkStore) UpdateVersion(ctx context.Context, appId string, version *types.Version, cb func()) error {
	app, err := s.GetApp(appId)
	if err != nil {
		return err
	}

	if app == nil {
		return ErrAppNotFound
	}

	var storeActions []*types.StoreAction
	updateVersionAction := &types.StoreAction{
		Action: types.StoreActionKindCreate,
		Target: &types.StoreAction_Version{app.Version},
	}
	storeActions = append(storeActions, updateVersionAction)

	version.PerviousVersionID = app.Version.ID
	app.Version = version
	updateAppAction := &types.StoreAction{
		Action: types.StoreActionKindUpdate,
		Target: &types.StoreAction_Application{app},
	}
	storeActions = append(storeActions, updateAppAction)

	return s.RaftNode.ProposeValue(ctx, storeActions, cb)
}

func (s *FrameworkStore) GetVersion(appId, versionId string) (*types.Version, error) {
	var version *types.Version

	if err := s.BoltbDb.View(func(tx *bolt.Tx) error {
		return raftstore.WithVersionBucket(tx, appId, versionId, func(bkt *bolt.Bucket) error {
			p := bkt.Get(raftstore.BucketKeyData)
			return version.Unmarshal(p)

		})
	}); err != nil {
		return nil, err
	}

	return version, nil
}

// retuns app history versions
func (s *FrameworkStore) ListVersions(appId string) ([]*types.Version, error) {
	var versions []*types.Version

	if err := s.BoltbDb.View(func(tx *bolt.Tx) error {
		bkt := raftstore.GetVersionsBucket(tx, appId)
		if bkt == nil {
			versions = []*types.Version{}
			return nil
		}

		return bkt.ForEach(func(k, v []byte) error {
			versionsBkt := raftstore.GetVersionBucket(tx, appId, string(k))
			if versionsBkt == nil {
				return nil
			}

			version := &types.Version{}
			p := versionsBkt.Get(raftstore.BucketKeyData)
			if err := version.Unmarshal(p); err != nil {
				return err
			}

			versions = append(versions, version)
			return nil
		})
	}); err != nil {
		return nil, err
	}

	return versions, nil
}
<commit_msg>fix bug task version was empty<commit_after>package store

import (
	raftstore "github.com/Dataman-Cloud/swan/src/manager/raft/store"
	"github.com/Dataman-Cloud/swan/src/manager/raft/types"

	"github.com/boltdb/bolt"
	"golang.org/x/net/context"
)

// To update an application version we need the follow steps in one transaction
// 1. find the old app info from database.
// 2. set new version's pervious versionId to the old version's id
// 3. push thie old version to version history
// 4. store the new version in app data
// 5. put all actions in one storeActions to propose data.
func (s *FrameworkStore) UpdateVersion(ctx context.Context, appId string, version *types.Version, cb func()) error {
	app, err := s.GetApp(appId)
	if err != nil {
		return err
	}

	if app == nil {
		return ErrAppNotFound
	}

	var storeActions []*types.StoreAction
	updateVersionAction := &types.StoreAction{
		Action: types.StoreActionKindCreate,
		Target: &types.StoreAction_Version{app.Version},
	}
	storeActions = append(storeActions, updateVersionAction)

	version.PerviousVersionID = app.Version.ID
	app.Version = version
	updateAppAction := &types.StoreAction{
		Action: types.StoreActionKindUpdate,
		Target: &types.StoreAction_Application{app},
	}
	storeActions = append(storeActions, updateAppAction)

	return s.RaftNode.ProposeValue(ctx, storeActions, cb)
}

func (s *FrameworkStore) GetVersion(appId, versionId string) (*types.Version, error) {
	var version *types.Version

	app, err := s.GetApp(appId)
	if err != nil {
		return nil, err
	}

	if app.Version.ID == versionId {
		return app.Version, err
	}

	if err := s.BoltbDb.View(func(tx *bolt.Tx) error {
		return raftstore.WithVersionBucket(tx, appId, versionId, func(bkt *bolt.Bucket) error {
			p := bkt.Get(raftstore.BucketKeyData)
			return version.Unmarshal(p)

		})
	}); err != nil {
		return nil, err
	}

	return version, nil
}

// retuns app history versions
func (s *FrameworkStore) ListVersions(appId string) ([]*types.Version, error) {
	var versions []*types.Version

	if err := s.BoltbDb.View(func(tx *bolt.Tx) error {
		bkt := raftstore.GetVersionsBucket(tx, appId)
		if bkt == nil {
			versions = []*types.Version{}
			return nil
		}

		return bkt.ForEach(func(k, v []byte) error {
			versionsBkt := raftstore.GetVersionBucket(tx, appId, string(k))
			if versionsBkt == nil {
				return nil
			}

			version := &types.Version{}
			p := versionsBkt.Get(raftstore.BucketKeyData)
			if err := version.Unmarshal(p); err != nil {
				return err
			}

			versions = append(versions, version)
			return nil
		})
	}); err != nil {
		return nil, err
	}

	return versions, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"

	"github.com/hashicorp/mdns"
)

func receive() {
	entries := make(chan *mdns.ServiceEntry, 16)

	go func() {
		for entry := range entries {
			if strings.Contains(entry.Name, "_airlift._tcp") {
				if printFrom(entry) {
					os.Exit(0)
				}
			}
		}
	}()

	qp := mdns.DefaultParams("_airlift._tcp")
	qp.Entries = entries
	qp.WantUnicastResponse = true
	if err := mdns.Query(qp); err != nil {
		fmt.Fprintf(os.Stderr, "mdns query: %v\n", err)
		return
	}
	close(entries)

	os.Exit(1)
}

func printFrom(entry *mdns.ServiceEntry) bool {
	ip := entry.AddrV4
	if ip == nil {
		ip = entry.AddrV6
	}
	if ip == nil {
		return false
	}

	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip.String(), entry.Port))
	if err != nil {
		return false
	}
	defer conn.Close()

	io.Copy(os.Stdout, conn)

	return true
}
<commit_msg>don't let the main goroutine end if we found a dns response<commit_after>package main

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"sync"

	"github.com/hashicorp/mdns"
)

func receive() {
	entries := make(chan *mdns.ServiceEntry, 16)
	wg := &sync.WaitGroup{}
	wg.Add(1)

	go func() {
		for entry := range entries {
			if strings.Contains(entry.Name, "_airlift._tcp") {
				if printFrom(entry) {
					os.Exit(0)
				}
			}
		}
		wg.Done()
	}()

	qp := mdns.DefaultParams("_airlift._tcp")
	qp.Entries = entries
	qp.WantUnicastResponse = true
	if err := mdns.Query(qp); err != nil {
		fmt.Fprintf(os.Stderr, "mdns query: %v\n", err)
		return
	}
	close(entries)

	wg.Wait()
	os.Exit(1)
}

func printFrom(entry *mdns.ServiceEntry) bool {
	ip := entry.AddrV4
	if ip == nil {
		ip = entry.AddrV6
	}
	if ip == nil {
		return false
	}

	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip.String(), entry.Port))
	if err != nil {
		return false
	}
	defer conn.Close()

	io.Copy(os.Stdout, conn)

	return true
}
<|endoftext|>


<commit_before>package search

type Bucket struct {
}

const BUCKET_TABLE_SIZE = 1 << 11
const BUCKET_TABLE_MASK = BUCKET_TABLE_SIZE - 1

type BucketTable struct {
	buckets []*Bucket
	first   *Bucket // head of valid list
}

func newBucketTable() *BucketTable {
	ans := &BucketTable{
		buckets: make([]*Bucket, BUCKET_TABLE_SIZE),
	}
	// Pre-fill to save the lazy init when collecting each sub:
	for i, _ := range ans.buckets {
		ans.buckets[i] = new(Bucket)
	}
	return ans
}

func (t *BucketTable) newCollector(mask int) Collector {
	panic("not implemented yet")
}

type SubScorer struct {
	scorer     BulkScorer
	prohibited bool
	collector  Collector
	next       *SubScorer
	more       bool
}

func newSubScorer(scorer BulkScorer, required, prohibited bool,
	collector Collector, next *SubScorer) *SubScorer {
	panic("not implemented yet")
}

/* Any time a prohibited clause matches we set bit 0: */
const PROHIBITED_MASK = 1

type BooleanScorer struct {
	*BulkScorerImpl
	scorers          *SubScorer
	bucketTable      *BucketTable
	coordFactors     []float32
	minNrShouldMatch int
	end              int
	current          *Bucket
	weight           Weight
}

func newBooleanScorer(weight *BooleanWeight,
	disableCoord bool, minNrShouldMatch int,
	optionalScorers, prohibitedScorers []BulkScorer,
	maxCoord int) *BooleanScorer {

	ans := &BooleanScorer{
		bucketTable:      newBucketTable(),
		minNrShouldMatch: minNrShouldMatch,
		weight:           weight,
	}

	for _, scorer := range optionalScorers {
		ans.scorers = newSubScorer(scorer, false, false,
			ans.bucketTable.newCollector(0), ans.scorers)
	}

	for _, scorer := range prohibitedScorers {
		ans.scorers = newSubScorer(scorer, false, true,
			ans.bucketTable.newCollector(PROHIBITED_MASK), ans.scorers)
	}

	ans.coordFactors = make([]float32, len(optionalScorers)+1)
	for i, _ := range ans.coordFactors {
		if disableCoord {
			ans.coordFactors[i] = 1
		} else {
			ans.coordFactors[i] = weight.coord(i, maxCoord)
		}
	}

	return ans
}

func (s *BooleanScorer) ScoreAndCollectUpto(collector Collector, max int) (bool, error) {
	panic("not implemented yet")
}

func (s *BooleanScorer) String() string {
	panic("not implemented yet")
}
<commit_msg>implement BucketTable.newCollector()<commit_after>package search

import (
	"github.com/balzaczyy/golucene/core/index"
)

type BooleanScorerCollector struct {
	bucketTable *BucketTable
	mask        int
	scorer      Scorer
}

func newBooleanScorerCollector(mask int, bucketTable *BucketTable) *BooleanScorerCollector {
	return &BooleanScorerCollector{
		mask:        mask,
		bucketTable: bucketTable,
	}
}

func (c *BooleanScorerCollector) Collect(doc int) error {
	panic("not implemented yet")
}

func (c *BooleanScorerCollector) SetNextReader(*index.AtomicReaderContext) {}
func (c *BooleanScorerCollector) SetScorer(Scorer)                         {}
func (c *BooleanScorerCollector) AcceptsDocsOutOfOrder() bool              { return true }

type Bucket struct {
}

const BUCKET_TABLE_SIZE = 1 << 11
const BUCKET_TABLE_MASK = BUCKET_TABLE_SIZE - 1

type BucketTable struct {
	buckets []*Bucket
	first   *Bucket // head of valid list
}

func newBucketTable() *BucketTable {
	ans := &BucketTable{
		buckets: make([]*Bucket, BUCKET_TABLE_SIZE),
	}
	// Pre-fill to save the lazy init when collecting each sub:
	for i, _ := range ans.buckets {
		ans.buckets[i] = new(Bucket)
	}
	return ans
}

func (t *BucketTable) newCollector(mask int) Collector {
	return newBooleanScorerCollector(mask, t)
}

type SubScorer struct {
	scorer     BulkScorer
	prohibited bool
	collector  Collector
	next       *SubScorer
	more       bool
}

func newSubScorer(scorer BulkScorer, required, prohibited bool,
	collector Collector, next *SubScorer) *SubScorer {
	panic("not implemented yet")
}

/* Any time a prohibited clause matches we set bit 0: */
const PROHIBITED_MASK = 1

type BooleanScorer struct {
	*BulkScorerImpl
	scorers          *SubScorer
	bucketTable      *BucketTable
	coordFactors     []float32
	minNrShouldMatch int
	end              int
	current          *Bucket
	weight           Weight
}

func newBooleanScorer(weight *BooleanWeight,
	disableCoord bool, minNrShouldMatch int,
	optionalScorers, prohibitedScorers []BulkScorer,
	maxCoord int) *BooleanScorer {

	ans := &BooleanScorer{
		bucketTable:      newBucketTable(),
		minNrShouldMatch: minNrShouldMatch,
		weight:           weight,
	}

	for _, scorer := range optionalScorers {
		ans.scorers = newSubScorer(scorer, false, false,
			ans.bucketTable.newCollector(0), ans.scorers)
	}

	for _, scorer := range prohibitedScorers {
		ans.scorers = newSubScorer(scorer, false, true,
			ans.bucketTable.newCollector(PROHIBITED_MASK), ans.scorers)
	}

	ans.coordFactors = make([]float32, len(optionalScorers)+1)
	for i, _ := range ans.coordFactors {
		if disableCoord {
			ans.coordFactors[i] = 1
		} else {
			ans.coordFactors[i] = weight.coord(i, maxCoord)
		}
	}

	return ans
}

func (s *BooleanScorer) ScoreAndCollectUpto(collector Collector, max int) (bool, error) {
	panic("not implemented yet")
}

func (s *BooleanScorer) String() string {
	panic("not implemented yet")
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"os"
	"path"

	"github.com/jweslley/procker"
)

func main() {
	procfile := flag.String("f", "Procfile", "Procfile declaring commands to run")

	file, err := os.Open(*procfile)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	processes, err := procker.ParseProcfile(file)
	if err != nil {
		log.Fatalf("procker: %v", err)
	}
	log.Println(processes)

	wd := path.Dir(*procfile)
	for name, process := range processes {
		log.Printf("starting %s - %s", name, process.Command)
		process.Start(wd, []string{}, os.Stdout, os.Stderr)
		process.Wait()
	}
}
<commit_msg>allow run multiple command and kill them on exit<commit_after>package main

import (
	"flag"
	"log"
	"os"
	"os/signal"
	"path"
	"syscall"

	"github.com/jweslley/procker"
)

func main() {
	procfile := flag.String("f", "Procfile", "Procfile declaring commands to run")

	file, err := os.Open(*procfile)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	processes, err := procker.ParseProcfile(file)
	if err != nil {
		log.Fatalf("procker: %v", err)
	}
	log.Println(processes)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		for sig := range c {
			log.Printf("%v received, stopping processes and exiting.", sig)
			for name, process := range processes {
				log.Printf("killing %s", name)
				process.Kill()
			}
			os.Exit(1)
		}
	}()

	wd := path.Dir(*procfile)
	for name, process := range processes {
		log.Printf("starting %s - %s", name, process.Command)
		process.Start(wd, []string{}, os.Stdout, os.Stderr)
	}

	for _, process := range processes {
		process.Wait()
	}
}
<|endoftext|>


<commit_before>package sarama

import "log"

type produceMessage struct {
	tp         topicPartition
	key, value []byte
	retried    bool
	sync       bool
}

type produceRequestBuilder []*produceMessage

// If the message is synchronous, we manually send it and wait for a return.
// Otherwise, we just hand it back to the producer to enqueue using the normal
// method.
func (msg *produceMessage) enqueue(p *Producer) error {
	if !msg.sync {
		return p.addMessage(msg)
	}

	var prb produceRequestBuilder = []*produceMessage{msg}
	bp, err := p.brokerProducerFor(msg.tp)
	if err != nil {
		return err
	}
	errs := make(chan error, 1)
	bp.flushRequest(p, prb, func(err error) {
		errs <- err
	})
	return <-errs

}

func (msg *produceMessage) reenqueue(p *Producer) error {
	if !msg.retried {
		msg.retried = true
		return msg.enqueue(p)
	}
	return nil
}

func (msg *produceMessage) hasTopicPartition(topic string, partition int32) bool {
	return msg.tp.partition == partition && msg.tp.topic == topic
}

func (b produceRequestBuilder) toRequest(config *ProducerConfig) *ProduceRequest {
	req := &ProduceRequest{RequiredAcks: config.RequiredAcks, Timeout: config.Timeout}

	// If compression is enabled, we need to group messages by topic-partition and
	// wrap them in MessageSets. We already discarded that grouping, so we
	// inefficiently re-sort them. This could be optimized (ie. pass a hash around
	// rather than an array. Not sure what the best way is.
	if config.Compression != CompressionNone {
		msgSets := make(map[topicPartition]*MessageSet)
		for _, pmsg := range b {
			msgSet, ok := msgSets[pmsg.tp]
			if !ok {
				msgSet = new(MessageSet)
				msgSets[pmsg.tp] = msgSet
			}

			msgSet.addMessage(&Message{Codec: CompressionNone, Key: pmsg.key, Value: pmsg.value})
		}
		for tp, msgSet := range msgSets {
			valBytes, err := encode(msgSet)
			if err != nil {
				log.Fatal(err) // if this happens, it's basically our fault.
			}
			msg := Message{Codec: config.Compression, Key: nil, Value: valBytes}
			req.AddMessage(tp.topic, tp.partition, &msg)
		}
		return req
	}

	// Compression is not enabled. Dumb-ly append each request directly to the
	// request, with no MessageSet wrapper.
	for _, pmsg := range b {
		msg := Message{Codec: config.Compression, Key: pmsg.key, Value: pmsg.value}
		req.AddMessage(pmsg.tp.topic, pmsg.tp.partition, &msg)
	}
	return req
}

func (msg *produceMessage) byteSize() uint32 {
	return uint32(len(msg.key) + len(msg.value))
}

func (b produceRequestBuilder) byteSize() uint32 {
	var size uint32
	for _, m := range b {
		size += m.byteSize()
	}
	return size
}

func (b produceRequestBuilder) reverseEach(fn func(m *produceMessage)) {
	for i := len(b) - 1; i >= 0; i-- {
		fn(b[i])
	}
}
<commit_msg>10 second timeout on sync sends, close broker producer and broker if send times out<commit_after>package sarama

import (
	"log"
	"time"
	"errors"
)

type produceMessage struct {
	tp         topicPartition
	key, value []byte
	retried    bool
	sync       bool
}

type produceRequestBuilder []*produceMessage

// If the message is synchronous, we manually send it and wait for a return.
// Otherwise, we just hand it back to the producer to enqueue using the normal
// method.
func (msg *produceMessage) enqueue(p *Producer) error {
	if !msg.sync {
		return p.addMessage(msg)
	}

	var prb produceRequestBuilder = []*produceMessage{msg}
	bp, err := p.brokerProducerFor(msg.tp)
	if err != nil {
		return err
	}
	errs := make(chan error, 1)
	bp.flushRequest(p, prb, func(err error) {
		errs <- err
	})
	select {
	case e := <-errs:
		return e
	case <- time.After(10 * time.Second):
		bp.Close()
		bp.broker.Close()
		return errors.New("send timed out")
	}

}

func (msg *produceMessage) reenqueue(p *Producer) error {
	if !msg.retried {
		msg.retried = true
		return msg.enqueue(p)
	}
	return nil
}

func (msg *produceMessage) hasTopicPartition(topic string, partition int32) bool {
	return msg.tp.partition == partition && msg.tp.topic == topic
}

func (b produceRequestBuilder) toRequest(config *ProducerConfig) *ProduceRequest {
	req := &ProduceRequest{RequiredAcks: config.RequiredAcks, Timeout: config.Timeout}

	// If compression is enabled, we need to group messages by topic-partition and
	// wrap them in MessageSets. We already discarded that grouping, so we
	// inefficiently re-sort them. This could be optimized (ie. pass a hash around
	// rather than an array. Not sure what the best way is.
	if config.Compression != CompressionNone {
		msgSets := make(map[topicPartition]*MessageSet)
		for _, pmsg := range b {
			msgSet, ok := msgSets[pmsg.tp]
			if !ok {
				msgSet = new(MessageSet)
				msgSets[pmsg.tp] = msgSet
			}

			msgSet.addMessage(&Message{Codec: CompressionNone, Key: pmsg.key, Value: pmsg.value})
		}
		for tp, msgSet := range msgSets {
			valBytes, err := encode(msgSet)
			if err != nil {
				log.Fatal(err) // if this happens, it's basically our fault.
			}
			msg := Message{Codec: config.Compression, Key: nil, Value: valBytes}
			req.AddMessage(tp.topic, tp.partition, &msg)
		}
		return req
	}

	// Compression is not enabled. Dumb-ly append each request directly to the
	// request, with no MessageSet wrapper.
	for _, pmsg := range b {
		msg := Message{Codec: config.Compression, Key: pmsg.key, Value: pmsg.value}
		req.AddMessage(pmsg.tp.topic, pmsg.tp.partition, &msg)
	}
	return req
}

func (msg *produceMessage) byteSize() uint32 {
	return uint32(len(msg.key) + len(msg.value))
}

func (b produceRequestBuilder) byteSize() uint32 {
	var size uint32
	for _, m := range b {
		size += m.byteSize()
	}
	return size
}

func (b produceRequestBuilder) reverseEach(fn func(m *produceMessage)) {
	for i := len(b) - 1; i >= 0; i-- {
		fn(b[i])
	}
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"os/signal"
	"os/exec"
	"path/filepath"
	"fmt"
	"github.com/jessevdk/go-flags"
	"time"
	"strings"
	"github.com/howeyc/fsnotify"
)

const usage = `
Usage:
  watch paths... [options]

Example:
  watch src --on-change 'make build'

Options:
      --on-change <arg>  Run command on any change
  -h, --halt             Exits on error (Default: false)
  -i, --interval <arg>   Run command once within this interval (Default: 1s)
  -r, --recursive        Watch subfolders (Default: true)
  -q, --quiet            Suppress standard output (Default: false)

Intervals can be milliseconds(ms), seconds(s), minutes(m), or hours(h).
The format is the integer followed by the abbreviation.
`

var (
	last        time.Time
	interval    time.Duration
	paths       []string
	err         error
)

var opts struct {
	Help      bool   `short:"h" long:"help"      description:"Show this help message" default:false`
	Halt      bool   `short:"h" long:"halt"      description:"Exits on error (Default: false)" default:false`
	Quiet     bool   `short:"q" long:"quiet"     description:"Suppress standard output (Default: false)" default:false`
	Interval  string `short:"i" long:"interval"  description:"Run command once within this interval (Default: 1s)" default:"1s"`
	Recursive bool   `short:"r" long:"recursive" description:"Watch subfolders (Default: true)" default:true`
	OnChange  string `long:"on-change"           description:"Run command on change."`
}

func init() {
	args, err := flags.ParseArgs(&opts, os.Args)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	paths, err = ResolvePaths(args[1:])

	if len(paths) <= 0 {
		fmt.Fprintln(os.Stderr, usage)
		os.Exit(2) // 2 for --help exit code
	}

	interval, err = time.ParseDuration(opts.Interval)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	last = time.Now().Add(-interval)
}

func main() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	done := make(chan bool)

	// clean-up watcher on interrupt (^C)
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)
	go func() {
		<-interrupt
		if !opts.Quiet {
			fmt.Fprintln(os.Stdout, "Interrupted. Cleaning up before exiting...")
		}
		watcher.Close()
		os.Exit(0)
	}()

	// process watcher events
	go func() {
		for {
			select {
			case ev := <-watcher.Event:
				if !opts.Quiet {
					fmt.Fprintln(os.Stdout, ev)
				}
				if time.Since(last).Nanoseconds() > interval.Nanoseconds() {
					last = time.Now()
					err = ExecCommand()
					if err != nil {
						fmt.Fprintln(os.Stderr, err)
						if opts.Halt {
							os.Exit(1)
						}
					}
				}
			case err := <-watcher.Error:
				fmt.Fprintln(os.Stderr, err)
				if opts.Halt {
					os.Exit(1)
				}
			}
		}
	}()

	// add paths to be watched
	for _, p := range paths {
		err = watcher.Watch(p)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}

	// wait and watch
	<-done
}

func ExecCommand() error {
	if opts.OnChange == "" {
		return nil
	} else {
		args := strings.Split(opts.OnChange, " ")
		cmd := exec.Command(args[0], args[1:]...)

		if !opts.Quiet {
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
		}
		cmd.Stdin = os.Stdin

		return cmd.Run()
	}
}

// Resolve path arguments by walking directories and adding subfolders.
func ResolvePaths(args []string) ([]string, error) {
	var stat os.FileInfo
	resolved := make([]string, 0)

	walker := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			resolved = append(resolved, path)
		}

		return nil
	}

	for _, path:= range args {
		if (path == "") {
			continue
		}

		stat, err = os.Stat(path)
		if err != nil {
			return nil, err
		}

		if !stat.IsDir() {
			resolved = append(resolved, path)
			continue
		}

		err = filepath.Walk(path, walker)
	}

	return resolved, nil
}
<commit_msg>--no-recursive option<commit_after>package main

import (
	"os"
	"os/signal"
	"os/exec"
	"path/filepath"
	"fmt"
	"github.com/jessevdk/go-flags"
	"time"
	"strings"
	"github.com/howeyc/fsnotify"
	"sync"
)

const usage = `
Usage:
  watch paths... [options]

Example:
  watch src --on-change 'make build'

Options:
      --on-change <arg>  Run command on any change
  -h, --halt             Exits on error (Default: false)
  -i, --interval <arg>   Run command once within this interval (Default: 1s)
  -r, --recursive        Watch subfolders (Default: true)
  -q, --quiet            Suppress standard output (Default: false)

Intervals can be milliseconds(ms), seconds(s), minutes(m), or hours(h).
The format is the integer followed by the abbreviation.
`

var (
	last        time.Time
	interval    time.Duration
	paths       []string
	err         error
)

var opts struct {
	Help      bool   `short:"h" long:"help"      description:"Show this help message" default:false`
	Halt      bool   `short:"h" long:"halt"      description:"Exits on error (Default: false)" default:false`
	Quiet     bool   `short:"q" long:"quiet"     description:"Suppress standard output (Default: false)" default:false`
	Interval  string `short:"i" long:"interval"  description:"Run command once within this interval (Default: 1s)" default:"1s"`
	NoRecursive bool   `short:"n" long:"no-recursive" description:"Skip subfolders (Default: false)" default:false`
	OnChange  string `long:"on-change"           description:"Run command on change."`
}

func init() {
	args, err := flags.ParseArgs(&opts, os.Args)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	paths, err = ResolvePaths(args[1:])

	if len(paths) <= 0 {
		fmt.Fprintln(os.Stderr, usage)
		os.Exit(2) // 2 for --help exit code
	}

	interval, err = time.ParseDuration(opts.Interval)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	last = time.Now().Add(-interval)
}

func main() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	done := make(chan bool)

	// clean-up watcher on interrupt (^C)
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)
	go func() {
		<-interrupt
		if !opts.Quiet {
			fmt.Fprintln(os.Stdout, "Interrupted. Cleaning up before exiting...")
		}
		watcher.Close()
		os.Exit(0)
	}()

	// process watcher events
	go func() {
		for {
			select {
			case ev := <-watcher.Event:
				if !opts.Quiet {
					fmt.Fprintln(os.Stdout, ev)
				}
				if time.Since(last).Nanoseconds() > interval.Nanoseconds() {
					last = time.Now()
					err = ExecCommand()
					if err != nil {
						fmt.Fprintln(os.Stderr, err)
						if opts.Halt {
							os.Exit(1)
						}
					}
				}
			case err := <-watcher.Error:
				fmt.Fprintln(os.Stderr, err)
				if opts.Halt {
					os.Exit(1)
				}
			}
		}
	}()

	// add paths to be watched
	for _, p := range paths {
		err = watcher.Watch(p)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}

	// wait and watch
	<-done
}

func ExecCommand() error {
	if opts.OnChange == "" {
		return nil
	} else {
		args := strings.Split(opts.OnChange, " ")
		cmd := exec.Command(args[0], args[1:]...)

		if !opts.Quiet {
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
		}
		cmd.Stdin = os.Stdin

		return cmd.Run()
	}
}

// Resolve path arguments by walking directories and adding subfolders.
func ResolvePaths(args []string) ([]string, error) {
	var stat os.FileInfo
	resolved := make([]string, 0)

	var once sync.Once
	var recurse error = nil

	walker := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if recurse == nil && opts.NoRecursive && info.IsDir() {
			once.Do(func() {
				recurse = filepath.SkipDir
			})
		}

		resolved = append(resolved, path)

		return recurse
	}

	for _, path:= range args {
		if (path == "") {
			continue
		}

		stat, err = os.Stat(path)
		if err != nil {
			return nil, err
		}

		if !stat.IsDir() {
			resolved = append(resolved, path)
			continue
		}

		err = filepath.Walk(path, walker)
	}

	return resolved, nil
}
<|endoftext|>


<commit_before>//watch handles reloading of a command by watching a directory and if supplied a set of given extensions for change
package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/howeyc/fsnotify"
)

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

func goDeps(targetdir string) (bool, error) {
	cmdline := []string{"go", "get"}

	cmdline = append(cmdline, targetdir)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go get failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

//goRun runs the runs a command
func goRun(cmd string) string {
	var cmdline []string
	com := strings.Split(cmd, " ")

	if len(com) < 0 {
		return ""
	}

	if len(com) == 1 {
		cmdline = append(cmdline, com...)
	} else {
		cmdline = append(cmdline, com[0])
		cmdline = append(cmdline, com[1:]...)
	}

	//setup the executor and use a shard buffer
	cmdo := exec.Command(cmdline[0], cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmdo.Stdout = buf
	cmdo.Stderr = buf

	_ = cmdo.Run()

	return buf.String()
}

//gobuild runs the build process and returns true/false and an error
func gobuild(dir, name string) (bool, error) {
	cmdline := []string{"go", "build"}

	if runtime.GOOS == "windows" {
		name = fmt.Sprintf("%s.exe", name)
	}

	target := filepath.Join(dir, name)
	cmdline = append(cmdline, "-o", target)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go build failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

// runBin runs the generated bin file with the arguments expected
func runBin(bindir, bin string, args []string) chan bool {
	var relunch = make(chan bool)
	go func() {
		binfile := fmt.Sprintf("%s/%s", bindir, bin)
		// cmdline := append([]string{bin}, args...)
		var proc *os.Process

		for dosig := range relunch {
			if proc != nil {
				if err := proc.Signal(os.Interrupt); err != nil {
					log.Printf("Error in sending signal %s", err)
					proc.Kill()
				}
				proc.Wait()
			}

			if !dosig {
				continue
			}

			cmd := exec.Command(binfile, args...)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr

			if err := cmd.Start(); err != nil {
				log.Printf("Error starting process: %s", err)
			}

			proc = cmd.Process
		}
	}()
	return relunch
}

func buildPkgWatcher(pkpath string, assets map[string]bool) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	add2Watcher(ws, pkpath, assets)
	return ws, err
}

func buildWatcher(pkpath string) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	ws.Watch(pkpath)
	return ws, err
}

func hasIn(paths []string, dt string) bool {
	for _, so := range paths {
		if strings.Contains(so, dt) || so == dt {
			return true
		}
	}
	return false
}

func watchDir(ws *fsnotify.Watcher, dir string, assets map[string]bool, skip []string) {

	mo, err := os.Stat(dir)

	if err != nil {
		return
	}

	if !mo.IsDir() {
		return
	}

	filepath.Walk(filepath.ToSlash(dir), func(path string, info os.FileInfo, err error) error {

		if strings.Contains(path, ".git") {
			return nil
		}

		if info == nil {
			return nil
		}

		if hasIn(skip, path) {
			return nil
		}

		// log.Printf("adding: %s", path)
		// if !info.IsDir() {
		// 	return nil
		// }

		if assets[path] {
			return nil
		}

		ws.Watch(path)
		assets[path] = true
		return nil
	})
}

func add2Watcher(ws *fsnotify.Watcher, pkgpath string, assets map[string]bool) {
	pkg, err := build.Import(pkgpath, "", 0)

	if err != nil {
		return
	}

	if pkg.Goroot {
		return
	}

	ws.Watch(pkg.Dir)
	assets[pkgpath] = true

	for _, imp := range pkg.Imports {
		if !assets[imp] {
			add2Watcher(ws, imp, assets)
		}
	}
}

func watch(command, importable, bin, exts string, dobuild, watchbuild, withdir bool, args []string) error {
	log.Printf("Command: %s %s %s %t", command, importable, bin, dobuild)

	extcls := multispaces.ReplaceAllString(exts, " ")
	extens := multispaces.Split(extcls, -1)

	if len(extens) == 1 && extens[0] == "" {
		extens = extens[:0]
	}

	var buildName string
	var ubin string

	var buildHandler = func() error {
		var pkgs *build.Package
		var err error
		pkgs, err = build.Import(importable, "", 0)

		if err != nil {
			return err
		}

		_, buildName = path.Split(pkgs.ImportPath)
		// _, buildName := path.Split("./")

		wd, _ := os.Getwd()
		if bin != "" {
			ubin = filepath.ToSlash(filepath.Join(wd, bin))
		} else {
			ubin = pkgs.BinDir
		}

		// lets install
		_, err = goDeps("./")

		if err != nil {
			log.Printf("go.install.err: %s", err.Error())
			// return err
		}

		log.Printf("Building Pkg %s \nBin: %s \nUsing name: %s", pkgs.ImportPath, ubin, buildName)

		done, err := gobuild(ubin, buildName)

		if err != nil {
			return err
		}

		_ = done
		return nil
	}

	var buildWatch = func() (*fsnotify.Watcher, error) {
		var err error
		var watch *fsnotify.Watcher

		added := make(map[string]bool)

		if watchbuild {
			watch, err = buildPkgWatcher(importable, added)
		} else {
			if !added["./"] {
				watch, err = buildWatcher("./")
			}
		}

		//lets watch the current directory also if allowed
		if withdir && err == nil {
			wod, ex := os.Getwd()

			if ex == nil && wod != "" {
				watchDir(watch, wod, added, []string{ubin})
			}
		}

		return watch, err
	}

	var err error
	var watch *fsnotify.Watcher
	var binRun bool
	var binChan chan bool

	//lets build if we are allowed
	if dobuild {
		if err = buildHandler(); err != nil {
			return err
		}

		binChan = runBin(ubin, buildName, args)
		binRun = true
		binChan <- true
	}

	log.Printf("Building dir watchers.....")
	watch, err = buildWatch()

	if err != nil {
		log.Printf("Unable to build err %s", err.Error())
		return err
	}

	for {

		//should we watch
		we, _ := <-watch.Event

		exo := filepath.Ext(we.Name)

		//if its a .git directory skip it
		if strings.Contains(filepath.ToSlash(we.Name), ".git") {
			continue
		}

		//if its our bin directory skip it
		if filepath.ToSlash(we.Name) == filepath.ToSlash(ubin) {
			continue
		}

		log.Printf("Watch: %s -> %s with extensions: %s", exo, we.Name, extens)

		if len(extens) > 0 {
			var found bool
			for _, mo := range extens {
				if exo == mo {
					found = true
					break
				}
			}

			if !found {
				continue
			}
		}

		log.Printf("Watcher notified change: %s", we.Name)

		watch.Close()

		go func(evs chan *fsnotify.FileEvent) {
			for _ = range evs {
			}
		}(watch.Event)

		log.Printf("Re-initiating watch scans .....")

		if command != "" {
			log.Printf("Running cmd '%s' with result: '%s'", command, goRun(command))
		}

		if dobuild {
			if err = buildHandler(); err != nil {
				return err
			}

			if binRun {
				binChan <- true
			}
		}

		watch, err = buildWatch()

		if err != nil {
			return err
		}

		go func(errors chan error) {
			for _ = range errors {
			}
		}(watch.Error)

	}
}

func usage() {
	fmt.Printf(`Watch:
    About: provides a simple but combined go dir builder and file watcher
    Version: %s
    Usage: watch [--import] <import path> [--cmd] <cmd_to_rerun> [--ext] <extensions> [--bin] <bin path to store> --dir --nobin
    `, version)
}

var version = "0.0.1"

func main() {
	exts := flag.String("ext", "", "a space seperated string of extensions to watch")
	cmd := flag.String("cmd", "", "Command to run instead on every change")
	withdir := flag.Bool("dir", false, "This sets the current directories and subdirectories to be watched")
	bindir := flag.String("bin", "./bin", "The build directory for storing the build file")
	importdir := flag.String("import", "", "Command to run instead on every change")
	nobin := flag.Bool("nobin", false, "This sets the watcher to watch for files in the package giving in the import option and in the current directory without building a binary file for running")

	flag.Parse()

	if *cmd == "" && *importdir == "" {
		usage()
		return
	}

	build := (*importdir != "" && !(*nobin))
	watchbuild := (*importdir != "")

	err := watch(*cmd, *importdir, *bindir, *exts, build, watchbuild, *withdir, flag.Args())

	if err != nil {
		log.Printf("Errored: %s", err.Error())
	}
}
<commit_msg>updated readme<commit_after>//watch handles reloading of a command by watching a directory and if supplied a set of given extensions for change
package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/howeyc/fsnotify"
)

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

func goDeps(targetdir string) (bool, error) {
	cmdline := []string{"go", "get"}

	cmdline = append(cmdline, targetdir)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go get failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

//goRun runs the runs a command
func goRun(cmd string) string {
	var cmdline []string
	com := strings.Split(cmd, " ")

	if len(com) < 0 {
		return ""
	}

	if len(com) == 1 {
		cmdline = append(cmdline, com...)
	} else {
		cmdline = append(cmdline, com[0])
		cmdline = append(cmdline, com[1:]...)
	}

	//setup the executor and use a shard buffer
	cmdo := exec.Command(cmdline[0], cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmdo.Stdout = buf
	cmdo.Stderr = buf

	_ = cmdo.Run()

	return buf.String()
}

//gobuild runs the build process and returns true/false and an error
func gobuild(dir, name string) (bool, error) {
	cmdline := []string{"go", "build"}

	if runtime.GOOS == "windows" {
		name = fmt.Sprintf("%s.exe", name)
	}

	target := filepath.Join(dir, name)
	cmdline = append(cmdline, "-o", target)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go build failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

// runBin runs the generated bin file with the arguments expected
func runBin(bindir, bin string, args []string) chan bool {
	var relunch = make(chan bool)
	go func() {
		binfile := fmt.Sprintf("%s/%s", bindir, bin)
		// cmdline := append([]string{bin}, args...)
		var proc *os.Process

		for dosig := range relunch {
			if proc != nil {
				if err := proc.Signal(os.Interrupt); err != nil {
					log.Printf("Error in sending signal %s", err)
					proc.Kill()
				}
				proc.Wait()
			}

			if !dosig {
				continue
			}

			cmd := exec.Command(binfile, args...)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr

			if err := cmd.Start(); err != nil {
				log.Printf("Error starting process: %s", err)
			}

			proc = cmd.Process
		}
	}()
	return relunch
}

func buildPkgWatcher(pkpath string, assets map[string]bool) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	add2Watcher(ws, pkpath, assets)
	return ws, err
}

func buildWatcher(pkpath string) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	ws.Watch(pkpath)
	return ws, err
}

func hasIn(paths []string, dt string) bool {
	for _, so := range paths {
		if strings.Contains(so, dt) || so == dt {
			return true
		}
	}
	return false
}

func watchDir(ws *fsnotify.Watcher, dir string, assets map[string]bool, skip []string) {

	mo, err := os.Stat(dir)

	if err != nil {
		return
	}

	if !mo.IsDir() {
		return
	}

	filepath.Walk(filepath.ToSlash(dir), func(path string, info os.FileInfo, err error) error {

		if strings.Contains(path, ".git") {
			return nil
		}

		if info == nil {
			return nil
		}

		if hasIn(skip, path) {
			return nil
		}

		// log.Printf("adding: %s", path)
		// if !info.IsDir() {
		// 	return nil
		// }

		if assets[path] {
			return nil
		}

		ws.Watch(path)
		assets[path] = true
		return nil
	})
}

func add2Watcher(ws *fsnotify.Watcher, pkgpath string, assets map[string]bool) {
	pkg, err := build.Import(pkgpath, "", 0)

	if err != nil {
		return
	}

	if pkg.Goroot {
		return
	}

	ws.Watch(pkg.Dir)
	assets[pkgpath] = true

	for _, imp := range pkg.Imports {
		if !assets[imp] {
			add2Watcher(ws, imp, assets)
		}
	}
}

func watch(command, importable, bin, exts string, dobuild, watchbuild, withdir bool, args []string) error {
	log.Printf("Command: %s %s %s %t", command, importable, bin, dobuild)

	extcls := multispaces.ReplaceAllString(exts, " ")
	extens := multispaces.Split(extcls, -1)

	if len(extens) == 1 && extens[0] == "" {
		extens = extens[:0]
	}

	var buildName string
	var ubin string

	var buildHandler = func() error {
		var pkgs *build.Package
		var err error
		pkgs, err = build.Import(importable, "", 0)

		if err != nil {
			return err
		}

		_, buildName = path.Split(pkgs.ImportPath)
		// _, buildName := path.Split("./")

		wd, _ := os.Getwd()
		if bin != "" {
			ubin = filepath.ToSlash(filepath.Join(wd, bin))
		} else {
			ubin = pkgs.BinDir
		}

		// lets install
		_, err = goDeps("./")

		if err != nil {
			log.Printf("go.install.err: %s", err.Error())
			// return err
		}

		log.Printf("Building Pkg %s \nBin: %s \nUsing name: %s", pkgs.ImportPath, ubin, buildName)

		done, err := gobuild(ubin, buildName)

		if err != nil {
			return err
		}

		_ = done
		return nil
	}

	var buildWatch = func() (*fsnotify.Watcher, error) {
		var err error
		var watch *fsnotify.Watcher

		added := make(map[string]bool)

		if watchbuild {
			watch, err = buildPkgWatcher(importable, added)
		} else {
			if !added["./"] {
				watch, err = buildWatcher("./")
			}
		}

		//lets watch the current directory also if allowed
		if withdir && err == nil {
			wod, ex := os.Getwd()

			if ex == nil && wod != "" {
				watchDir(watch, wod, added, []string{ubin})
			}
		}

		return watch, err
	}

	var err error
	var watch *fsnotify.Watcher
	var binRun bool
	var binChan chan bool

	//lets build if we are allowed
	if dobuild {
		if err = buildHandler(); err != nil {
			return err
		}

		binChan = runBin(ubin, buildName, args)
		binRun = true
		binChan <- true
	}

	log.Printf("Building dir watchers.....")
	watch, err = buildWatch()

	if err != nil {
		log.Printf("Unable to build err %s", err.Error())
		return err
	}

	for {

		//should we watch
		we, _ := <-watch.Event

		exo := filepath.Ext(we.Name)

		//if its a .git directory skip it
		if strings.Contains(filepath.ToSlash(we.Name), ".git") {
			continue
		}

		//if its our bin directory skip it
		if filepath.ToSlash(we.Name) == filepath.ToSlash(ubin) {
			continue
		}

		log.Printf("Watch: %s -> %s with extensions: %s", exo, we.Name, extens)

		if len(extens) > 0 {
			var found bool
			for _, mo := range extens {
				if exo == mo {
					found = true
					break
				}
			}

			if !found {
				continue
			}
		}

		log.Printf("Watcher notified change: %s", we.Name)

		watch.Close()

		go func(evs chan *fsnotify.FileEvent) {
			for _ = range evs {
			}
		}(watch.Event)

		log.Printf("Re-initiating watch scans .....")

		if command != "" {
			log.Printf("Running cmd '%s' with result: '%s'", command, goRun(command))
		}

		if dobuild {
			if err = buildHandler(); err != nil {
				return err
			}

			if binRun {
				binChan <- true
			}
		}

		watch, err = buildWatch()

		if err != nil {
			return err
		}

		go func(errors chan error) {
			for _ = range errors {
			}
		}(watch.Error)

	}
}

func usage() {
	fmt.Printf(`Watch:
    About: provides a simple but combined go dir builder and file watcher
    Version: %s
    Usage: watch [--import] <import path> [--cmd] <cmd_to_rerun>
		[--ext] <extensions> [--bin] <bin path to store> --dir --nobin
    `, version)
}

var version = "0.0.1"

func main() {
	exts := flag.String("ext", "", "a space seperated string of extensions to watch")
	cmd := flag.String("cmd", "", "Command to run instead on every change")
	withdir := flag.Bool("dir", false, "This sets the current directories and subdirectories to be watched")
	bindir := flag.String("bin", "./bin", "The build directory for storing the build file")
	importdir := flag.String("import", "", "Command to run instead on every change")
	nobin := flag.Bool("nobin", false, "This sets the watcher to watch for files in the package giving in the import option and in the current directory without building a binary file for running")

	flag.Parse()

	if *cmd == "" && *importdir == "" {
		usage()
		return
	}

	build := (*importdir != "" && !(*nobin))
	watchbuild := (*importdir != "")

	err := watch(*cmd, *importdir, *bindir, *exts, build, watchbuild, *withdir, flag.Args())

	if err != nil {
		log.Printf("Errored: %s", err.Error())
	}
}
<|endoftext|>