<commit_before>package php

import (
	"stephensearles.com/php/ast"
	"stephensearles.com/php/token"
)

func (p *parser) parseInstantiation() ast.Expression {
	p.expectCurrent(token.NewOperator)
	expr := &ast.NewExpression{}
	expr.Class = p.parseNextExpression()

	if p.peek().typ == token.OpenParen {
		p.expect(token.OpenParen)
		if p.peek().typ != token.CloseParen {
			expr.Arguments = append(expr.Arguments, p.parseNextExpression())
			for p.peek().typ == token.Comma {
				p.expect(token.Comma)
				expr.Arguments = append(expr.Arguments, p.parseNextExpression())
			}
		}
		p.expect(token.CloseParen)
	}
	return expr
}

func (p *parser) parseClass() ast.Class {
	if p.current.typ == token.Abstract {
		p.expect(token.Class)
	}
	p.expect(token.Identifier)
	name := p.current.val
	if p.peek().typ == token.Extends {
		p.expect(token.Extends)
		p.expect(token.Identifier)
	}
	if p.peek().typ == token.Implements {
		p.expect(token.Implements)
		p.expect(token.Identifier)
		for p.peek().typ == token.Comma {
			p.expect(token.Comma)
			p.expect(token.Identifier)
		}
	}
	p.expect(token.BlockBegin)
	return p.parseClassFields(ast.Class{Name: name})
}

func (p *parser) parseObjectLookup(r ast.Expression) (expr ast.Expression) {
	p.expectCurrent(token.ObjectOperator)
	prop := &ast.PropertyExpression{
		Receiver: r,
	}
	switch p.next(); p.current.typ {
	case token.BlockBegin:
		prop.Name = p.parseNextExpression()
		p.expect(token.BlockEnd)
	case token.VariableOperator:
		prop.Name = p.parseExpression()
	case token.Identifier:
		prop.Name = ast.Identifier{Value: p.current.val}
	}
	expr = prop
	switch pk := p.peek(); pk.typ {
	case token.OpenParen:
		expr = &ast.MethodCallExpression{
			Receiver:               r,
			FunctionCallExpression: p.parseFunctionCall(prop.Name),
		}
	}
	expr = p.parseOperation(p.parenLevel, expr)
	return
}

func (p *parser) parseVisibility() (vis ast.Visibility, found bool) {
	switch p.peek().typ {
	case token.Private:
		vis = ast.Private
	case token.Public:
		vis = ast.Public
	case token.Protected:
		vis = ast.Protected
	default:
		return ast.Public, false
	}
	p.next()
	return vis, true
}

func (p *parser) parseAbstract() bool {
	if p.peek().typ == token.Abstract {
		p.next()
		return true
	}
	return false
}

func (p *parser) parseClassFields(c ast.Class) ast.Class {
	// Starting on BlockBegin
	c.Methods = make([]ast.Method, 0)
	c.Properties = make([]ast.Property, 0)
	for p.peek().typ != token.BlockEnd {
		vis, _, _, abstract := p.parseClassMemberSettings()
		p.next()
		switch p.current.typ {
		case token.Function:
			if abstract {
				f := p.parseFunctionDefinition()
				m := ast.Method{
					Visibility:   vis,
					FunctionStmt: &ast.FunctionStmt{FunctionDefinition: f},
				}
				c.Methods = append(c.Methods, m)
				p.expect(token.StatementEnd)
			} else {
				c.Methods = append(c.Methods, ast.Method{
					Visibility:   vis,
					FunctionStmt: p.parseFunctionStmt(),
				})
			}
		case token.Var:
			p.expect(token.VariableOperator)
			fallthrough
		case token.VariableOperator:
			p.expect(token.Identifier)
			prop := ast.Property{
				Visibility: vis,
				Name:       "$" + p.current.val,
			}
			if p.peek().typ == token.AssignmentOperator {
				p.expect(token.AssignmentOperator)
				prop.Initialization = p.parseNextExpression()
			}
			c.Properties = append(c.Properties, prop)
			p.expect(token.StatementEnd)
		case token.Const:
			constant := ast.Constant{}
			p.expect(token.Identifier)
			constant.Variable = ast.NewVariable(p.current.val)
			if p.peek().typ == token.AssignmentOperator {
				p.expect(token.AssignmentOperator)
				constant.Value = p.parseNextExpression()
			}
			c.Constants = append(c.Constants, constant)
			p.expect(token.StatementEnd)
		default:
			p.errorf("unexpected class member %v", p.current)
		}
	}
	p.expect(token.BlockEnd)
	return c
}

func (p *parser) parseInterface() *ast.Interface {
	i := &ast.Interface{
		Inherits: make([]string, 0),
	}
	p.expect(token.Identifier)
	i.Name = p.current.val
	if p.peek().typ == token.Extends {
		p.expect(token.Extends)
		for {
			p.expect(token.Identifier)
			i.Inherits = append(i.Inherits, p.current.val)
			if p.peek().typ != token.Comma {
				break
			}
			p.expect(token.Comma)
		}
	}
	p.expect(token.BlockBegin)
	for p.peek().typ != token.BlockEnd {
		vis, _ := p.parseVisibility()
		if p.peek().typ == token.Static {
			p.next()
		}
		p.next()
		switch p.current.typ {
		case token.Function:
			f := p.parseFunctionDefinition()
			m := ast.Method{
				Visibility:   vis,
				FunctionStmt: &ast.FunctionStmt{FunctionDefinition: f},
			}
			i.Methods = append(i.Methods, m)
			p.expect(token.StatementEnd)
		default:
			p.errorf("unexpected interface member %v", p.current)
		}
	}
	p.expect(token.BlockEnd)
	return i
}

func (p *parser) parseClassMemberSettings() (vis ast.Visibility, static, final, abstract bool) {
	var foundVis bool
	vis = ast.Public
	for {
		switch p.peek().typ {
		case token.Abstract:
			if abstract {
				p.errorf("found multiple abstract declarations")
			}
			abstract = true
			p.next()
		case token.Private, token.Public, token.Protected:
			if foundVis {
				p.errorf("found multiple visibility declarations")
			}
			vis, foundVis = p.parseVisibility()
		case token.Final:
			if final {
				p.errorf("found multiple final declarations")
			}
			final = true
			p.next()
		case token.Static:
			if static {
				p.errorf("found multiple static declarations")
			}
			static = true
			p.next()
		default:
			return
		}
	}
	return
}
<commit_msg>Fixed instantiation parsing<commit_after>package php

import (
	"stephensearles.com/php/ast"
	"stephensearles.com/php/token"
)

func (p *parser) parseInstantiation() ast.Expression {
	p.expectCurrent(token.NewOperator)
	p.next()

	expr := &ast.NewExpression{}
	expr.Class = p.parseOperand()

	if p.peek().typ == token.OpenParen {
		p.expect(token.OpenParen)
		if p.peek().typ != token.CloseParen {
			expr.Arguments = append(expr.Arguments, p.parseNextExpression())
			for p.peek().typ == token.Comma {
				p.expect(token.Comma)
				expr.Arguments = append(expr.Arguments, p.parseNextExpression())
			}
		}
		p.expect(token.CloseParen)
	}
	return expr
}

func (p *parser) parseClass() ast.Class {
	if p.current.typ == token.Abstract {
		p.expect(token.Class)
	}
	p.expect(token.Identifier)
	name := p.current.val
	if p.peek().typ == token.Extends {
		p.expect(token.Extends)
		p.expect(token.Identifier)
	}
	if p.peek().typ == token.Implements {
		p.expect(token.Implements)
		p.expect(token.Identifier)
		for p.peek().typ == token.Comma {
			p.expect(token.Comma)
			p.expect(token.Identifier)
		}
	}
	p.expect(token.BlockBegin)
	return p.parseClassFields(ast.Class{Name: name})
}

func (p *parser) parseObjectLookup(r ast.Expression) (expr ast.Expression) {
	p.expectCurrent(token.ObjectOperator)
	prop := &ast.PropertyExpression{
		Receiver: r,
	}
	switch p.next(); p.current.typ {
	case token.BlockBegin:
		prop.Name = p.parseNextExpression()
		p.expect(token.BlockEnd)
	case token.VariableOperator:
		prop.Name = p.parseExpression()
	case token.Identifier:
		prop.Name = ast.Identifier{Value: p.current.val}
	}
	expr = prop
	switch pk := p.peek(); pk.typ {
	case token.OpenParen:
		expr = &ast.MethodCallExpression{
			Receiver:               r,
			FunctionCallExpression: p.parseFunctionCall(prop.Name),
		}
	}
	expr = p.parseOperation(p.parenLevel, expr)
	return
}

func (p *parser) parseVisibility() (vis ast.Visibility, found bool) {
	switch p.peek().typ {
	case token.Private:
		vis = ast.Private
	case token.Public:
		vis = ast.Public
	case token.Protected:
		vis = ast.Protected
	default:
		return ast.Public, false
	}
	p.next()
	return vis, true
}

func (p *parser) parseAbstract() bool {
	if p.peek().typ == token.Abstract {
		p.next()
		return true
	}
	return false
}

func (p *parser) parseClassFields(c ast.Class) ast.Class {
	// Starting on BlockBegin
	c.Methods = make([]ast.Method, 0)
	c.Properties = make([]ast.Property, 0)
	for p.peek().typ != token.BlockEnd {
		vis, _, _, abstract := p.parseClassMemberSettings()
		p.next()
		switch p.current.typ {
		case token.Function:
			if abstract {
				f := p.parseFunctionDefinition()
				m := ast.Method{
					Visibility:   vis,
					FunctionStmt: &ast.FunctionStmt{FunctionDefinition: f},
				}
				c.Methods = append(c.Methods, m)
				p.expect(token.StatementEnd)
			} else {
				c.Methods = append(c.Methods, ast.Method{
					Visibility:   vis,
					FunctionStmt: p.parseFunctionStmt(),
				})
			}
		case token.Var:
			p.expect(token.VariableOperator)
			fallthrough
		case token.VariableOperator:
			p.expect(token.Identifier)
			prop := ast.Property{
				Visibility: vis,
				Name:       "$" + p.current.val,
			}
			if p.peek().typ == token.AssignmentOperator {
				p.expect(token.AssignmentOperator)
				prop.Initialization = p.parseNextExpression()
			}
			c.Properties = append(c.Properties, prop)
			p.expect(token.StatementEnd)
		case token.Const:
			constant := ast.Constant{}
			p.expect(token.Identifier)
			constant.Variable = ast.NewVariable(p.current.val)
			if p.peek().typ == token.AssignmentOperator {
				p.expect(token.AssignmentOperator)
				constant.Value = p.parseNextExpression()
			}
			c.Constants = append(c.Constants, constant)
			p.expect(token.StatementEnd)
		default:
			p.errorf("unexpected class member %v", p.current)
		}
	}
	p.expect(token.BlockEnd)
	return c
}

func (p *parser) parseInterface() *ast.Interface {
	i := &ast.Interface{
		Inherits: make([]string, 0),
	}
	p.expect(token.Identifier)
	i.Name = p.current.val
	if p.peek().typ == token.Extends {
		p.expect(token.Extends)
		for {
			p.expect(token.Identifier)
			i.Inherits = append(i.Inherits, p.current.val)
			if p.peek().typ != token.Comma {
				break
			}
			p.expect(token.Comma)
		}
	}
	p.expect(token.BlockBegin)
	for p.peek().typ != token.BlockEnd {
		vis, _ := p.parseVisibility()
		if p.peek().typ == token.Static {
			p.next()
		}
		p.next()
		switch p.current.typ {
		case token.Function:
			f := p.parseFunctionDefinition()
			m := ast.Method{
				Visibility:   vis,
				FunctionStmt: &ast.FunctionStmt{FunctionDefinition: f},
			}
			i.Methods = append(i.Methods, m)
			p.expect(token.StatementEnd)
		default:
			p.errorf("unexpected interface member %v", p.current)
		}
	}
	p.expect(token.BlockEnd)
	return i
}

