<commit_before>/*
Copyright 2012 Google Inc. 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 shlex

/*
Package shlex implements a simple lexer which splits input in to tokens using
shell-style rules for quoting and commenting.
*/
import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"strings"
)

/*
A TokenType is a top-level token; a word, space, comment, unknown.
*/
type TokenType int

/*
A RuneTokenType is the type of a UTF-8 character; a character, quote, space, escape.
*/
type RuneTokenType int

type lexerState int

type Token struct {
	tokenType TokenType
	value     string
}

/*
Two tokens are equal if both their types and values are equal. A nil token can
never equal another token.
*/
func (a *Token) Equal(b *Token) bool {
	if a == nil || b == nil {
		return false
	}
	if a.tokenType != b.tokenType {
		return false
	}
	return a.value == b.value
}

const (
	RUNE_CHAR              string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-,/@$*()+=><:;&^%~"
	RUNE_SPACE             string = " \t\r\n"
	RUNE_ESCAPING_QUOTE    string = "\""
	RUNE_NONESCAPING_QUOTE string = "'"
	RUNE_ESCAPE                   = "\\"
	RUNE_COMMENT                  = "#"

	RUNETOKEN_UNKNOWN           RuneTokenType = 0
	RUNETOKEN_CHAR              RuneTokenType = 1
	RUNETOKEN_SPACE             RuneTokenType = 2
	RUNETOKEN_ESCAPING_QUOTE    RuneTokenType = 3
	RUNETOKEN_NONESCAPING_QUOTE RuneTokenType = 4
	RUNETOKEN_ESCAPE            RuneTokenType = 5
	RUNETOKEN_COMMENT           RuneTokenType = 6
	RUNETOKEN_EOF               RuneTokenType = 7

	TOKEN_UNKNOWN TokenType = 0
	TOKEN_WORD    TokenType = 1
	TOKEN_SPACE   TokenType = 2
	TOKEN_COMMENT TokenType = 3

	STATE_START           lexerState = 0
	STATE_INWORD          lexerState = 1
	STATE_ESCAPING        lexerState = 2
	STATE_ESCAPING_QUOTED lexerState = 3
	STATE_QUOTED_ESCAPING lexerState = 4
	STATE_QUOTED          lexerState = 5
	STATE_COMMENT         lexerState = 6

	INITIAL_TOKEN_CAPACITY int = 100
)

/*
A type for classifying characters. This allows for different sorts of
classifiers - those accepting extended non-ascii chars, or strict posix
compatibility, for example.
*/
type TokenClassifier struct {
	typeMap map[int32]RuneTokenType
}

func addRuneClass(typeMap *map[int32]RuneTokenType, runes string, tokenType RuneTokenType) {
	for _, rune := range runes {
		(*typeMap)[int32(rune)] = tokenType
	}
}

/*
Create a new classifier for basic ASCII characters.
*/
func NewDefaultClassifier() *TokenClassifier {
	typeMap := map[int32]RuneTokenType{}
	addRuneClass(&typeMap, RUNE_CHAR, RUNETOKEN_CHAR)
	addRuneClass(&typeMap, RUNE_SPACE, RUNETOKEN_SPACE)
	addRuneClass(&typeMap, RUNE_ESCAPING_QUOTE, RUNETOKEN_ESCAPING_QUOTE)
	addRuneClass(&typeMap, RUNE_NONESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE)
	addRuneClass(&typeMap, RUNE_ESCAPE, RUNETOKEN_ESCAPE)
	addRuneClass(&typeMap, RUNE_COMMENT, RUNETOKEN_COMMENT)
	return &TokenClassifier{
		typeMap: typeMap}
}

func (classifier *TokenClassifier) ClassifyRune(rune int32) RuneTokenType {
	return classifier.typeMap[rune]
}

/*
A type for turning an input stream in to a sequence of strings. Whitespace and
comments are skipped.
*/
type Lexer struct {
	tokenizer *Tokenizer
}

/*
Create a new lexer.
*/
func NewLexer(r io.Reader) (*Lexer, error) {

	tokenizer, err := NewTokenizer(r)
	if err != nil {
		return nil, err
	}
	lexer := &Lexer{tokenizer: tokenizer}
	return lexer, nil
}

/*
Return the next word, and an error value. If there are no more words, the error
will be io.EOF.
*/
func (l *Lexer) NextWord() (string, error) {
	var token *Token
	var err error
	for {
		token, err = l.tokenizer.NextToken()
		if err != nil {
			return "", err
		}
		switch token.tokenType {
		case TOKEN_WORD:
			{
				return token.value, nil
			}
		case TOKEN_COMMENT:
			{
				// skip comments
			}
		default:
			{
				panic(fmt.Sprintf("Unknown token type: %v", token.tokenType))
			}
		}
	}
	return "", io.EOF
}

/*
A type for turning an input stream in to a sequence of typed tokens.
*/
type Tokenizer struct {
	input      *bufio.Reader
	classifier *TokenClassifier
}

/*
Create a new tokenizer.
*/
func NewTokenizer(r io.Reader) (*Tokenizer, error) {
	input := bufio.NewReader(r)
	classifier := NewDefaultClassifier()
	tokenizer := &Tokenizer{
		input:      input,
		classifier: classifier}
	return tokenizer, nil
}

