<commit_before>package prompt

import (
	"context"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

const (
	logfile      = "/tmp/go-prompt-debug.log"
	envEnableLog = "GO_PROMPT_ENABLE_LOG"
)

type Executor func(context.Context, string) string
type Completer func(string) []Completion
type Completion struct {
	Text        string
	Description string
}

type Prompt struct {
	in             ConsoleParser
	buf            *Buffer
	renderer       *Render
	executor       Executor
	completer      Completer
	maxCompletions uint16
	selected       int // -1 means nothing one is selected.
	history        []string
}

func (p *Prompt) Run() {
	p.setUp()
	defer p.tearDown()

	if os.Getenv(envEnableLog) != "true" {
		log.SetOutput(ioutil.Discard)
	} else if f, err := os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666); err != nil {
		log.SetOutput(ioutil.Discard)
	} else {
		defer f.Close()
		log.SetOutput(f)
		log.Println("Logging is enabled.")
	}

	p.renderer.Render(p.buf, p.completer(p.buf.Text()), p.maxCompletions, p.selected)

	bufCh := make(chan []byte, 128)
	go readBuffer(bufCh)

	exitCh := make(chan struct{})
	winSizeCh := make(chan *WinSize)
	go handleSignals(p.in, exitCh, winSizeCh)

	for {
		select {
		case b := <-bufCh:
			if shouldExecute, shouldExit, input := p.feed(b); shouldExit {
				return
			} else if shouldExecute {
				ctx, _ := context.WithCancel(context.Background())
				p.renderer.RenderResult(p.executor(ctx, input))

				completions := p.completer(p.buf.Text())
				p.updateSelectedCompletion(completions)
				p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
			} else {
				completions := p.completer(p.buf.Text())
				p.updateSelectedCompletion(completions)
				p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
			}
		case w := <-winSizeCh:
			p.renderer.UpdateWinSize(w)
			completions := p.completer(p.buf.Text())
			p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
		case <-exitCh:
			return
		default:
			time.Sleep(10 * time.Millisecond)
		}
	}
}

func (p *Prompt) feed(b []byte) (shouldExecute, shouldExit bool, input string) {
	shouldExecute = false
	key := p.in.GetKey(b)

	switch key {
	case ControlJ, Enter:
		if p.selected != -1 {
			c := p.completer(p.buf.Text())[p.selected]
			w := p.buf.Document().GetWordBeforeCursor()
			if w != "" {
				p.buf.DeleteBeforeCursor(len([]rune(w)))
			}
			p.buf.InsertText(c.Text, false, true)
		}
		p.renderer.BreakLine(p.buf)

		shouldExecute = true
		input = p.buf.Text()
		p.history = append(p.history, input)
		log.Printf("History: %s", input)
		p.buf = NewBuffer()
		p.selected = -1
	case ControlC:
		p.renderer.BreakLine(p.buf)
		p.buf = NewBuffer()
		p.selected = -1
	case ControlD:
		shouldExit = true
	case Up:
		fallthrough
	case BackTab:
		p.selected -= 1
	case ControlI:
		fallthrough
	case Down:
		fallthrough
	case Tab:
		p.selected += 1
	case Left:
		p.buf.CursorLeft(1)
	case Right:
		p.buf.CursorRight(1)
	case Backspace:
		p.buf.DeleteBeforeCursor(1)
	case NotDefined:
		if p.selected != -1 {
			c := p.completer(p.buf.Text())[p.selected]
			w := p.buf.Document().GetWordBeforeCursor()
			if w != "" {
				p.buf.DeleteBeforeCursor(len([]rune(w)))
			}
			p.buf.InsertText(c.Text, false, true)
		}
		p.selected = -1
		p.buf.InsertText(string(b), false, true)
	default:
		p.selected = -1
	}
	return
}

func (p *Prompt) updateSelectedCompletion(completions []Completion) {
	max := int(p.maxCompletions)
	if len(completions) < max {
		max = len(completions)
	}
	if p.selected >= max {
		p.selected = -1
	} else if p.selected < -1 {
		p.selected = max - 1
	}
}

func (p *Prompt) setUp() {
	p.in.Setup()
	p.renderer.Setup()
	p.renderer.UpdateWinSize(p.in.GetWinSize())
	p.selected = -1 // -1 means nothing one is selected.
}

func (p *Prompt) tearDown() {
	p.in.TearDown()
	p.renderer.TearDown()
}

func readBuffer(bufCh chan []byte) {
	buf := make([]byte, 1024)

	for {
		if n, err := syscall.Read(syscall.Stdin, buf); err == nil {
			bufCh <- buf[:n]
		}
	}
}

func handleSignals(in ConsoleParser, exitCh chan struct{}, winSizeCh chan *WinSize) {
	sigCh := make(chan os.Signal, 1)
	signal.Notify(
		sigCh,
		syscall.SIGHUP,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT,
		syscall.SIGWINCH,
	)

	for {
		s := <-sigCh
		switch s {
		// kill -SIGHUP XXXX
		case syscall.SIGHUP:
			exitCh <- struct{}{}

			// kill -SIGINT XXXX or Ctrl+c
		case syscall.SIGINT:
			exitCh <- struct{}{}

			// kill -SIGTERM XXXX
		case syscall.SIGTERM:
			exitCh <- struct{}{}

			// kill -SIGQUIT XXXX
		case syscall.SIGQUIT:
			exitCh <- struct{}{}

		case syscall.SIGWINCH:
			winSizeCh <- in.GetWinSize()
		default:
		}
	}
}
<commit_msg>Update prompt<commit_after>package prompt

import (
	"context"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

const (
	logfile      = "/tmp/go-prompt-debug.log"
	envEnableLog = "GO_PROMPT_ENABLE_LOG"
)

type Executor func(context.Context, string) string
type Completer func(string) []Completion
type Completion struct {
	Text        string
	Description string
}

type Prompt struct {
	in             ConsoleParser
	buf            *Buffer
	renderer       *Render
	executor       Executor
	completer      Completer
	maxCompletions uint16
	selected       int // -1 means nothing one is selected.
	history        []string
}

func (p *Prompt) Run() {
	p.setUp()
	defer p.tearDown()

	if os.Getenv(envEnableLog) != "true" {
		log.SetOutput(ioutil.Discard)
	} else if f, err := os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666); err != nil {
		log.SetOutput(ioutil.Discard)
	} else {
		defer f.Close()
		log.SetOutput(f)
		log.Println("Logging is enabled.")
	}

	p.renderer.Render(p.buf, p.completer(p.buf.Text()), p.maxCompletions, p.selected)

	bufCh := make(chan []byte, 128)
	go readBuffer(bufCh)

	exitCh := make(chan int)
	winSizeCh := make(chan *WinSize)
	go handleSignals(p.in, exitCh, winSizeCh)

	for {
		select {
		case b := <-bufCh:
			if shouldExecute, shouldExit, input := p.feed(b); shouldExit {
				return
			} else if shouldExecute {
				ctx, _ := context.WithCancel(context.Background())
				p.renderer.RenderResult(p.executor(ctx, input))

				completions := p.completer(p.buf.Text())
				p.updateSelectedCompletion(completions)
				p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
			} else {
				completions := p.completer(p.buf.Text())
				p.updateSelectedCompletion(completions)
				p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
			}
		case w := <-winSizeCh:
			p.renderer.UpdateWinSize(w)
			completions := p.completer(p.buf.Text())
			p.renderer.Render(p.buf, completions, p.maxCompletions, p.selected)
		case code := <-exitCh:
			p.tearDown()
			os.Exit(code)
		default:
			time.Sleep(10 * time.Millisecond)
		}
	}
}

func (p *Prompt) feed(b []byte) (shouldExecute, shouldExit bool, input string) {
	shouldExecute = false
	key := p.in.GetKey(b)

	switch key {
	case ControlJ, Enter:
		if p.selected != -1 {
			c := p.completer(p.buf.Text())[p.selected]
			w := p.buf.Document().GetWordBeforeCursor()
			if w != "" {
				p.buf.DeleteBeforeCursor(len([]rune(w)))
			}
			p.buf.InsertText(c.Text, false, true)
		}
		p.renderer.BreakLine(p.buf)

		shouldExecute = true
		input = p.buf.Text()
		p.history = append(p.history, input)
		log.Printf("History: %s", input)
		p.buf = NewBuffer()
		p.selected = -1
	case ControlC:
		p.renderer.BreakLine(p.buf)
		p.buf = NewBuffer()
		p.selected = -1
	case ControlD:
		shouldExit = true
	case Up:
		fallthrough
	case BackTab:
		p.selected -= 1
	case Down:
		fallthrough
	case Tab, ControlI:
		p.selected += 1
	case Left:
		p.buf.CursorLeft(1)
	case Right:
		p.buf.CursorRight(1)
	case Backspace:
		p.buf.DeleteBeforeCursor(1)
	case NotDefined:
		if p.selected != -1 {
			c := p.completer(p.buf.Text())[p.selected]
			w := p.buf.Document().GetWordBeforeCursor()
			if w != "" {
				p.buf.DeleteBeforeCursor(len([]rune(w)))
			}
			p.buf.InsertText(c.Text, false, true)
		}
		p.selected = -1
		p.buf.InsertText(string(b), false, true)
	default:
		p.selected = -1
	}
	return
}

func (p *Prompt) updateSelectedCompletion(completions []Completion) {
	max := int(p.maxCompletions)
	if len(completions) < max {
		max = len(completions)
	}
	if p.selected >= max {
		p.selected = -1
	} else if p.selected < -1 {
		p.selected = max - 1
	}
}

func (p *Prompt) setUp() {
	p.in.Setup()
	p.renderer.Setup()
	p.renderer.UpdateWinSize(p.in.GetWinSize())
	p.selected = -1 // -1 means nothing one is selected.
}

func (p *Prompt) tearDown() {
	p.in.TearDown()
	p.renderer.TearDown()
}

func readBuffer(bufCh chan []byte) {
	buf := make([]byte, 1024)

	for {
		if n, err := syscall.Read(syscall.Stdin, buf); err == nil {
			bufCh <- buf[:n]
		}
	}
}

func handleSignals(in ConsoleParser, exitCh chan int, winSizeCh chan *WinSize) {
	sigCh := make(chan os.Signal, 1)
	signal.Notify(
		sigCh,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT,
		syscall.SIGWINCH,
	)

	for {
		s := <-sigCh
		switch s {
		case syscall.SIGINT:  // kill -SIGINT XXXX or Ctrl+c
			log.Println("SIGNAL: Catch SIGINT")
			exitCh <- 0

		case syscall.SIGTERM:  // kill -SIGTERM XXXX
			log.Println("SIGNAL: Catch SIGTERM")
			exitCh <- 1

		case syscall.SIGQUIT:  // kill -SIGQUIT XXXX
			log.Println("SIGNAL: Catch SIGQUIT")
			exitCh <- 0

		case syscall.SIGWINCH:
			log.Println("SIGNAL: Catch SIGWINCH")
			winSizeCh <- in.GetWinSize()

		// TODO: SIGUSR1 -> Reopen log file.
		default:
		}
	}
}
<|endoftext|>


<commit_before>package promptui

import (
	"fmt"
	"io"
	"strings"
	"text/template"

	"github.com/chzyer/readline"
	"github.com/manifoldco/promptui/screenbuf"
)

const cursor = "\u2588"

// Prompt represents a single line text field input.
type Prompt struct {
	// Label is the value displayed on the command line prompt. It can be any
	// value one would pass to a text/template Execute(), including just a string.
	Label interface{}

	Default string // Default is the initial value to populate in the prompt

	// Validate is optional. If set, this function is used to validate the input
	// after each character entry.
	Validate ValidateFunc

	// If mask is set, this value is displayed instead of the actual input
	// characters.
	Mask rune

	// Templates can be used to customize the prompt output. If nil is passed, the
	// default templates are used.
	Templates *PromptTemplates

	IsConfirm bool
	IsVimMode bool

	stdin  io.Reader
	stdout io.Writer
}