func (p *parser) parseClassMemberSettings() (vis ast.Visibility, static, final, abstract bool) {
	var foundVis bool
	vis = ast.Public
	for {
		switch p.peek().typ {
		case token.Abstract:
			if abstract {
				p.errorf("found multiple abstract declarations")
			}
			abstract = true
			p.next()
		case token.Private, token.Public, token.Protected:
			if foundVis {
				p.errorf("found multiple visibility declarations")
			}
			vis, foundVis = p.parseVisibility()
		case token.Final:
			if final {
				p.errorf("found multiple final declarations")
			}
			final = true
			p.next()
		case token.Static:
			if static {
				p.errorf("found multiple static declarations")
			}
			static = true
			p.next()
		default:
			return
		}
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"text/template"

	"bitbucket.org/kardianos/osext"
	"github.com/justone/pmb/api"
)

func handleOSXCommand(bus *pmb.PMB, command string, arguments string) error {

	var err error

	logger.Debugf("Handling %s with args of %s\n", command, arguments)

	// launch agent name
	args := strings.Split(arguments, " ")
	agentName := fmt.Sprintf("org.endot.pmb.%s", args[0])
	logger.Debugf("Name of launchagent: %s", agentName)

	// figure out launch agent config path
	launchAgentFile := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), agentName)
	logger.Debugf("launchagent file: %s\n", launchAgentFile)

	// create launch data
	executable, err := osext.Executable()
	if err != nil {
		return err
	}

	launchData := struct {
		Name, Executable, Args, Primary string
	}{
		agentName, executable, arguments, bus.PrimaryURI(),
	}

	switch command {
	case "list":
		fmt.Printf(`
Available commands for running '%s' as a background process (agent):

start - Starts agent via launchctl.
stop - Stops agent via launchctl.
restart - Restarts agent via launchctl.
configure - This will configure the agent, but not start it.
unconfigure - This will remove the agent configuration.

`, fmt.Sprintf("pmb %s", arguments))

	case "restart":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}

		err = stop(launchAgentFile, agentName)
		if err != nil {
			return err
		}

		err = start(launchAgentFile, agentName)
		if err != nil {
			return err
		}
	case "stop":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}

		err = stop(launchAgentFile, agentName)
		if err != nil {
			return err
		}

		err = unconfigure(launchAgentFile)
		if err != nil {
			return err
		}
	case "start":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}

		err = start(launchAgentFile, agentName)
		if err != nil {
			return err
		}
	case "configure":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}
	case "unconfigure":
		err = unconfigure(launchAgentFile)
		if err != nil {
			return err
		}
	}

	return nil
}

func start(launchAgentFile string, agentName string) error {
	listCmd := exec.Command("/bin/launchctl", "list", agentName)
	err := listCmd.Run()

	if _, ok := err.(*exec.ExitError); ok {
		// launch agent wasn't loaded yet, so load to start
		startCmd := exec.Command("/bin/launchctl", "load", launchAgentFile)
		startErr := startCmd.Run()
		if startErr != nil {
			return startErr
		}
	} else if err != nil {
		// some error running the list command
		return err
	} else {
		// launch agent was already loaded
		logger.Infof("Already running")
	}

	return nil
}

func stop(launchAgentFile string, agentName string) error {
	listCmd := exec.Command("/bin/launchctl", "list", agentName)
	err := listCmd.Run()

	if err == nil {
		// launch agent was loaded, so unload to stop
		stopCmd := exec.Command("/bin/launchctl", "unload", launchAgentFile)
		stopErr := stopCmd.Run()
		if stopErr != nil {
			return stopErr
		}
	} else if _, ok := err.(*exec.ExitError); ok {
		// launch agent wasn't already loaded
		logger.Infof("Already stopped")
	} else {
		// some error running the list command
		return err
	}

	return nil
}

func configure(launchAgentFile string, config string) error {

	err := ioutil.WriteFile(launchAgentFile, []byte(config), 0644)
	if err != nil {
		return err
	}

	logger.Debugf("Created %s: %s", launchAgentFile, config)

	return nil
}

func generateLaunchConfig(launchData interface{}) string {
	configureTemplate := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>{{ .Name }}</string>
        <key>OnDemand</key>
        <false/>
        <key>EnvironmentVariables</key>
        <dict>
            <key>PATH</key>
            <string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin</string>
            <key>PMB_PRIMARY_URI</key>
            <string>{{ .Primary }}</string>
        </dict>
        <key>ProgramArguments</key>
        <array>
            <string>{{ .Executable }}</string>
            <string>{{ .Args }}</string>
        </array>
    </dict>
</plist>`

	tmpl := template.Must(template.New("configure").Parse(configureTemplate))
	var output bytes.Buffer

	err := tmpl.Execute(&output, launchData)
	if err != nil {
		return ""
	}

	return output.String()
}

func unconfigure(launchAgentFile string) error {
	logger.Debugf("Removing %s", launchAgentFile)
	return os.Remove(launchAgentFile)
}
<commit_msg>add todo about redirecting stderr/out to a log<commit_after>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"text/template"

	"bitbucket.org/kardianos/osext"
	"github.com/justone/pmb/api"
)

func handleOSXCommand(bus *pmb.PMB, command string, arguments string) error {

	var err error

	logger.Debugf("Handling %s with args of %s\n", command, arguments)

	// launch agent name
	args := strings.Split(arguments, " ")
	agentName := fmt.Sprintf("org.endot.pmb.%s", args[0])
	logger.Debugf("Name of launchagent: %s", agentName)

	// figure out launch agent config path
	launchAgentFile := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), agentName)
	logger.Debugf("launchagent file: %s\n", launchAgentFile)

	// create launch data
	executable, err := osext.Executable()
	if err != nil {
		return err
	}

	launchData := struct {
		Name, Executable, Args, Primary string
	}{
		agentName, executable, arguments, bus.PrimaryURI(),
	}

	switch command {
	case "list":
		fmt.Printf(`
Available commands for running '%s' as a background process (agent):

start - Starts agent via launchctl.
stop - Stops agent via launchctl.
restart - Restarts agent via launchctl.
configure - This will configure the agent, but not start it.
unconfigure - This will remove the agent configuration.

`, fmt.Sprintf("pmb %s", arguments))

	case "restart":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}

		err = stop(launchAgentFile, agentName)
		if err != nil {
			return err
		}

		err = start(launchAgentFile, agentName)
		if err != nil {
			return err
		}
	case "stop":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}

		err = stop(launchAgentFile, agentName)
		if err != nil {
			return err
		}

		err = unconfigure(launchAgentFile)
		if err != nil {
			return err
		}
	case "start":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}

		err = start(launchAgentFile, agentName)
		if err != nil {
			return err
		}
	case "configure":
		err = configure(launchAgentFile, generateLaunchConfig(launchData))
		if err != nil {
			return err
		}
	case "unconfigure":
		err = unconfigure(launchAgentFile)
		if err != nil {
			return err
		}
	}

	return nil
}

func start(launchAgentFile string, agentName string) error {
	listCmd := exec.Command("/bin/launchctl", "list", agentName)
	err := listCmd.Run()

	if _, ok := err.(*exec.ExitError); ok {
		// launch agent wasn't loaded yet, so load to start
		startCmd := exec.Command("/bin/launchctl", "load", launchAgentFile)
		startErr := startCmd.Run()
		if startErr != nil {
			return startErr
		}
	} else if err != nil {
		// some error running the list command
		return err
	} else {
		// launch agent was already loaded
		logger.Infof("Already running")
	}

	return nil
}

func stop(launchAgentFile string, agentName string) error {
	listCmd := exec.Command("/bin/launchctl", "list", agentName)
	err := listCmd.Run()

	if err == nil {
		// launch agent was loaded, so unload to stop
		stopCmd := exec.Command("/bin/launchctl", "unload", launchAgentFile)
		stopErr := stopCmd.Run()
		if stopErr != nil {
			return stopErr
		}
	} else if _, ok := err.(*exec.ExitError); ok {
		// launch agent wasn't already loaded
		logger.Infof("Already stopped")
	} else {
		// some error running the list command
		return err
	}

	return nil
}

func configure(launchAgentFile string, config string) error {

	err := ioutil.WriteFile(launchAgentFile, []byte(config), 0644)
	if err != nil {
		return err
	}

	logger.Debugf("Created %s: %s", launchAgentFile, config)

	return nil
}

func generateLaunchConfig(launchData interface{}) string {
	configureTemplate := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>{{ .Name }}</string>
        <key>OnDemand</key>
        <false/>
        <key>EnvironmentVariables</key>
        <dict>
            <key>PATH</key>
            <string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin</string>
            <key>PMB_PRIMARY_URI</key>
            <string>{{ .Primary }}</string>
        </dict>
        <key>ProgramArguments</key>
        <array>
            <string>{{ .Executable }}</string>
            <string>{{ .Args }}</string>
        </array>
    </dict>
</plist>`

	// TODO: add lines like this to show logs
	// <key>StandardOutPath</key>
	// <string>/Users/foo/pmb_out.log</string>
	// <key>StandardErrorPath</key>
	// <string>/Users/foo/pmb_err.log</string>

	tmpl := template.Must(template.New("configure").Parse(configureTemplate))
	var output bytes.Buffer

	err := tmpl.Execute(&output, launchData)
	if err != nil {
		return ""
	}

	return output.String()
}

func unconfigure(launchAgentFile string) error {
	logger.Debugf("Removing %s", launchAgentFile)
	return os.Remove(launchAgentFile)
}
<|endoftext|>


<commit_before>package otp

import (
	"fmt"
)

type Pad struct {
	pages       [][]byte
	currentPage int
}

// NewPad creates a new "one-time pad"
func NewPad(material []byte, pageSize int, startPage int) (*Pad, error) {
	if len(material)%pageSize != 0 {
		return nil, fmt.Errorf("pad size must be divisible by page size")
	}

	// Do the page-splitting work up front
	var pages [][]byte
	for i := 0; i < len(material); i += pageSize {
		pages = append(pages, material[i:i+pageSize])
	}

	if startPage < 1 || startPage > len(pages) {
		return nil, fmt.Errorf("start page (%d) out of bounds", startPage)
	}

	p := Pad{
		pages:       pages,
		currentPage: startPage - 1,
	}

	return &p, nil
}

// TotalPages returns the number of pages in the pad
func (p *Pad) TotalPages() int {
	return len(p.pages)
}

// UnusedPages returns the number of unused pages in the pad
func (p *Pad) RemainingPages() int {
	return len(p.pages) - (p.currentPage + 1)
}

// UsedPages returns the number of pages that have been used
func (p *Pad) UsedPages() int {
	return p.currentPage + 1
}

// PreviousPage returns the payload of the last used page
func (p *Pad) PreviousPage() ([]byte, error) {
	if p.currentPage == 0 {
		return nil, fmt.Errorf("no previous pages")
	}
	return p.pages[p.currentPage-1], nil
}

// CurrentPage returns the payload of the current page
func (p *Pad) CurrentPage() []byte {
	return p.pages[p.currentPage]
}

// NextPage will advance the page pointer, and return the payload of the
// new current key.
func (p *Pad) NextPage() ([]byte, error) {
	if p.RemainingPages() == 0 {
		return nil, fmt.Errorf("pad depleted")
	}
	p.currentPage++
	return p.CurrentPage(), nil
}

func (p *Pad) Encode(in []byte) ([]byte, error) {
	var result []byte
	key := p.CurrentPage()

	// Key must be at least as long as plain text
	if len(key) < len(in) {
		return nil, fmt.Errorf("insufficient key size")
	}

	for i := range in {
		bdec := int64(in[i])
		kdec := int64(key[i])
		encoded := uint64(bdec+kdec) % (1 << 63)
		result = append(result, byte(encoded))
	}
	return result, nil
}