/*
Scan the stream for the next token.

This uses an internal state machine. It will panic if it encounters a character
which it does not know how to handle.
*/
func (t *Tokenizer) scanStream() (*Token, error) {
	state := STATE_START
	var tokenType TokenType
	value := make([]int32, 0, INITIAL_TOKEN_CAPACITY)
	var (
		nextRune     int32
		nextRuneType RuneTokenType
		err          error
	)
SCAN:
	for {
		nextRune, _, err = t.input.ReadRune()
		nextRuneType = t.classifier.ClassifyRune(nextRune)
		if err != nil {
			if err == io.EOF {
				nextRuneType = RUNETOKEN_EOF
				err = nil
			} else {
				return nil, err
			}
		}
		switch state {
		case STATE_START: // no runes read yet
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						return nil, io.EOF
					}
				case RUNETOKEN_CHAR:
					{
						tokenType = TOKEN_WORD
						value = append(value, nextRune)
						state = STATE_INWORD
					}
				case RUNETOKEN_SPACE:
					{
					}
				case RUNETOKEN_ESCAPING_QUOTE:
					{
						tokenType = TOKEN_WORD
						state = STATE_QUOTED_ESCAPING
					}
				case RUNETOKEN_NONESCAPING_QUOTE:
					{
						tokenType = TOKEN_WORD
						state = STATE_QUOTED
					}
				case RUNETOKEN_ESCAPE:
					{
						tokenType = TOKEN_WORD
						state = STATE_ESCAPING
					}
				case RUNETOKEN_COMMENT:
					{
						tokenType = TOKEN_COMMENT
						state = STATE_COMMENT
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Unknown rune: %v", nextRune))
					}
				}
			}
		case STATE_INWORD: // in a regular word
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_COMMENT:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_SPACE:
					{
						t.input.UnreadRune()
						break SCAN
					}
				case RUNETOKEN_ESCAPING_QUOTE:
					{
						state = STATE_QUOTED_ESCAPING
					}
				case RUNETOKEN_NONESCAPING_QUOTE:
					{
						state = STATE_QUOTED
					}
				case RUNETOKEN_ESCAPE:
					{
						state = STATE_ESCAPING
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_ESCAPING: // the next rune after an escape character
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found after escape character")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT:
					{
						state = STATE_INWORD
						value = append(value, nextRune)
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_ESCAPING_QUOTED: // the next rune after an escape character, in double quotes
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found after escape character")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT:
					{
						state = STATE_QUOTED_ESCAPING
						value = append(value, nextRune)
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_QUOTED_ESCAPING: // in escaping double quotes
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found when expecting closing quote.")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_COMMENT:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_ESCAPING_QUOTE:
					{
						state = STATE_INWORD
					}
				case RUNETOKEN_ESCAPE:
					{
						state = STATE_ESCAPING_QUOTED
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_QUOTED: // in non-escaping single quotes
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found when expecting closing quote.")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_NONESCAPING_QUOTE:
					{
						state = STATE_INWORD
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_COMMENT:
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT, RUNETOKEN_NONESCAPING_QUOTE:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_SPACE:
					{
						if nextRune == '\n' {
							state = STATE_START
							break SCAN
						} else {
							value = append(value, nextRune)
						}
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		default:
			{
				panic(fmt.Sprintf("Unexpected state: %v", state))
			}
		}
	}
	token := &Token{
		tokenType: tokenType,
		value:     string(value)}
	return token, err
}

/*
Return the next token in the stream, and an error value. If there are no more
tokens available, the error value will be io.EOF.
*/
func (t *Tokenizer) NextToken() (*Token, error) {
	return t.scanStream()
}

/*
Split a string in to a slice of strings, based upon shell-style rules for
quoting, escaping, and spaces.
*/
func Split(s string) ([]string, error) {
	l, err := NewLexer(strings.NewReader(s))
	if err != nil {
		return nil, err
	}
	subStrings := []string{}
	for {
		word, err := l.NextWord()
		if err != nil {
			if err == io.EOF {
				return subStrings, nil
			}
			return subStrings, err
		}
		subStrings = append(subStrings, word)
	}
	return subStrings, nil
}
<commit_msg>Adding pipe support<commit_after>/*
Copyright 2012 Google Inc. 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 shlex

/*
Package shlex implements a simple lexer which splits input in to tokens using
shell-style rules for quoting and commenting.
*/
import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"strings"
)

/*
A TokenType is a top-level token; a word, space, comment, unknown.
*/
type TokenType int

/*
A RuneTokenType is the type of a UTF-8 character; a character, quote, space, escape.
*/
type RuneTokenType int

type lexerState int

type Token struct {
	tokenType TokenType
	value     string
}

/*
Two tokens are equal if both their types and values are equal. A nil token can
never equal another token.
*/
func (a *Token) Equal(b *Token) bool {
	if a == nil || b == nil {
		return false
	}
	if a.tokenType != b.tokenType {
		return false
	}
	return a.value == b.value
}

const (
	RUNE_CHAR              string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-,/@$*()+=><:;&^%~|"
	RUNE_SPACE             string = " \t\r\n"
	RUNE_ESCAPING_QUOTE    string = "\""
	RUNE_NONESCAPING_QUOTE string = "'"
	RUNE_ESCAPE                   = "\\"
	RUNE_COMMENT                  = "#"

	RUNETOKEN_UNKNOWN           RuneTokenType = 0
	RUNETOKEN_CHAR              RuneTokenType = 1
	RUNETOKEN_SPACE             RuneTokenType = 2
	RUNETOKEN_ESCAPING_QUOTE    RuneTokenType = 3
	RUNETOKEN_NONESCAPING_QUOTE RuneTokenType = 4
	RUNETOKEN_ESCAPE            RuneTokenType = 5
	RUNETOKEN_COMMENT           RuneTokenType = 6
	RUNETOKEN_EOF               RuneTokenType = 7

	TOKEN_UNKNOWN TokenType = 0
	TOKEN_WORD    TokenType = 1
	TOKEN_SPACE   TokenType = 2
	TOKEN_COMMENT TokenType = 3

	STATE_START           lexerState = 0
	STATE_INWORD          lexerState = 1
	STATE_ESCAPING        lexerState = 2
	STATE_ESCAPING_QUOTED lexerState = 3
	STATE_QUOTED_ESCAPING lexerState = 4
	STATE_QUOTED          lexerState = 5
	STATE_COMMENT         lexerState = 6

	INITIAL_TOKEN_CAPACITY int = 100
)

/*
A type for classifying characters. This allows for different sorts of
classifiers - those accepting extended non-ascii chars, or strict posix
compatibility, for example.
*/
type TokenClassifier struct {
	typeMap map[int32]RuneTokenType
}

func addRuneClass(typeMap *map[int32]RuneTokenType, runes string, tokenType RuneTokenType) {
	for _, rune := range runes {
		(*typeMap)[int32(rune)] = tokenType
	}
}

/*
Create a new classifier for basic ASCII characters.
*/
func NewDefaultClassifier() *TokenClassifier {
	typeMap := map[int32]RuneTokenType{}
	addRuneClass(&typeMap, RUNE_CHAR, RUNETOKEN_CHAR)
	addRuneClass(&typeMap, RUNE_SPACE, RUNETOKEN_SPACE)
	addRuneClass(&typeMap, RUNE_ESCAPING_QUOTE, RUNETOKEN_ESCAPING_QUOTE)
	addRuneClass(&typeMap, RUNE_NONESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE)
	addRuneClass(&typeMap, RUNE_ESCAPE, RUNETOKEN_ESCAPE)
	addRuneClass(&typeMap, RUNE_COMMENT, RUNETOKEN_COMMENT)
	return &TokenClassifier{
		typeMap: typeMap}
}

func (classifier *TokenClassifier) ClassifyRune(rune int32) RuneTokenType {
	return classifier.typeMap[rune]
}

/*
A type for turning an input stream in to a sequence of strings. Whitespace and
comments are skipped.
*/
type Lexer struct {
	tokenizer *Tokenizer
}

/*
Create a new lexer.
*/
func NewLexer(r io.Reader) (*Lexer, error) {

	tokenizer, err := NewTokenizer(r)
	if err != nil {
		return nil, err
	}
	lexer := &Lexer{tokenizer: tokenizer}
	return lexer, nil
}

/*
Return the next word, and an error value. If there are no more words, the error
will be io.EOF.
*/
func (l *Lexer) NextWord() (string, error) {
	var token *Token
	var err error
	for {
		token, err = l.tokenizer.NextToken()
		if err != nil {
			return "", err
		}
		switch token.tokenType {
		case TOKEN_WORD:
			{
				return token.value, nil
			}
		case TOKEN_COMMENT:
			{
				// skip comments
			}
		default:
			{
				panic(fmt.Sprintf("Unknown token type: %v", token.tokenType))
			}
		}
	}
	return "", io.EOF
}

/*
A type for turning an input stream in to a sequence of typed tokens.
*/
type Tokenizer struct {
	input      *bufio.Reader
	classifier *TokenClassifier
}

/*
Create a new tokenizer.
*/
func NewTokenizer(r io.Reader) (*Tokenizer, error) {
	input := bufio.NewReader(r)
	classifier := NewDefaultClassifier()
	tokenizer := &Tokenizer{
		input:      input,
		classifier: classifier}
	return tokenizer, nil
}

/*
Scan the stream for the next token.

This uses an internal state machine. It will panic if it encounters a character
which it does not know how to handle.
*/
func (t *Tokenizer) scanStream() (*Token, error) {
	state := STATE_START
	var tokenType TokenType
	value := make([]int32, 0, INITIAL_TOKEN_CAPACITY)
	var (
		nextRune     int32
		nextRuneType RuneTokenType
		err          error
	)
SCAN:
	for {
		nextRune, _, err = t.input.ReadRune()
		nextRuneType = t.classifier.ClassifyRune(nextRune)
		if err != nil {
			if err == io.EOF {
				nextRuneType = RUNETOKEN_EOF
				err = nil
			} else {
				return nil, err
			}
		}
		switch state {
		case STATE_START: // no runes read yet
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						return nil, io.EOF
					}
				case RUNETOKEN_CHAR:
					{
						tokenType = TOKEN_WORD
						value = append(value, nextRune)
						state = STATE_INWORD
					}
				case RUNETOKEN_SPACE:
					{
					}
				case RUNETOKEN_ESCAPING_QUOTE:
					{
						tokenType = TOKEN_WORD
						state = STATE_QUOTED_ESCAPING
					}
				case RUNETOKEN_NONESCAPING_QUOTE:
					{
						tokenType = TOKEN_WORD
						state = STATE_QUOTED
					}
				case RUNETOKEN_ESCAPE:
					{
						tokenType = TOKEN_WORD
						state = STATE_ESCAPING
					}
				case RUNETOKEN_COMMENT:
					{
						tokenType = TOKEN_COMMENT
						state = STATE_COMMENT
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Unknown rune: %v", nextRune))
					}
				}
			}
		case STATE_INWORD: // in a regular word
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_COMMENT:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_SPACE:
					{
						t.input.UnreadRune()
						break SCAN
					}
				case RUNETOKEN_ESCAPING_QUOTE:
					{
						state = STATE_QUOTED_ESCAPING
					}
				case RUNETOKEN_NONESCAPING_QUOTE:
					{
						state = STATE_QUOTED
					}
				case RUNETOKEN_ESCAPE:
					{
						state = STATE_ESCAPING
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_ESCAPING: // the next rune after an escape character
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found after escape character")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT:
					{
						state = STATE_INWORD
						value = append(value, nextRune)
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_ESCAPING_QUOTED: // the next rune after an escape character, in double quotes
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found after escape character")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT:
					{
						state = STATE_QUOTED_ESCAPING
						value = append(value, nextRune)
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_QUOTED_ESCAPING: // in escaping double quotes
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found when expecting closing quote.")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_COMMENT:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_ESCAPING_QUOTE:
					{
						state = STATE_INWORD
					}
				case RUNETOKEN_ESCAPE:
					{
						state = STATE_ESCAPING_QUOTED
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_QUOTED: // in non-escaping single quotes
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						err = errors.New("EOF found when expecting closing quote.")
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_NONESCAPING_QUOTE:
					{
						state = STATE_INWORD
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		case STATE_COMMENT:
			{
				switch nextRuneType {
				case RUNETOKEN_EOF:
					{
						break SCAN
					}
				case RUNETOKEN_CHAR, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT, RUNETOKEN_NONESCAPING_QUOTE:
					{
						value = append(value, nextRune)
					}
				case RUNETOKEN_SPACE:
					{
						if nextRune == '\n' {
							state = STATE_START
							break SCAN
						} else {
							value = append(value, nextRune)
						}
					}
				default:
					{
						return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune))
					}
				}
			}
		default:
			{
				panic(fmt.Sprintf("Unexpected state: %v", state))
			}
		}
	}
	token := &Token{
		tokenType: tokenType,
		value:     string(value)}
	return token, err
}

/*
Return the next token in the stream, and an error value. If there are no more
tokens available, the error value will be io.EOF.
*/
func (t *Tokenizer) NextToken() (*Token, error) {
	return t.scanStream()
}

/*
Split a string in to a slice of strings, based upon shell-style rules for
quoting, escaping, and spaces.
*/
func Split(s string) ([]string, error) {
	l, err := NewLexer(strings.NewReader(s))
	if err != nil {
		return nil, err
	}
	subStrings := []string{}
	for {
		word, err := l.NextWord()
		if err != nil {
			if err == io.EOF {
				return subStrings, nil
			}
			return subStrings, err
		}
		subStrings = append(subStrings, word)
	}
	return subStrings, nil
}
<|endoftext|>


<commit_before>package extra

import (
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os/exec"
	"strings"

	"github.com/belak/go-seabird"
	"github.com/go-irc/irc"
)

func init() {
	seabird.RegisterPlugin("nettools", newNetToolsPlugin)
}

type netToolsPlugin struct {
	Key string
}

func newNetToolsPlugin(b *seabird.Bot, cm *seabird.CommandMux) error {
	p := &netToolsPlugin{}

	err := b.Config("net_tools", p)
	if err != nil {
		return err
	}

	cm.Event("rdns", p.RDNS, &seabird.HelpInfo{
		Usage:       "<ip>",
		Description: "Does a reverse DNS lookup on the given IP",
	})
	cm.Event("dig", p.Dig, &seabird.HelpInfo{
		Usage:       "<domain>",
		Description: "Retrieves IP records for given domain",
	})
	cm.Event("ping", p.Ping, &seabird.HelpInfo{
		Usage:       "<host>",
		Description: "Pings given host once",
	})
	cm.Event("traceroute", p.Traceroute, &seabird.HelpInfo{
		Usage:       "<host>",
		Description: "Runs traceroute on given host and returns pastebin URL for results",
	})
	cm.Event("whois", p.Whois, &seabird.HelpInfo{
		Usage:       "<domain>",
		Description: "Runs whois on given domain and returns pastebin URL for results",
	})
	cm.Event("dnscheck", p.DNSCheck, &seabird.HelpInfo{
		Usage:       "<domain>",
		Description: "Returns DNSCheck URL for domain",
	})

	return nil
}

func (p *netToolsPlugin) RDNS(b *seabird.Bot, m *irc.Message) {
	go func() {
		if m.Trailing() == "" {
			b.MentionReply(m, "Argument required")
			return
		}
		names, err := net.LookupAddr(m.Trailing())
		if err != nil {
			b.MentionReply(m, err.Error())
			return
		}

		if len(names) == 0 {
			b.MentionReply(m, "No results found")
			return
		}

		b.MentionReply(m, names[0])

		if len(names) > 1 {
			for _, name := range names[1:] {
				b.Writef("NOTICE %s :%s", m.Prefix.Name, name)
			}
		}
	}()
}

func (p *netToolsPlugin) Dig(b *seabird.Bot, m *irc.Message) {
	go func() {
		if m.Trailing() == "" {
			b.MentionReply(m, "Domain required")
			return
		}

		addrs, err := net.LookupHost(m.Trailing())
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}

		if len(addrs) == 0 {
			b.MentionReply(m, "No results found")
			return
		}

		b.MentionReply(m, addrs[0])

		if len(addrs) > 1 {
			for _, addr := range addrs[1:] {
				b.Writef("NOTICE %s :%s", m.Prefix.Name, addr)
			}
		}
	}()
}

func (p *netToolsPlugin) Ping(b *seabird.Bot, m *irc.Message) {
	go func() {
		if m.Trailing() == "" {
			b.MentionReply(m, "Host required")
			return
		}

		out, err := exec.Command("ping", "-c1", m.Trailing()).Output()
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}

		arr := strings.Split(string(out), "\n")
		if len(arr) < 2 {
			b.MentionReply(m, "Error retrieving ping results")
			return
		}

		b.MentionReply(m, arr[1])
	}()
}

func (p *netToolsPlugin) pasteData(data string) (string, error) {
	resp, err := http.PostForm("http://pastebin.com/api/api_post.php", url.Values{
		"api_dev_key":    {p.Key},
		"api_option":     {"paste"},
		"api_paste_code": {data},
	})
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	return string(body), err
}

func (p *netToolsPlugin) runCommand(cmd string, args ...string) (string, error) {
	out, err := exec.Command(cmd, args...).Output()
	if err != nil {
		return "", err
	}

	return p.pasteData(string(out))
}

func (p *netToolsPlugin) handleCommand(b *seabird.Bot, m *irc.Message, command string, emptyMsg string) {
	if m.Trailing() == "" {
		b.MentionReply(m, "Host required")
		return
	}

	url, err := p.runCommand("traceroute", m.Trailing())
	if err != nil {
		b.MentionReply(m, "%s", err)
		return
	}

	b.MentionReply(m, "%s", url)

}

func (p *netToolsPlugin) Traceroute(b *seabird.Bot, m *irc.Message) {
	go p.handleCommand(b, m, "traceroute", "Host required")
}

func (p *netToolsPlugin) Whois(b *seabird.Bot, m *irc.Message) {
	go p.handleCommand(b, m, "whois", "Domain required")
}