// PromptTemplates allow a prompt to be customized following stdlib
// text/template syntax. If any field is blank a default template is used.
type PromptTemplates struct {
	// Prompt is a text/template for the initial prompt question.
	Prompt string

	// Prompt is a text/template if the prompt is a confirmation.
	Confirm string

	// Valid is a text/template for when the current input is valid.
	Valid string

	// Invalid is a text/template for when the current input is invalid.
	Invalid string

	// Success is a text/template for the successful result.
	Success string

	// Prompt is a text/template when there is a validation error.
	ValidationError string

	// FuncMap is a map of helpers for the templates. If nil, the default helpers
	// are used.
	FuncMap template.FuncMap

	prompt     *template.Template
	valid      *template.Template
	invalid    *template.Template
	validation *template.Template
	success    *template.Template
}

// Run runs the prompt, returning the validated input.
func (p *Prompt) Run() (string, error) {
	c := &readline.Config{}
	err := c.Init()
	if err != nil {
		return "", err
	}

	err = p.prepareTemplates()
	if err != nil {
		return "", err
	}

	if p.stdin != nil {
		c.Stdin = p.stdin
	}

	if p.stdout != nil {
		c.Stdout = p.stdout
	}

	if p.Mask != 0 {
		c.EnableMask = true
		c.MaskRune = p.Mask
	}

	if p.IsVimMode {
		c.VimMode = true
	}

	c.HistoryLimit = -1
	c.UniqueEditLine = true

	rl, err := readline.NewEx(c)
	if err != nil {
		return "", err
	}

	rl.Write([]byte(hideCursor))
	sb := screenbuf.New(rl)

	validFn := func(x string) error {
		return nil
	}

	if p.Validate != nil {
		validFn = p.Validate
	}

	var inputErr error
	input := p.Default
	eraseDefault := input != ""

	c.SetListener(func(line []rune, pos int, key rune) ([]rune, int, bool) {
		if line != nil {
			input += string(line)
		}

		switch key {
		case 0: // empty
		case KeyEnter:
			return nil, 0, false
		case KeyBackspace:
			if eraseDefault {
				eraseDefault = false
				input = ""
			}
			if len(input) > 0 {
				input = input[:len(input)-1]
			}
		default:
			if eraseDefault {
				eraseDefault = false
				input = string(line)
			}
		}

		err := validFn(input)
		var prompt []byte

		if err != nil {
			prompt = render(p.Templates.invalid, p.Label)
		} else {
			prompt = render(p.Templates.valid, p.Label)
			if p.IsConfirm {
				prompt = render(p.Templates.prompt, p.Label)
			}
		}

		echo := input
		if p.Mask != 0 {
			echo = strings.Repeat(string(p.Mask), len(echo))
		}

		prompt = append(prompt, []byte(echo+cursor)...)

		sb.Reset()
		sb.Write(prompt)

		if inputErr != nil {
			validation := render(p.Templates.validation, inputErr)
			sb.Write(validation)
			inputErr = nil
		}

		sb.Flush()

		return nil, 0, true
	})

	for {
		_, err = rl.Readline()

		inputErr = validFn(input)
		if inputErr == nil {
			break
		}

		if err != nil {
			switch err {
			case readline.ErrInterrupt:
				err = ErrInterrupt
			case io.EOF:
				err = ErrEOF
			}
			break
		}
	}

	if err != nil {
		if err.Error() == "Interrupt" {
			err = ErrInterrupt
		}
		sb.Reset()
		sb.WriteString("")
		sb.Flush()
		rl.Write([]byte(showCursor))
		rl.Close()
		return "", err
	}

	echo := input
	if p.Mask != 0 {
		echo = strings.Repeat(string(p.Mask), len(echo))
	}

	prompt := render(p.Templates.valid, p.Label)
	prompt = append(prompt, []byte(echo)...)

	if p.IsConfirm && strings.ToLower(echo) != "y" {
		prompt = render(p.Templates.invalid, p.Label)
		err = ErrAbort
	}

	sb.Reset()
	sb.Write(prompt)
	sb.Flush()
	rl.Write([]byte(showCursor))
	rl.Close()

	return input, err
}

func (p *Prompt) prepareTemplates() error {
	tpls := p.Templates
	if tpls == nil {
		tpls = &PromptTemplates{}
	}

	if tpls.FuncMap == nil {
		tpls.FuncMap = FuncMap
	}

	bold := Styler(FGBold)

	if p.IsConfirm {
		p.Default = ""
		if tpls.Confirm == "" {
			confirm := "y/N"
			if strings.ToLower(p.Default) == "y" {
				confirm = "Y/n"
			}
			tpls.Confirm = fmt.Sprintf(`{{ "%s" | bold }} {{ . | bold }}? {{ "[%s]" | faint }} `, IconInitial, confirm)
		}

		tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Confirm)
		if err != nil {
			return err
		}

		tpls.prompt = tpl
	} else {
		if tpls.Prompt == "" {
			tpls.Prompt = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconInitial), bold(":"))
		}

		tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Prompt)
		if err != nil {
			return err
		}

		tpls.prompt = tpl
	}

	if tpls.Valid == "" {
		tpls.Valid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconGood), bold(":"))
	}

	tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Valid)
	if err != nil {
		return err
	}

	tpls.valid = tpl

	if tpls.Invalid == "" {
		tpls.Invalid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconBad), bold(":"))
	}

	tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Invalid)
	if err != nil {
		return err
	}

	tpls.invalid = tpl

	if tpls.ValidationError == "" {
		tpls.ValidationError = `{{ ">>" | red }} {{ . | red }}`
	}

	tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.ValidationError)
	if err != nil {
		return err
	}

	tpls.validation = tpl

	if tpls.Success == "" {
		tpls.Success = `{{ . | faint }}`
	}

	tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Success)
	if err != nil {
		return err
	}

	tpls.success = tpl

	p.Templates = tpls

	return nil
}
<commit_msg>prompt.go: Adopt io.ReadCloser and io.WriteCloser (#47)<commit_after>package promptui

import (
	"fmt"
	"io"
	"strings"
	"text/template"

	"github.com/chzyer/readline"
	"github.com/manifoldco/promptui/screenbuf"
)

const cursor = "\u2588"

// Prompt represents a single line text field input.
type Prompt struct {
	// Label is the value displayed on the command line prompt. It can be any
	// value one would pass to a text/template Execute(), including just a string.
	Label interface{}

	Default string // Default is the initial value to populate in the prompt

	// Validate is optional. If set, this function is used to validate the input
	// after each character entry.
	Validate ValidateFunc

	// If mask is set, this value is displayed instead of the actual input
	// characters.
	Mask rune

	// Templates can be used to customize the prompt output. If nil is passed, the
	// default templates are used.
	Templates *PromptTemplates

	IsConfirm bool
	IsVimMode bool

	stdin  io.ReadCloser
	stdout io.WriteCloser
}

// PromptTemplates allow a prompt to be customized following stdlib
// text/template syntax. If any field is blank a default template is used.
type PromptTemplates struct {
	// Prompt is a text/template for the initial prompt question.
	Prompt string

	// Prompt is a text/template if the prompt is a confirmation.
	Confirm string

	// Valid is a text/template for when the current input is valid.
	Valid string

	// Invalid is a text/template for when the current input is invalid.
	Invalid string

	// Success is a text/template for the successful result.
	Success string

	// Prompt is a text/template when there is a validation error.
	ValidationError string

	// FuncMap is a map of helpers for the templates. If nil, the default helpers
	// are used.
	FuncMap template.FuncMap

	prompt     *template.Template
	valid      *template.Template
	invalid    *template.Template
	validation *template.Template
	success    *template.Template
}

// Run runs the prompt, returning the validated input.
func (p *Prompt) Run() (string, error) {
	c := &readline.Config{}
	err := c.Init()
	if err != nil {
		return "", err
	}

	err = p.prepareTemplates()
	if err != nil {
		return "", err
	}

	if p.stdin != nil {
		c.Stdin = p.stdin
	}

	if p.stdout != nil {
		c.Stdout = p.stdout
	}

	if p.Mask != 0 {
		c.EnableMask = true
		c.MaskRune = p.Mask
	}

	if p.IsVimMode {
		c.VimMode = true
	}

	c.HistoryLimit = -1
	c.UniqueEditLine = true

	rl, err := readline.NewEx(c)
	if err != nil {
		return "", err
	}

	rl.Write([]byte(hideCursor))
	sb := screenbuf.New(rl)

	validFn := func(x string) error {
		return nil
	}

	if p.Validate != nil {
		validFn = p.Validate
	}

	var inputErr error
	input := p.Default
	eraseDefault := input != ""

	c.SetListener(func(line []rune, pos int, key rune) ([]rune, int, bool) {
		if line != nil {
			input += string(line)
		}

		switch key {
		case 0: // empty
		case KeyEnter:
			return nil, 0, false
		case KeyBackspace:
			if eraseDefault {
				eraseDefault = false
				input = ""
			}
			if len(input) > 0 {
				input = input[:len(input)-1]
			}
		default:
			if eraseDefault {
				eraseDefault = false
				input = string(line)
			}
		}

		err := validFn(input)
		var prompt []byte

		if err != nil {
			prompt = render(p.Templates.invalid, p.Label)
		} else {
			prompt = render(p.Templates.valid, p.Label)
			if p.IsConfirm {
				prompt = render(p.Templates.prompt, p.Label)
			}
		}

		echo := input
		if p.Mask != 0 {
			echo = strings.Repeat(string(p.Mask), len(echo))
		}

		prompt = append(prompt, []byte(echo+cursor)...)

		sb.Reset()
		sb.Write(prompt)

		if inputErr != nil {
			validation := render(p.Templates.validation, inputErr)
			sb.Write(validation)
			inputErr = nil
		}

		sb.Flush()

		return nil, 0, true
	})

	for {
		_, err = rl.Readline()

		inputErr = validFn(input)
		if inputErr == nil {
			break
		}

		if err != nil {
			switch err {
			case readline.ErrInterrupt:
				err = ErrInterrupt
			case io.EOF:
				err = ErrEOF
			}
			break
		}
	}

	if err != nil {
		if err.Error() == "Interrupt" {
			err = ErrInterrupt
		}
		sb.Reset()
		sb.WriteString("")
		sb.Flush()
		rl.Write([]byte(showCursor))
		rl.Close()
		return "", err
	}

	echo := input
	if p.Mask != 0 {
		echo = strings.Repeat(string(p.Mask), len(echo))
	}

	prompt := render(p.Templates.valid, p.Label)
	prompt = append(prompt, []byte(echo)...)

	if p.IsConfirm && strings.ToLower(echo) != "y" {
		prompt = render(p.Templates.invalid, p.Label)
		err = ErrAbort
	}

	sb.Reset()
	sb.Write(prompt)
	sb.Flush()
	rl.Write([]byte(showCursor))
	rl.Close()

	return input, err
}

func (p *Prompt) prepareTemplates() error {
	tpls := p.Templates
	if tpls == nil {
		tpls = &PromptTemplates{}
	}

	if tpls.FuncMap == nil {
		tpls.FuncMap = FuncMap
	}

	bold := Styler(FGBold)

	if p.IsConfirm {
		p.Default = ""
		if tpls.Confirm == "" {
			confirm := "y/N"
			if strings.ToLower(p.Default) == "y" {
				confirm = "Y/n"
			}
			tpls.Confirm = fmt.Sprintf(`{{ "%s" | bold }} {{ . | bold }}? {{ "[%s]" | faint }} `, IconInitial, confirm)
		}

		tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Confirm)
		if err != nil {
			return err
		}

		tpls.prompt = tpl
	} else {
		if tpls.Prompt == "" {
			tpls.Prompt = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconInitial), bold(":"))
		}

		tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Prompt)
		if err != nil {
			return err
		}

		tpls.prompt = tpl
	}

	if tpls.Valid == "" {
		tpls.Valid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconGood), bold(":"))
	}

	tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Valid)
	if err != nil {
		return err
	}

	tpls.valid = tpl

	if tpls.Invalid == "" {
		tpls.Invalid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconBad), bold(":"))
	}

	tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Invalid)
	if err != nil {
		return err
	}

	tpls.invalid = tpl

	if tpls.ValidationError == "" {
		tpls.ValidationError = `{{ ">>" | red }} {{ . | red }}`
	}

	tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.ValidationError)
	if err != nil {
		return err
	}

	tpls.validation = tpl

	if tpls.Success == "" {
		tpls.Success = `{{ . | faint }}`
	}

	tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Success)
	if err != nil {
		return err
	}

	tpls.success = tpl

	p.Templates = tpls

	return nil
}
<|endoftext|>