func (p *Pad) Decode(in []byte) ([]byte, error) {
	var result []byte
	key := p.CurrentPage()

	// Key must be at least as long as plain text
	if len(key) < len(in) {
		return nil, fmt.Errorf("insufficient key size")
	}

	for i := range in {
		bdec := int64(in[i])
		kdec := int64(key[i])
		decoded := uint64(bdec-kdec) % (1 << 63)
		if decoded < 0 {
			decoded += 26
		}
		result = append(result, byte(decoded))
	}
	return result, nil
}
<commit_msg>Documented encode/decode functions<commit_after>package otp

import (
	"fmt"
)

type Pad struct {
	pages       [][]byte
	currentPage int
}

// NewPad creates a new "one-time pad"
func NewPad(material []byte, pageSize int, startPage int) (*Pad, error) {
	if len(material)%pageSize != 0 {
		return nil, fmt.Errorf("pad size must be divisible by page size")
	}

	// Do the page-splitting work up front
	var pages [][]byte
	for i := 0; i < len(material); i += pageSize {
		pages = append(pages, material[i:i+pageSize])
	}

	if startPage < 1 || startPage > len(pages) {
		return nil, fmt.Errorf("start page (%d) out of bounds", startPage)
	}

	p := Pad{
		pages:       pages,
		currentPage: startPage - 1,
	}

	return &p, nil
}

// TotalPages returns the number of pages in the pad
func (p *Pad) TotalPages() int {
	return len(p.pages)
}

// UnusedPages returns the number of unused pages in the pad
func (p *Pad) RemainingPages() int {
	return len(p.pages) - (p.currentPage + 1)
}

// UsedPages returns the number of pages that have been used
func (p *Pad) UsedPages() int {
	return p.currentPage + 1
}

// PreviousPage returns the payload of the last used page
func (p *Pad) PreviousPage() ([]byte, error) {
	if p.currentPage == 0 {
		return nil, fmt.Errorf("no previous pages")
	}
	return p.pages[p.currentPage-1], nil
}

// CurrentPage returns the payload of the current page
func (p *Pad) CurrentPage() []byte {
	return p.pages[p.currentPage]
}

// NextPage will advance the page pointer, and return the payload of the
// new current key.
func (p *Pad) NextPage() ([]byte, error) {
	if p.RemainingPages() == 0 {
		return nil, fmt.Errorf("pad depleted")
	}
	p.currentPage++
	return p.CurrentPage(), nil
}

// Encode will take a byte slice and use modular addition to encrypt the
// payload using the current page.
func (p *Pad) Encode(payload []byte) ([]byte, error) {
	var result []byte
	key := p.CurrentPage()

	// Key must be at least as long as plain text
	if len(key) < len(payload) {
		return nil, fmt.Errorf("insufficient key size")
	}

	for i := 0; i < len(payload); i++ {
		bdec := int64(payload[i])
		kdec := int64(key[i])
		encoded := uint64(bdec+kdec) % (1 << 63)
		result = append(result, byte(encoded))
	}
	return result, nil
}

// Decode will accept a byte slice and reverse the process taken by Encode to
// translate encrypted text back into raw bytes. It is required that the page
// pointer be set to the same position as it was during Encode().
func (p *Pad) Decode(payload []byte) ([]byte, error) {
	var result []byte
	key := p.CurrentPage()

	// Key must be at least as long as plain text
	if len(key) < len(payload) {
		return nil, fmt.Errorf("insufficient key size")
	}

	for i := 0; i < len(payload); i++ {
		bdec := int64(payload[i])
		kdec := int64(key[i])
		decoded := uint64(bdec-kdec) % (1 << 63)
		if decoded < 0 {
			decoded += 26
		}
		result = append(result, byte(decoded))
	}
	return result, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/miekg/dns"

	"golang.org/x/net/publicsuffix"
)

// handlePACFile serves an automatically-generated PAC (Proxy Auto-Config) file
// pointing to this proxy server.
func handlePACFile(w http.ResponseWriter, r *http.Request) {
	proxyAddr := r.Host

	if a := r.FormValue("a"); a != "" {
		if user, pass, ok := decodeBase64Credentials(a); ok {
			conf := getConfig()
			if conf.ValidCredentials(user, pass) {
				proxyForUserLock.RLock()
				p := proxyForUser[user]
				proxyForUserLock.RUnlock()
				if p != nil {
					client := r.RemoteAddr
					host, _, err := net.SplitHostPort(client)
					if err == nil {
						client = host
					}
					p.AllowIP(client)
					proxyHost, _, err := net.SplitHostPort(proxyAddr)
					if err == nil {
						proxyAddr = net.JoinHostPort(proxyHost, strconv.Itoa(p.Port))
					}
				}
			}
		}
	}

	w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig")
	fmt.Fprintf(w, pacTemplate, proxyAddr)
}

var pacTemplate = `function FindProxyForURL(url, host) {
	if (
		shExpMatch(url, "ftp:*") ||
		host == "localhost" ||
		isInNet(host, "127.0.0.0", "255.0.0.0") ||
		isInNet(host, "10.0.0.0", "255.0.0.0") ||
		isInNet(host, "172.16.0.0", "255.240.0.0") ||
		isInNet(host, "192.168.0.0", "255.255.0.0")
	) {
		return "DIRECT";
	}

	return "PROXY %s";
}`

type perUserProxy struct {
	User           string
	Port           int
	Handler        http.Handler
	ClientPlatform string
	allowedIPs     map[string]bool
	allowedIPLock  sync.RWMutex

	expectedDomains  map[string]bool
	expectedIPBlocks []*net.IPNet
	expectedNetLock  sync.RWMutex
}

func (c *config) newPerUserProxy(user string, portInfo customPortInfo) (*perUserProxy, error) {
	p := &perUserProxy{
		User:            user,
		Port:            portInfo.Port,
		ClientPlatform:  portInfo.ClientPlatform,
		Handler:         proxyHandler{user: user},
		allowedIPs:      map[string]bool{},
		expectedDomains: map[string]bool{},
	}

	for _, network := range portInfo.ExpectedNetworks {
		if _, nw, err := net.ParseCIDR(network); err == nil {
			p.expectedIPBlocks = append(p.expectedIPBlocks, nw)
		} else if ip := net.ParseIP(network); ip != nil {
			if ip4 := ip.To4(); ip4 != nil {
				p.expectedIPBlocks = append(p.expectedIPBlocks, &net.IPNet{IP: ip4, Mask: net.CIDRMask(32, 32)})
			} else {
				p.expectedIPBlocks = append(p.expectedIPBlocks, &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)})
			}
		} else {
			p.expectedDomains[network] = true
		}
	}

	proxyForUserLock.Lock()
	proxyForUser[user] = p
	proxyForUserLock.Unlock()
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", portInfo.Port))
	if err != nil {
		return nil, err
	}
	listener = tcpKeepAliveListener{listener.(*net.TCPListener)}

	go func() {
		<-shutdownChan
		listener.Close()
	}()

	server := http.Server{
		Handler:     p,
		IdleTimeout: c.CloseIdleConnections,
	}
	go server.Serve(listener)
	log.Printf("opened per-user listener for %s on port %d", user, portInfo.Port)

	return p, nil
}

func (p *perUserProxy) AllowIP(ip string) {
	p.allowedIPLock.Lock()
	p.allowedIPs[ip] = true
	p.allowedIPLock.Unlock()
	log.Printf("Added IP address %s, authenticated as %s, on port %d", ip, p.User, p.Port)

	domain := rdnsDomain(ip)
	if domain != "" {
		p.expectedNetLock.Lock()
		alreadyExpected := p.expectedDomains[domain]
		if !alreadyExpected {
			p.expectedDomains[domain] = true
		}
		p.expectedNetLock.Unlock()
		if !alreadyExpected {
			log.Printf("Added %s to the list of expected domains on port %d", domain, p.Port)
		}
	}
}

// rdnsDomain returns the base domain name of ip's reverse-DNS hostname (or the
// empty string if it is unavailable).
func rdnsDomain(ip string) string {
	var host string
	names, err := net.LookupAddr(ip)
	if err == nil && len(names) > 0 {
		host = names[0]
	}
	if host == "" {
		// If a PTR record isn't available, fall back to SOA.
		host, _ = rdnsSOA(ip)
	}
	if host == "" {
		return ""
	}
	host = strings.TrimSuffix(host, ".")
	ps := publicsuffix.List.PublicSuffix(host)
	dot := strings.LastIndex(strings.TrimSuffix(strings.TrimSuffix(host, ps), "."), ".")
	if dot == -1 {
		return host
	}
	return host[dot+1:]
}

var dnsServer string

func init() {
	conf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
	if err != nil || len(conf.Servers) == 0 {
		return
	}
	dnsServer = conf.Servers[0] + ":" + conf.Port
}

// rdnsSOA returns the nameserver from the SOA (start of authority) reverse-DNS
// record for ip.
func rdnsSOA(ip string) (server string, err error) {
	octets := strings.Split(ip, ".")
	if len(octets) != 4 {
		return "", errors.New("invalid IPv4 address")
	}
	octets[0], octets[1], octets[2], octets[3] = octets[3], octets[2], octets[1], octets[0]

	m := new(dns.Msg)

	for i := 0; i < 4; i++ {
		m.SetQuestion(strings.Join(octets[i:], ".")+".in-addr.arpa.", dns.TypeSOA)
		soa, err := dns.Exchange(m, dnsServer)
		if err != nil {
			return "", err
		}
		if soa.Rcode != dns.RcodeSuccess || len(soa.Answer) == 0 {
			continue
		}
		trsoa, ok := soa.Answer[0].(*dns.SOA)
		if !ok {
			continue
		}
		return trsoa.Ns, nil
	}

	return "", errors.New("SOA not found")
}

func (p *perUserProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	host, _, _ := net.SplitHostPort(r.RemoteAddr)
	p.allowedIPLock.RLock()
	ok := p.allowedIPs[host]
	p.allowedIPLock.RUnlock()

	if ok {
		p.Handler.ServeHTTP(w, r)
		return
	}

	// This client's IP address is not pre-authorized for this port, but
	// maybe it sent credentials and we can authorize it now.
	// We accept credentials in either the Proxy-Authorization header or
	// a URL parameter named "a".
	conf := getConfig()

	user, pass, ok := ProxyCredentials(r)
	if ok {
		if user == p.User && conf.ValidCredentials(user, pass) {
			p.AllowIP(host)
			p.Handler.ServeHTTP(w, r)
			return
		} else {
			log.Printf("Incorrect username or password in Proxy-Authorization header from %v: %s:%s, on port %d", r.RemoteAddr, user, pass, p.Port)
		}
	}

	user, pass, ok = decodeBase64Credentials(r.FormValue("a"))
	if ok {
		if user == p.User && conf.ValidCredentials(user, pass) {
			p.AllowIP(host)
			p.Handler.ServeHTTP(w, r)
			return
		} else {
			log.Printf("Incorrect username or password in URL parameter from %v: %s:%s, on port %d", r.RemoteAddr, user, pass, p.Port)
		}
	}

	expectedNetwork := false
	ip := net.ParseIP(host)
	for _, nw := range p.expectedIPBlocks {
		if nw.Contains(ip) {
			expectedNetwork = true
			break
		}
	}

	if !expectedNetwork {
		names, err := net.LookupAddr(host)
		if err == nil {
			p.expectedNetLock.RLock()
		nameListLoop:
			for _, name := range names {
				name = strings.TrimSuffix(name, ".")
				for {
					if p.expectedDomains[name] {
						expectedNetwork = true
						break nameListLoop
					}
					dot := strings.Index(name, ".")
					if dot == -1 {
						continue nameListLoop
					}
					name = name[dot+1:]
				}
			}
			p.expectedNetLock.RUnlock()
		}
	}

	if expectedNetwork {
		pf := platform(r.Header.Get("User-Agent"))
		if p.ClientPlatform != "" && pf == p.ClientPlatform || darwinPlatforms[p.ClientPlatform] && pf == "Darwin" {
			log.Printf("Accepting %s as %s because of User-Agent string %q", host, p.ClientPlatform, r.Header.Get("User-Agent"))
			p.AllowIP(host)
			p.Handler.ServeHTTP(w, r)
			return
		}
	}

	log.Printf("Missing required proxy authentication from %v to %v, on port %d (User-Agent: %s, domain: %s)", r.RemoteAddr, r.URL, p.Port, r.Header.Get("User-Agent"), rdnsDomain(host))
	conf.send407(w)
}