func (p *netToolsPlugin) DNSCheck(b *seabird.Bot, m *irc.Message) {
	if m.Trailing() == "" {
		b.MentionReply(m, "Domain required")
		return
	}

	b.MentionReply(m, "https://www.whatsmydns.net/#A/"+m.Trailing())
}
<commit_msg>net_tools: Switch ping command to use go-ping (#283)<commit_after>package extra

import (
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os/exec"

	ping "github.com/belak/go-ping"
	"github.com/belak/go-seabird"
	"github.com/go-irc/irc"
)

func init() {
	seabird.RegisterPlugin("nettools", newNetToolsPlugin)
}

type netToolsPlugin struct {
	Key string
}

func newNetToolsPlugin(b *seabird.Bot, cm *seabird.CommandMux) error {
	p := &netToolsPlugin{}

	err := b.Config("net_tools", p)
	if err != nil {
		return err
	}

	cm.Event("rdns", p.RDNS, &seabird.HelpInfo{
		Usage:       "<ip>",
		Description: "Does a reverse DNS lookup on the given IP",
	})
	cm.Event("dig", p.Dig, &seabird.HelpInfo{
		Usage:       "<domain>",
		Description: "Retrieves IP records for given domain",
	})
	cm.Event("ping", p.Ping, &seabird.HelpInfo{
		Usage:       "<host>",
		Description: "Pings given host once",
	})
	cm.Event("traceroute", p.Traceroute, &seabird.HelpInfo{
		Usage:       "<host>",
		Description: "Runs traceroute on given host and returns pastebin URL for results",
	})
	cm.Event("whois", p.Whois, &seabird.HelpInfo{
		Usage:       "<domain>",
		Description: "Runs whois on given domain and returns pastebin URL for results",
	})
	cm.Event("dnscheck", p.DNSCheck, &seabird.HelpInfo{
		Usage:       "<domain>",
		Description: "Returns DNSCheck URL for domain",
	})

	return nil
}

func (p *netToolsPlugin) RDNS(b *seabird.Bot, m *irc.Message) {
	go func() {
		if m.Trailing() == "" {
			b.MentionReply(m, "Argument required")
			return
		}
		names, err := net.LookupAddr(m.Trailing())
		if err != nil {
			b.MentionReply(m, err.Error())
			return
		}

		if len(names) == 0 {
			b.MentionReply(m, "No results found")
			return
		}

		b.MentionReply(m, names[0])

		if len(names) > 1 {
			for _, name := range names[1:] {
				b.Writef("NOTICE %s :%s", m.Prefix.Name, name)
			}
		}
	}()
}

func (p *netToolsPlugin) Dig(b *seabird.Bot, m *irc.Message) {
	go func() {
		if m.Trailing() == "" {
			b.MentionReply(m, "Domain required")
			return
		}

		addrs, err := net.LookupHost(m.Trailing())
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}

		if len(addrs) == 0 {
			b.MentionReply(m, "No results found")
			return
		}

		b.MentionReply(m, addrs[0])

		if len(addrs) > 1 {
			for _, addr := range addrs[1:] {
				b.Writef("NOTICE %s :%s", m.Prefix.Name, addr)
			}
		}
	}()
}

func (p *netToolsPlugin) Ping(b *seabird.Bot, m *irc.Message) {
	go func() {
		if m.Trailing() == "" {
			b.MentionReply(m, "Host required")
			return
		}

		pinger, err := ping.NewPinger(m.Trailing())
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}
		pinger.Count = 1

		pinger.OnRecv = func(pkt *ping.Packet) {
			b.MentionReply(m, "%d bytes from %s: icmp_seq=%d time=%s",
				pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
		}
		pinger.Run()
	}()
}

func (p *netToolsPlugin) pasteData(data string) (string, error) {
	resp, err := http.PostForm("http://pastebin.com/api/api_post.php", url.Values{
		"api_dev_key":    {p.Key},
		"api_option":     {"paste"},
		"api_paste_code": {data},
	})
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	return string(body), err
}

func (p *netToolsPlugin) runCommand(cmd string, args ...string) (string, error) {
	out, err := exec.Command(cmd, args...).Output()
	if err != nil {
		return "", err
	}

	return p.pasteData(string(out))
}

func (p *netToolsPlugin) handleCommand(b *seabird.Bot, m *irc.Message, command string, emptyMsg string) {
	if m.Trailing() == "" {
		b.MentionReply(m, "Host required")
		return
	}

	url, err := p.runCommand("traceroute", m.Trailing())
	if err != nil {
		b.MentionReply(m, "%s", err)
		return
	}

	b.MentionReply(m, "%s", url)

}

func (p *netToolsPlugin) Traceroute(b *seabird.Bot, m *irc.Message) {
	go p.handleCommand(b, m, "traceroute", "Host required")
}

func (p *netToolsPlugin) Whois(b *seabird.Bot, m *irc.Message) {
	go p.handleCommand(b, m, "whois", "Domain required")
}

func (p *netToolsPlugin) DNSCheck(b *seabird.Bot, m *irc.Message) {
	if m.Trailing() == "" {
		b.MentionReply(m, "Domain required")
		return
	}

	b.MentionReply(m, "https://www.whatsmydns.net/#A/"+m.Trailing())
}
<|endoftext|>


<commit_before>package gps

import (
	"strings"
	"fmt"
	"log"
	"strconv"
	"reflect"
	"time"
)

// func validateNMEAChecksum determines if a string is a properly formatted NMEA sentence with a valid checksum.
//
// If the input string is valid, output is the input stripped of the "$" token and checksum, along with a boolean 'true'
// If the input string is the incorrect format, the checksum is missing/invalid, or checksum calculation fails, an error string and
// boolean 'false' are returned
//
// Checksum is calculated as XOR of all bytes between "$" and "*"
func validateNMEAChecksum(s string) (string, bool) {
	//validate format. NMEA sentences start with "$" and end in "*xx" where xx is the XOR value of all bytes between
	if !(strings.HasPrefix(s, "$") && strings.Contains(s, "*")) {
		return "Invalid NMEA message", false
	}

	// strip leading "$" and split at "*"
	s_split := strings.Split(strings.TrimPrefix(s, "$"), "*")
	s_out := s_split[0]
	s_cs := s_split[1]

	if len(s_cs) < 2 {
		return "Missing checksum. Fewer than two bytes after asterisk", false
	}

	cs, err := strconv.ParseUint(s_cs[:2], 16, 8)
	if err != nil {
		return "Invalid checksum", false
	}

	cs_calc := byte(0)
	for i := range s_out {
		cs_calc = cs_calc ^ byte(s_out[i])
	}

	if cs_calc != byte(cs) {
		return fmt.Sprintf("Checksum failed. Calculated %#X; expected %#X", cs_calc, cs), false
	}

	return s_out, true
}

func createChecksummedNMEASentence(raw []byte) []byte {
	cs_calc := byte(0)
	for _,v := range raw {
		cs_calc ^= v
	}

	return []byte(fmt.Sprintf("$%s*%02X\r\n", raw, cs_calc))
}

func processNMEASentence(line string, situation *SituationData) {
	sentence, valid := validateNMEAChecksum(line)
	if !valid {
		log.Printf("GPS Error: invalid NMEA string: %s\n", sentence)
		return
	}

	//log.Printf("Begin parse of %s\n", sentence)
	ParseMessage(sentence, situation)
}

type NMEA struct {
	Sentence string
	Tokens []string
	Situation *SituationData
}

// we split the sentence on commas, and use the first field via reflection to find a method with the same name
func ParseMessage(sentence string, situation *SituationData) *NMEA {
	n := &NMEA{ sentence, strings.Split(sentence, ","), situation }

	//log.Printf("NMEA Message type %s, data: %v\n", n.Tokens[0], n.Tokens[1:])

	v := reflect.ValueOf(n)
	m := v.MethodByName(n.Tokens[0])

	if (m == reflect.Value{}) {
		return nil
	}

	m.Call(nil)

	return n
}

func durationSinceMidnight(fixtime string) (time.Duration, error) {
	hr, err := strconv.Atoi(fixtime[0:2]); if err != nil { return err }
	min, err := strconv.Atoi(fixtime[2:4]); if err != nil { return err }
	sec, err := strconv.Atoi(fixtime[4:6]); if err != nil { return err }

	return time.Second * sec + time.Minute * min + time.Hour * hr
}

func parseLatLon(s string, neg bool) (float32, error) {
	minpos := len(s) - 6
	deg, err := strconv.Atoi(s[0:minpos]); if err != nil { return 0.0, err }
	min, err := strconv.ParseFloat(s[minpos:], 32); if err != nil { return 0.0, err }

	sign := 1; if neg { sign = -1 }

	return sign * (float32(deg) + float32(min/60.0)), nil
}

func (n *NMEA) GNGGA() { n.GPGGA() } // ublox 8 uses GNGGA in place of GPGGA to indicate multiple nav sources (GPS/GLONASS)
func (n *NMEA) GPGGA() {
	log.Printf("In GPGGA\n")
	s := n.Situation

	s.Mu_GPS.Lock(); defer s.Mu_GPS.Unlock()

	d, err := durationSinceMidnight(n.Tokens[1]); if err != nil { return }
	s.LastFixSinceMidnight = uint32(durationSinceMidnight(n.Tokens[1]) / time.Second)

	if len(n.Tokens[2]) < 4 || len(n.Tokens[4]) < 4 { return } // sanity check lat/lon

	lat, err := parseLatLon(n.Tokens[2], n.Tokens[3] == "S"); if err != nil { return }
	lon, err := parseLatLon(n.Tokens[4], n.Tokens[5] == "W"); if err != nil { return }

	s.Lat = lat; s.Lon = lon

	log.Printf("Situation: %v\n", s)
}

func (n *NMEA) GPGSA() {
	log.Printf("In GPGSA\n")

}
<commit_msg>GGA parsing<commit_after>package gps

import (
	"strings"
	"fmt"
	"log"
	"strconv"
	"reflect"
	"time"
)

// func validateNMEAChecksum determines if a string is a properly formatted NMEA sentence with a valid checksum.
//
// If the input string is valid, output is the input stripped of the "$" token and checksum, along with a boolean 'true'
// If the input string is the incorrect format, the checksum is missing/invalid, or checksum calculation fails, an error string and
// boolean 'false' are returned
//
// Checksum is calculated as XOR of all bytes between "$" and "*"
func validateNMEAChecksum(s string) (string, bool) {
	//validate format. NMEA sentences start with "$" and end in "*xx" where xx is the XOR value of all bytes between
	if !(strings.HasPrefix(s, "$") && strings.Contains(s, "*")) {
		return "Invalid NMEA message", false
	}

	// strip leading "$" and split at "*"
	s_split := strings.Split(strings.TrimPrefix(s, "$"), "*")
	s_out := s_split[0]
	s_cs := s_split[1]

	if len(s_cs) < 2 {
		return "Missing checksum. Fewer than two bytes after asterisk", false
	}

	cs, err := strconv.ParseUint(s_cs[:2], 16, 8)
	if err != nil {
		return "Invalid checksum", false
	}

	cs_calc := byte(0)
	for i := range s_out {
		cs_calc = cs_calc ^ byte(s_out[i])
	}

	if cs_calc != byte(cs) {
		return fmt.Sprintf("Checksum failed. Calculated %#X; expected %#X", cs_calc, cs), false
	}

	return s_out, true
}

func createChecksummedNMEASentence(raw []byte) []byte {
	cs_calc := byte(0)
	for _,v := range raw {
		cs_calc ^= v
	}

	return []byte(fmt.Sprintf("$%s*%02X\r\n", raw, cs_calc))
}

func processNMEASentence(line string, situation *SituationData) {
	sentence, valid := validateNMEAChecksum(line)
	if !valid {
		log.Printf("GPS Error: invalid NMEA string: %s\n", sentence)
		return
	}

	//log.Printf("Begin parse of %s\n", sentence)
	ParseMessage(sentence, situation)
}