<commit_before>/* Copyright 2018 The Bazel Authors. All rights reserved.

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 testtools

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
)

// FileSpec specifies the content of a test file.
type FileSpec struct {
	// Path is a slash-separated path relative to the test directory. If Path
	// ends with a slash, it indicates a directory should be created
	// instead of a file.
	Path string

	// Symlink is a slash-separated path relative to the test directory. If set,
	// it indicates a symbolic link should be created with this path instead of a
	// file.
	Symlink string

	// Content is the content of the test file.
	Content string

	// NotExist asserts that no file at this path exists.
	// It is only valid in CheckFiles.
	NotExist bool
}

// CreateFiles creates a directory of test files. This is a more compact
// alternative to testdata directories. CreateFiles returns a canonical path
// to the directory and a function to call to clean up the directory
// after the test.
func CreateFiles(t *testing.T, files []FileSpec) (dir string, cleanup func()) {
	t.Helper()
	dir, err := ioutil.TempDir(os.Getenv("TEST_TEMPDIR"), "gazelle_test")
	if err != nil {
		t.Fatal(err)
	}
	dir, err = filepath.EvalSymlinks(dir)
	if err != nil {
		t.Fatal(err)
	}

	for _, f := range files {
		if f.NotExist {
			t.Fatalf("CreateFiles: NotExist may not be set: %s", f.Path)
		}
		path := filepath.Join(dir, filepath.FromSlash(f.Path))
		if strings.HasSuffix(f.Path, "/") {
			if err := os.MkdirAll(path, 0700); err != nil {
				os.RemoveAll(dir)
				t.Fatal(err)
			}
			continue
		}
		if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
			os.RemoveAll(dir)
			t.Fatal(err)
		}
		if f.Symlink != "" {
			if err := os.Symlink(f.Symlink, path); err != nil {
				t.Fatal(err)
			}
			continue
		}
		if err := ioutil.WriteFile(path, []byte(f.Content), 0600); err != nil {
			os.RemoveAll(dir)
			t.Fatal(err)
		}
	}

	return dir, func() { os.RemoveAll(dir) }
}

// CheckFiles checks that files in "dir" exist and have the content specified
// in "files". Files not listed in "files" are not tested, so extra files
// are allowed.
func CheckFiles(t *testing.T, dir string, files []FileSpec) {
	t.Helper()
	for _, f := range files {
		path := filepath.Join(dir, f.Path)

		st, err := os.Stat(path)
		if f.NotExist {
			if err == nil {
				t.Errorf("asserted to not exist, but does: %s", f.Path)
			} else if !os.IsNotExist(err) {
				t.Errorf("could not stat %s: %v", f.Path, err)
			}
			continue
		}

		if strings.HasSuffix(f.Path, "/") {
			if err != nil {
				t.Errorf("could not stat %s: %v", f.Path, err)
			} else if !st.IsDir() {
				t.Errorf("not a directory: %s", f.Path)
			}
		} else {
			want := strings.TrimSpace(f.Content)
			gotBytes, err := ioutil.ReadFile(filepath.Join(dir, f.Path))
			if err != nil {
				t.Errorf("could not read %s: %v", f.Path, err)
				continue
			}
			got := strings.TrimSpace(string(gotBytes))
			if diff := cmp.Diff(want, got); diff != "" {
				t.Errorf("%s diff (-want,+got):\n%s", f.Path, diff)
			}
		}
	}
}

type TestGazelleGenerationArgs struct {
	// Name is the name of the test.
	Name string
	// TestDataPathAbsolute is the absolute path to the test data directory.
	// For example, /home/user/workspace/path/to/test_data/my_testcase.
	TestDataPathAbsolute string
	// TestDataPathRealtive is the workspace relative path to the test data directory.
	// For example, path/to/test_data/my_testcase.
	TestDataPathRelative string
	// GazelleBinaryPath is the workspace relative path to the location of the gazelle binary
	// we want to test.
	GazelleBinaryPath string

	// BuildInSuffix is the suffix for all test input build files. Includes the ".".
	// Default: ".in", so input BUILD files should be named BUILD.in.
	BuildInSuffix string

	// BuildOutSuffix is the suffix for all test output build files. Includes the ".".
	// Default: ".out", so out BUILD files should be named BUILD.out.
	BuildOutSuffix string
}

var (
	argumentsFilename        = "arguments.txt"
	expectedStdoutFilename   = "expectedStdout.txt"
	expectedStderrFilename   = "expectedStderr.txt"
	expectedExitCodeFilename = "expectedExitCode.txt"
)

// TestGazelleGenerationOnPath runs a full gazelle binary on a testdata directory.
// With a test data directory of the form:
//└── <testDataPath>
//    └── some_test
//        ├── WORKSPACE
//        ├── README.md --> README describing what the test does.
//        ├── arguments.txt --> newline delimited list of arguments to pass in (ignored if empty).
//        ├── expectedStdout.txt --> Expected stdout for this test.
//        ├── expectedStderr.txt --> Expected stderr for this test.
//        ├── expectedExitCode.txt --> Expected exit code for this test.
//        └── app
//            └── sourceFile.foo
//            └── BUILD.in --> BUILD file prior to running gazelle.
//            └── BUILD.out --> BUILD file expected after running gazelle.
func TestGazelleGenerationOnPath(t *testing.T, args *TestGazelleGenerationArgs) {
	t.Run(args.Name, func(t *testing.T) {
		t.Helper() // Make the stack trace a little bit more clear.
		if args.BuildInSuffix == "" {
			args.BuildInSuffix = ".in"
		}
		if args.BuildOutSuffix == "" {
			args.BuildOutSuffix = ".out"
		}
		var inputs []FileSpec
		var goldens []FileSpec

		config := &testConfig{}
		filepath.WalkDir(args.TestDataPathAbsolute, func(path string, d fs.DirEntry, err error) error {
			shortPath := strings.TrimPrefix(path, args.TestDataPathAbsolute)

			info, err := d.Info()
			if err != nil {
				t.Fatalf("File info error on path %q. Error: %v", path, err)
			}

			if info.IsDir() {
				return nil
			}

			content, err := ioutil.ReadFile(path)
			if err != nil {
				t.Errorf("ioutil.ReadFile(%q) error: %v", path, err)
			}

			// Read in expected stdout, stderr, and exit code files.
			if d.Name() == argumentsFilename {
				config.Args = strings.Split(string(content), "\n")
				return nil
			}
			if d.Name() == expectedStdoutFilename {
				config.Stdout = string(content)
				return nil
			}
			if d.Name() == expectedStderrFilename {
				config.Stderr = string(content)
				return nil
			}
			if d.Name() == expectedExitCodeFilename {
				config.ExitCode, err = strconv.Atoi(string(content))
				if err != nil {
					// Set the ExitCode to a sentinel value (-1) to ensure that if the caller is updating the files on disk the value is updated.
					config.ExitCode = -1
					t.Errorf("Failed to parse expected exit code (%q) error: %v", path, err)
				}
				return nil
			}

			if strings.HasSuffix(shortPath, args.BuildInSuffix) {
				inputs = append(inputs, FileSpec{
					Path:    filepath.Join(args.Name, strings.TrimSuffix(shortPath, args.BuildInSuffix)+".bazel"),
					Content: string(content),
				})
			} else if strings.HasSuffix(shortPath, args.BuildOutSuffix) {
				goldens = append(goldens, FileSpec{
					Path:    filepath.Join(args.Name, strings.TrimSuffix(shortPath, args.BuildOutSuffix)+".bazel"),
					Content: string(content),
				})
			} else {
				inputs = append(inputs, FileSpec{
					Path:    filepath.Join(args.Name, shortPath),
					Content: string(content),
				})
				goldens = append(goldens, FileSpec{
					Path:    filepath.Join(args.Name, shortPath),
					Content: string(content),
				})
			}
			return nil
		})

		testdataDir, cleanup := CreateFiles(t, inputs)

		var stdout, stderr bytes.Buffer
		var actualExitCode int
		defer cleanup()
		defer func() {
			if t.Failed() {
				shouldUpdate := os.Getenv("UPDATE_SNAPSHOTS") != ""
				buildWorkspaceDirectory := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
				updateCommand := fmt.Sprintf("UPDATE_SNAPSHOTS=true bazel run %s", os.Getenv("TEST_TARGET"))
				// srcTestDirectory is the directory of the source code of the test case.
				srcTestDirectory := path.Join(buildWorkspaceDirectory, path.Dir(args.TestDataPathRelative), args.Name)
				if shouldUpdate {
					// Update stdout, stderr, exit code.
					updateExpectedConfig(t, config.Stdout, stdout.String(), srcTestDirectory, expectedStdoutFilename)
					updateExpectedConfig(t, config.Stderr, stderr.String(), srcTestDirectory, expectedStderrFilename)
					updateExpectedConfig(t, fmt.Sprintf("%d", config.ExitCode), fmt.Sprintf("%d", actualExitCode), srcTestDirectory, expectedExitCodeFilename)

					filepath.Walk(testdataDir, func(walkedPath string, info os.FileInfo, err error) error {
						if err != nil {
							return err
						}
						relativePath := strings.TrimPrefix(walkedPath, testdataDir)
						if shouldUpdate {
							if buildWorkspaceDirectory == "" {
								t.Fatalf("Tried to update snapshots but no BUILD_WORKSPACE_DIRECTORY specified.\n Try %s.", updateCommand)
							}

							if info.Name() == "BUILD.bazel" {
								destFile := strings.TrimSuffix(path.Join(buildWorkspaceDirectory, path.Dir(args.TestDataPathRelative)+relativePath), ".bazel") + ".out"

								err := copyFile(walkedPath, destFile)
								if err != nil {
									t.Fatalf("Failed to copy file %v to %v. Error: %v\n", walkedPath, destFile, err)
								}
							}

						}
						t.Logf("%q exists in %v", relativePath, testdataDir)
						return nil
					})

				} else {
					t.Logf(`
=====================================================================================
Run %s to update BUILD.out and expected{Stdout,Stderr,ExitCode}.txt files.
=====================================================================================
`, updateCommand)
				}
			}
		}()

		workspaceRoot := filepath.Join(testdataDir, args.Name)
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		cmd := exec.CommandContext(ctx, args.GazelleBinaryPath, config.Args...)
		cmd.Stdout = &stdout
		cmd.Stderr = &stderr
		cmd.Dir = workspaceRoot
		cmd.Env = append(os.Environ(), fmt.Sprintf("BUILD_WORKSPACE_DIRECTORY=%v", workspaceRoot))
		if err := cmd.Run(); err != nil {
			var e *exec.ExitError
			if !errors.As(err, &e) {
				t.Fatal(err)
			}
		}
		errs := make([]error, 0)
		actualExitCode = cmd.ProcessState.ExitCode()
		if config.ExitCode != actualExitCode {
			errs = append(errs, fmt.Errorf("expected gazelle exit code: %d\ngot: %d",
				config.ExitCode, actualExitCode,
			))
		}
		actualStdout := stdout.String()
		if strings.TrimSpace(config.Stdout) != strings.TrimSpace(actualStdout) {
			errs = append(errs, fmt.Errorf("expected gazelle stdout: %s\ngot: %s",
				config.Stdout, actualStdout,
			))
		}
		actualStderr := stderr.String()
		if strings.TrimSpace(config.Stderr) != strings.TrimSpace(actualStderr) {
			errs = append(errs, fmt.Errorf("expected gazelle stderr: %s\ngot: %s",
				config.Stderr, actualStderr,
			))
		}
		if len(errs) > 0 {
			for _, err := range errs {
				t.Log(err)
			}
			t.FailNow()
		}

		CheckFiles(t, testdataDir, goldens)
	})
}

func copyFile(src string, dest string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	destFile, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, srcFile)
	if err != nil {
		return err
	}
	err = destFile.Sync()
	if err != nil {
		return err
	}
	return nil
}

type testConfig struct {
	Args     []string
	ExitCode int
	Stdout   string
	Stderr   string
}

// updateExpectedConfig writes to an expected stdout, stderr, or exit code file
// with the latest results of a test.
func updateExpectedConfig(t *testing.T, expected string, actual string, srcTestDirectory string, expectedFilename string) {
	if expected != actual {
		destFile := path.Join(srcTestDirectory, expectedFilename)

		err := os.WriteFile(destFile, []byte(actual), 0644)
		if err != nil {
			t.Fatalf("Failed to write file %v. Error: %v\n", destFile, err)
		}
	}
}
<commit_msg>gazelle_generation_test: redact workspace path from output<commit_after>/* Copyright 2018 The Bazel Authors. All rights reserved.

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 testtools

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
)

// FileSpec specifies the content of a test file.
type FileSpec struct {
	// Path is a slash-separated path relative to the test directory. If Path
	// ends with a slash, it indicates a directory should be created
	// instead of a file.
	Path string

	// Symlink is a slash-separated path relative to the test directory. If set,
	// it indicates a symbolic link should be created with this path instead of a
	// file.
	Symlink string

	// Content is the content of the test file.
	Content string

	// NotExist asserts that no file at this path exists.
	// It is only valid in CheckFiles.
	NotExist bool
}

// CreateFiles creates a directory of test files. This is a more compact
// alternative to testdata directories. CreateFiles returns a canonical path
// to the directory and a function to call to clean up the directory
// after the test.
func CreateFiles(t *testing.T, files []FileSpec) (dir string, cleanup func()) {
	t.Helper()
	dir, err := ioutil.TempDir(os.Getenv("TEST_TEMPDIR"), "gazelle_test")
	if err != nil {
		t.Fatal(err)
	}
	dir, err = filepath.EvalSymlinks(dir)
	if err != nil {
		t.Fatal(err)
	}

	for _, f := range files {
		if f.NotExist {
			t.Fatalf("CreateFiles: NotExist may not be set: %s", f.Path)
		}
		path := filepath.Join(dir, filepath.FromSlash(f.Path))
		if strings.HasSuffix(f.Path, "/") {
			if err := os.MkdirAll(path, 0700); err != nil {
				os.RemoveAll(dir)
				t.Fatal(err)
			}
			continue
		}
		if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
			os.RemoveAll(dir)
			t.Fatal(err)
		}
		if f.Symlink != "" {
			if err := os.Symlink(f.Symlink, path); err != nil {
				t.Fatal(err)
			}
			continue
		}
		if err := ioutil.WriteFile(path, []byte(f.Content), 0600); err != nil {
			os.RemoveAll(dir)
			t.Fatal(err)
		}
	}

	return dir, func() { os.RemoveAll(dir) }
}

// CheckFiles checks that files in "dir" exist and have the content specified
// in "files". Files not listed in "files" are not tested, so extra files
// are allowed.
func CheckFiles(t *testing.T, dir string, files []FileSpec) {
	t.Helper()
	for _, f := range files {
		path := filepath.Join(dir, f.Path)

		st, err := os.Stat(path)
		if f.NotExist {
			if err == nil {
				t.Errorf("asserted to not exist, but does: %s", f.Path)
			} else if !os.IsNotExist(err) {
				t.Errorf("could not stat %s: %v", f.Path, err)
			}
			continue
		}

		if strings.HasSuffix(f.Path, "/") {
			if err != nil {
				t.Errorf("could not stat %s: %v", f.Path, err)
			} else if !st.IsDir() {
				t.Errorf("not a directory: %s", f.Path)
			}
		} else {
			want := strings.TrimSpace(f.Content)
			gotBytes, err := ioutil.ReadFile(filepath.Join(dir, f.Path))
			if err != nil {
				t.Errorf("could not read %s: %v", f.Path, err)
				continue
			}
			got := strings.TrimSpace(string(gotBytes))
			if diff := cmp.Diff(want, got); diff != "" {
				t.Errorf("%s diff (-want,+got):\n%s", f.Path, diff)
			}
		}
	}
}

type TestGazelleGenerationArgs struct {
	// Name is the name of the test.
	Name string
	// TestDataPathAbsolute is the absolute path to the test data directory.
	// For example, /home/user/workspace/path/to/test_data/my_testcase.
	TestDataPathAbsolute string
	// TestDataPathRealtive is the workspace relative path to the test data directory.
	// For example, path/to/test_data/my_testcase.
	TestDataPathRelative string
	// GazelleBinaryPath is the workspace relative path to the location of the gazelle binary
	// we want to test.
	GazelleBinaryPath string

	// BuildInSuffix is the suffix for all test input build files. Includes the ".".
	// Default: ".in", so input BUILD files should be named BUILD.in.
	BuildInSuffix string

	// BuildOutSuffix is the suffix for all test output build files. Includes the ".".
	// Default: ".out", so out BUILD files should be named BUILD.out.
	BuildOutSuffix string
}

var (
	argumentsFilename        = "arguments.txt"
	expectedStdoutFilename   = "expectedStdout.txt"
	expectedStderrFilename   = "expectedStderr.txt"
	expectedExitCodeFilename = "expectedExitCode.txt"
)

// TestGazelleGenerationOnPath runs a full gazelle binary on a testdata directory.
// With a test data directory of the form:
//└── <testDataPath>
//    └── some_test
//        ├── WORKSPACE
//        ├── README.md --> README describing what the test does.
//        ├── arguments.txt --> newline delimited list of arguments to pass in (ignored if empty).
//        ├── expectedStdout.txt --> Expected stdout for this test.
//        ├── expectedStderr.txt --> Expected stderr for this test.
//        ├── expectedExitCode.txt --> Expected exit code for this test.
//        └── app
//            └── sourceFile.foo
//            └── BUILD.in --> BUILD file prior to running gazelle.
//            └── BUILD.out --> BUILD file expected after running gazelle.
func TestGazelleGenerationOnPath(t *testing.T, args *TestGazelleGenerationArgs) {
	t.Run(args.Name, func(t *testing.T) {
		t.Helper() // Make the stack trace a little bit more clear.
		if args.BuildInSuffix == "" {
			args.BuildInSuffix = ".in"
		}
		if args.BuildOutSuffix == "" {
			args.BuildOutSuffix = ".out"
		}
		var inputs []FileSpec
		var goldens []FileSpec

		config := &testConfig{}
		filepath.WalkDir(args.TestDataPathAbsolute, func(path string, d fs.DirEntry, err error) error {
			shortPath := strings.TrimPrefix(path, args.TestDataPathAbsolute)

			info, err := d.Info()
			if err != nil {
				t.Fatalf("File info error on path %q. Error: %v", path, err)
			}

			if info.IsDir() {
				return nil
			}

			content, err := ioutil.ReadFile(path)
			if err != nil {
				t.Errorf("ioutil.ReadFile(%q) error: %v", path, err)
			}

			// Read in expected stdout, stderr, and exit code files.
			if d.Name() == argumentsFilename {
				config.Args = strings.Split(string(content), "\n")
				return nil
			}
			if d.Name() == expectedStdoutFilename {
				config.Stdout = string(content)
				return nil
			}
			if d.Name() == expectedStderrFilename {
				config.Stderr = string(content)
				return nil
			}
			if d.Name() == expectedExitCodeFilename {
				config.ExitCode, err = strconv.Atoi(string(content))
				if err != nil {
					// Set the ExitCode to a sentinel value (-1) to ensure that if the caller is updating the files on disk the value is updated.
					config.ExitCode = -1
					t.Errorf("Failed to parse expected exit code (%q) error: %v", path, err)
				}
				return nil
			}

			if strings.HasSuffix(shortPath, args.BuildInSuffix) {
				inputs = append(inputs, FileSpec{
					Path:    filepath.Join(args.Name, strings.TrimSuffix(shortPath, args.BuildInSuffix)+".bazel"),
					Content: string(content),
				})
			} else if strings.HasSuffix(shortPath, args.BuildOutSuffix) {
				goldens = append(goldens, FileSpec{
					Path:    filepath.Join(args.Name, strings.TrimSuffix(shortPath, args.BuildOutSuffix)+".bazel"),
					Content: string(content),
				})
			} else {
				inputs = append(inputs, FileSpec{
					Path:    filepath.Join(args.Name, shortPath),
					Content: string(content),
				})
				goldens = append(goldens, FileSpec{
					Path:    filepath.Join(args.Name, shortPath),
					Content: string(content),
				})
			}
			return nil
		})

		testdataDir, cleanup := CreateFiles(t, inputs)
		workspaceRoot := filepath.Join(testdataDir, args.Name)

		var stdout, stderr bytes.Buffer
		var actualExitCode int
		defer cleanup()
		defer func() {
			if t.Failed() {
				shouldUpdate := os.Getenv("UPDATE_SNAPSHOTS") != ""
				buildWorkspaceDirectory := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
				updateCommand := fmt.Sprintf("UPDATE_SNAPSHOTS=true bazel run %s", os.Getenv("TEST_TARGET"))
				// srcTestDirectory is the directory of the source code of the test case.
				srcTestDirectory := path.Join(buildWorkspaceDirectory, path.Dir(args.TestDataPathRelative), args.Name)
				if shouldUpdate {
					// Update stdout, stderr, exit code.
					updateExpectedConfig(t, config.Stdout, redactWorkspacePath(stdout.String(), workspaceRoot), srcTestDirectory, expectedStdoutFilename)
					updateExpectedConfig(t, config.Stderr, redactWorkspacePath(stderr.String(), workspaceRoot), srcTestDirectory, expectedStderrFilename)
					updateExpectedConfig(t, fmt.Sprintf("%d", config.ExitCode), fmt.Sprintf("%d", actualExitCode), srcTestDirectory, expectedExitCodeFilename)

					filepath.Walk(testdataDir, func(walkedPath string, info os.FileInfo, err error) error {
						if err != nil {
							return err
						}
						relativePath := strings.TrimPrefix(walkedPath, testdataDir)
						if shouldUpdate {
							if buildWorkspaceDirectory == "" {
								t.Fatalf("Tried to update snapshots but no BUILD_WORKSPACE_DIRECTORY specified.\n Try %s.", updateCommand)
							}

							if info.Name() == "BUILD.bazel" {
								destFile := strings.TrimSuffix(path.Join(buildWorkspaceDirectory, path.Dir(args.TestDataPathRelative)+relativePath), ".bazel") + ".out"

								err := copyFile(walkedPath, destFile)
								if err != nil {
									t.Fatalf("Failed to copy file %v to %v. Error: %v\n", walkedPath, destFile, err)
								}
							}

						}
						t.Logf("%q exists in %v", relativePath, testdataDir)
						return nil
					})

				} else {
					t.Logf(`
=====================================================================================
Run %s to update BUILD.out and expected{Stdout,Stderr,ExitCode}.txt files.
=====================================================================================
`, updateCommand)
				}
			}
		}()

		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		cmd := exec.CommandContext(ctx, args.GazelleBinaryPath, config.Args...)
		cmd.Stdout = &stdout
		cmd.Stderr = &stderr
		cmd.Dir = workspaceRoot
		cmd.Env = append(os.Environ(), fmt.Sprintf("BUILD_WORKSPACE_DIRECTORY=%v", workspaceRoot))
		if err := cmd.Run(); err != nil {
			var e *exec.ExitError
			if !errors.As(err, &e) {
				t.Fatal(err)
			}
		}
		errs := make([]error, 0)
		actualExitCode = cmd.ProcessState.ExitCode()
		if config.ExitCode != actualExitCode {
			errs = append(errs, fmt.Errorf("expected gazelle exit code: %d\ngot: %d",
				config.ExitCode, actualExitCode,
			))
		}
		actualStdout := redactWorkspacePath(stdout.String(), workspaceRoot)
		if strings.TrimSpace(config.Stdout) != strings.TrimSpace(actualStdout) {
			errs = append(errs, fmt.Errorf("expected gazelle stdout: %s\ngot: %s",
				config.Stdout, actualStdout,
			))
		}
		actualStderr := redactWorkspacePath(stderr.String(), workspaceRoot)
		if strings.TrimSpace(config.Stderr) != strings.TrimSpace(actualStderr) {
			errs = append(errs, fmt.Errorf("expected gazelle stderr: %s\ngot: %s",
				config.Stderr, actualStderr,
			))
		}
		if len(errs) > 0 {
			for _, err := range errs {
				t.Log(err)
			}
			t.FailNow()
		}

		CheckFiles(t, testdataDir, goldens)
	})
}

func copyFile(src string, dest string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	destFile, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, srcFile)
	if err != nil {
		return err
	}
	err = destFile.Sync()
	if err != nil {
		return err
	}
	return nil
}

type testConfig struct {
	Args     []string
	ExitCode int
	Stdout   string
	Stderr   string
}

// updateExpectedConfig writes to an expected stdout, stderr, or exit code file
// with the latest results of a test.
func updateExpectedConfig(t *testing.T, expected string, actual string, srcTestDirectory string, expectedFilename string) {
	if expected != actual {
		destFile := path.Join(srcTestDirectory, expectedFilename)

		err := os.WriteFile(destFile, []byte(actual), 0644)
		if err != nil {
			t.Fatalf("Failed to write file %v. Error: %v\n", destFile, err)
		}
	}
}

// redactWorkspacePath replaces workspace path with a constant to make the test
// output reproducible.
func redactWorkspacePath(s, wsPath string) string {
	return strings.ReplaceAll(s, wsPath, "%WORKSPACEPATH%")
}
<|endoftext|>


<commit_before>package gojson

import (
	"encoding/json"
	"github.com/buger/jsonparser"
)

func Unmarshal(data []byte, v interface{}) error {
	if o, ok := v.(JSON); ok {
		return json.Unmarshal(data, o.JSONUnmarshal())
	}
	return json.Unmarshal(data, v)
}

func JSONGetString(data []byte, keys ...string) (string, error) {
	return jsonparser.GetString(data, keys...)
}

func JSONGetFloat(data []byte, keys ...string) (float64, error) {
	return jsonparser.GetFloat(data, keys...)
}

func JSONGetInt(data []byte, keys ...string) (int64, error) {
	return jsonparser.GetInt(data, keys...)
}

func JSONGetBoolean(data []byte, keys ...string) (bool, error) {
	return jsonparser.GetBoolean(data, keys...)
}
<commit_msg>feat: json parser<commit_after>package gojson

import (
	"encoding/json"
	"github.com/buger/jsonparser"
)

func Unmarshal(data []byte, v interface{}) error {
	if o, ok := v.(JSON); ok {
		return json.Unmarshal(data, o.JSONUnmarshal())
	}
	return json.Unmarshal(data, v)
}

func JSONGetString(data []byte, keys ...string) (string, error) {
	return jsonparser.GetString(data, keys...)
}

func JSONMustGetString(data []byte, keys ...string) string {
	s, err := JSONGetString(data, keys...)
	if err != nil {
		return ""
	}
	return s
}

func JSONGetFloat(data []byte, keys ...string) (float64, error) {
	return jsonparser.GetFloat(data, keys...)
}

func JSONMustGetFloat(data []byte, keys ...string) float64 {
	f, err := JSONGetFloat(data, keys...)
	if err != nil {
		return 0.0
	}
	return f
}

func JSONGetInt(data []byte, keys ...string) (int64, error) {
	return jsonparser.GetInt(data, keys...)
}

func JSONMustGetInt(data []byte, keys ...string) int64 {
	i, err := JSONGetInt(data, keys...)
	if err != nil {
		return 0
	}
	return i
}

func JSONGetBoolean(data []byte, keys ...string) (bool, error) {
	return jsonparser.GetBoolean(data, keys...)
}

func JSONMustGetBoolean(data []byte, keys ...string) bool {
	b, err := JSONGetBoolean(data, keys...)
	if err != nil {
		return false
	}
	return b
}
<|endoftext|>


<commit_before>package json

import (
	"errors"
	"io"
)

type Encoder struct {
	w   io.Writer
	err error
}

func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{w: w}
}

func (e *Encoder) Encode(v interface{}) error {
	b, err := Marshal(v)
	if err != nil {
		return err
	}
	_, err = e.w.Write(b)
	e.err = err
	return err
}

type Decoder struct {
	p parser
}

type byteReader struct {
	io.Reader
}

func (br byteReader) ReadByte() (byte, error) {
	b := make([]byte, 1)
	_, err := br.Read(b)
	if err != nil {
		return 0, err
	}
	return b[0], nil
}

type parser struct {
	io.ByteReader
	err  error
	buf  byte
	read []byte
}

func (p *parser) Next() byte {
	var b byte
	if p.buf > 0 {
		b = p.buf
		p.buf = 0
	} else {
		var err error
		b, err = p.ByteReader.ReadByte()
		if err != nil {
			p.err = err
			return 0
		}
	}
	p.read = append(p.read, b)
	return b
}

func (p *parser) Backup() {
	p.buf = p.read[len(p.read)-1]
	p.read = p.read[:len(p.read)-1]
}

func (p *parser) Accept(s string) bool {
	b := p.Next()
	for i := 0; i < len(s); i++ {
		if s[i] == b {
			return true
		}
	}
	p.Backup()
	return false
}

func (p *parser) AcceptRun(s string) {
Loop:
	for {
		b := p.Next()
		for i := 0; i < len(s); i++ {
			if s[i] == b {
				continue Loop
			}
		}
		p.Backup()
		return
	}
}

func (p *parser) Peek() byte {
	b := p.Next()
	p.Backup()
	return b
}

func NewDecoder(r io.Reader) *Decoder {
	b, ok := r.(io.ByteReader)
	if !ok {
		b = byteReader{r}
	}
	return &Decoder{p: parser{ByteReader: b}}
}

func (d *Decoder) Decode(v interface{}) error {
	d.p.err = nil
	d.p.read = d.p.read[:0]
	if !d.readValue() {
		return errInvalidJSON
	}
	if d.p.err != nil {
		err := d.p.err
		return err
	}
	if !d.readValue() {
		if d.p.err != nil {
			return d.p.err
		}
		return errInvalidJSON
	}
	s := string(d.p.read)
	return UnmarshalString(s, v)
}

const whitespace = " 	\n\r"

func (d *Decoder) readValue() bool {
	d.p.AcceptRun(whitespace)
	var valid bool
	switch d.p.Peek() {
	case '"':
		valid = d.readString()
	case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		valid = d.readNumber()
	case '{':
		valid = d.readObject()
	case '[':
		valid = d.readArray()
	case 't':
		valid = d.readTrue()
	case 'f':
		valid = d.readFalse()
	case 'n':
		valid = d.readNull()
	}
	return valid
}

func (d *Decoder) readString() bool {
	if !d.p.Accept("\"") {
		return false
	}
	for {
		switch c := d.p.Next(); c {
		case '"':
			return true
		case '\\':
			switch d.p.Next() {
			case '"', '\\', '/', 'b', 'n', 'r', 't':
			case 'u':
				if !d.p.Accept("0123456789abcdefABCDEF") || !d.p.Accept("0123456789abcdefABCDEF") || !d.p.Accept("0123456789abcdefABCDEF") || !d.p.Accept("0123456789abcdefABCDEF") {
					return false
				}
			default:
				return false
			}
		case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f:
			return false
		}
	}
}

func (d *Decoder) readNumber() bool {
	d.p.Accept("-")
	if !d.p.Accept("0") {
		d.p.Accept("123456789")
		d.p.AcceptRun("0123456789")
	}
	if d.p.Accept(".") {
		d.p.AcceptRun("0123456789")
	}
	if d.p.Accept("eE") {
		d.p.Accept("+-")
		d.p.AcceptRun("0123456789")
	}
	return true
}

func (d *Decoder) readObject() bool {
	d.p.Accept("{")
	d.p.AcceptRun(whitespace)
	if d.p.Accept("}") {
		return true
	}
	for {
		if !d.readString() {
			return false
		}
		d.p.AcceptRun(whitespace)
		if !d.p.Accept(":") {
			return false
		}
		if !d.readValue() {
			return false
		}
		d.p.AcceptRun(whitespace)
		if d.p.Accept("}") {
			return true
		}
		if !d.p.Accept(",") {
			return false
		}
		d.p.AcceptRun(whitespace)
	}
}

func (d *Decoder) readArray() bool {
	d.p.Accept("[")
	d.p.AcceptRun(whitespace)
	if d.p.Accept("]") {
		return true
	}
	for {
		if !d.readValue() {
			return false
		}
		d.p.AcceptRun(whitespace)
		if d.p.Accept("]") {
			return true
		}
		if !d.p.Accept(",") {
			return false
		}
	}
}

func (d *Decoder) readTrue() bool {
	return d.p.Accept("t") && d.p.Accept("r") && d.p.Accept("u") && d.p.Accept("e")
}

func (d *Decoder) readFalse() bool {
	return d.p.Accept("f") && d.p.Accept("a") && d.p.Accept("l") && d.p.Accept("s") && d.p.Accept("e")
}

func (d *Decoder) readNull() bool {
	return d.p.Accept("n") && d.p.Accept("u") && d.p.Accept("l") && d.p.Accept("l")
}

var errInvalidJSON = errors.New("invalid JSON")
<commit_msg>Added simple RawMessage impl.<commit_after>package json

import (
	"errors"
	"io"
)

type RawMessage []byte

func (r *RawMessage) MarshalJSON() ([]byte, error) {
	return *r, nil
}

func (r *RawMessage) UnmarshalJSON(data []byte) error {
	if r == nil {
		return ErrRawNil
	}
	*r = append((*r)[:0], data...)
	return nil
}

type Encoder struct {
	w   io.Writer
	err error
}

func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{w: w}
}

func (e *Encoder) Encode(v interface{}) error {
	b, err := Marshal(v)
	if err != nil {
		return err
	}
	_, err = e.w.Write(b)
	e.err = err
	return err
}

type Decoder struct {
	p parser
}

type byteReader struct {
	io.Reader
}

func (br byteReader) ReadByte() (byte, error) {
	b := make([]byte, 1)
	_, err := br.Read(b)
	if err != nil {
		return 0, err
	}
	return b[0], nil
}

type parser struct {
	io.ByteReader
	err  error
	buf  byte
	read []byte
}

func (p *parser) Next() byte {
	var b byte
	if p.buf > 0 {
		b = p.buf
		p.buf = 0
	} else {
		var err error
		b, err = p.ByteReader.ReadByte()
		if err != nil {
			p.err = err
			return 0
		}
	}
	p.read = append(p.read, b)
	return b
}

func (p *parser) Backup() {
	p.buf = p.read[len(p.read)-1]
	p.read = p.read[:len(p.read)-1]
}

func (p *parser) Accept(s string) bool {
	b := p.Next()
	for i := 0; i < len(s); i++ {
		if s[i] == b {
			return true
		}
	}
	p.Backup()
	return false
}

func (p *parser) AcceptRun(s string) {
Loop:
	for {
		b := p.Next()
		for i := 0; i < len(s); i++ {
			if s[i] == b {
				continue Loop
			}
		}
		p.Backup()
		return
	}
}

func (p *parser) Peek() byte {
	b := p.Next()
	p.Backup()
	return b
}

func NewDecoder(r io.Reader) *Decoder {
	b, ok := r.(io.ByteReader)
	if !ok {
		b = byteReader{r}
	}
	return &Decoder{p: parser{ByteReader: b}}
}

func (d *Decoder) Decode(v interface{}) error {
	d.p.err = nil
	d.p.read = d.p.read[:0]
	if !d.readValue() {
		return errInvalidJSON
	}
	if d.p.err != nil {
		err := d.p.err
		return err
	}
	if !d.readValue() {
		if d.p.err != nil {
			return d.p.err
		}
		return errInvalidJSON
	}
	s := string(d.p.read)
	return UnmarshalString(s, v)
}

const whitespace = " 	\n\r"

func (d *Decoder) readValue() bool {
	d.p.AcceptRun(whitespace)
	var valid bool
	switch d.p.Peek() {
	case '"':
		valid = d.readString()
	case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		valid = d.readNumber()
	case '{':
		valid = d.readObject()
	case '[':
		valid = d.readArray()
	case 't':
		valid = d.readTrue()
	case 'f':
		valid = d.readFalse()
	case 'n':
		valid = d.readNull()
	}
	return valid
}

func (d *Decoder) readString() bool {
	if !d.p.Accept("\"") {
		return false
	}
	for {
		switch c := d.p.Next(); c {
		case '"':
			return true
		case '\\':
			switch d.p.Next() {
			case '"', '\\', '/', 'b', 'n', 'r', 't':
			case 'u':
				if !d.p.Accept("0123456789abcdefABCDEF") || !d.p.Accept("0123456789abcdefABCDEF") || !d.p.Accept("0123456789abcdefABCDEF") || !d.p.Accept("0123456789abcdefABCDEF") {
					return false
				}
			default:
				return false
			}
		case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f:
			return false
		}
	}
}

func (d *Decoder) readNumber() bool {
	d.p.Accept("-")
	if !d.p.Accept("0") {
		d.p.Accept("123456789")
		d.p.AcceptRun("0123456789")
	}
	if d.p.Accept(".") {
		d.p.AcceptRun("0123456789")
	}
	if d.p.Accept("eE") {
		d.p.Accept("+-")
		d.p.AcceptRun("0123456789")
	}
	return true
}

func (d *Decoder) readObject() bool {
	d.p.Accept("{")
	d.p.AcceptRun(whitespace)
	if d.p.Accept("}") {
		return true
	}
	for {
		if !d.readString() {
			return false
		}
		d.p.AcceptRun(whitespace)
		if !d.p.Accept(":") {
			return false
		}
		if !d.readValue() {
			return false
		}
		d.p.AcceptRun(whitespace)
		if d.p.Accept("}") {
			return true
		}
		if !d.p.Accept(",") {
			return false
		}
		d.p.AcceptRun(whitespace)
	}
}

func (d *Decoder) readArray() bool {
	d.p.Accept("[")
	d.p.AcceptRun(whitespace)
	if d.p.Accept("]") {
		return true
	}
	for {
		if !d.readValue() {
			return false
		}
		d.p.AcceptRun(whitespace)
		if d.p.Accept("]") {
			return true
		}
		if !d.p.Accept(",") {
			return false
		}
	}
}

func (d *Decoder) readTrue() bool {
	return d.p.Accept("t") && d.p.Accept("r") && d.p.Accept("u") && d.p.Accept("e")
}

func (d *Decoder) readFalse() bool {
	return d.p.Accept("f") && d.p.Accept("a") && d.p.Accept("l") && d.p.Accept("s") && d.p.Accept("e")
}

func (d *Decoder) readNull() bool {
	return d.p.Accept("n") && d.p.Accept("u") && d.p.Accept("l") && d.p.Accept("l")
}

// Errors
var (
	errInvalidJSON = errors.New("invalid JSON")
	ErrRawNil      = errors.New("RawMessage cannot be nil pointer")
)
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Comcast Cable Communications Management, LLC
 *
 * 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 jtl

import (
	"encoding/json"
	"sync"

	. "github.com/Comcast/eel/util"
)

// handleEvent processes an event (usually from the work queue) by selecting the correct handlers, applying the appropriate transformations and then sending off the tranformed event via appropriate publisher(s).
func handleEvent(ctx Context, stats *ServiceStats, event *JDoc, raw string, debug bool, syncExec bool) interface{} {
	debuginfo := make([]interface{}, 0)
	ctx.AddLogValue("destination", "unknown")
	handlers := GetHandlerFactory(ctx).GetHandlersForEvent(ctx, event)
	if len(handlers) == 0 {
		ctx.Log().Info("action", "no_matching_handlers")
		ctx.Log().Debug("debug_action", "no_matching_handlers", "payload", event.GetOriginalObject())
	}
	initialCtx := ctx
	ctx = ctx.SubContext()
	var wg sync.WaitGroup
	for _, handler := range handlers {
		ctx.AddLogValue("topic", handler.Topic)
		ctx.AddLogValue("tenantId", handler.TenantId)
		ctx.AddLogValue("handler", handler.Name)
		publishers, err := handler.ProcessEvent(initialCtx.SubContext(), event)
		if err != nil {
			ctx.Log().Error("error_type", "transformation", "cause", "bad_transformation", "handler", handler.Name, "tenant", handler.TenantId, "trace.in.data", event.GetOriginalObject(), "error", err.Error())
			ctx.Log().Metric("bad_transformation", M_Namespace, "xrs", M_Metric, "bad_transformation", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
			stats.IncErrors()
			continue
		}
		for _, publisher := range publishers {
			dc := ctx.Value(EelDuplicateChecker).(DuplicateChecker)
			if dc.GetTtl() > 0 && dc.IsDuplicate(ctx, []byte(publisher.GetUrl()+"\n"+publisher.GetPayload())) {
				ctx.Log().Info("action", "dropping_duplicate", "handler", handler.Name, "tenant", handler.TenantId)
				ctx.Log().Metric("dropping_duplicate", M_Namespace, "xrs", M_Metric, "dropping_duplicate", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
				continue
			}
			// trace header
			traceHeaderKey := GetConfig(ctx).HttpTransactionHeader
			if publisher.GetHeaders() == nil {
				publisher.SetHeaders(make(map[string]string, 0))
			}
			if publisher.GetHeaders()[traceHeaderKey] == "" && ctx.LogValue("tx.traceId") != nil {
				publisher.GetHeaders()[traceHeaderKey] = ctx.LogValue("tx.traceId").(string)
			}
			ctx.AddLogValue("tx.traceId", publisher.GetHeaders()[traceHeaderKey])
			ctx.AddValue("tx.traceId", publisher.GetHeaders()[traceHeaderKey])
			// other log params
			ctx.AddLogValue("trace.out.url", publisher.GetUrl())
			//ctx.AddLogValue("trace.in.data", event.GetOriginalObject())
			//ctx.AddLogValue("trace.out.data", publisher.GetPayload())
			ctx.AddLogValue("trace.out.protocol", publisher.GetProtocol())
			//ctx.AddLogValue("trace.out.path", publisher.GetPath())
			ctx.AddLogValue("trace.out.headers", publisher.GetHeaders())
			ctx.AddLogValue("trace.out.protocol", publisher.GetProtocol())
			//ctx.AddLogValue("trace.out.endpoint", publisher.GetEndpoint())
			ctx.AddLogValue("trace.out.verb", publisher.GetVerb())
			ctx.AddLogValue("trace.out.url", publisher.GetUrl())
			if syncExec {
				// no need to call out to endpoint in sync mode
				debuginfo = append(debuginfo, publisher.GetPayloadParsed().GetOriginalObject())
			} else if debug {
				// sequential execution to collect debug info
				_, err := publisher.Publish()

				if publisher.GetAsyncReplyTo() != nil {
					sendAsyncResponse(ctx, publisher.GetAsyncReplyTo(), err)
				}

				AddLatencyLog(ctx, stats, "stat.eel.time")
				ctx.AddLogValue("trace.out.endpoint", publisher.GetEndpoint())
				ctx.AddLogValue("trace.out.url", publisher.GetUrl())
				if err != nil {
					ctx.Log().Error("error_type", "publish_event", "error", err.Error(), "cause", "publish_event")
					ctx.Log().Metric("publish_failed", M_Namespace, "xrs", M_Metric, "publish_failed", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
					stats.IncErrors()
				} else {
					ctx.Log().Info("action", "published_event")
					ctx.Log().Metric("published_event", M_Namespace, "xrs", M_Metric, "published_event", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
					stats.IncOutCount()
				}
				de := make(map[string]interface{}, 0)
				de["trace.out.endpoint"] = publisher.GetEndpoint()
				de["trace.out.path"] = publisher.GetPath()
				de["trace.out.headers"] = publisher.GetHeaders()
				de["trace.out.protocol"] = publisher.GetProtocol()
				de["trace.out.verb"] = publisher.GetVerb()
				de["trace.out.url"] = publisher.GetUrl()
				if publisher.GetPayload() != "" {
					data := make(map[string]interface{})
					err := json.Unmarshal([]byte(publisher.GetPayload()), &data)
					if err == nil {
						de["trace.out.data"] = data
					} else {
						de["trace.out.data"] = publisher.GetPayload()
					}
				} else {
					de["trace.out.data"] = ""
				}
				de["trace.in.data"] = event.GetOriginalObject()
				de["tenant.id"] = handler.TenantId
				de["handler"] = handler.Name
				de["api"] = publisher.GetApi()
				de["tx.id"] = ctx.Id()
				de["tx.traceId"] = publisher.GetHeaders()[traceHeaderKey]
				if errs := publisher.GetErrors(); errs != nil {
					de["tx.errors"] = errs
				}
				debuginfo = append(debuginfo, de)
			} else {
				//c := ctx
				//p := publisher
				wg.Add(1)
				go func(c Context, p EventPublisher) {
					defer wg.Done()
					defer c.HandlePanic()
					_, err := p.Publish()

					if p.GetAsyncReplyTo() != nil {
						sendAsyncResponse(c, p.GetAsyncReplyTo(), err)
					}

					AddLatencyLog(c, stats, "stat.eel.time")
					//c.AddLogValue("trace.out.endpoint", p.GetEndpoint())
					c.AddLogValue("trace.out.url", p.GetUrl())
					if err != nil {
						c.Log().Error("error_type", "publish_event", "error", err.Error(), "cause", "publish_event")
						c.Log().Metric("publish_failed", M_Namespace, "xrs", M_Metric, "publish_failed", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
						stats.IncErrors()
					} else {
						if p.GetProtocol() != "null" {
							c.Log().Info("action", "published_event")
							c.Log().Metric("published_event", M_Namespace, "xrs", M_Metric, "published_event", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
							stats.IncOutCount()
						}
					}
				}(ctx.SubContext(), publisher)
			}
		}
	}
	wg.Wait()
	return debuginfo
}

func sendAsyncResponse(c Context, replyTo map[string]string, err error) {
	c.Log().Debug("op", "sendAsyncResponse", "replyTo", replyTo, "error", err)

	t, ok := replyTo["type"]
	if !ok {
		c.Log().Error("op", "sendAsyncResponse", "action", "getReplyType", "error", "reply_type_not_found")
		return
	}
	switch t {
	case "eel":
		sendAsyncResponseToEEL(c, replyTo, err)
	default:
		c.Log().Error("op", "sendAsyncResponse", "error", "unsupported_type", "type", t)
	}
}

func sendAsyncResponseToEEL(c Context, replyTo map[string]string, err error) {
	location, ok := replyTo["location"]
	if !ok {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "getLocation", "error", "location_not_found", "replyTo", replyTo)
		return
	}
	endpoint, ok := replyTo["endpoint"]
	if !ok {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "getEndpoint", "error", "endpoint_not_found", "replyTo", replyTo)
		return
	}

	body := make(map[string]string)
	if err != nil {
		body["status"] = "fail"
		body["message"] = err.Error()
	} else {
		body["status"] = "success"
	}
	body["location"] = location

	payload := make(map[string]interface{})
	payload["traceId"] = c.Value("tx.traceId")
	payload["body"] = body

	payloadData, err := json.Marshal(payload)
	if err != nil {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "marshalPayload", "error", err, "replyTo", replyTo, "payload", payload)
		return
	}

	resp, status, err := GetRetrier(c).RetryEndpoint(c, endpoint, string(payloadData), "POST", nil, nil)
	if err != nil {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "postEndpoint", "error", err, "replyTo", replyTo, "resp", resp)
		return
	}
	if status < 200 || status >= 300 {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "postEndpoint", "statusCode", status, "replyTo", replyTo, "resp", resp)
		return
	}
}
<commit_msg>Add message type  to the code<commit_after>/**
 * Copyright 2015 Comcast Cable Communications Management, LLC
 *
 * 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 jtl

import (
	"encoding/json"
	"sync"

	. "github.com/Comcast/eel/util"
)

// handleEvent processes an event (usually from the work queue) by selecting the correct handlers, applying the appropriate transformations and then sending off the tranformed event via appropriate publisher(s).
func handleEvent(ctx Context, stats *ServiceStats, event *JDoc, raw string, debug bool, syncExec bool) interface{} {
	debuginfo := make([]interface{}, 0)
	ctx.AddLogValue("destination", "unknown")
	handlers := GetHandlerFactory(ctx).GetHandlersForEvent(ctx, event)
	if len(handlers) == 0 {
		ctx.Log().Info("action", "no_matching_handlers")
		ctx.Log().Debug("debug_action", "no_matching_handlers", "payload", event.GetOriginalObject())
	}
	initialCtx := ctx
	ctx = ctx.SubContext()
	var wg sync.WaitGroup
	for _, handler := range handlers {
		ctx.AddLogValue("topic", handler.Topic)
		ctx.AddLogValue("tenantId", handler.TenantId)
		ctx.AddLogValue("handler", handler.Name)
		publishers, err := handler.ProcessEvent(initialCtx.SubContext(), event)
		if err != nil {
			ctx.Log().Error("error_type", "transformation", "cause", "bad_transformation", "handler", handler.Name, "tenant", handler.TenantId, "trace.in.data", event.GetOriginalObject(), "error", err.Error())
			ctx.Log().Metric("bad_transformation", M_Namespace, "xrs", M_Metric, "bad_transformation", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
			stats.IncErrors()
			continue
		}
		for _, publisher := range publishers {
			dc := ctx.Value(EelDuplicateChecker).(DuplicateChecker)
			if dc.GetTtl() > 0 && dc.IsDuplicate(ctx, []byte(publisher.GetUrl()+"\n"+publisher.GetPayload())) {
				ctx.Log().Info("action", "dropping_duplicate", "handler", handler.Name, "tenant", handler.TenantId)
				ctx.Log().Metric("dropping_duplicate", M_Namespace, "xrs", M_Metric, "dropping_duplicate", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
				continue
			}
			// trace header
			traceHeaderKey := GetConfig(ctx).HttpTransactionHeader
			if publisher.GetHeaders() == nil {
				publisher.SetHeaders(make(map[string]string, 0))
			}
			if publisher.GetHeaders()[traceHeaderKey] == "" && ctx.LogValue("tx.traceId") != nil {
				publisher.GetHeaders()[traceHeaderKey] = ctx.LogValue("tx.traceId").(string)
			}
			ctx.AddLogValue("tx.traceId", publisher.GetHeaders()[traceHeaderKey])
			ctx.AddValue("tx.traceId", publisher.GetHeaders()[traceHeaderKey])
			// other log params
			ctx.AddLogValue("trace.out.url", publisher.GetUrl())
			//ctx.AddLogValue("trace.in.data", event.GetOriginalObject())
			//ctx.AddLogValue("trace.out.data", publisher.GetPayload())
			ctx.AddLogValue("trace.out.protocol", publisher.GetProtocol())
			//ctx.AddLogValue("trace.out.path", publisher.GetPath())
			ctx.AddLogValue("trace.out.headers", publisher.GetHeaders())
			ctx.AddLogValue("trace.out.protocol", publisher.GetProtocol())
			//ctx.AddLogValue("trace.out.endpoint", publisher.GetEndpoint())
			ctx.AddLogValue("trace.out.verb", publisher.GetVerb())
			ctx.AddLogValue("trace.out.url", publisher.GetUrl())
			if syncExec {
				// no need to call out to endpoint in sync mode
				debuginfo = append(debuginfo, publisher.GetPayloadParsed().GetOriginalObject())
			} else if debug {
				// sequential execution to collect debug info
				_, err := publisher.Publish()

				if publisher.GetAsyncReplyTo() != nil {
					sendAsyncResponse(ctx, publisher.GetAsyncReplyTo(), err)
				}

				AddLatencyLog(ctx, stats, "stat.eel.time")
				ctx.AddLogValue("trace.out.endpoint", publisher.GetEndpoint())
				ctx.AddLogValue("trace.out.url", publisher.GetUrl())
				if err != nil {
					ctx.Log().Error("error_type", "publish_event", "error", err.Error(), "cause", "publish_event")
					ctx.Log().Metric("publish_failed", M_Namespace, "xrs", M_Metric, "publish_failed", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
					stats.IncErrors()
				} else {
					ctx.Log().Info("action", "published_event")
					ctx.Log().Metric("published_event", M_Namespace, "xrs", M_Metric, "published_event", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
					stats.IncOutCount()
				}
				de := make(map[string]interface{}, 0)
				de["trace.out.endpoint"] = publisher.GetEndpoint()
				de["trace.out.path"] = publisher.GetPath()
				de["trace.out.headers"] = publisher.GetHeaders()
				de["trace.out.protocol"] = publisher.GetProtocol()
				de["trace.out.verb"] = publisher.GetVerb()
				de["trace.out.url"] = publisher.GetUrl()
				if publisher.GetPayload() != "" {
					data := make(map[string]interface{})
					err := json.Unmarshal([]byte(publisher.GetPayload()), &data)
					if err == nil {
						de["trace.out.data"] = data
					} else {
						de["trace.out.data"] = publisher.GetPayload()
					}
				} else {
					de["trace.out.data"] = ""
				}
				de["trace.in.data"] = event.GetOriginalObject()
				de["tenant.id"] = handler.TenantId
				de["handler"] = handler.Name
				de["api"] = publisher.GetApi()
				de["tx.id"] = ctx.Id()
				de["tx.traceId"] = publisher.GetHeaders()[traceHeaderKey]
				if errs := publisher.GetErrors(); errs != nil {
					de["tx.errors"] = errs
				}
				debuginfo = append(debuginfo, de)
			} else {
				//c := ctx
				//p := publisher
				wg.Add(1)
				go func(c Context, p EventPublisher) {
					defer wg.Done()
					defer c.HandlePanic()
					_, err := p.Publish()

					if p.GetAsyncReplyTo() != nil {
						sendAsyncResponse(c, p.GetAsyncReplyTo(), err)
					}

					AddLatencyLog(c, stats, "stat.eel.time")
					//c.AddLogValue("trace.out.endpoint", p.GetEndpoint())
					c.AddLogValue("trace.out.url", p.GetUrl())
					if err != nil {
						c.Log().Error("error_type", "publish_event", "error", err.Error(), "cause", "publish_event")
						c.Log().Metric("publish_failed", M_Namespace, "xrs", M_Metric, "publish_failed", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
						stats.IncErrors()
					} else {
						if p.GetProtocol() != "null" {
							c.Log().Info("action", "published_event")
							c.Log().Metric("published_event", M_Namespace, "xrs", M_Metric, "published_event", M_Unit, "Count", M_Dims, "app="+AppId+"&env="+EnvName+"&instance="+InstanceName+"&destination="+ctx.LogValue("destination").(string), M_Val, 1.0)
							stats.IncOutCount()
						}
					}
				}(ctx.SubContext(), publisher)
			}
		}
	}
	wg.Wait()
	return debuginfo
}

func sendAsyncResponse(c Context, replyTo map[string]string, err error) {
	c.Log().Debug("op", "sendAsyncResponse", "replyTo", replyTo, "error", err)

	t, ok := replyTo["type"]
	if !ok {
		c.Log().Error("op", "sendAsyncResponse", "action", "getReplyType", "error", "reply_type_not_found")
		return
	}
	switch t {
	case "eel":
		sendAsyncResponseToEEL(c, replyTo, err)
	default:
		c.Log().Error("op", "sendAsyncResponse", "error", "unsupported_type", "type", t)
	}
}

func sendAsyncResponseToEEL(c Context, replyTo map[string]string, err error) {
	location, ok := replyTo["location"]
	if !ok {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "getLocation", "error", "location_not_found", "replyTo", replyTo)
		return
	}
	endpoint, ok := replyTo["endpoint"]
	if !ok {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "getEndpoint", "error", "endpoint_not_found", "replyTo", replyTo)
		return
	}

	body := make(map[string]string)
	if err != nil {
		body["status"] = "fail"
		body["message"] = err.Error()
	} else {
		body["status"] = "success"
	}
	body["location"] = location

	payload := make(map[string]interface{})
	payload["traceId"] = c.Value("tx.traceId")
	payload["messageType"] = "AsyncResponse"
	payload["body"] = body

	payloadData, err := json.Marshal(payload)
	if err != nil {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "marshalPayload", "error", err, "replyTo", replyTo, "payload", payload)
		return
	}

	resp, status, err := GetRetrier(c).RetryEndpoint(c, endpoint, string(payloadData), "POST", nil, nil)
	if err != nil {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "postEndpoint", "error", err, "replyTo", replyTo, "resp", resp)
		return
	}
	if status < 200 || status >= 300 {
		c.Log().Error("op", "sendAsyncResponseToEEL", "action", "postEndpoint", "statusCode", status, "replyTo", replyTo, "resp", resp)
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"runtime"
	"strings"
	"syscall"
)

func main() {
	var dependencies []StaticLibrary
	parallels := 0
	done := make(chan bool)

	// set parallel numbers
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Parse flags
	version := flag.String("v", NGINX_VERSION, "nginx version")
	nginxConfigurePath := flag.String("c", "", "configuration file for building nginx")
	modulesConfPath := flag.String("m", "", "configuration file for 3rd party modules")
	workParentDir := flag.String("d", "", "working directory")
	jobs := flag.Int("j", runtime.NumCPU(), "jobs to build nginx")
	verbose := flag.Bool("verbose", false, "verbose mode")
	pcreStatic := flag.Bool("pcre", false, "embedded PCRE statically")
	pcreVersion := flag.String("pcreversion", PCRE_VERSION, "PCRE version")
	openSSLStatic := flag.Bool("openssl", false, "embedded PCRE statically")
	openSSLVersion := flag.String("opensslversion", OPENSSL_VERSION, "OpenSSL version")
	zlibStatic := flag.Bool("zlib", false, "embedded zlib statically")
	zlibVersion := flag.String("zlibversion", ZLIB_VERSION, "zlib version")
	clear := flag.Bool("clear", false, "remove entries in working directory")
	versionPrint := flag.Bool("version", false, "print nginx-build versions")
	versionsPrint := flag.Bool("versions", false, "print nginx versions")
	openResty := flag.Bool("openresty", false, "download openresty instead of nginx")
	openRestyVersion := flag.String("openrestyversion", OPENRESTY_VERSION, "openresty version")
	flag.Parse()

	if *versionPrint {
		printNginxBuildVersion()
		os.Exit(0)
	}

	if *versionsPrint {
		printNginxVersions()
		os.Exit(0)
	}

	printFirstMsg()

	// set verbose mode
	VerboseEnabled = *verbose

	var nginxBuilder Builder
	if *openResty {
		nginxBuilder = makeBuilder(COMPONENT_OPENRESTY, *openRestyVersion)
	} else {
		nginxBuilder = makeBuilder(COMPONENT_NGINX, *version)
	}
	pcreBuilder := makeBuilder(COMPONENT_PCRE, *pcreVersion)
	openSSLBuilder := makeBuilder(COMPONENT_OPENSSL, *openSSLVersion)
	zlibBuilder := makeBuilder(COMPONENT_ZLIB, *zlibVersion)

	// change default umask
	_ = syscall.Umask(0)

	versionCheck(*version)

	nginxConfigure, err := fileGetContents(*nginxConfigurePath)
	if err != nil {
		log.Fatal(err.Error())
	}
	nginxConfigure = normalizeConfigure(nginxConfigure)

	modules3rd, err := loadModules3rdFile(*modulesConfPath)
	if err != nil {
		log.Fatal(err.Error())
	}

	if len(*workParentDir) == 0 {
		log.Fatal("set working directory with -d")
	}

	if !fileExists(*workParentDir) {
		err := os.Mkdir(*workParentDir, 0755)
		if err != nil {
			log.Fatalf("Failed to create working directory(%s) does not exist.", *workParentDir)
		}
	}

	var workDir string
	if *openResty {
		workDir = *workParentDir + "/" + *openRestyVersion
	} else {
		workDir = *workParentDir + "/" + *version
	}
	if *clear {
		err := clearWorkDir(workDir)
		if err != nil {
			log.Fatal(err.Error())
		}
	}

	if !fileExists(workDir) {
		err := os.Mkdir(workDir, 0755)
		if err != nil {
			log.Fatalf("Failed to create working directory(%s) does not exist.", workDir)
		}
	}

	rootDir := saveCurrentDir()
	// cd workDir
	err = os.Chdir(workDir)
	if err != nil {
		log.Fatal(err.Error())
	}

	if *pcreStatic {
		parallels++
		go downloadAndExtractParallel(&pcreBuilder, done)
	}

	if *openSSLStatic {
		parallels++
		go downloadAndExtractParallel(&openSSLBuilder, done)
	}

	if *zlibStatic {
		parallels++
		go downloadAndExtractParallel(&zlibBuilder, done)
	}

	parallels++
	go downloadAndExtractParallel(&nginxBuilder, done)

	if len(modules3rd) > 0 {
		parallels += len(modules3rd)
		for _, m := range modules3rd {
			go downloadAndExtractModule3rdParallel(m, done)
		}

	}

	// wait until all downloading processes by goroutine finish
	for i := 0; i < parallels; i++ {
		<-done
	}

	if len(modules3rd) > 0 {
		for _, m := range modules3rd {
			provideModule3rd(&m)
		}
	}

	// cd workDir/nginx-${version}
	os.Chdir(nginxBuilder.sourcePath())

	if *pcreStatic {
		dependencies = append(dependencies, makeStaticLibrary(&pcreBuilder))
	}

	if *openSSLStatic {
		dependencies = append(dependencies, makeStaticLibrary(&openSSLBuilder))
	}

	if *zlibStatic {
		dependencies = append(dependencies, makeStaticLibrary(&zlibBuilder))
	}

	log.Printf("Generate configure script for %s.....", nginxBuilder.sourcePath())

	if *pcreStatic && pcreBuilder.isIncludeWithOption(nginxConfigure) {
		log.Printf(pcreBuilder.warnMsgWithLibrary())
	}

	if *openSSLStatic && openSSLBuilder.isIncludeWithOption(nginxConfigure) {
		log.Printf(openSSLBuilder.warnMsgWithLibrary())

	}

	if *zlibStatic && zlibBuilder.isIncludeWithOption(nginxConfigure) {
		log.Printf(zlibBuilder.warnMsgWithLibrary())
	}

	if strings.Contains(nginxConfigure, "--add-module=") {
		log.Println("[warn]Using '--add-module' is discouraged. Instead give ini-file with '-m' to 'nginx-build'")
	}

	configureScript := configureGen(nginxConfigure, modules3rd, dependencies)
	err = ioutil.WriteFile("./nginx-configure", []byte(configureScript), 0655)
	if err != nil {
		log.Fatalf("Failed to generate configure script for %s", nginxBuilder.sourcePath())
	}
	log.Printf("Configure %s.....", nginxBuilder.sourcePath())
	err = configureNginx()
	if err != nil {
		log.Fatalf("Failed to configure %s", nginxBuilder.sourcePath())
	}

	log.Printf("Build %s.....", nginxBuilder.sourcePath())
	if *openSSLStatic {
		// Workarounds for protecting a failure of building nginx with static-linked OpenSSL.

		// Unfortunately a build of OpenSSL fails when multi-CPUs are used.
		*jobs = 1

		// Sometime machine hardware name('uname -m') is different
		// from machine processor architecture name('uname -p') on Mac.
		// Specifically, `uname -p` is 'i386' and `uname -m` is 'x86_64'.
		// In this case, a build of OpenSSL fails.
		// So it needs to convince OpenSSL with KERNEL_BITS.
		if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" {
			os.Setenv("KERNEL_BITS", "64")
		}
	}
	err = buildNginx(*jobs)
	if err != nil {
		fmt.Println(err.Error())
		log.Fatalf("Failed to build %s", nginxBuilder.sourcePath())
	}

	printLastMsg(workDir, nginxBuilder.sourcePath(), *openResty)

	// cd rootDir
	os.Chdir(rootDir)
}
<commit_msg>Typo fixed.<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"runtime"
	"strings"
	"syscall"
)

func main() {
	var dependencies []StaticLibrary
	parallels := 0
	done := make(chan bool)

	// set parallel numbers
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Parse flags
	version := flag.String("v", NGINX_VERSION, "nginx version")
	nginxConfigurePath := flag.String("c", "", "configuration file for building nginx")
	modulesConfPath := flag.String("m", "", "configuration file for 3rd party modules")
	workParentDir := flag.String("d", "", "working directory")
	jobs := flag.Int("j", runtime.NumCPU(), "jobs to build nginx")
	verbose := flag.Bool("verbose", false, "verbose mode")
	pcreStatic := flag.Bool("pcre", false, "embedded PCRE statically")
	pcreVersion := flag.String("pcreversion", PCRE_VERSION, "PCRE version")
	openSSLStatic := flag.Bool("openssl", false, "embedded PCRE statically")
	openSSLVersion := flag.String("opensslversion", OPENSSL_VERSION, "OpenSSL version")
	zlibStatic := flag.Bool("zlib", false, "embedded zlib statically")
	zlibVersion := flag.String("zlibversion", ZLIB_VERSION, "zlib version")
	clear := flag.Bool("clear", false, "remove entries in working directory")
	versionPrint := flag.Bool("version", false, "print nginx-build versions")
	versionsPrint := flag.Bool("versions", false, "print nginx versions")
	openResty := flag.Bool("openresty", false, "download openresty instead of nginx")
	openRestyVersion := flag.String("openrestyversion", OPENRESTY_VERSION, "openresty version")
	flag.Parse()

	if *versionPrint {
		printNginxBuildVersion()
		os.Exit(0)
	}

	if *versionsPrint {
		printNginxVersions()
		os.Exit(0)
	}

	printFirstMsg()

	// set verbose mode
	VerboseEnabled = *verbose

	var nginxBuilder Builder
	if *openResty {
		nginxBuilder = makeBuilder(COMPONENT_OPENRESTY, *openRestyVersion)
	} else {
		nginxBuilder = makeBuilder(COMPONENT_NGINX, *version)
	}
	pcreBuilder := makeBuilder(COMPONENT_PCRE, *pcreVersion)
	openSSLBuilder := makeBuilder(COMPONENT_OPENSSL, *openSSLVersion)
	zlibBuilder := makeBuilder(COMPONENT_ZLIB, *zlibVersion)

	// change default umask
	_ = syscall.Umask(0)

	versionCheck(*version)

	nginxConfigure, err := fileGetContents(*nginxConfigurePath)
	if err != nil {
		log.Fatal(err.Error())
	}
	nginxConfigure = normalizeConfigure(nginxConfigure)

	modules3rd, err := loadModules3rdFile(*modulesConfPath)
	if err != nil {
		log.Fatal(err.Error())
	}

	if len(*workParentDir) == 0 {
		log.Fatal("set working directory with -d")
	}

	if !fileExists(*workParentDir) {
		err := os.Mkdir(*workParentDir, 0755)
		if err != nil {
			log.Fatalf("Failed to create working directory(%s) does not exist.", *workParentDir)
		}
	}

	var workDir string
	if *openResty {
		workDir = *workParentDir + "/" + *openRestyVersion
	} else {
		workDir = *workParentDir + "/" + *version
	}
	if *clear {
		err := clearWorkDir(workDir)
		if err != nil {
			log.Fatal(err.Error())
		}
	}

	if !fileExists(workDir) {
		err := os.Mkdir(workDir, 0755)
		if err != nil {
			log.Fatalf("Failed to create working directory(%s) does not exist.", workDir)
		}
	}

	rootDir := saveCurrentDir()
	// cd workDir
	err = os.Chdir(workDir)
	if err != nil {
		log.Fatal(err.Error())
	}

	if *pcreStatic {
		parallels++
		go downloadAndExtractParallel(&pcreBuilder, done)
	}

	if *openSSLStatic {
		parallels++
		go downloadAndExtractParallel(&openSSLBuilder, done)
	}

	if *zlibStatic {
		parallels++
		go downloadAndExtractParallel(&zlibBuilder, done)
	}

	parallels++
	go downloadAndExtractParallel(&nginxBuilder, done)

	if len(modules3rd) > 0 {
		parallels += len(modules3rd)
		for _, m := range modules3rd {
			go downloadAndExtractModule3rdParallel(m, done)
		}

	}

	// wait until all downloading processes by goroutine finish
	for i := 0; i < parallels; i++ {
		<-done
	}

	if len(modules3rd) > 0 {
		for _, m := range modules3rd {
			provideModule3rd(&m)
		}
	}

	// cd workDir/nginx-${version}
	os.Chdir(nginxBuilder.sourcePath())

	if *pcreStatic {
		dependencies = append(dependencies, makeStaticLibrary(&pcreBuilder))
	}

	if *openSSLStatic {
		dependencies = append(dependencies, makeStaticLibrary(&openSSLBuilder))
	}

	if *zlibStatic {
		dependencies = append(dependencies, makeStaticLibrary(&zlibBuilder))
	}

	log.Printf("Generate configure script for %s.....", nginxBuilder.sourcePath())

	if *pcreStatic && pcreBuilder.isIncludeWithOption(nginxConfigure) {
		log.Printf(pcreBuilder.warnMsgWithLibrary())
	}

	if *openSSLStatic && openSSLBuilder.isIncludeWithOption(nginxConfigure) {
		log.Printf(openSSLBuilder.warnMsgWithLibrary())

	}

	if *zlibStatic && zlibBuilder.isIncludeWithOption(nginxConfigure) {
		log.Printf(zlibBuilder.warnMsgWithLibrary())
	}

	if strings.Contains(nginxConfigure, "--add-module=") {
		log.Println("[warn]Using '--add-module' is discouraged. Instead give ini-file with '-m' to 'nginx-build'")
	}

	configureScript := configureGen(nginxConfigure, modules3rd, dependencies)
	err = ioutil.WriteFile("./nginx-configure", []byte(configureScript), 0655)
	if err != nil {
		log.Fatalf("Failed to generate configure script for %s", nginxBuilder.sourcePath())
	}
	log.Printf("Configure %s.....", nginxBuilder.sourcePath())
	err = configureNginx()
	if err != nil {
		log.Fatalf("Failed to configure %s", nginxBuilder.sourcePath())
	}

	log.Printf("Build %s.....", nginxBuilder.sourcePath())
	if *openSSLStatic {
		// Workarounds for protecting a failure of building nginx with static-linked OpenSSL.

		// Unfortunately a build of OpenSSL fails when multi-CPUs are used.
		*jobs = 1

		// Sometimes machine hardware name('uname -m') is different
		// from machine processor architecture name('uname -p') on Mac.
		// Specifically, `uname -p` is 'i386' and `uname -m` is 'x86_64'.
		// In this case, a build of OpenSSL fails.
		// So it needs to convince OpenSSL with KERNEL_BITS.
		if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" {
			os.Setenv("KERNEL_BITS", "64")
		}
	}
	err = buildNginx(*jobs)
	if err != nil {
		fmt.Println(err.Error())
		log.Fatalf("Failed to build %s", nginxBuilder.sourcePath())
	}

	printLastMsg(workDir, nginxBuilder.sourcePath(), *openResty)

	// cd rootDir
	os.Chdir(rootDir)
}
<|endoftext|>