var proxyForUser = make(map[string]*perUserProxy)
var proxyForUserLock sync.RWMutex

func (c *config) openPerUserPorts() {
	for user, portInfo := range c.CustomPorts {
		proxyForUserLock.RLock()
		p := proxyForUser[user]
		proxyForUserLock.RUnlock()
		if p == nil {
			_, err := c.newPerUserProxy(user, portInfo)
			if err != nil {
				log.Printf("error opening per-user listener for %s: %v", user, err)
			}
		}
	}
}

type portListEntry struct {
	User                 string
	Port                 int
	Platform             string
	AuthenticatedClients []string
	ExpectedNetworks     []string
}

func handlePerUserPortList(w http.ResponseWriter, r *http.Request) {
	var data []portListEntry

	proxyForUserLock.RLock()

	for _, p := range proxyForUser {
		var clients []string
		p.allowedIPLock.RLock()

		for c := range p.allowedIPs {
			clients = append(clients, c)
		}

		p.allowedIPLock.RUnlock()

		var networks []string
		p.expectedNetLock.RLock()
		for d := range p.expectedDomains {
			networks = append(networks, d)
		}
		for _, nw := range p.expectedIPBlocks {
			networks = append(networks, nw.String())
		}
		p.expectedNetLock.RUnlock()

		data = append(data, portListEntry{
			User:                 p.User,
			Port:                 p.Port,
			Platform:             p.ClientPlatform,
			AuthenticatedClients: clients,
			ExpectedNetworks:     networks,
		})
	}

	proxyForUserLock.RUnlock()

	ServeJSON(w, r, data)
}

func handlePerUserAuthenticate(w http.ResponseWriter, r *http.Request) {
	user := r.FormValue("user")
	if user == "" {
		http.Error(w, `You must specify which user to authenticate with the "user" form parameter.`, 400)
		return
	}
	proxyForUserLock.RLock()
	p := proxyForUser[user]
	proxyForUserLock.RUnlock()
	if p == nil {
		http.Error(w, user+" does not have a per-user proxy port set up.", 500)
		return
	}

	ip := r.FormValue("ip")
	if ip == "" {
		http.Error(w, `You must specify the client IP address with the "ip" form parameter.`, 400)
		return
	}

	p.AllowIP(ip)
	fmt.Fprintf(w, "Added %s as an authenticated IP address for %s.", ip, user)
}
<commit_msg>More refinements to expected-network authentication.<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/miekg/dns"

	"golang.org/x/net/publicsuffix"
)

// handlePACFile serves an automatically-generated PAC (Proxy Auto-Config) file
// pointing to this proxy server.
func handlePACFile(w http.ResponseWriter, r *http.Request) {
	proxyAddr := r.Host

	if a := r.FormValue("a"); a != "" {
		if user, pass, ok := decodeBase64Credentials(a); ok {
			conf := getConfig()
			if conf.ValidCredentials(user, pass) {
				proxyForUserLock.RLock()
				p := proxyForUser[user]
				proxyForUserLock.RUnlock()
				if p != nil {
					client := r.RemoteAddr
					host, _, err := net.SplitHostPort(client)
					if err == nil {
						client = host
					}
					p.AllowIP(client)
					proxyHost, _, err := net.SplitHostPort(proxyAddr)
					if err == nil {
						proxyAddr = net.JoinHostPort(proxyHost, strconv.Itoa(p.Port))
					}
				}
			}
		}
	}

	w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig")
	fmt.Fprintf(w, pacTemplate, proxyAddr)
}

var pacTemplate = `function FindProxyForURL(url, host) {
	if (
		shExpMatch(url, "ftp:*") ||
		host == "localhost" ||
		isInNet(host, "127.0.0.0", "255.0.0.0") ||
		isInNet(host, "10.0.0.0", "255.0.0.0") ||
		isInNet(host, "172.16.0.0", "255.240.0.0") ||
		isInNet(host, "192.168.0.0", "255.255.0.0")
	) {
		return "DIRECT";
	}

	return "PROXY %s";
}`

type perUserProxy struct {
	User          string
	Port          int
	Handler       http.Handler
	allowedIPs    map[string]bool
	allowedIPLock sync.RWMutex

	expectedDomains  map[string]bool
	expectedIPBlocks []*net.IPNet
	ClientPlatform   string
	expectedNetLock  sync.RWMutex
}

func (p *perUserProxy) addExpectedNetwork(network string) {
	p.expectedNetLock.Lock()
	defer p.expectedNetLock.Unlock()
	if _, nw, err := net.ParseCIDR(network); err == nil {
		p.expectedIPBlocks = append(p.expectedIPBlocks, nw)
	} else if ip := net.ParseIP(network); ip != nil {
		if ip4 := ip.To4(); ip4 != nil {
			p.expectedIPBlocks = append(p.expectedIPBlocks, &net.IPNet{IP: ip4, Mask: net.CIDRMask(32, 32)})
		} else {
			p.expectedIPBlocks = append(p.expectedIPBlocks, &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)})
		}
	} else {
		domain, err := publicsuffix.EffectiveTLDPlusOne(network)
		if err != nil {
			domain = network
		}
		p.expectedDomains[domain] = true
	}
}

func (c *config) newPerUserProxy(user string, portInfo customPortInfo) (*perUserProxy, error) {
	p := &perUserProxy{
		User:            user,
		Port:            portInfo.Port,
		ClientPlatform:  portInfo.ClientPlatform,
		Handler:         proxyHandler{user: user},
		allowedIPs:      map[string]bool{},
		expectedDomains: map[string]bool{},
	}

	for _, network := range portInfo.ExpectedNetworks {
		p.addExpectedNetwork(network)
	}

	proxyForUserLock.Lock()
	proxyForUser[user] = p
	proxyForUserLock.Unlock()
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", portInfo.Port))
	if err != nil {
		return nil, err
	}
	listener = tcpKeepAliveListener{listener.(*net.TCPListener)}

	go func() {
		<-shutdownChan
		listener.Close()
	}()

	server := http.Server{
		Handler:     p,
		IdleTimeout: c.CloseIdleConnections,
	}
	go server.Serve(listener)
	log.Printf("opened per-user listener for %s on port %d", user, portInfo.Port)

	return p, nil
}

func (p *perUserProxy) AllowIP(ip string) {
	p.allowedIPLock.Lock()
	p.allowedIPs[ip] = true
	p.allowedIPLock.Unlock()
	log.Printf("Added IP address %s, authenticated as %s, on port %d", ip, p.User, p.Port)

	domain := rdnsDomain(ip)
	if domain != "" {
		p.expectedNetLock.Lock()
		alreadyExpected := p.expectedDomains[domain]
		if !alreadyExpected {
			p.expectedDomains[domain] = true
		}
		p.expectedNetLock.Unlock()
		if !alreadyExpected {
			log.Printf("Added %s to the list of expected domains on port %d", domain, p.Port)
		}
	}
}

// rdnsDomain returns the base domain name of ip's reverse-DNS hostname (or the
// empty string if it is unavailable).
func rdnsDomain(ip string) string {
	var host string
	names, err := net.LookupAddr(ip)
	if err == nil && len(names) > 0 {
		host = names[0]
	}
	if host == "" {
		// If a PTR record isn't available, fall back to SOA.
		host, _ = rdnsSOA(ip)
	}
	if host == "" {
		return ""
	}
	host = strings.TrimSuffix(host, ".")
	domain, err := publicsuffix.EffectiveTLDPlusOne(host)
	if err != nil {
		return host
	}
	return domain
}

var dnsServer string

func init() {
	conf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
	if err != nil || len(conf.Servers) == 0 {
		return
	}
	dnsServer = conf.Servers[0] + ":" + conf.Port
}

// rdnsSOA returns the nameserver from the SOA (start of authority) reverse-DNS
// record for ip.
func rdnsSOA(ip string) (server string, err error) {
	octets := strings.Split(ip, ".")
	if len(octets) != 4 {
		return "", errors.New("invalid IPv4 address")
	}
	octets[0], octets[1], octets[2], octets[3] = octets[3], octets[2], octets[1], octets[0]

	m := new(dns.Msg)

	for i := 0; i < 4; i++ {
		m.SetQuestion(strings.Join(octets[i:], ".")+".in-addr.arpa.", dns.TypeSOA)
		soa, err := dns.Exchange(m, dnsServer)
		if err != nil {
			return "", err
		}
		if soa.Rcode != dns.RcodeSuccess || len(soa.Answer) == 0 {
			continue
		}
		trsoa, ok := soa.Answer[0].(*dns.SOA)
		if !ok {
			continue
		}
		return trsoa.Ns, nil
	}

	return "", errors.New("SOA not found")
}

func (p *perUserProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	host, _, _ := net.SplitHostPort(r.RemoteAddr)
	p.allowedIPLock.RLock()
	ok := p.allowedIPs[host]
	p.allowedIPLock.RUnlock()

	if ok {
		p.Handler.ServeHTTP(w, r)
		return
	}

	// This client's IP address is not pre-authorized for this port, but
	// maybe it sent credentials and we can authorize it now.
	// We accept credentials in either the Proxy-Authorization header or
	// a URL parameter named "a".
	conf := getConfig()

	user, pass, ok := ProxyCredentials(r)
	if ok {
		if user == p.User && conf.ValidCredentials(user, pass) {
			p.AllowIP(host)
			p.Handler.ServeHTTP(w, r)
			return
		} else {
			log.Printf("Incorrect username or password in Proxy-Authorization header from %v: %s:%s, on port %d", r.RemoteAddr, user, pass, p.Port)
		}
	}

	user, pass, ok = decodeBase64Credentials(r.FormValue("a"))
	if ok {
		if user == p.User && conf.ValidCredentials(user, pass) {
			p.AllowIP(host)
			p.Handler.ServeHTTP(w, r)
			return
		} else {
			log.Printf("Incorrect username or password in URL parameter from %v: %s:%s, on port %d", r.RemoteAddr, user, pass, p.Port)
		}
	}

	expectedNetwork := false
	ip := net.ParseIP(host)
	p.expectedNetLock.RLock()
	expectedPlatform := p.ClientPlatform
	for _, nw := range p.expectedIPBlocks {
		if nw.Contains(ip) {
			expectedNetwork = true
			break
		}
	}
	p.expectedNetLock.RUnlock()

	domain := rdnsDomain(host)
	if !expectedNetwork && domain != "" {
		p.expectedNetLock.RLock()
		expectedNetwork = p.expectedDomains[domain]
		p.expectedNetLock.RUnlock()
	}

	if expectedNetwork {
		pf := platform(r.Header.Get("User-Agent"))
		if expectedPlatform != "" && pf == expectedPlatform || darwinPlatforms[expectedPlatform] && pf == "Darwin" {
			log.Printf("Accepting %s (domain: %s) as %s because of User-Agent string %q", host, domain, expectedPlatform, r.Header.Get("User-Agent"))
			p.AllowIP(host)
			p.Handler.ServeHTTP(w, r)
			return
		}
	}

	log.Printf("Missing required proxy authentication from %v to %v, on port %d (User-Agent: %s, domain: %s)", r.RemoteAddr, r.URL, p.Port, r.Header.Get("User-Agent"), rdnsDomain(host))
	conf.send407(w)
}

var proxyForUser = make(map[string]*perUserProxy)
var proxyForUserLock sync.RWMutex