type NMEA struct {
	Sentence string
	Tokens []string
	Situation *SituationData
}

// we split the sentence on commas, and use the first field via reflection to find a method with the same name
func ParseMessage(sentence string, situation *SituationData) *NMEA {
	n := &NMEA{ sentence, strings.Split(sentence, ","), situation }

	//log.Printf("NMEA Message type %s, data: %v\n", n.Tokens[0], n.Tokens[1:])

	v := reflect.ValueOf(n)
	m := v.MethodByName(n.Tokens[0])

	if (m == reflect.Value{}) {
		return nil
	}

	m.Call(nil)

	return n
}

func durationSinceMidnight(fixtime string) (int, error) {
	hr, err := strconv.Atoi(fixtime[0:2]); if err != nil { return err }
	min, err := strconv.Atoi(fixtime[2:4]); if err != nil { return err }
	sec, err := strconv.Atoi(fixtime[4:6]); if err != nil { return err }

	return sec + min*60 + hr*60*60, nil
}

func parseLatLon(s string, neg bool) (float32, error) {
	minpos := len(s) - 6
	deg, err := strconv.Atoi(s[0:minpos]); if err != nil { return 0.0, err }
	min, err := strconv.ParseFloat(s[minpos:], 32); if err != nil { return 0.0, err }

	sign := 1; if neg { sign = -1 }

	return float32(sign) * (float32(deg) + float32(min/60.0)), nil
}

func (n *NMEA) GNGGA() { n.GPGGA() } // ublox 8 uses GNGGA in place of GPGGA to indicate multiple nav sources (GPS/GLONASS)
func (n *NMEA) GPGGA() {
	log.Printf("In GPGGA\n")
	s := n.Situation

	s.Mu_GPS.Lock(); defer s.Mu_GPS.Unlock()

	d, err := durationSinceMidnight(n.Tokens[1]); if err != nil { return }
	s.LastFixSinceMidnightUTC = uint32(d)

	if len(n.Tokens[2]) < 4 || len(n.Tokens[4]) < 4 { return } // sanity check lat/lon

	lat, err := parseLatLon(n.Tokens[2], n.Tokens[3] == "S"); if err != nil { return }
	lon, err := parseLatLon(n.Tokens[4], n.Tokens[5] == "W"); if err != nil { return }

	s.Lat = lat; s.Lng = lon

	log.Printf("Situation: %v\n", s)
}

func (n *NMEA) GPGSA() {
	log.Printf("In GPGSA\n")

}
<|endoftext|>


<commit_before>package components

import (
	"github.com/scipipe/scipipe"
	"path/filepath"
)

// FileGlobber is initiated with a set of glob patterns paths, which it will
// use to find concrete file paths, for which it will return a stream of
// corresponding File IPs on its outport Out()
type FileGlobber struct {
	scipipe.BaseProcess
	globPatterns []string
}

// NewFileGlobber returns a new initialized FileGlobber process
func NewFileGlobber(wf *scipipe.Workflow, name string, globPatterns ...string) *FileGlobber {
	if len(globPatterns) < 1 {
		scipipe.Failf("FileGlobber with name '%s': No glob paths supplied! Must take at least one glob path. You might also have forgot to provide a name to the fileglobber.", name)
	}
	p := &FileGlobber{
		BaseProcess:  scipipe.NewBaseProcess(wf, name),
		globPatterns: globPatterns,
	}
	p.InitOutPort(p, "out")
	wf.AddProc(p)
	return p
}

// Out returns the out-port, on which file IPs based on the file paths the
// process was initialized with, will be retrieved.
func (p *FileGlobber) Out() *scipipe.OutPort { return p.OutPort("out") }

// Run runs the FileGlobber process
func (p *FileGlobber) Run() {
	defer p.CloseAllOutPorts()
	for _, globPtn := range p.globPatterns {
		scipipe.Audit.Printf("%s: Globbing for files, with pattern: %s", p.Name(), globPtn)
		matches, err := filepath.Glob(globPtn)
		scipipe.CheckWithMsg(err, "FileGlobber: This glob pattern doesn't look right: "+globPtn)
		for _, filePath := range matches {
			scipipe.Audit.Println("%s: Sending concrete file %s", p.Name(), filePath)
			p.Out().Send(scipipe.NewFileIP(filePath))
		}
	}
}
<commit_msg>Fix log message formatting<commit_after>package components

import (
	"github.com/scipipe/scipipe"
	"path/filepath"
)

// FileGlobber is initiated with a set of glob patterns paths, which it will
// use to find concrete file paths, for which it will return a stream of
// corresponding File IPs on its outport Out()
type FileGlobber struct {
	scipipe.BaseProcess
	globPatterns []string
}

// NewFileGlobber returns a new initialized FileGlobber process
func NewFileGlobber(wf *scipipe.Workflow, name string, globPatterns ...string) *FileGlobber {
	if len(globPatterns) < 1 {
		scipipe.Failf("FileGlobber with name '%s': No glob paths supplied! Must take at least one glob path. You might also have forgot to provide a name to the fileglobber.", name)
	}
	p := &FileGlobber{
		BaseProcess:  scipipe.NewBaseProcess(wf, name),
		globPatterns: globPatterns,
	}
	p.InitOutPort(p, "out")
	wf.AddProc(p)
	return p
}

// Out returns the out-port, on which file IPs based on the file paths the
// process was initialized with, will be retrieved.
func (p *FileGlobber) Out() *scipipe.OutPort { return p.OutPort("out") }

// Run runs the FileGlobber process
func (p *FileGlobber) Run() {
	defer p.CloseAllOutPorts()
	for _, globPtn := range p.globPatterns {
		scipipe.Audit.Printf("%s: Globbing for files, with pattern: %s", p.Name(), globPtn)
		matches, err := filepath.Glob(globPtn)
		scipipe.CheckWithMsg(err, "FileGlobber: This glob pattern doesn't look right: "+globPtn)
		for _, filePath := range matches {
			scipipe.Audit.Printf("%s: Sending concrete file %s", p.Name(), filePath)
			p.Out().Send(scipipe.NewFileIP(filePath))
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"os"
	"runtime"
)

func errStr(err error) string {
	if err != nil {
		return err.Error()
	}
	return ""
}

func envSlice(envMap map[string]string) []string {
	env := []string{}
	for k, v := range envMap {
		env = append(env, k+"="+v)
	}
	if len(env) == 0 {
		env = os.Environ()
	}
	return env
}

func defaultEnv() map[string]string {
	return map[string]string{
		"GOROOT": runtime.GOROOT(),
		"GOARCH": runtime.GOARCH,
		"GOOS":   runtime.GOOS,
	}
}

func orString(a ...string) string {
	for _, s := range a {
		if s != "" {
			return s
		}
	}
	return ""
}

func parseAstFile(fn string, s string, mode parser.Mode) (fset *token.FileSet, af *ast.File, err error) {
	fset = token.NewFileSet()
	var src interface{}
	if s != "" {
		src = s
	}
	if fn == "" {
		fn = "<stdin>"
	}
	af, err = parser.ParseFile(fset, fn, src, mode)
	return
}

func newPrinter(tabIndent bool, tabWidth int) *printer.Config {
	mode := printer.UseSpaces
	if tabIndent {
		mode |= printer.TabIndent
	}
	return &printer.Config{
		Mode:     mode,
		Tabwidth: tabWidth,
	}
}

func printSrc(fset *token.FileSet, v interface{}, tabIndent bool, tabWidth int) (src string, err error) {
	p := newPrinter(tabIndent, tabWidth)
	buf := &bytes.Buffer{}
	if err = p.Fprint(buf, fset, v); err == nil {
		src = buf.String()
	}
	return
}
<commit_msg>* add jString type to deal with json null strings<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"os"
	"runtime"
)

type jString string

func (s jString) String() string {
	return string(s)
}

func (s *jString) UnmarshalJSON(p []byte) error {
	if bytes.Equal(p, []byte("null")) {
		return nil
	}
	return json.Unmarshal(p, (*string)(s))
}

func errStr(err error) string {
	if err != nil {
		return err.Error()
	}
	return ""
}

func envSlice(envMap map[string]string) []string {
	env := []string{}
	for k, v := range envMap {
		env = append(env, k+"="+v)
	}
	if len(env) == 0 {
		env = os.Environ()
	}
	return env
}

func defaultEnv() map[string]string {
	return map[string]string{
		"GOROOT": runtime.GOROOT(),
		"GOARCH": runtime.GOARCH,
		"GOOS":   runtime.GOOS,
	}
}

func orString(a ...string) string {
	for _, s := range a {
		if s != "" {
			return s
		}
	}
	return ""
}

func parseAstFile(fn string, s string, mode parser.Mode) (fset *token.FileSet, af *ast.File, err error) {
	fset = token.NewFileSet()
	var src interface{}
	if s != "" {
		src = s
	}
	if fn == "" {
		fn = "<stdin>"
	}
	af, err = parser.ParseFile(fset, fn, src, mode)
	return
}

func newPrinter(tabIndent bool, tabWidth int) *printer.Config {
	mode := printer.UseSpaces
	if tabIndent {
		mode |= printer.TabIndent
	}
	return &printer.Config{
		Mode:     mode,
		Tabwidth: tabWidth,
	}
}

func printSrc(fset *token.FileSet, v interface{}, tabIndent bool, tabWidth int) (src string, err error) {
	p := newPrinter(tabIndent, tabWidth)
	buf := &bytes.Buffer{}
	if err = p.Fprint(buf, fset, v); err == nil {
		src = buf.String()
	}
	return
}
<|endoftext|>


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

package main

import "os"
//import "github.com/maxymania/anyfs/dskimg/bitmap"
import "github.com/maxymania/anyfs/dskimg/fs1"
//import _ "github.com/maxymania/anyfs/dskimg/ods"
import "fmt"
import "flag"

const (
	BZ_0 = 1<<iota
	BZ_1
	BZ_2
	BZ_3
	BZ_4
	BZ_5
	BZ_6
	BZ_7
	BZ_8
	BZ_9
)

var image = flag.String("image", "", "The file-system image to be formatted")
var bzk = flag.Int("bsize", 4, fmt.Sprint("block size (in kb) (valid is ",BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9,")"))
var bom = flag.String("bsord", "K", "M = 'block size in MB instead of KB';  * = 'block size in byte instead of KB'")

var mzk = flag.Int("mft", 4, fmt.Sprint("mft size (in kb) (valid is ",BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9,")"))
var mom = flag.String("mftord", "K", "M = 'mft size in MB instead of KB';  * = 'mft size in byte instead of KB'")

var offset = flag.Int("sbo",512,"Superblock Offset")

func main(){
	flag.Parse()
	if *image=="" {
		flag.PrintDefaults()
		return
	}
	f,e := os.OpenFile(*image,os.O_RDWR,0666) //
	if e!=nil {
		fmt.Println("Error: ",e)
		flag.PrintDefaults()
		return
	}
	defer f.Close()
	mkfs := new(fs1.MkfsInfo)
	mkfs.BlockSize = uint32(*bzk)
	switch mkfs.BlockSize {
	case BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9:
		if *bom=="K" {
			mkfs.BlockSize<<=10
		} else if *bom=="M" {
			mkfs.BlockSize<<=20
		}
	default:
		flag.PrintDefaults()
		return
	}
	mkfs.MftBlocks = uint32(*mzk)
	switch mkfs.MftBlocks {
	case BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9:
		if *bom=="K" {
			mkfs.MftBlocks<<=10
		} else if *bom=="M" {
			mkfs.MftBlocks<<=20
		}
	default:
		flag.PrintDefaults()
		return
	}
	mkfs.MftBlocks += mkfs.BlockSize-1
	mkfs.MftBlocks /= mkfs.BlockSize
	fs := new(fs1.FileSystem)
	fs.Device = f
	err := fs.Mkfs(*offset,mkfs)
	if err!=nil {
		fmt.Println("Error: ",err)
		flag.PrintDefaults()
		return
	}
}

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

package main

import "os"
//import "github.com/maxymania/anyfs/dskimg/bitmap"
import "github.com/maxymania/anyfs/dskimg/fs1"
//import _ "github.com/maxymania/anyfs/dskimg/ods"
import "fmt"
import "flag"

const (
	BZ_0 = 1<<iota
	BZ_1
	BZ_2
	BZ_3
	BZ_4
	BZ_5
	BZ_6
	BZ_7
	BZ_8
	BZ_9
)

var image = flag.String("image", "", "The file-system image to be formatted")
var bzk = flag.Int("bsize", 4, fmt.Sprint("block size (in kb) (valid is ",BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9,")"))
var bom = flag.String("bsord", "K", "M = 'block size in MB instead of KB';  * = 'block size in byte instead of KB'")

var mzk = flag.Int("mft", 4, fmt.Sprint("mft size (in kb) (valid is ",BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9,")"))
var mom = flag.String("mftord", "K", "M = 'mft size in MB instead of KB';  * = 'mft size in byte instead of KB'")

var offset = flag.Int("sbo",512,"Superblock Offset")

func main(){
	flag.Parse()
	if *image=="" {
		flag.PrintDefaults()
		return
	}
	f,e := os.OpenFile(*image,os.O_RDWR,0666) //
	if e!=nil {
		fmt.Println("Error: ",e)
		flag.PrintDefaults()
		return
	}
	defer f.Close()
	mkfs := new(fs1.MkfsInfo)
	mkfs.BlockSize = uint32(*bzk)
	switch mkfs.BlockSize {
	case BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9:
		if *bom=="K" {
			mkfs.BlockSize<<=10
		} else if *bom=="M" {
			mkfs.BlockSize<<=20
		}
	default:
		flag.PrintDefaults()
		return
	}
	mkfs.MftBlocks = uint32(*mzk)
	switch mkfs.MftBlocks {
	case BZ_0,BZ_1,BZ_2,BZ_3,BZ_4,BZ_5,BZ_6,BZ_7,BZ_8,BZ_9:
		if *bom=="K" {
			mkfs.MftBlocks<<=10
		} else if *bom=="M" {
			mkfs.MftBlocks<<=20
		}
	default:
		flag.PrintDefaults()
		return
	}
	mkfs.MftBlocks += mkfs.BlockSize-1
	mkfs.MftBlocks /= mkfs.BlockSize
	fs := new(fs1.FileSystem)
	fs.Device = f
	err := fs.Mkfs(int64(*offset),mkfs)
	if err!=nil {
		fmt.Println("Error: ",err)
		flag.PrintDefaults()
		return
	}
}

<|endoftext|>


<commit_before>// +build experimental

package stack

import (
	"fmt"
	"io"
	"os"

	"github.com/docker/docker/api/client/bundlefile"
	"github.com/spf13/pflag"
)

func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
	flags.StringVarP(
		opt,
		"bundle", "f", "",
		"Path to a Distributed Application Bundle file (Default: STACK.dab)")
}