func (c *config) openPerUserPorts() {
	for user, portInfo := range c.CustomPorts {
		proxyForUserLock.RLock()
		p := proxyForUser[user]
		proxyForUserLock.RUnlock()
		if p == nil {
			_, err := c.newPerUserProxy(user, portInfo)
			if err != nil {
				log.Printf("error opening per-user listener for %s: %v", user, err)
			}
		} else {
			for _, network := range portInfo.ExpectedNetworks {
				p.addExpectedNetwork(network)
			}
			if portInfo.ClientPlatform != "" {
				p.expectedNetLock.Lock()
				p.ClientPlatform = portInfo.ClientPlatform
				p.expectedNetLock.Unlock()
			}
		}
	}
}

type portListEntry struct {
	User                 string
	Port                 int
	Platform             string
	AuthenticatedClients []string
	ExpectedNetworks     []string
}

func handlePerUserPortList(w http.ResponseWriter, r *http.Request) {
	var data []portListEntry

	proxyForUserLock.RLock()

	for _, p := range proxyForUser {
		var clients []string
		p.allowedIPLock.RLock()

		for c := range p.allowedIPs {
			clients = append(clients, c)
		}

		p.allowedIPLock.RUnlock()

		var networks []string
		p.expectedNetLock.RLock()
		for d := range p.expectedDomains {
			networks = append(networks, d)
		}
		for _, nw := range p.expectedIPBlocks {
			networks = append(networks, nw.String())
		}
		clientPlatform := p.ClientPlatform
		p.expectedNetLock.RUnlock()

		data = append(data, portListEntry{
			User:                 p.User,
			Port:                 p.Port,
			Platform:             clientPlatform,
			AuthenticatedClients: clients,
			ExpectedNetworks:     networks,
		})
	}

	proxyForUserLock.RUnlock()

	ServeJSON(w, r, data)
}

func handlePerUserAuthenticate(w http.ResponseWriter, r *http.Request) {
	user := r.FormValue("user")
	if user == "" {
		http.Error(w, `You must specify which user to authenticate with the "user" form parameter.`, 400)
		return
	}
	proxyForUserLock.RLock()
	p := proxyForUser[user]
	proxyForUserLock.RUnlock()
	if p == nil {
		http.Error(w, user+" does not have a per-user proxy port set up.", 500)
		return
	}

	ip := r.FormValue("ip")
	if ip == "" {
		http.Error(w, `You must specify the client IP address with the "ip" form parameter.`, 400)
		return
	}

	p.AllowIP(ip)
	fmt.Fprintf(w, "Added %s as an authenticated IP address for %s.", ip, user)
}
<|endoftext|>


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

package gochimp

import (
	"fmt"
	"strings"
)

const (
	get_content_endpoint     string = "/campaigns/content.%s"
	campaign_create_endpoint string = "/campaigns/create.json"
	campaign_send_endpoint   string = "/campaigns/send.json"
	campaign_list_endpoint   string = "/campaigns/list.json"
)

func (a *ChimpAPI) GetContentAsXML(cid string, options map[string]interface{}) (ContentResponse, error) {
	return a.GetContent(cid, options, "xml")
}

func (a *ChimpAPI) GetContentAsJson(cid string, options map[string]interface{}) (ContentResponse, error) {
	return a.GetContent(cid, options, "json")
}

func (a *ChimpAPI) GetContent(cid string, options map[string]interface{}, contentFormat string) (ContentResponse, error) {
	var response ContentResponse
	if !strings.EqualFold(strings.ToLower(contentFormat), "xml") && strings.EqualFold(strings.ToLower(contentFormat), "json") {
		return response, fmt.Errorf("contentFormat should be one of xml or json, you passed an unsupported value %s", contentFormat)
	}
	var params map[string]interface{} = make(map[string]interface{})
	params["apikey"] = a.Key
	params["cid"] = cid
	params["options"] = options
	err := parseChimpJson(a, fmt.Sprintf(get_content_endpoint, contentFormat), params, &response)
	return response, err
}

func (a *ChimpAPI) CampaignCreate(req CampaignCreate) (CampaignResponse, error) {
	req.ApiKey = a.Key
	var response CampaignResponse
	err := parseChimpJson(a, campaign_create_endpoint, req, &response)
	return response, err
}

func (a *ChimpAPI) CampaignSend(cid string) (CampaignSendResponse, error) {
	req := campaignSend{
		ApiKey:     a.Key,
		CampaignId: cid,
	}
	var response CampaignSendResponse
	err := parseChimpJson(a, campaign_send_endpoint, req, &response)
	return response, err
}

func (a *ChimpAPI) CampaignList(req CampaignList) (CampaignListResponse, error) {
	req.ApiKey = a.Key
	var response CampaignListResponse
	err := parseChimpJson(a, campaign_list_endpoint, req, &response)
	return response, err
}

type CampaignListResponse struct {
	Total     int                `json:"total"`
	Campaigns []CampaignResponse `json:"data"`
}

type CampaignList struct {
	// A valid API Key for your user account. Get by visiting your API dashboard
	ApiKey string `json:"apikey"`

	// Filters to apply to this query - all are optional:
	Filter CampaignListFilter `json:"filters,omitempty"`

	// Control paging of campaigns, start results at this campaign #,
	// defaults to 1st page of data (page 0)
	Start int `json:"start,omitempty"`

	// Control paging of campaigns, number of campaigns to return with each call, defaults to 25 (max=1000)
	Limit int `json:"limit,omitempty"`

	// One of "create_time", "send_time", "title", "subject". Invalid values
	// will fall back on "create_time" - case insensitive.
	SortField string `json:"sort_field,omitempty"`

	// "DESC" for descending (default), "ASC" for Ascending. Invalid values
	// will fall back on "DESC" - case insensitive.
	OrderOrder string `json:"sort_dir,omitempty"`
}

type CampaignListFilter struct {
	// Return the campaign using a know campaign_id. Accepts
	// multiples separated by commas when not using exact matching.
	CampaignID string `json:"campaign_id,omitempty"`

	// Return the child campaigns using a known parent campaign_id.
	// Accepts multiples separated by commas when not using exact matching.
	ParentID string `json:"parent_id,omitempty"`

	// The list to send this campaign to - Get lists using ListList.
	// Accepts multiples separated by commas when not using exact matching.
	ListID string `json:"list_id,omitempty"`

	// Only show campaigns from this folder id - get folders using FoldersList.
	// Accepts multiples separated by commas when not using exact matching.
	FolderID int `json:"folder_id,omitempty"`

	// Only show campaigns using this template id - get templates using TemplatesList.
	// Accepts multiples separated by commas when not using exact matching.
	TemplateID int `json:"template_id,omitempty"`

	// Return campaigns of a specific status - one of "sent", "save", "paused", "schedule", "sending".
	// Accepts multiples separated by commas when not using exact matching.
	Status string `json:"status,omitempty"`

	// Return campaigns of a specific type - one of "regular", "plaintext", "absplit", "rss", "auto".
	// Accepts multiples separated by commas when not using exact matching.
	Type string `json:"type,omitempty"`

	// Only show campaigns that have this "From Name"
	FromName string `json:"from_name,omitempty"`

	// Only show campaigns that have this "Reply-to Email"
	FromEmail string `json:"from_email,omitempty"`

	// Only show campaigns that have this title
	Title string `json:"title"`

	// Only show campaigns that have this subject
	Subject string `json:"subject"`

	// Only show campaigns that have been sent since this date/time (in GMT) - -
	// 24 hour format in GMT, eg "2013-12-30 20:30:00" - if this is invalid the whole call fails
	SendTimeStart string `json:"sendtime_start,omitempty"`

	// Only show campaigns that have been sent before this date/time (in GMT) - -
	// 24 hour format in GMT, eg "2013-12-30 20:30:00" - if this is invalid the whole call fails
	SendTimeEnd string `json:"sendtime_end,omitempty"`

	// Whether to return just campaigns with or without segments
	UsesSegment bool `json:"uses_segment,omitempty"`

	// Flag for whether to filter on exact values when filtering, or search within content for
	// filter values - defaults to true. Using this disables the use of any filters that accept multiples.
	Exact bool `json:"exact,omitempty"`
}

type campaignSend struct {
	ApiKey     string `json:"apikey"`
	CampaignId string `json:"cid"`
}

type CampaignSendResponse struct {
	Complete bool `json:"complete"`
}

type CampaignCreate struct {
	ApiKey  string                `json:"apikey"`
	Type    string                `json:"type"`
	Options CampaignCreateOptions `json:"options"`
	Content CampaignCreateContent `json:"content"`
}

type CampaignCreateOptions struct {
	// ListID is the list to send this campaign to
	ListID string `json:"list_id"`

	// Title is the title on created campaign
	Title string `json:"title"`
	// TemplateID is the user-created template from which the HTML
	// content of the campaign should be created
	TemplateID string `json:"template_id"`

	// Subject is the subject line for your campaign message
	Subject string `json:"subject"`

	// FromEmail is the From: email address for your campaign message
	FromEmail string `json:"from_email"`

	// FromName is the From: name for your campaign message (not an email address)
	FromName string `json:"from_name"`

	// ToName is the To: name recipients will see (not email address)
	ToName string `json:"to_name"`
}

type CampaignCreateContent struct {
	// HTML is the raw/pasted HTML content for the campaign
	HTML string `json:"html"`

	// When using a template instead of raw HTML, each key
	// in the map should be the unique mc:edit area name from
	// the template.
	Sections map[string]string `json:"sections,omitempty"`

	// Text is the plain-text version of the body
	Text string `json:"text"`

	// MailChimp will pull in content from this URL. Note,
	// this will override any other content options - for lists
	// with Email Format options, you'll need to turn on
	// generate_text as well
	URL string `json:"url,omitempty"`

	// A Base64 encoded archive file for MailChimp to import all
	// media from. Note, this will override any other content
	// options - for lists with Email Format options, you'll
	// need to turn on generate_text as well
	Archive string `json:"archive,omitempty"`

	// ArchiveType only applies to the Archive field. Supported
	// formats are: zip, tar.gz, tar.bz2, tar, tgz, tbz.
	// If not included, we will default to zip
	ArchiveType string `json:"archive_options,omitempty"`
}

type CampaignResponse struct {
	Id                 string           `json:"id"`
	WebId              int              `json:"web_id"`
	ListId             string           `json:"list_id"`
	FolderId           int              `json:"folder_id"`
	TemplateId         int              `json:"template_id"`
	ContentType        string           `json:"content_type"`
	ContentEditedBy    string           `json:"content_edited_by"`
	Title              string           `json:"title"`
	Type               string           `json:"type"`
	CreateTime         string           `json:"create_time"`
	SendTime           string           `json:"send_time"`
	ContentUpdatedTime string           `json:"content_updated_time"`
	Status             string           `json:"status"`
	FromName           string           `json:"from_name"`
	FromEmail          string           `json:"from_email"`
	Subject            string           `json:"subject"`
	ToName             string           `json:"to_name"`
	ArchiveURL         string           `json:"archive_url"`
	ArchiveURLLong     string           `json:"archive_url_long"`
	EmailsSent         int              `json:"emails_sent"`
	Analytics          string           `json:"analytics"`
	AnalyticsTag       string           `json:"analytics_tag"`
	InlineCSS          bool             `json:"inline_css"`
	Authenticate       bool             `json:"authenticate"`
	Ecommm360          bool             `json:"ecomm360"`
	AutoTweet          bool             `json:"auto_tweet"`
	AutoFacebookPort   string           `json:"auto_fb_post"`
	AutoFooter         bool             `json:"auto_footer"`
	Timewarp           bool             `json:"timewarp"`
	TimewarpSchedule   string           `json:"timewarp_schedule,omitempty"`
	Tracking           CampaignTracking `json:"tracking"`
	ParentId           string           `json:"parent_id"`
	IsChild            bool             `json:"is_child"`
	TestsRemaining     int              `json:"tests_remain"`
	SegmentText        string           `json:"segment_text"`
}

type CampaignTracking struct {
	HTMLClicks bool `json:"html_clicks"`
	TextClicks bool `json:"text_clicks"`
	Opens      bool `json:"opens"`
}

type ContentResponse struct {
	Html string `json:"html"`
	Text string `json:"text"`
}
<commit_msg>comments fix<commit_after>// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gochimp

import (
	"fmt"
	"strings"
)

const (
	get_content_endpoint     string = "/campaigns/content.%s"
	campaign_create_endpoint string = "/campaigns/create.json"
	campaign_send_endpoint   string = "/campaigns/send.json"
	campaign_list_endpoint   string = "/campaigns/list.json"
)

func (a *ChimpAPI) GetContentAsXML(cid string, options map[string]interface{}) (ContentResponse, error) {
	return a.GetContent(cid, options, "xml")
}

func (a *ChimpAPI) GetContentAsJson(cid string, options map[string]interface{}) (ContentResponse, error) {
	return a.GetContent(cid, options, "json")
}

func (a *ChimpAPI) GetContent(cid string, options map[string]interface{}, contentFormat string) (ContentResponse, error) {
	var response ContentResponse
	if !strings.EqualFold(strings.ToLower(contentFormat), "xml") && strings.EqualFold(strings.ToLower(contentFormat), "json") {
		return response, fmt.Errorf("contentFormat should be one of xml or json, you passed an unsupported value %s", contentFormat)
	}
	var params map[string]interface{} = make(map[string]interface{})
	params["apikey"] = a.Key
	params["cid"] = cid
	params["options"] = options
	err := parseChimpJson(a, fmt.Sprintf(get_content_endpoint, contentFormat), params, &response)
	return response, err
}

func (a *ChimpAPI) CampaignCreate(req CampaignCreate) (CampaignResponse, error) {
	req.ApiKey = a.Key
	var response CampaignResponse
	err := parseChimpJson(a, campaign_create_endpoint, req, &response)
	return response, err
}

func (a *ChimpAPI) CampaignSend(cid string) (CampaignSendResponse, error) {
	req := campaignSend{
		ApiKey:     a.Key,
		CampaignId: cid,
	}
	var response CampaignSendResponse
	err := parseChimpJson(a, campaign_send_endpoint, req, &response)
	return response, err
}

func (a *ChimpAPI) CampaignList(req CampaignList) (CampaignListResponse, error) {
	req.ApiKey = a.Key
	var response CampaignListResponse
	err := parseChimpJson(a, campaign_list_endpoint, req, &response)
	return response, err
}

type CampaignListResponse struct {
	Total     int                `json:"total"`
	Campaigns []CampaignResponse `json:"data"`
}

type CampaignList struct {
	// A valid API Key for your user account. Get by visiting your API dashboard
	ApiKey string `json:"apikey"`

	// Filters to apply to this query - all are optional:
	Filter CampaignListFilter `json:"filters,omitempty"`

	// Control paging of campaigns, start results at this campaign #,
	// defaults to 1st page of data (page 0)
	Start int `json:"start,omitempty"`

	// Control paging of campaigns, number of campaigns to return with each call, defaults to 25 (max=1000)
	Limit int `json:"limit,omitempty"`

	// One of "create_time", "send_time", "title", "subject". Invalid values
	// will fall back on "create_time" - case insensitive.
	SortField string `json:"sort_field,omitempty"`

	// "DESC" for descending (default), "ASC" for Ascending. Invalid values
	// will fall back on "DESC" - case insensitive.
	OrderOrder string `json:"sort_dir,omitempty"`
}

type CampaignListFilter struct {
	// Return the campaign using a know campaign_id. Accepts
	// multiples separated by commas when not using exact matching.
	CampaignID string `json:"campaign_id,omitempty"`

	// Return the child campaigns using a known parent campaign_id.
	// Accepts multiples separated by commas when not using exact matching.
	ParentID string `json:"parent_id,omitempty"`

	// The list to send this campaign to - Get lists using ListList.
	// Accepts multiples separated by commas when not using exact matching.
	ListID string `json:"list_id,omitempty"`

	// Only show campaigns from this folder id - get folders using FoldersList.
	// Accepts multiples separated by commas when not using exact matching.
	FolderID int `json:"folder_id,omitempty"`

	// Only show campaigns using this template id - get templates using TemplatesList.
	// Accepts multiples separated by commas when not using exact matching.
	TemplateID int `json:"template_id,omitempty"`

	// Return campaigns of a specific status - one of "sent", "save", "paused", "schedule", "sending".
	// Accepts multiples separated by commas when not using exact matching.
	Status string `json:"status,omitempty"`

	// Return campaigns of a specific type - one of "regular", "plaintext", "absplit", "rss", "auto".
	// Accepts multiples separated by commas when not using exact matching.
	Type string `json:"type,omitempty"`

	// Only show campaigns that have this "From Name"
	FromName string `json:"from_name,omitempty"`

	// Only show campaigns that have this "Reply-to Email"
	FromEmail string `json:"from_email,omitempty"`

	// Only show campaigns that have this title
	Title string `json:"title"`

	// Only show campaigns that have this subject
	Subject string `json:"subject"`

	// Only show campaigns that have been sent since this date/time (in GMT) - -
	// 24 hour format in GMT, eg "2013-12-30 20:30:00" - if this is invalid the whole call fails
	SendTimeStart string `json:"sendtime_start,omitempty"`

	// Only show campaigns that have been sent before this date/time (in GMT) - -
	// 24 hour format in GMT, eg "2013-12-30 20:30:00" - if this is invalid the whole call fails
	SendTimeEnd string `json:"sendtime_end,omitempty"`

	// Whether to return just campaigns with or without segments
	UsesSegment bool `json:"uses_segment,omitempty"`

	// Flag for whether to filter on exact values when filtering, or search within content for
	// filter values - defaults to true. Using this disables the use of any filters that accept multiples.
	Exact bool `json:"exact,omitempty"`
}

type campaignSend struct {
	ApiKey     string `json:"apikey"`
	CampaignId string `json:"cid"`
}

type CampaignSendResponse struct {
	Complete bool `json:"complete"`
}

type CampaignCreate struct {
	ApiKey  string                `json:"apikey"`
	Type    string                `json:"type"`
	Options CampaignCreateOptions `json:"options"`
	Content CampaignCreateContent `json:"content"`
}

type CampaignCreateOptions struct {
	// ListID is the list to send this campaign to
	ListID string `json:"list_id"`

	// Title is the title for new campaign
	Title string `json:"title"`
	// TemplateID is the user-created template from which the HTML
	// content of the campaign should be created
	TemplateID string `json:"template_id"`

	// Subject is the subject line for your campaign message
	Subject string `json:"subject"`

	// FromEmail is the From: email address for your campaign message
	FromEmail string `json:"from_email"`

	// FromName is the From: name for your campaign message (not an email address)
	FromName string `json:"from_name"`

	// ToName is the To: name recipients will see (not email address)
	ToName string `json:"to_name"`
}

type CampaignCreateContent struct {
	// HTML is the raw/pasted HTML content for the campaign
	HTML string `json:"html"`

	// When using a template instead of raw HTML, each key
	// in the map should be the unique mc:edit area name from
	// the template.
	Sections map[string]string `json:"sections,omitempty"`

	// Text is the plain-text version of the body
	Text string `json:"text"`

	// MailChimp will pull in content from this URL. Note,
	// this will override any other content options - for lists
	// with Email Format options, you'll need to turn on
	// generate_text as well
	URL string `json:"url,omitempty"`

	// A Base64 encoded archive file for MailChimp to import all
	// media from. Note, this will override any other content
	// options - for lists with Email Format options, you'll
	// need to turn on generate_text as well
	Archive string `json:"archive,omitempty"`

	// ArchiveType only applies to the Archive field. Supported
	// formats are: zip, tar.gz, tar.bz2, tar, tgz, tbz.
	// If not included, we will default to zip
	ArchiveType string `json:"archive_options,omitempty"`
}

type CampaignResponse struct {
	Id                 string           `json:"id"`
	WebId              int              `json:"web_id"`
	ListId             string           `json:"list_id"`
	FolderId           int              `json:"folder_id"`
	TemplateId         int              `json:"template_id"`
	ContentType        string           `json:"content_type"`
	ContentEditedBy    string           `json:"content_edited_by"`
	Title              string           `json:"title"`
	Type               string           `json:"type"`
	CreateTime         string           `json:"create_time"`
	SendTime           string           `json:"send_time"`
	ContentUpdatedTime string           `json:"content_updated_time"`
	Status             string           `json:"status"`
	FromName           string           `json:"from_name"`
	FromEmail          string           `json:"from_email"`
	Subject            string           `json:"subject"`
	ToName             string           `json:"to_name"`
	ArchiveURL         string           `json:"archive_url"`
	ArchiveURLLong     string           `json:"archive_url_long"`
	EmailsSent         int              `json:"emails_sent"`
	Analytics          string           `json:"analytics"`
	AnalyticsTag       string           `json:"analytics_tag"`
	InlineCSS          bool             `json:"inline_css"`
	Authenticate       bool             `json:"authenticate"`
	Ecommm360          bool             `json:"ecomm360"`
	AutoTweet          bool             `json:"auto_tweet"`
	AutoFacebookPort   string           `json:"auto_fb_post"`
	AutoFooter         bool             `json:"auto_footer"`
	Timewarp           bool             `json:"timewarp"`
	TimewarpSchedule   string           `json:"timewarp_schedule,omitempty"`
	Tracking           CampaignTracking `json:"tracking"`
	ParentId           string           `json:"parent_id"`
	IsChild            bool             `json:"is_child"`
	TestsRemaining     int              `json:"tests_remain"`
	SegmentText        string           `json:"segment_text"`
}

type CampaignTracking struct {
	HTMLClicks bool `json:"html_clicks"`
	TextClicks bool `json:"text_clicks"`
	Opens      bool `json:"opens"`
}

type ContentResponse struct {
	Html string `json:"html"`
	Text string `json:"text"`
}
<|endoftext|>


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

// createmock is used to generate source code for mock versions of interfaces
// from installed packages.
package main

import (
	"errors"
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"regexp"
	"text/template"

	// Ensure that the generate package, which is used by the generated code, is
	// installed by goinstall.
	_ "github.com/jacobsa/oglemock/generate"
)

// A template for generated code that is used to print the result.
const tmplStr =
`
{{$inputPkg := .InputPkg}}
{{$outputPkg := .OutputPkg}}

package main

import (
	{{range $identifier, $import := .Imports}}
		{{$identifier}} "{{$import}}"
	{{end}}
)

func getTypeForPtr(ptr interface{}) reflect.Type {
	return reflect.TypeOf(ptr).Elem()
}

func main() {
	// Reduce noise in logging output.
	log.SetFlags(0)

	interfaces := []reflect.Type{
		{{range $typeName := .TypeNames}}
			getTypeForPtr((*{{base $inputPkg}}.{{$typeName}})(nil)),
		{{end}}
	}

	err := generate.GenerateMockSource(os.Stdout, "{{$outputPkg}}", interfaces)
	if err != nil {
		log.Fatalf("Error generating mock source: %v", err)
	}
}
`

// A map from import identifier to package to use that identifier for,
// containing elements for each import needed by the generated code.
type importMap map[string]string

type tmplArg struct {
	InputPkg string
	OutputPkg string

	// Imports needed by the generated code.
	Imports importMap

	// Types to be mocked, relative to their package's name.
	TypeNames []string
}

var unknownPackageRegexp =
	regexp.MustCompile(
		`(?s)imports ([\pL_0-9/]+): package could not be found`)

var undefinedInterfaceRegexp =
	regexp.MustCompile(`tool\.go:\d+: undefined: [\pL_0-9]+\.([\pL_0-9]+)`)