func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
	defaultPath := fmt.Sprintf("%s.dab", namespace)

	if path == "" {
		path = defaultPath
	}
	if _, err := os.Stat(path); err != nil {
		return nil, fmt.Errorf(
			"Bundle %s not found. Specify the path with -f or --bundle",
			path)
	}

	fmt.Fprintf(stderr, "Loading bundle from %s\n", path)
	reader, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	bundle, err := bundlefile.LoadFile(reader)
	if err != nil {
		return nil, fmt.Errorf("Error reading %s: %v\n", path, err)
	}
	return bundle, err
}
<commit_msg>fix severe fd leak in stack<commit_after>// +build experimental

package stack

import (
	"fmt"
	"io"
	"os"

	"github.com/docker/docker/api/client/bundlefile"
	"github.com/spf13/pflag"
)

func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
	flags.StringVarP(
		opt,
		"bundle", "f", "",
		"Path to a Distributed Application Bundle file (Default: STACK.dab)")
}

func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
	defaultPath := fmt.Sprintf("%s.dab", namespace)

	if path == "" {
		path = defaultPath
	}
	if _, err := os.Stat(path); err != nil {
		return nil, fmt.Errorf(
			"Bundle %s not found. Specify the path with -f or --bundle",
			path)
	}

	fmt.Fprintf(stderr, "Loading bundle from %s\n", path)
	reader, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	bundle, err := bundlefile.LoadFile(reader)
	if err != nil {
		return nil, fmt.Errorf("Error reading %s: %v\n", path, err)
	}
	return bundle, err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/evolsnow/robot/conn"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"
)

//zmz.tv needs to login before downloading
var zmzClient http.Client

func (rb *Robot) Start(update tgbotapi.Update) string {
	user := update.Message.Chat.UserName
	go conn.SetUserChatId(user, update.Message.Chat.ID)
	return "welcome: " + user
}

func (rb *Robot) Help(update tgbotapi.Update) string {
	helpMsg := `
/alarm - set a reminder
/evolve	- self evolution of me
/movie - find movie download links
/show - find American show download links
/trans - translate words between english and chinese
/help - show this help message
`
	return helpMsg
}

func (rb *Robot) Evolve(update tgbotapi.Update) {
	if update.Message.Chat.FirstName != "Evol" || update.Message.Chat.LastName != "Gan" {
		rb.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, "sorry, unauthorized"))
		return
	}
	<-saidGoodBye
	close(saidGoodBye)
	cmd := exec.Command("bash", "/root/evolve_"+rb.nickName)
	cmd.Start()
	os.Exit(1)
}

func (rb *Robot) Translate(update tgbotapi.Update) string {
	var info string
	if string(update.Message.Text[0]) == "/" {
		raw := strings.SplitAfterN(update.Message.Text, " ", 2)
		if len(raw) < 2 {
			return "what do you want to translate, try '/trans cat'?"
		} else {
			info = "翻译" + raw[1]
		}
	} else {
		info = update.Message.Text
	}

	return qinAI(info)

}
func (rb *Robot) Talk(update tgbotapi.Update) string {
	info := update.Message.Text
	if strings.Contains(info, rb.name) {
		if strings.Contains(info, "闭嘴") || strings.Contains(info, "别说话") {
			rb.shutUp = true
		} else if rb.shutUp && strings.Contains(info, "说话") {
			rb.shutUp = false
			return fmt.Sprintf("%s终于可以说话啦", rb.nickName)
		}
		info = strings.Replace(info, fmt.Sprintf("@%s", rb.name), "", -1)
	}

	if rb.shutUp {
		return ""
	}
	log.Printf(info)

	if rb.nickName == "samaritan" {
		if chinese(info) {
			return tlAI(info)
		} else {
			return mitAI(info)
		}
	} else { //jarvis use another AI
		return qinAI(info)
	}
	//	return response
}

func (rb *Robot) SetReminder(update tgbotapi.Update, step int) string {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpTask := userTask[user]
		tmpTask.ChatId = update.Message.Chat.ID
		tmpTask.Owner = update.Message.Chat.UserName
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "Ok, what should I remind you to do?"
	case 1:
		//save thing
		tmpTask := userTask[user]
		tmpTask.Desc = update.Message.Text
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "When or how much time after?\n" +
			"You can type:\n" +
			"'*2/14 11:30*' means 11:30 at 2/14 \n" + //first format
			"'*11:30*' means  11:30 today\n" + //second format
			"'*5m10s*' means 5 minutes 10 seconds later" //third format
	case 2:
		defer delete(userAction, user)
		//save time duration
		text := update.Message.Text
		text = strings.Replace(text, "：", ":", -1)
		firstFormat := "1/02 15:04"
		secondFormat := "15:04"
		thirdFormat := "15:04:05"
		var showTime string
		var scheduledTime time.Time
		var nowTime = time.Now()
		var du time.Duration
		var err error
		if strings.Contains(text, ":") {
			scheduledTime, err = time.Parse(firstFormat, text)
			nowTime, _ = time.Parse(firstFormat, nowTime.Format(firstFormat))
			showTime = scheduledTime.Format(firstFormat)
			if err != nil { //try to parse with first format
				scheduledTime, err = time.Parse(secondFormat, text)
				nowTime, _ = time.Parse(secondFormat, nowTime.Format(secondFormat))
				showTime = scheduledTime.Format(secondFormat)
				if err != nil {
					return "wrong format, try '2/14 11:30' or '11:30'?"
				}
			}
			du = scheduledTime.Sub(nowTime)
		} else {

			du, err = time.ParseDuration(text)
			scheduledTime = nowTime.Add(du)
			showTime = scheduledTime.Format(thirdFormat)
			if err != nil {
				return "wrong format, try '1h2m3s'?"
			}
		}
		//		tmpTask := userTask[user]
		//		tmpTask.When = scheduledTime
		//		userTask[user] = tmpTask
		go func(rb *Robot, ts Task) {
			timer := time.NewTimer(du)
			rawMsg := fmt.Sprintf("Hi %s, maybe it's time to:\n*%s*", ts.Owner, ts.Desc)
			msg := tgbotapi.NewMessage(ts.ChatId, rawMsg)
			msg.ParseMode = "markdown"
			<-timer.C
			_, err := rb.bot.Send(msg)
			if err != nil {
				rb.bot.Send(tgbotapi.NewMessage(conn.GetUserChatId(ts.Owner), rawMsg))
			}
			delete(userTask, user)
		}(rb, userTask[user])

		//		delete(userAction, user)
		return fmt.Sprintf("Ok, I will remind you that\n*%s* - *%s*", showTime, userTask[user].Desc)
	}
	return ""
}

func (rb *Robot) DownloadMovie(update tgbotapi.Update, step int, results chan string) (ret string) {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		ret = "Ok, which movie do you want to download?"
	case 1:
		defer func() {
			delete(userAction, user)
			results <- "done"
		}()
		results <- "Searching movie..."
		movie := update.Message.Text
		var wg sync.WaitGroup
		wg.Add(2)
		go getMovieFromZMZ(movie, results, &wg)
		go getMovieFromLBL(movie, results, &wg)
		wg.Wait()
	}
	return
}

func (rb *Robot) DownloadShow(update tgbotapi.Update, step int, results chan string) (ret string) {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		ret = "Ok, which American show do you want to download?"
	case 1:
		defer func() {
			delete(userAction, user)
			results <- "done"
		}()
		results <- "Searching American show..."
		movie := update.Message.Text
		GetShowFromZMZ(movie, results)
	}
	return
}

func getMovieFromLBL(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	var id string
	resp, _ := http.Get("http://www.lbldy.com/search/" + movie)
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	re, _ := regexp.Compile("
")
	//find first match case
	firstId := re.FindSubmatch(body)
	if len(firstId) == 0 {
		results <- fmt.Sprintf("no result for *%s* from lbl", movie)
		return
	} else {
		id = string(firstId[1])
		resp, _ = http.Get("http://www.lbldy.com/movie/" + id + ".html")
		defer resp.Body.Close()
		re, _ = regexp.Compile(`<p><a href="(.*?)"( target="_blank">|>)(.*?)</a></p>`)
		body, _ := ioutil.ReadAll(resp.Body)
		//go does not support (?!)
		body = []byte(strings.Replace(string(body), `<a href="/xunlei/"`, "", -1))
		downloads := re.FindAllSubmatch(body, -1)
		if len(downloads) == 0 {
			results <- fmt.Sprintf("no result for *%s* from lbl", movie)
			return
		} else {
			ret := "Results from lbl:\n\n"
			for i := range downloads {
				ret += fmt.Sprintf("*%s*\n```%s```\n\n", string(downloads[i][3]), string(downloads[i][1]))
			}
			results <- ret
		}
	}
}

func getMovieFromZMZ(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	if downloads := getZMZResource(movie); downloads == nil {
		results <- fmt.Sprintf("no result for *%s* from zmz", movie)
		return
	} else {
		ret := "Results from zmz:\n\n"
		for i := range downloads {
			name := string(downloads[i][1])
			size := string(downloads[i][2])
			link := string(downloads[i][3])
			ret += fmt.Sprintf("*%s*(%s)\n```%s```\n\n", name, size, link)
		}
		results <- ret
	}
	return
}

func GetShowFromZMZ(movie string, results chan string) {
	if downloads := getZMZResource(movie); downloads == nil {
		results <- fmt.Sprintf("no result for *%s* from zmz", movie)
		return
	} else {
		results <- "Results from zmz:\n\n"
		for i := range downloads {
			name := string(downloads[i][1])
			size := string(downloads[i][2])
			link := string(downloads[i][3])
			results <- fmt.Sprintf("*%s*(%s)\n```%s```\n\n", name, size, link)
		}
	}
	return
}

func getZMZResource(name string) [][][]byte {
	id := getZMZResourceId(name)
	if id == "" {
		return nil
	}
	resourceURL := "http://www.zimuzu.tv/resource/list/" + id
	resp, _ := zmzClient.Get(resourceURL)
	defer resp.Body.Close()
	//1.name 2.size 3.link
	re, _ := regexp.Compile(`<input type="checkbox"><a title="(.*?)".*?<font class="f3">(.*?)</font>.*?<a href="(.*?)" type="ed2k">`)
	body, _ := ioutil.ReadAll(resp.Body)
	tmp := (strings.Replace(string(body), "
\n", "", -1))
	body = []byte(strings.Replace(tmp, `<div class="fr">\n`, "", -1))
	downloads := re.FindAllSubmatch(body, -1)
	if len(downloads) == 0 {
		return nil
	}
	//else {
	//		results <- "Results from zmz:\n\n"
	return downloads
	//		for i := range downloads {
	//			name := string(downloads[i][1])
	//			size := string(downloads[i][2])
	//			link := string(downloads[i][3])
	//			results<- fmt.Sprintf("*%s*(%s)\n```%s```\n\n", name, size, link)
	//		}
	//	}
	//	return
}

func getZMZResourceId(name string) (id string) {
	queryURL := fmt.Sprintf("http://www.zimuzu.tv/search?keyword=%s&type=resource", name)
	re, _ := regexp.Compile(`<div class="t f14"><a href="/resource/(.*?)"><strong class="list_title">`)
	resp, _ := zmzClient.Get(queryURL)
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	//find first match case
	firstId := re.FindSubmatch(body)
	if len(firstId) == 0 {
		return
	} else {
		log.Println(id)
		id = string(firstId[1])
		return
	}
}

func loginZMZ() {
	gCookieJar, _ := cookiejar.New(nil)
	zmzURL := "http://www.zimuzu.tv/User/Login/ajaxLogin"
	zmzClient = http.Client{
		Jar: gCookieJar,
	}
	zmzClient.PostForm(zmzURL, url.Values{"account": {"evol4snow"}, "password": {"104545"}, "remember": {"0"}})
}
<commit_msg>regex error<commit_after>package main

import (
	"fmt"
	"github.com/evolsnow/robot/conn"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"
)

//zmz.tv needs to login before downloading
var zmzClient http.Client

func (rb *Robot) Start(update tgbotapi.Update) string {
	user := update.Message.Chat.UserName
	go conn.SetUserChatId(user, update.Message.Chat.ID)
	return "welcome: " + user
}

func (rb *Robot) Help(update tgbotapi.Update) string {
	helpMsg := `
/alarm - set a reminder
/evolve	- self evolution of me
/movie - find movie download links
/show - find American show download links
/trans - translate words between english and chinese
/help - show this help message
`
	return helpMsg
}

func (rb *Robot) Evolve(update tgbotapi.Update) {
	if update.Message.Chat.FirstName != "Evol" || update.Message.Chat.LastName != "Gan" {
		rb.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, "sorry, unauthorized"))
		return
	}
	<-saidGoodBye
	close(saidGoodBye)
	cmd := exec.Command("bash", "/root/evolve_"+rb.nickName)
	cmd.Start()
	os.Exit(1)
}

func (rb *Robot) Translate(update tgbotapi.Update) string {
	var info string
	if string(update.Message.Text[0]) == "/" {
		raw := strings.SplitAfterN(update.Message.Text, " ", 2)
		if len(raw) < 2 {
			return "what do you want to translate, try '/trans cat'?"
		} else {
			info = "翻译" + raw[1]
		}
	} else {
		info = update.Message.Text
	}

	return qinAI(info)

}
func (rb *Robot) Talk(update tgbotapi.Update) string {
	info := update.Message.Text
	if strings.Contains(info, rb.name) {
		if strings.Contains(info, "闭嘴") || strings.Contains(info, "别说话") {
			rb.shutUp = true
		} else if rb.shutUp && strings.Contains(info, "说话") {
			rb.shutUp = false
			return fmt.Sprintf("%s终于可以说话啦", rb.nickName)
		}
		info = strings.Replace(info, fmt.Sprintf("@%s", rb.name), "", -1)
	}

	if rb.shutUp {
		return ""
	}
	log.Printf(info)

	if rb.nickName == "samaritan" {
		if chinese(info) {
			return tlAI(info)
		} else {
			return mitAI(info)
		}
	} else { //jarvis use another AI
		return qinAI(info)
	}
	//	return response
}

func (rb *Robot) SetReminder(update tgbotapi.Update, step int) string {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpTask := userTask[user]
		tmpTask.ChatId = update.Message.Chat.ID
		tmpTask.Owner = update.Message.Chat.UserName
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "Ok, what should I remind you to do?"
	case 1:
		//save thing
		tmpTask := userTask[user]
		tmpTask.Desc = update.Message.Text
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "When or how much time after?\n" +
			"You can type:\n" +
			"'*2/14 11:30*' means 11:30 at 2/14 \n" + //first format
			"'*11:30*' means  11:30 today\n" + //second format
			"'*5m10s*' means 5 minutes 10 seconds later" //third format
	case 2:
		defer delete(userAction, user)
		//save time duration
		text := update.Message.Text
		text = strings.Replace(text, "：", ":", -1)
		firstFormat := "1/02 15:04"
		secondFormat := "15:04"
		thirdFormat := "15:04:05"
		var showTime string
		var scheduledTime time.Time
		var nowTime = time.Now()
		var du time.Duration
		var err error
		if strings.Contains(text, ":") {
			scheduledTime, err = time.Parse(firstFormat, text)
			nowTime, _ = time.Parse(firstFormat, nowTime.Format(firstFormat))
			showTime = scheduledTime.Format(firstFormat)
			if err != nil { //try to parse with first format
				scheduledTime, err = time.Parse(secondFormat, text)
				nowTime, _ = time.Parse(secondFormat, nowTime.Format(secondFormat))
				showTime = scheduledTime.Format(secondFormat)
				if err != nil {
					return "wrong format, try '2/14 11:30' or '11:30'?"
				}
			}
			du = scheduledTime.Sub(nowTime)
		} else {

			du, err = time.ParseDuration(text)
			scheduledTime = nowTime.Add(du)
			showTime = scheduledTime.Format(thirdFormat)
			if err != nil {
				return "wrong format, try '1h2m3s'?"
			}
		}
		//		tmpTask := userTask[user]
		//		tmpTask.When = scheduledTime
		//		userTask[user] = tmpTask
		go func(rb *Robot, ts Task) {
			timer := time.NewTimer(du)
			rawMsg := fmt.Sprintf("Hi %s, maybe it's time to:\n*%s*", ts.Owner, ts.Desc)
			msg := tgbotapi.NewMessage(ts.ChatId, rawMsg)
			msg.ParseMode = "markdown"
			<-timer.C
			_, err := rb.bot.Send(msg)
			if err != nil {
				rb.bot.Send(tgbotapi.NewMessage(conn.GetUserChatId(ts.Owner), rawMsg))
			}
			delete(userTask, user)
		}(rb, userTask[user])

		//		delete(userAction, user)
		return fmt.Sprintf("Ok, I will remind you that\n*%s* - *%s*", showTime, userTask[user].Desc)
	}
	return ""
}

func (rb *Robot) DownloadMovie(update tgbotapi.Update, step int, results chan string) (ret string) {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		ret = "Ok, which movie do you want to download?"
	case 1:
		defer func() {
			delete(userAction, user)
			results <- "done"
		}()
		results <- "Searching movie..."
		movie := update.Message.Text
		var wg sync.WaitGroup
		wg.Add(2)
		go getMovieFromZMZ(movie, results, &wg)
		go getMovieFromLBL(movie, results, &wg)
		wg.Wait()
	}
	return
}

func (rb *Robot) DownloadShow(update tgbotapi.Update, step int, results chan string) (ret string) {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		ret = "Ok, which American show do you want to download?"
	case 1:
		defer func() {
			delete(userAction, user)
			results <- "done"
		}()
		results <- "Searching American show..."
		movie := update.Message.Text
		GetShowFromZMZ(movie, results)
	}
	return
}