// Does the 'go build' output indicate that a package wasn't found? If so,
// return the name of the package.
func findUnknownPackage(output []byte) *string {
	if match := unknownPackageRegexp.FindSubmatch(output); match != nil {
		res := string(match[1])
		return &res
	}

	return nil
}

// Does the 'go build' output indicate that an interface wasn't found? If so,
// return the name of the interface.
func findUndefinedInterface(output []byte) *string {
	if match := undefinedInterfaceRegexp.FindSubmatch(output); match != nil {
		res := string(match[1])
		return &res
	}

	return nil
}

// Split out from main so that deferred calls are executed even in the event of
// an error.
func run() error {
	// Reduce noise in logging output.
	log.SetFlags(0)

	// Check the command-line arguments.
	flag.Parse()

	cmdLineArgs := flag.Args()
	if len(cmdLineArgs) < 2 {
		return errors.New("Usage: createmock [package] [interface ...]")
	}

	// Create a temporary directory inside of $GOPATH to hold generated code.
	buildPkg, err := build.Import("github.com/jacobsa/oglemock", "", build.FindOnly)
	if err != nil {
		return errors.New(fmt.Sprintf("Couldn't find oglemock in $GOPATH: %v", err))
	}

	tmpDir, err := ioutil.TempDir(buildPkg.SrcRoot, "tmp-createmock-")
	if err != nil {
		return errors.New(fmt.Sprintf("Creating temp dir: %v", err))
	}

	defer os.RemoveAll(tmpDir)

	// Create a file to hold generated code.
	codeFile, err := os.Create(path.Join(tmpDir, "tool.go"))
	if err != nil {
		return errors.New(fmt.Sprintf("Couldn't create a file to hold code: %v", err))
	}

	// Create an appropriate path for the built binary.
	binaryPath := path.Join(tmpDir, "tool")

	// Create an appropriate template argument.
	var arg tmplArg
	arg.InputPkg = cmdLineArgs[0]
	arg.OutputPkg = "mock_" + path.Base(arg.InputPkg)
	arg.TypeNames = cmdLineArgs[1:]

	arg.Imports = make(importMap)
	arg.Imports[path.Base(arg.InputPkg)] = arg.InputPkg
	arg.Imports["generate"] = "github.com/jacobsa/oglemock/generate"
	arg.Imports["log"] = "log"
	arg.Imports["os"] = "os"
	arg.Imports["reflect"] = "reflect"

	// Execute the template to generate code that will itself generate the mock
	// code. Write the code to the temp file.
	tmpl := template.Must(
		template.New("code").Funcs(
			template.FuncMap{
			"base": path.Base,
		}).Parse(tmplStr))
	if err := tmpl.Execute(codeFile, arg); err != nil {
		return errors.New(fmt.Sprintf("Error executing template: %v", err))
	}

	codeFile.Close()

	// Attempt to build the code.
	cmd := exec.Command("go", "build", "-o", binaryPath)
	cmd.Dir = tmpDir
	buildOutput, err := cmd.CombinedOutput()

	if err != nil {
		// Did the compilation fail due to the user-specified package not being found?
		if pkg := findUnknownPackage(buildOutput); pkg != nil && *pkg == arg.InputPkg {
			return errors.New(fmt.Sprintf("Unknown package: %s", *pkg))
		}

		// Did the compilation fail due to an unknown interface?
		if in := findUndefinedInterface(buildOutput); in != nil {
			return errors.New(fmt.Sprintf("Unknown interface: %s", *in))
		}

		// Otherwise return a generic error.
		return errors.New(fmt.Sprintf(
			"%s\n\nError building generated code:\n\n" +
				"    %v\n\n Please report this oglemock bug.",
			buildOutput,
		err))
	}

	// Run the binary.
	cmd = exec.Command(binaryPath)
	binaryOutput, err := cmd.CombinedOutput()

	if err != nil {
		return errors.New(fmt.Sprintf(
			"%s\n\nError running generated code:\n\n" +
				"    %v\n\n Please report this oglemock bug.",
			binaryOutput,
		err))
	}

	// Copy its output.
	_, err = os.Stdout.Write(binaryOutput)
	if err != nil {
		return errors.New(fmt.Sprintf("Error copying binary output: %v", err))
	}

	return nil
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}
<commit_msg>Fixed some problems for weekly.2012-03-04.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// createmock is used to generate source code for mock versions of interfaces
// from installed packages.
package main

import (
	"errors"
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"regexp"
	"text/template"

	// Ensure that the generate package, which is used by the generated code, is
	// installed by goinstall.
	_ "github.com/jacobsa/oglemock/generate"
)

// A template for generated code that is used to print the result.
const tmplStr =
`
{{$inputPkg := .InputPkg}}
{{$outputPkg := .OutputPkg}}

package main

import (
	{{range $identifier, $import := .Imports}}
		{{$identifier}} "{{$import}}"
	{{end}}
)

func getTypeForPtr(ptr interface{}) reflect.Type {
	return reflect.TypeOf(ptr).Elem()
}

func main() {
	// Reduce noise in logging output.
	log.SetFlags(0)

	interfaces := []reflect.Type{
		{{range $typeName := .TypeNames}}
			getTypeForPtr((*{{base $inputPkg}}.{{$typeName}})(nil)),
		{{end}}
	}

	err := generate.GenerateMockSource(os.Stdout, "{{$outputPkg}}", interfaces)
	if err != nil {
		log.Fatalf("Error generating mock source: %v", err)
	}
}
`

// A map from import identifier to package to use that identifier for,
// containing elements for each import needed by the generated code.
type importMap map[string]string

type tmplArg struct {
	InputPkg string
	OutputPkg string

	// Imports needed by the generated code.
	Imports importMap

	// Types to be mocked, relative to their package's name.
	TypeNames []string
}

var unknownPackageRegexp =
	regexp.MustCompile(
		`tool\.go:\d+:\d+: import "([^"]+)": cannot find package`)

var undefinedInterfaceRegexp =
	regexp.MustCompile(`tool\.go:\d+: undefined: [\pL_0-9]+\.([\pL_0-9]+)`)

// Does the 'go build' output indicate that a package wasn't found? If so,
// return the name of the package.
func findUnknownPackage(output []byte) *string {
	if match := unknownPackageRegexp.FindSubmatch(output); match != nil {
		res := string(match[1])
		return &res
	}

	return nil
}

// Does the 'go build' output indicate that an interface wasn't found? If so,
// return the name of the interface.
func findUndefinedInterface(output []byte) *string {
	if match := undefinedInterfaceRegexp.FindSubmatch(output); match != nil {
		res := string(match[1])
		return &res
	}

	return nil
}

// Split out from main so that deferred calls are executed even in the event of
// an error.
func run() error {
	// Reduce noise in logging output.
	log.SetFlags(0)

	// Check the command-line arguments.
	flag.Parse()

	cmdLineArgs := flag.Args()
	if len(cmdLineArgs) < 2 {
		return errors.New("Usage: createmock [package] [interface ...]")
	}

	// Create a temporary directory inside of $GOPATH to hold generated code.
	buildPkg, err := build.Import("github.com/jacobsa/oglemock", "", build.FindOnly)
	if err != nil {
		return errors.New(fmt.Sprintf("Couldn't find oglemock in $GOPATH: %v", err))
	}

	tmpDir, err := ioutil.TempDir(buildPkg.SrcRoot, "tmp-createmock-")
	if err != nil {
		return errors.New(fmt.Sprintf("Creating temp dir: %v", err))
	}

	defer os.RemoveAll(tmpDir)

	// Create a file to hold generated code.
	codeFile, err := os.Create(path.Join(tmpDir, "tool.go"))
	if err != nil {
		return errors.New(fmt.Sprintf("Couldn't create a file to hold code: %v", err))
	}

	// Create an appropriate path for the built binary.
	binaryPath := path.Join(tmpDir, "tool")

	// Create an appropriate template argument.
	var arg tmplArg
	arg.InputPkg = cmdLineArgs[0]
	arg.OutputPkg = "mock_" + path.Base(arg.InputPkg)
	arg.TypeNames = cmdLineArgs[1:]

	arg.Imports = make(importMap)
	arg.Imports[path.Base(arg.InputPkg)] = arg.InputPkg
	arg.Imports["generate"] = "github.com/jacobsa/oglemock/generate"
	arg.Imports["log"] = "log"
	arg.Imports["os"] = "os"
	arg.Imports["reflect"] = "reflect"

	// Execute the template to generate code that will itself generate the mock
	// code. Write the code to the temp file.
	tmpl := template.Must(
		template.New("code").Funcs(
			template.FuncMap{
			"base": path.Base,
		}).Parse(tmplStr))
	if err := tmpl.Execute(codeFile, arg); err != nil {
		return errors.New(fmt.Sprintf("Error executing template: %v", err))
	}

	codeFile.Close()

	// Attempt to build the code.
	cmd := exec.Command("go", "build", "-o", binaryPath)
	cmd.Dir = tmpDir
	buildOutput, err := cmd.CombinedOutput()

	if err != nil {
		// Did the compilation fail due to the user-specified package not being found?
		if pkg := findUnknownPackage(buildOutput); pkg != nil && *pkg == arg.InputPkg {
			return errors.New(fmt.Sprintf("Unknown package: %s", *pkg))
		}

		// Did the compilation fail due to an unknown interface?
		if in := findUndefinedInterface(buildOutput); in != nil {
			return errors.New(fmt.Sprintf("Unknown interface: %s", *in))
		}

		// Otherwise return a generic error.
		return errors.New(fmt.Sprintf(
			"%s\n\nError building generated code:\n\n" +
				"    %v\n\nPlease report this oglemock bug.",
			buildOutput,
		err))
	}

	// Run the binary.
	cmd = exec.Command(binaryPath)
	binaryOutput, err := cmd.CombinedOutput()

	if err != nil {
		return errors.New(fmt.Sprintf(
			"%s\n\nError running generated code:\n\n" +
				"    %v\n\n Please report this oglemock bug.",
			binaryOutput,
		err))
	}

	// Copy its output.
	_, err = os.Stdout.Write(binaryOutput)
	if err != nil {
		return errors.New(fmt.Sprintf("Error copying binary output: %v", err))
	}

	return nil
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/mnkd/slackposter"
	// "strings"
)

type Usernames []string

func (usernames Usernames) isContain(username string) bool {
	for _, u := range usernames {
		if u == username {
			return true
		}
	}
	return false
}

func UsernameFromRequestedReviewers(requestedReviewers []PullRequestUser) []string {
	var array []string
	for _, r := range requestedReviewers {
		array = append(array, r.Login)
	}
	return array
}

type MessageBuilder struct {
	GitHubOwner     string
	GitHubRepo      string
	UsersManager    UsersManager
	MinimumApproved int
}

func (builder MessageBuilder) fieldTitleString(pull PullRequest) string {
	return fmt.Sprintf("#%d", pull.Number)
}

func (builder MessageBuilder) fieldValueString(pull PullRequest) string {
	return fmt.Sprintf("<%s|%s> by %s", pull.HTMLURL, pull.Title, pull.User.Login)
}

func (builder MessageBuilder) allReviewersString(pull PullRequest, requestedReviewers []PullRequestUser) string {
	if len(requestedReviewers) == 0 {
		name := builder.UsersManager.ConvertGitHubToSlack(pull.User.Login)
		return "@" + name + " *Reviewers の指定をお願いします*"
	}

	var str = ""
	for _, reviewer := range requestedReviewers {
		name := builder.UsersManager.ConvertGitHubToSlack(reviewer.Login)
		str += "@" + name + " "
	}
	return str
}

func (builder MessageBuilder) reviewerString(pull PullRequest, reviewers []string) string {
	var str = ""
	for _, reviewer := range reviewers {
		login := builder.UsersManager.ConvertGitHubToSlack(reviewer)
		str += "@" + login + " "
	}
	return str
}