func getMovieFromLBL(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	var id string
	resp, _ := http.Get("http://www.lbldy.com/search/" + movie)
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	re, _ := regexp.Compile("
")
	//find first match case
	firstId := re.FindSubmatch(body)
	if len(firstId) == 0 {
		results <- fmt.Sprintf("no result for *%s* from lbl", movie)
		return
	} else {
		id = string(firstId[1])
		resp, _ = http.Get("http://www.lbldy.com/movie/" + id + ".html")
		defer resp.Body.Close()
		re, _ = regexp.Compile(`<p><a href="(.*?)"( target="_blank">|>)(.*?)</a></p>`)
		body, _ := ioutil.ReadAll(resp.Body)
		//go does not support (?!)
		body = []byte(strings.Replace(string(body), `<a href="/xunlei/"`, "", -1))
		downloads := re.FindAllSubmatch(body, -1)
		if len(downloads) == 0 {
			results <- fmt.Sprintf("no result for *%s* from lbl", movie)
			return
		} else {
			ret := "Results from lbl:\n\n"
			for i := range downloads {
				ret += fmt.Sprintf("*%s*\n```%s```\n\n", string(downloads[i][3]), string(downloads[i][1]))
			}
			results <- ret
		}
	}
}

func getMovieFromZMZ(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	if downloads := getZMZResource(movie); downloads == nil {
		results <- fmt.Sprintf("no result for *%s* from zmz", movie)
		return
	} else {
		ret := "Results from zmz:\n\n"
		for i := range downloads {
			name := string(downloads[i][1])
			size := string(downloads[i][2])
			link := string(downloads[i][3])
			ret += fmt.Sprintf("*%s*(%s)\n```%s```\n\n", name, size, link)
		}
		results <- ret
	}
	return
}

func GetShowFromZMZ(movie string, results chan string) {
	if downloads := getZMZResource(movie); downloads == nil {
		results <- fmt.Sprintf("no result for *%s* from zmz", movie)
		return
	} else {
		results <- "Results from zmz:\n\n"
		for i := range downloads {
			name := string(downloads[i][1])
			size := string(downloads[i][2])
			link := string(downloads[i][3])
			results <- fmt.Sprintf("*%s*(%s)\n```%s```\n\n", name, size, link)
		}
	}
	return
}

func getZMZResource(name string) [][][]byte {
	id := getZMZResourceId(name)
	if id == "" {
		return nil
	}
	resourceURL := "http://www.zimuzu.tv/resource/list/" + id
	resp, _ := zmzClient.Get(resourceURL)
	defer resp.Body.Close()
	//1.name 2.size 3.link
	re, _ := regexp.Compile(`<input type="checkbox"><a title="(.*?)".*?<font class="f3">(.*?)</font>.*?<a href="(.*?)" type="ed2k">`)
	body, _ := ioutil.ReadAll(resp.Body)
	tmp := (strings.Replace(string(body), "
\n", "", -1))
	body = []byte(strings.Replace(tmp, "
\n", "", -1))
	downloads := re.FindAllSubmatch(body, -1)
	if len(downloads) == 0 {
		return nil
	}
	//else {
	//		results <- "Results from zmz:\n\n"
	return downloads
	//		for i := range downloads {
	//			name := string(downloads[i][1])
	//			size := string(downloads[i][2])
	//			link := string(downloads[i][3])
	//			results<- fmt.Sprintf("*%s*(%s)\n```%s```\n\n", name, size, link)
	//		}
	//	}
	//	return
}

func getZMZResourceId(name string) (id string) {
	queryURL := fmt.Sprintf("http://www.zimuzu.tv/search?keyword=%s&type=resource", name)
	re, _ := regexp.Compile(`<div class="t f14"><a href="/resource/(.*?)"><strong class="list_title">`)
	resp, _ := zmzClient.Get(queryURL)
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	//find first match case
	firstId := re.FindSubmatch(body)
	if len(firstId) == 0 {
		return
	} else {
		log.Println(id)
		id = string(firstId[1])
		return
	}
}

func loginZMZ() {
	gCookieJar, _ := cookiejar.New(nil)
	zmzURL := "http://www.zimuzu.tv/User/Login/ajaxLogin"
	zmzClient = http.Client{
		Jar: gCookieJar,
	}
	zmzClient.PostForm(zmzURL, url.Values{"account": {"evol4snow"}, "password": {"104545"}, "remember": {"0"}})
}
<|endoftext|>


<commit_before>//go:generate bitfanDoc
// Send email when an output is received. Alternatively, you may include or exclude the email output execution using conditionals.
package email

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	uuid "github.com/nu7hatch/gouuid"
	"github.com/vjeantet/bitfan/commons"
	"github.com/vjeantet/bitfan/processors"
	gomail "gopkg.in/gomail.v2"
)

func New() processors.Processor {
	return &processor{opt: &options{}}
}

// Sends email to a specified address when output is received
type processor struct {
	processors.Base

	opt *options
}

type options struct {
	// The address used to connect to the mail server
	// @Default "localhost"
	Host string `mapstructure:"address"`

	// Port used to communicate with the mail server
	// @Default 25
	Port int `mapstructure:"port"`

	// Username to authenticate with the server
	Username string `mapstructure:"username"`

	// Password to authenticate with the server
	Password string `mapstructure:"password"`

	// The fully-qualified email address for the From: field in the email
	// @Default "bitfan@nowhere.com"
	From string `mapstructure:"from"`

	// The fully qualified email address for the Reply-To: field
	// @ExampleLS replyto => "test@nowhere.com"
	Replyto string `mapstructure:"replyto"`

	// The fully-qualified email address to send the email to.
	//
	// This field also accepts a comma-separated string of addresses, for example: `"me@host.com, you@host.com"`
	//
	// You can also use dynamic fields from the event with the %{fieldname} syntax
	// @ExampleLS to => "me@host.com, you@host.com"
	To string `mapstructure:"to" validate:"required"`

	// The fully-qualified email address(es) to include as cc: address(es).
	//
	// This field also accepts a comma-separated string of addresses, for example: `"me@host.com, you@host.com"`
	// @ExampleLS cc => "me@host.com, you@host.com"
	Cc string `mapstructure:"cc"`

	// The fully-qualified email address(es) to include as bcc: address(es).
	//
	// This field also accepts a comma-separated string of addresses, for example: `"me@host.com, you@host.com"`
	// @ExampleLS bcc => "me@host.com, you@host.com"
	Bcc string `mapstructure:"bcc"`

	// Subject: for the email
	//
	// You can use template
	// @ExampleLS subject => "message from {{.host}}"
	Subject string `mapstructure:"subject"`

	// Path to Subject template file for the email
	Subjectfile string `mapstructure:"subjectfile"`

	// HTML Body for the email, which may contain HTML markup
	// @ExampleLS htmlBody => "
Hello
 message received : {{.message}}"
	// @Type Location
	HTMLBody string `mapstructure:"htmlbody"`

	// Body for the email - plain text only.
	// @ExampleLS body => "message : {{.message}}. from {{.host}}."
	// @Type Location
	Body string `mapstructure:"body"`

	// Attachments - specify the name(s) and location(s) of the files
	Attachments []string `mapstructure:"attachments"`

	// Use event field's values as attachment content
	// each pair is  : event field's path => attachment's name
	// @ExampleLS  attachments_with_event=>{"mydata"=>"myimage.jpg"}
	AttachEventData map[string]string `mapstructure:"attachments_with_event"`

	// Images - specify the name(s) and location(s) of the images
	Images []string `mapstructure:"images"`

	// Search for img:data in HTML body, and replace them to a reference to inline attachment
	// @Default false
	EmbedB64Images bool `mapstructure:"embed_b64_images"`
}

func (p *processor) Configure(ctx processors.ProcessorContext, conf map[string]interface{}) error {
	defaults := options{
		Host:           "localhost",
		From:           "bitfan@nowhere.com",
		Port:           25,
		EmbedB64Images: false,
	}
	p.opt = &defaults
	err := p.ConfigureAndValidate(ctx, conf, p.opt)
	if err != nil {
		return err
	}

	return nil
}

func (p *processor) Start(e processors.IPacket) error {
	// TODO prepare TEMPLATE HTML et SIMPLE
	// connect
	return nil
}

func (p *processor) Receive(e processors.IPacket) error {
	// TODO use prepared template
	// connnect only if needed
	toStr := p.opt.To
	processors.Dynamic(&toStr, e.Fields())
	to := strings.Split(toStr, ",")

	ccStr := p.opt.Cc
	processors.Dynamic(&ccStr, e.Fields())
	cc := strings.Split(ccStr, ",")

	bccStr := p.opt.Bcc
	processors.Dynamic(&bccStr, e.Fields())
	bcc := strings.Split(bccStr, ",")

	m := gomail.NewMessage()

	fromStr := p.opt.From
	processors.Dynamic(&fromStr, e.Fields())
	m.SetHeader("From", fromStr)

	if p.opt.Replyto != "" {
		replyToStr := p.opt.Replyto
		processors.Dynamic(&replyToStr, e.Fields())
		m.SetHeader("Reply-To", replyToStr)
	}

	m.SetHeader("To", to...)

	if p.opt.Cc != "" {
		m.SetHeader("Cc", cc...)
	}
	if p.opt.Bcc != "" {
		m.SetHeader("Bcc", bcc...)
	}
	// m.SetAddressHeader("Cc", p.opt.Cc, "")

	// // Subject with Dynamics subject => "message from %{host}!"
	// subject := p.opt.Subject
	// processors.Dynamic(&subject, e.Fields())
	// m.SetHeader("Subject", subject)
	// pp.Println("subject-->", subject)

	if p.opt.Subject != "" {
		loc, err := commons.NewLocation(p.opt.Subject, p.ConfigWorkingLocation)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		tmpl, _, err := loc.TemplateWithOptions(nil)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		buff := bytes.NewBufferString("")
		tmpl.Execute(buff, e.Fields())
		m.SetHeader("Subject", buff.String())
	}

	if p.opt.HTMLBody != "" {
		loc, err := commons.NewLocation(p.opt.HTMLBody, p.ConfigWorkingLocation)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		tmpl, _, err := loc.TemplateWithOptions(nil)
		if err != nil {
			p.Logger.Errorf("email template error : %v", err)
			return err
		}

		buff := bytes.NewBufferString("")
		tmpl.Execute(buff, e.Fields())

		if p.opt.EmbedB64Images == false {
			m.SetBody("text/html", buff.String())
		} else {
			content := buff.String()
			//find all
			r, _ := regexp.Compile(`<img src="data:image/png;base64,([a-zA-Z0-9+=/]*)"/>`)

			for i, match := range r.FindAllStringSubmatch(content, -1) {
				imgTag := match[0]
				b64Data := match[1]

				imgUid := fmt.Sprintf("embed-%d.png", i)
				content = strings.Replace(content, imgTag, `<img src='cid:`+imgUid+`'/>`, 1)
				imgPath := filepath.Join(os.TempDir(), imgUid)

				sDec, err := base64.StdEncoding.DecodeString(b64Data)
				if err != nil {
					p.Logger.Errorf("error while decoding base64 %s", err.Error())
					continue
				}

				if err := ioutil.WriteFile(imgPath, sDec, 0644); err != nil {
					p.Logger.Errorf("error while writing image to %s", imgPath)
					continue
				}

				p.opt.Images = append(p.opt.Images, imgPath)
			}
			m.SetBody("text/html", content)
		}

	}

	if p.opt.Body != "" {
		loc, err := commons.NewLocation(p.opt.Body, p.ConfigWorkingLocation)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		tmpl, _, err := loc.TemplateWithOptions(nil)
		if err != nil {
			p.Logger.Errorf("email template error : %v", err)
			return err
		}
		buff := bytes.NewBufferString("")
		tmpl.Execute(buff, e.Fields())
		if p.opt.HTMLBody != "" {
			m.AddAlternative("text/plain", buff.String())
		} else {
			m.SetBody("text/plain", buff.String())
		}
	}

	for _, ref := range p.opt.Images {
		// todo build image from event data images{}
		f := ""
		if _, err := os.Stat(ref); err == nil {
			if err != nil {
				p.Logger.Errorf("Image file error %v", err)
			}
			var err error
			f, err = filepath.Abs(ref)
			if err != nil {
				p.Logger.Errorf("Image file path error %v", err)
				continue
			}
		} else if _, err := os.Stat(filepath.Join(p.ConfigWorkingLocation, ref)); err == nil {
			f = filepath.Join(p.ConfigWorkingLocation, ref)
		} else {
			p.Logger.Errorf("Image file path unknow %v", ref)
			continue
		}

		m.Embed(f)
		// m.Embed(f, gomail.Rename("o.png"))
	}

	for _, ref := range p.opt.Attachments {

		// todo build attachments from event data attachments{}
		f := ""
		if _, err := os.Stat(ref); err == nil {
			if err != nil {
				p.Logger.Errorf("Attachment file error %v", err)
			}
			var err error
			f, err = filepath.Abs(ref)
			if err != nil {
				p.Logger.Errorf("Attachment file path error %v", err)
				continue
			}
		} else if _, err := os.Stat(filepath.Join(p.ConfigWorkingLocation, ref)); err == nil {
			f = filepath.Join(p.ConfigWorkingLocation, ref)
		} else {
			p.Logger.Errorf("Attachment file path unknow %s", ref)
			continue
		}

		m.Attach(f)
	}

	for path, attachmentName := range p.opt.AttachEventData {
		value, err := e.Fields().ValueForPath(path)
		if err != nil {
			p.Logger.Warningf("Attach event data failed for path %s : %s", path, err)
			continue
		}

		name, _ := uuid.NewV4()
		attachFileName := filepath.Join(os.TempDir(), "bitfan-tmp-email-"+name.String())
		switch vt := value.(type) {
		case string:
			sDec, err := base64.StdEncoding.DecodeString(vt)
			if err != nil {
				p.Logger.Warningf("base64 decode %s, %s", path, err)
				return err
			}
			if err = ioutil.WriteFile(attachFileName, sDec, 0644); err != nil {
				p.Logger.Warningf("write file error %s : %s", attachFileName, err)
				continue
			}
		case []byte:
			if err = ioutil.WriteFile(attachFileName, vt, 0644); err != nil {
				p.Logger.Warningf("write file error %s : %s", attachFileName, err)
				continue
			}
		default:
			p.Logger.Warningf("Unknow content type for %V", value)
			continue
		}

		m.Attach(attachFileName, gomail.Rename(attachmentName))
	}

	d := gomail.NewDialer(p.opt.Host, p.opt.Port, p.opt.Username, p.opt.Password)
	if err := d.DialAndSend(m); err != nil {
		p.Logger.Errorf("email send error : %v", err)
		return err
	}

	return nil
}

func (p *processor) Stop(e processors.IPacket) error {
	//TODO disconnect from SMTP
	return nil
}
<commit_msg>processors: email - attachments_with_event : When field'value is string and base64 fails, use raw string value as content<commit_after>//go:generate bitfanDoc
// Send email when an output is received. Alternatively, you may include or exclude the email output execution using conditionals.
package email

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	uuid "github.com/nu7hatch/gouuid"
	"github.com/vjeantet/bitfan/commons"
	"github.com/vjeantet/bitfan/processors"
	gomail "gopkg.in/gomail.v2"
)

func New() processors.Processor {
	return &processor{opt: &options{}}
}

// Sends email to a specified address when output is received
type processor struct {
	processors.Base

	opt *options
}

type options struct {
	// The address used to connect to the mail server
	// @Default "localhost"
	Host string `mapstructure:"address"`

	// Port used to communicate with the mail server
	// @Default 25
	Port int `mapstructure:"port"`

	// Username to authenticate with the server
	Username string `mapstructure:"username"`

	// Password to authenticate with the server
	Password string `mapstructure:"password"`

	// The fully-qualified email address for the From: field in the email
	// @Default "bitfan@nowhere.com"
	From string `mapstructure:"from"`

	// The fully qualified email address for the Reply-To: field
	// @ExampleLS replyto => "test@nowhere.com"
	Replyto string `mapstructure:"replyto"`

	// The fully-qualified email address to send the email to.
	//
	// This field also accepts a comma-separated string of addresses, for example: `"me@host.com, you@host.com"`
	//
	// You can also use dynamic fields from the event with the %{fieldname} syntax
	// @ExampleLS to => "me@host.com, you@host.com"
	To string `mapstructure:"to" validate:"required"`

	// The fully-qualified email address(es) to include as cc: address(es).
	//
	// This field also accepts a comma-separated string of addresses, for example: `"me@host.com, you@host.com"`
	// @ExampleLS cc => "me@host.com, you@host.com"
	Cc string `mapstructure:"cc"`

	// The fully-qualified email address(es) to include as bcc: address(es).
	//
	// This field also accepts a comma-separated string of addresses, for example: `"me@host.com, you@host.com"`
	// @ExampleLS bcc => "me@host.com, you@host.com"
	Bcc string `mapstructure:"bcc"`

	// Subject: for the email
	//
	// You can use template
	// @ExampleLS subject => "message from {{.host}}"
	Subject string `mapstructure:"subject"`

	// Path to Subject template file for the email
	Subjectfile string `mapstructure:"subjectfile"`

	// HTML Body for the email, which may contain HTML markup
	// @ExampleLS htmlBody => "
Hello
 message received : {{.message}}"
	// @Type Location
	HTMLBody string `mapstructure:"htmlbody"`

	// Body for the email - plain text only.
	// @ExampleLS body => "message : {{.message}}. from {{.host}}."
	// @Type Location
	Body string `mapstructure:"body"`

	// Attachments - specify the name(s) and location(s) of the files
	Attachments []string `mapstructure:"attachments"`

	// Use event field's values as attachment content
	// each pair is  : event field's path => attachment's name
	// @ExampleLS  attachments_with_event=>{"mydata"=>"myimage.jpg"}
	AttachEventData map[string]string `mapstructure:"attachments_with_event"`

	// Images - specify the name(s) and location(s) of the images
	Images []string `mapstructure:"images"`

	// Search for img:data in HTML body, and replace them to a reference to inline attachment
	// @Default false
	EmbedB64Images bool `mapstructure:"embed_b64_images"`
}

func (p *processor) Configure(ctx processors.ProcessorContext, conf map[string]interface{}) error {
	defaults := options{
		Host:           "localhost",
		From:           "bitfan@nowhere.com",
		Port:           25,
		EmbedB64Images: false,
	}
	p.opt = &defaults
	err := p.ConfigureAndValidate(ctx, conf, p.opt)
	if err != nil {
		return err
	}

	return nil
}

func (p *processor) Start(e processors.IPacket) error {
	// TODO prepare TEMPLATE HTML et SIMPLE
	// connect
	return nil
}

func (p *processor) Receive(e processors.IPacket) error {
	// TODO use prepared template
	// connnect only if needed
	toStr := p.opt.To
	processors.Dynamic(&toStr, e.Fields())
	to := strings.Split(toStr, ",")

	ccStr := p.opt.Cc
	processors.Dynamic(&ccStr, e.Fields())
	cc := strings.Split(ccStr, ",")

	bccStr := p.opt.Bcc
	processors.Dynamic(&bccStr, e.Fields())
	bcc := strings.Split(bccStr, ",")

	m := gomail.NewMessage()

	fromStr := p.opt.From
	processors.Dynamic(&fromStr, e.Fields())
	m.SetHeader("From", fromStr)

	if p.opt.Replyto != "" {
		replyToStr := p.opt.Replyto
		processors.Dynamic(&replyToStr, e.Fields())
		m.SetHeader("Reply-To", replyToStr)
	}

	m.SetHeader("To", to...)

	if p.opt.Cc != "" {
		m.SetHeader("Cc", cc...)
	}
	if p.opt.Bcc != "" {
		m.SetHeader("Bcc", bcc...)
	}
	// m.SetAddressHeader("Cc", p.opt.Cc, "")

	// // Subject with Dynamics subject => "message from %{host}!"
	// subject := p.opt.Subject
	// processors.Dynamic(&subject, e.Fields())
	// m.SetHeader("Subject", subject)
	// pp.Println("subject-->", subject)

	if p.opt.Subject != "" {
		loc, err := commons.NewLocation(p.opt.Subject, p.ConfigWorkingLocation)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		tmpl, _, err := loc.TemplateWithOptions(nil)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		buff := bytes.NewBufferString("")
		tmpl.Execute(buff, e.Fields())
		m.SetHeader("Subject", buff.String())
	}

	if p.opt.HTMLBody != "" {
		loc, err := commons.NewLocation(p.opt.HTMLBody, p.ConfigWorkingLocation)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		tmpl, _, err := loc.TemplateWithOptions(nil)
		if err != nil {
			p.Logger.Errorf("email template error : %v", err)
			return err
		}

		buff := bytes.NewBufferString("")
		tmpl.Execute(buff, e.Fields())

		if p.opt.EmbedB64Images == false {
			m.SetBody("text/html", buff.String())
		} else {
			content := buff.String()
			//find all
			r, _ := regexp.Compile(`<img src="data:image/png;base64,([a-zA-Z0-9+=/]*)"/>`)

			for i, match := range r.FindAllStringSubmatch(content, -1) {
				imgTag := match[0]
				b64Data := match[1]

				imgUid := fmt.Sprintf("embed-%d.png", i)
				content = strings.Replace(content, imgTag, `<img src='cid:`+imgUid+`'/>`, 1)
				imgPath := filepath.Join(os.TempDir(), imgUid)

				sDec, err := base64.StdEncoding.DecodeString(b64Data)
				if err != nil {
					p.Logger.Errorf("error while decoding base64 %s", err.Error())
					continue
				}

				if err := ioutil.WriteFile(imgPath, sDec, 0644); err != nil {
					p.Logger.Errorf("error while writing image to %s", imgPath)
					continue
				}

				p.opt.Images = append(p.opt.Images, imgPath)
			}
			m.SetBody("text/html", content)
		}

	}

	if p.opt.Body != "" {
		loc, err := commons.NewLocation(p.opt.Body, p.ConfigWorkingLocation)
		if err != nil {
			p.Logger.Errorf("email subject template error : %v", err)
			return err
		}
		tmpl, _, err := loc.TemplateWithOptions(nil)
		if err != nil {
			p.Logger.Errorf("email template error : %v", err)
			return err
		}
		buff := bytes.NewBufferString("")
		tmpl.Execute(buff, e.Fields())
		if p.opt.HTMLBody != "" {
			m.AddAlternative("text/plain", buff.String())
		} else {
			m.SetBody("text/plain", buff.String())
		}
	}

	for _, ref := range p.opt.Images {
		// todo build image from event data images{}
		f := ""
		if _, err := os.Stat(ref); err == nil {
			if err != nil {
				p.Logger.Errorf("Image file error %v", err)
			}
			var err error
			f, err = filepath.Abs(ref)
			if err != nil {
				p.Logger.Errorf("Image file path error %v", err)
				continue
			}
		} else if _, err := os.Stat(filepath.Join(p.ConfigWorkingLocation, ref)); err == nil {
			f = filepath.Join(p.ConfigWorkingLocation, ref)
		} else {
			p.Logger.Errorf("Image file path unknow %v", ref)
			continue
		}

		m.Embed(f)
		// m.Embed(f, gomail.Rename("o.png"))
	}

	for _, ref := range p.opt.Attachments {

		// todo build attachments from event data attachments{}
		f := ""
		if _, err := os.Stat(ref); err == nil {
			if err != nil {
				p.Logger.Errorf("Attachment file error %v", err)
			}
			var err error
			f, err = filepath.Abs(ref)
			if err != nil {
				p.Logger.Errorf("Attachment file path error %v", err)
				continue
			}
		} else if _, err := os.Stat(filepath.Join(p.ConfigWorkingLocation, ref)); err == nil {
			f = filepath.Join(p.ConfigWorkingLocation, ref)
		} else {
			p.Logger.Errorf("Attachment file path unknow %s", ref)
			continue
		}

		m.Attach(f)
	}

	for path, attachmentName := range p.opt.AttachEventData {
		value, err := e.Fields().ValueForPath(path)
		if err != nil {
			p.Logger.Warningf("Attach event data failed for path %s : %s", path, err)
			continue
		}

		name, _ := uuid.NewV4()
		attachFileName := filepath.Join(os.TempDir(), "bitfan-tmp-email-"+name.String())
		switch vt := value.(type) {
		case string:
			sDec, err := base64.StdEncoding.DecodeString(vt)
			if err != nil {
				p.Logger.Debugf("base64 decode %s, %s", path, err)
				if err = ioutil.WriteFile(attachFileName, []byte(vt), 0644); err != nil {
					p.Logger.Warningf("write file error %s : %s", attachFileName, err)
					continue
				}
			}
			if err = ioutil.WriteFile(attachFileName, sDec, 0644); err != nil {
				p.Logger.Warningf("write file error %s : %s", attachFileName, err)
				continue
			}
		case []byte:
			if err = ioutil.WriteFile(attachFileName, vt, 0644); err != nil {
				p.Logger.Warningf("write file error %s : %s", attachFileName, err)
				continue
			}
		default:
			p.Logger.Warningf("Unknow content type for %V", value)
			continue
		}

		m.Attach(attachFileName, gomail.Rename(attachmentName))
	}

	d := gomail.NewDialer(p.opt.Host, p.opt.Port, p.opt.Username, p.opt.Password)
	if err := d.DialAndSend(m); err != nil {
		p.Logger.Errorf("email send error : %v", err)
		return err
	}

	return nil
}

func (p *processor) Stop(e processors.IPacket) error {
	//TODO disconnect from SMTP
	return nil
}
<|endoftext|>