func (builder MessageBuilder) BudildSummary(pullsCount int) string {
	repo := builder.GitHubOwner + "/" + builder.GitHubRepo
	url := "https://github.com/" + repo
	link := fmt.Sprintf("<%s|%s>", url, repo)

	var summary string
	switch pullsCount {
	case 0:
		summary = fmt.Sprintf("There's no open pull request for %s :tada: Let's take a break :dango: :tea:", link)
	case 1:
		summary = fmt.Sprintf("There's only one open pull request for %s :point_up:", link)
	default:
		summary = fmt.Sprintf("I found %d open pull requests for %s:\n", pullsCount, link)
	}
	return summary
}

func (builder MessageBuilder) isAssigneeReview(review Review, pull PullRequest) bool {
	for _, assignee := range pull.Assignees {
		if assignee.Login == review.User.Login {
			return true
		}
	}
	return false
}

func (builder MessageBuilder) isRequestedReviewerReview(review Review, pull PullRequest) bool {
	for _, requestedReviewer := range pull.RequestedReviewers {
		if requestedReviewer.Login == review.User.Login {
			return true
		}
	}
	return false
}

func (builder MessageBuilder) BuildField(pull PullRequest, reviews []Review) (slackposter.Field, AttachmentType) {
	var approvedUsers Usernames    // User who have approved this pull request.
	var notApprovedUsers Usernames // User who have not approved this pull request.
	requestedReviewers := UsernameFromRequestedReviewers(pull.RequestedReviewers)

	for _, review := range reviews {
		username := builder.UsersManager.ConvertGitHubToSlack(review.User.Login)

		if review.IsApproved() {
			approvedUsers = append(approvedUsers, username)
		} else if builder.isAssigneeReview(review, pull) == false &&
			builder.isRequestedReviewerReview(review, pull) == false &&
			notApprovedUsers.isContain(username) == false {
			notApprovedUsers = append(notApprovedUsers, username)
		}
	}

	fmt.Println("requestedReviewers:", requestedReviewers)
	fmt.Println("approvedUsers:", approvedUsers)
	fmt.Println("notApprovedUsers:", notApprovedUsers)

	var attachmentType AttachmentType
	title := builder.fieldTitleString(pull)
	value := builder.fieldValueString(pull)
	pullUsername := builder.UsersManager.ConvertGitHubToSlack(pull.User.Login)
	name := ""

	if len(approvedUsers) >= builder.MinimumApproved {
		attachmentType = MERGE
		name = "@" + pullUsername

	} else if len(requestedReviewers) > 0 {
		attachmentType = REVIEW
		name = builder.reviewerString(pull, requestedReviewers) + " " + builder.reviewerString(pull, notApprovedUsers)

	} else if len(requestedReviewers) == 0 && len(reviews) == 0 {
		attachmentType = ASSIGN_REVIEWER
		name = "@" + pullUsername

	} else {
		attachmentType = CHECK
		name = "@" + pullUsername + " " + builder.reviewerString(pull, notApprovedUsers)
	}

	value = value + " => " + name

	field := slackposter.Field{
		Title: title,
		Value: value,
		Short: false,
	}

	return field, attachmentType
}

func (builder MessageBuilder) BuildAttachmentWithType(attachmentType AttachmentType) slackposter.Attachment {
	var color, message string
	switch attachmentType {
	case MERGE:
		color = "good"
		message = ":+1::+1: *マージお願いします*"
	case REVIEW:
		color = "warning"
		message = ":smiley: *引き続きレビューお願いします！*"
	case CHECK:
		color = "warning"
		message = ":wink: *進捗／Reviewers の再指定／APPROVED し忘れ／など確認お願いします！*"
	case ASSIGN_REVIEWER:
		color = "danger"
		message = ":sweat_smile: *Reviewers の指定をお願いします！*"
	}

	var attachment slackposter.Attachment
	attachment = slackposter.Attachment{
		Fallback: message,
		Text:     message,
		Color:    color,
		Fields:   []slackposter.Field{},
		MrkdwnIn: []string{"text", "fallback"},
		// AuthorIcon: "https://assets-cdn.github.com/images/modules/logos_page/GitHub-Mark.png",
		// AuthorLink: "https://github.com/",
		// AuthorName: "GitHub",
	}
	return attachment
}

func NewMessageBuilder(gh GitHubAPI, usersManager UsersManager, config Config) MessageBuilder {
	return MessageBuilder{
		GitHubOwner:     gh.Owner,
		GitHubRepo:      gh.Repo,
		UsersManager:    usersManager,
		MinimumApproved: config.GitHub.MinimumApproved,
	}
}
<commit_msg>:ant: fix logic of building a message<commit_after>package main

import (
	"fmt"

	"github.com/mnkd/slackposter"
	// "strings"
)

type Usernames []string

func (usernames Usernames) isContain(username string) bool {
	for _, u := range usernames {
		if u == username {
			return true
		}
	}
	return false
}

func UsernameFromRequestedReviewers(requestedReviewers []PullRequestUser) []string {
	var array []string
	for _, r := range requestedReviewers {
		array = append(array, r.Login)
	}
	return array
}

func (usernames Usernames) remove(username string) []string {
	result := []string{}
	for _, v := range usernames {
		if v != username {
			result = append(result, v)
		}
	}
	return result
}

type MessageBuilder struct {
	GitHubOwner     string
	GitHubRepo      string
	UsersManager    UsersManager
	MinimumApproved int
}

func (builder MessageBuilder) fieldTitleString(pull PullRequest) string {
	return fmt.Sprintf("#%d", pull.Number)
}

func (builder MessageBuilder) fieldValueString(pull PullRequest) string {
	return fmt.Sprintf("<%s|%s> by %s", pull.HTMLURL, pull.Title, pull.User.Login)
}

func (builder MessageBuilder) allReviewersString(pull PullRequest, requestedReviewers []PullRequestUser) string {
	if len(requestedReviewers) == 0 {
		name := builder.UsersManager.ConvertGitHubToSlack(pull.User.Login)
		return "@" + name + " *Reviewers の指定をお願いします*"
	}

	var str = ""
	for _, reviewer := range requestedReviewers {
		name := builder.UsersManager.ConvertGitHubToSlack(reviewer.Login)
		str += "@" + name + " "
	}
	return str
}

func (builder MessageBuilder) reviewerString(pull PullRequest, reviewers []string) string {
	var str = ""
	for _, reviewer := range reviewers {
		login := builder.UsersManager.ConvertGitHubToSlack(reviewer)
		str += "@" + login + " "
	}
	return str
}

func (builder MessageBuilder) BudildSummary(pullsCount int) string {
	repo := builder.GitHubOwner + "/" + builder.GitHubRepo
	url := "https://github.com/" + repo
	link := fmt.Sprintf("<%s|%s>", url, repo)

	var summary string
	switch pullsCount {
	case 0:
		summary = fmt.Sprintf("There's no open pull request for %s :tada: Let's take a break :dango: :tea:", link)
	case 1:
		summary = fmt.Sprintf("There's only one open pull request for %s :point_up:", link)
	default:
		summary = fmt.Sprintf("I found %d open pull requests for %s:\n", pullsCount, link)
	}
	return summary
}

func (builder MessageBuilder) isAssigneeReview(review Review, pull PullRequest) bool {
	for _, assignee := range pull.Assignees {
		if assignee.Login == review.User.Login {
			return true
		}
	}
	return false
}

func (builder MessageBuilder) isRequestedReviewerReview(review Review, pull PullRequest) bool {
	for _, requestedReviewer := range pull.RequestedReviewers {
		if requestedReviewer.Login == review.User.Login {
			return true
		}
	}
	return false
}

func (builder MessageBuilder) BuildField(pull PullRequest, reviews []Review) (slackposter.Field, AttachmentType) {
	var approvedUsers Usernames    // User who have approved this pull request.
	var notApprovedUsers Usernames // User who have not approved this pull request.
	requestedReviewers := UsernameFromRequestedReviewers(pull.RequestedReviewers)

	for _, review := range reviews {
		username := builder.UsersManager.ConvertGitHubToSlack(review.User.Login)

		if review.IsApproved() {
			approvedUsers = append(approvedUsers, username)
			notApprovedUsers = notApprovedUsers.remove(username)

		} else if builder.isAssigneeReview(review, pull) == false &&
			builder.isRequestedReviewerReview(review, pull) == false &&
			approvedUsers.isContain(username) == false &&
			notApprovedUsers.isContain(username) == false {
			notApprovedUsers = append(notApprovedUsers, username)
		}
	}

	fmt.Println("requestedReviewers:", requestedReviewers)
	fmt.Println("approvedUsers:", approvedUsers)
	fmt.Println("notApprovedUsers:", notApprovedUsers)

	var attachmentType AttachmentType
	title := builder.fieldTitleString(pull)
	value := builder.fieldValueString(pull)
	pullUsername := builder.UsersManager.ConvertGitHubToSlack(pull.User.Login)
	name := ""

	if len(approvedUsers) >= builder.MinimumApproved {
		attachmentType = MERGE
		name = "@" + pullUsername

	} else if len(requestedReviewers) > 0 {
		attachmentType = REVIEW
		name = builder.reviewerString(pull, requestedReviewers) + " " + builder.reviewerString(pull, notApprovedUsers)

	} else if len(approvedUsers) >= 1 {
		attachmentType = REVIEW
		name = builder.reviewerString(pull, requestedReviewers) + " " + builder.reviewerString(pull, notApprovedUsers)

	} else if len(requestedReviewers) == 0 && len(reviews) == 0 {
		attachmentType = ASSIGN_REVIEWER
		name = "@" + pullUsername

	} else {
		attachmentType = CHECK
		name = "@" + pullUsername + " " + builder.reviewerString(pull, notApprovedUsers)
	}

	value = value + " => " + name

	field := slackposter.Field{
		Title: title,
		Value: value,
		Short: false,
	}

	return field, attachmentType
}

func (builder MessageBuilder) BuildAttachmentWithType(attachmentType AttachmentType) slackposter.Attachment {
	var color, message string
	switch attachmentType {
	case MERGE:
		color = "good"
		message = ":+1::+1: *マージお願いします*"
	case REVIEW:
		color = "warning"
		message = ":smiley: *引き続きレビューお願いします！*"
	case CHECK:
		color = "warning"
		message = ":wink: *進捗／Reviewers の再指定／APPROVED し忘れ／など確認お願いします！*"
	case ASSIGN_REVIEWER:
		color = "danger"
		message = ":sweat_smile: *Reviewers の指定をお願いします！*"
	}

	var attachment slackposter.Attachment
	attachment = slackposter.Attachment{
		Fallback: message,
		Text:     message,
		Color:    color,
		Fields:   []slackposter.Field{},
		MrkdwnIn: []string{"text", "fallback"},
		// AuthorIcon: "https://assets-cdn.github.com/images/modules/logos_page/GitHub-Mark.png",
		// AuthorLink: "https://github.com/",
		// AuthorName: "GitHub",
	}
	return attachment
}

func NewMessageBuilder(gh GitHubAPI, usersManager UsersManager, config Config) MessageBuilder {
	return MessageBuilder{
		GitHubOwner:     gh.Owner,
		GitHubRepo:      gh.Repo,
		UsersManager:    usersManager,
		MinimumApproved: config.GitHub.MinimumApproved,
	}
}
<|endoftext|>


<commit_before>package wuxia

func entryScript() string {
	return `
var system=sys();
var Tpl={};
Tpl.funcs={};
Tpl.getTplFuncs=function(){
	var rst=[]
	for (var prop in Tpl.funcs){
		if (Tpl.funcs.hasOwnProperty(prop)){
			rst.push(prop)
		}
	}
	return rst
}
`
}
<commit_msg>Update entryScript<commit_after>package wuxia

func entryScript() string {
	return `
var System=sys();
var Tpl={};
Tpl.funcs={};
Tpl.getTplFuncs=function(){
	var rst=[]
	for (var prop in Tpl.funcs){
		if (Tpl.funcs.hasOwnProperty(prop)){
			rst.push(prop)
		}
	}
	return rst
}
`
}
<|endoftext|>