<commit_before>package telegram

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	elasticsearch7 "github.com/elastic/go-elasticsearch/v7"
	"github.com/go-redis/redis"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
	bt "github.com/ikool-cn/gobeanstalk-connection-pool"
	"github.com/sirupsen/logrus"

	"yubari/pixiv"
)

const (
	tgDeleteTube = "tg_delete"
	tgPixivTube  = "tg_pixiv"

	pixivSplitWidth = 10000000
)

var (
	rePixivFileName = regexp.MustCompile(`(?P<id>\d+)_p(?P<seq>d+)\.(?P<ext>\w+)`)
)

type Config struct {
	Token          string  `json:"token"`
	SelfID         int64   `json:"selfID"`
	AdmissionID    int64   `json:"admissionID"`
	WhitelistChats []int64 `json:"whitelistChats"`
	ComicPath      string  `json:"comicPath"`
	DeleteDelay    string  `json:"deleteDelay"`
}

type DownloadPixiv struct {
	ChatID    int64
	MessageID int
	PixivID   uint64
}

type Bot struct {
	Name           string
	SelfID         int64
	AdmissionID    int64
	WhitelistChats []int64
	ComicPath      string
	PixivPath      string
	TwitterImgPath string
	DeleteDelay    time.Duration
	Client         *tgbotapi.BotAPI
	Queue          *bt.Pool
	logger         *logrus.Logger
	redis          *redis.Client
	es             *elasticsearch7.Client
}

func NewBot(cfg *Config) (b *Bot, err error) {
	bot, err := tgbotapi.NewBotAPI(cfg.Token)
	if err != nil {
		return nil, fmt.Errorf("tg bot init failed: %+v", err)
	}
	delay, err := time.ParseDuration(cfg.DeleteDelay)
	if err != nil {
		return nil, fmt.Errorf("delete delay error: %+v", err)
	}

	b = &Bot{
		Name:           bot.Self.UserName,
		SelfID:         cfg.SelfID,
		AdmissionID:    cfg.AdmissionID,
		WhitelistChats: cfg.WhitelistChats,
		ComicPath:      cfg.ComicPath,
		DeleteDelay:    delay,
		Client:         bot,
	}
	return
}

func (b *Bot) WithLogger(logger *logrus.Logger) *Bot {
	b.logger = logger
	return b
}

func (b *Bot) WithRedis(rds *redis.Client) *Bot {
	b.redis = rds
	return b
}

func (b *Bot) WithPixivImg(imgPath string) *Bot {
	b.PixivPath = imgPath
	return b
}

func (b *Bot) WithTwitterImg(imgPath string) *Bot {
	b.TwitterImgPath = imgPath
	return b
}

func (b *Bot) WithQueue(queue *bt.Pool) *Bot {
	b.Queue = queue
	return b
}

func (b *Bot) WithES(es *elasticsearch7.Client) *Bot {
	b.es = es
	return b
}

func (b *Bot) putQueue(msg []byte, tube string) {
	conn, err := b.Queue.Get()
	if err != nil {
		b.logger.Errorf("%+v: %s", err, string(msg))
		return
	}
	conn.Use(tube)
	_, err = conn.Put(msg, 1, b.DeleteDelay, time.Minute)
	if err != nil {
		b.logger.Errorf("%+v", err)
		return
	}
}

func (b *Bot) isAuthedChat(c *tgbotapi.Chat) bool {
	for _, w := range b.WhitelistChats {
		if c.ID == w {
			return true
		}
	}
	return false
}

func (b *Bot) Send(chat int64, msg string) (tgbotapi.Message, error) {
	b.logger.Debugf("[%d]%s", chat, msg)
	message := tgbotapi.NewMessage(chat, msg)
	message.DisableNotification = true
	return b.Client.Send(message)
}

func (b *Bot) GetUserName(chatID int64, userID int) (name string, err error) {
	cacheKey := fmt.Sprintf("tg:user:%d", userID)
	cache, err := b.redis.Get(cacheKey).Result()
	if err == nil {
		name = cache
		return
	} else {
		if err != redis.Nil {
			return
		}
	}
	member, err := b.Client.GetChatMember(tgbotapi.ChatConfigWithUser{
		ChatID: chatID,
		UserID: userID,
	})
	if err != nil {
		return
	}
	name = member.User.String()
	if name != "" {
		b.redis.Set(cacheKey, name, 0)
	}
	return
}

func (b *Bot) SendPixivCandidate(target int64, id uint64) {
	row := tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⭕", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "like")),
		tgbotapi.NewInlineKeyboardButtonData("❌", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "diss")),
	)
	msg := tgbotapi.NewMessage(target, pixiv.URLWithID(id))
	msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(row)
	msg.DisableNotification = true
	_, err := b.Client.Send(msg)
	if err != nil {
		b.logger.Errorf("%+v", err)
	}
}

func (b *Bot) startDownloadPixiv() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgPixivTube)
		job, err := conn.Reserve()
		if err != nil {
			b.logger.Warningf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		msg := &DownloadPixiv{}
		err = json.Unmarshal(job.Body, msg)
		if err != nil {
			b.logger.Errorf("%+v", err)
			err = conn.Bury(job.ID, 0)
			if err != nil {
				b.logger.Errorf("%+v", err)
			}
			time.Sleep(3 * time.Second)
			continue
		}

		sizes, errs := pixiv.Download(msg.PixivID, b.PixivPath)
		for i := range sizes {
			if errs[i] != nil {
				err = errs[i]
				break
			}
			if sizes[i] == 0 {
				continue
			}
			b.logger.Debugf("download pixiv %d_p%d: %d bytes", msg.PixivID, i, sizes[i])
		}
		if err != nil {
			b.logger.Errorf("%+v", err)
			continue
		}

		editMsg := tgbotapi.NewEditMessageCaption(
			msg.ChatID, msg.MessageID, "succeed",
		)
		_, err = b.Client.Send(editMsg)
		if err != nil {
			b.logger.Warningf("set success caption failed: %+v", err)
		}

		err = conn.Delete(job.ID)
		if err != nil {
			b.logger.Errorf("delete job error: %+v", err)
			time.Sleep(3 * time.Second)
		}
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) startDeleteMessage() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgDeleteTube)
		job, err := conn.Reserve()
		if err != nil {
			b.logger.Warningf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}

		func() {
			var err error
			defer func() {
				if err != nil {
					b.logger.Errorf("%+v", err)
					if e := conn.Bury(job.ID, 0); e != nil {
						b.logger.Errorf("%+v", err)
					}
					time.Sleep(3 * time.Second)
				} else {
					if e := conn.Delete(job.ID); e != nil {
						b.logger.Errorf("%+v", err)
						time.Sleep(3 * time.Second)
					}
				}
			}()

			msg := &tgbotapi.Message{}
			err = json.Unmarshal(job.Body, msg)
			if err != nil {
				return
			}

			if msg.Chat == nil {
				err = fmt.Errorf("err msg with no chat: %+v", msg)
				return
			}
			delMsg := tgbotapi.DeleteMessageConfig{
				ChatID:    msg.Chat.ID,
				MessageID: msg.MessageID,
			}
			b.logger.Infof("del:[%s]{%s}", getMsgTitle(msg), strconv.Quote(msg.Text))
			_, err = b.Client.DeleteMessage(delMsg)

		}()
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) Start() {
	go b.startDeleteMessage()
	go b.startDownloadPixiv()

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 30
	for {
		updates, err := b.Client.GetUpdatesChan(u)
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		var message *tgbotapi.Message
		for update := range updates {
			if update.Message != nil {
				message = update.Message
			} else if update.EditedMessage != nil {
				message = update.EditedMessage
			} else if update.CallbackQuery != nil {
				b.logger.Infof(
					"recv:(%d)[%s]reaction:{%s}",
					update.CallbackQuery.Message.Chat.ID,
					update.CallbackQuery.From.String(),
					update.CallbackQuery.Data,
				)
				data := strings.SplitN(update.CallbackQuery.Data, ":", 2)
				switch data[0] {
				case "comic", "pic", "pixiv":
					go onReaction(b, update.CallbackQuery)
				case "pixivCandidate":
					if !b.isAuthedChat(update.CallbackQuery.Message.Chat) {
						b.logger.Warning("reaction from illegal chat, ignore")
						break
					}
					go onReactionCandidate(b, update.CallbackQuery)
				case "search":
					go onReactionSearch(b, update.CallbackQuery)
				default:
				}
				continue
			} else {
				continue
			}

			if !b.checkInWhitelist(message.Chat.ID) {
				continue
			}
			if message.Chat.IsGroup() {
				b.logger.Infof(
					"recv:(%d)[%s:%s]{%s}",
					message.Chat.ID,
					message.Chat.Title,
					message.From.String(),
					strconv.Quote(message.Text))
			} else {
				b.logger.Infof(
					"recv:(%d)[%s]{%s}",
					message.Chat.ID,
					message.From.String(),
					strconv.Quote(message.Text),
				)
			}
			if message.IsCommand() {
				switch message.Command() {
				case "start":
					go onStart(b, message)
				case "roll":
					go onRoll(b, message)
				case "comic":
					go onComic(b, message)
				case "pic":
					go onPic(b, message)
				case "pixiv":
					go onPixiv(b, message)
				case "search":
					go onSearch(b, message)
				default:
					b.logger.Infof("ignore unknown cmd: %+v", message.Command())
					continue
				}
			} else {
				if message.Text == "" {
					continue
				}
				go checkRepeat(b, message)
				go checkPixiv(b, message)
				go checkSave(b, message)
			}
		}
		b.logger.Warning("tg bot restarted.")
		time.Sleep(3 * time.Second)
	}
}

func (b *Bot) checkInWhitelist(id int64) bool {
	for _, c := range b.WhitelistChats {
		if c == id {
			return true
		}
	}
	b.logger.Debugf("ignore msg from %d", id)
	return false
}

func (b *Bot) probate(_type, _id string) error {
	b.logger.Infof("%s: %s", _type, _id)
	switch _type {
	case "comic":
		fileName := "nhentai.net@" + _id + ".epub"
		return os.Rename(
			filepath.Join(b.ComicPath, fileName),
			filepath.Join(b.ComicPath, "probation", fileName),
		)
	case "pic":
		return os.Rename(
			filepath.Join(b.TwitterImgPath, _id),
			filepath.Join(b.TwitterImgPath, "probation", _id),
		)
	case "pixiv":
		return os.Rename(
			filepath.Join(b.PixivPath, _id),
			filepath.Join(b.PixivPath, "probation", _id),
		)
	default:
		return fmt.Errorf("prohibit unkown type")
	}
}

func (b *Bot) setChatAction(chatID int64, action string) error {
	a := tgbotapi.NewChatAction(chatID, action)
	_, err := b.Client.Send(a)
	if err != nil {
		b.logger.Errorf("set action %s failed: %+v", action, err)
	}
	return err
}

func getMsgTitle(m *tgbotapi.Message) string {
	if m.Chat.IsGroup() {
		return m.Chat.Title
	}
	return m.From.String()
}
<commit_msg>fix: do not edit message caption<commit_after>package telegram

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	elasticsearch7 "github.com/elastic/go-elasticsearch/v7"
	"github.com/go-redis/redis"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
	bt "github.com/ikool-cn/gobeanstalk-connection-pool"
	"github.com/sirupsen/logrus"

	"yubari/pixiv"
)

const (
	tgDeleteTube = "tg_delete"
	tgPixivTube  = "tg_pixiv"

	pixivSplitWidth = 10000000
)

var (
	rePixivFileName = regexp.MustCompile(`(?P<id>\d+)_p(?P<seq>d+)\.(?P<ext>\w+)`)
)

type Config struct {
	Token          string  `json:"token"`
	SelfID         int64   `json:"selfID"`
	AdmissionID    int64   `json:"admissionID"`
	WhitelistChats []int64 `json:"whitelistChats"`
	ComicPath      string  `json:"comicPath"`
	DeleteDelay    string  `json:"deleteDelay"`
}

type DownloadPixiv struct {
	ChatID    int64
	MessageID int
	PixivID   uint64
}

type Bot struct {
	Name           string
	SelfID         int64
	AdmissionID    int64
	WhitelistChats []int64
	ComicPath      string
	PixivPath      string
	TwitterImgPath string
	DeleteDelay    time.Duration
	Client         *tgbotapi.BotAPI
	Queue          *bt.Pool
	logger         *logrus.Logger
	redis          *redis.Client
	es             *elasticsearch7.Client
}

func NewBot(cfg *Config) (b *Bot, err error) {
	bot, err := tgbotapi.NewBotAPI(cfg.Token)
	if err != nil {
		return nil, fmt.Errorf("tg bot init failed: %+v", err)
	}
	delay, err := time.ParseDuration(cfg.DeleteDelay)
	if err != nil {
		return nil, fmt.Errorf("delete delay error: %+v", err)
	}

	b = &Bot{
		Name:           bot.Self.UserName,
		SelfID:         cfg.SelfID,
		AdmissionID:    cfg.AdmissionID,
		WhitelistChats: cfg.WhitelistChats,
		ComicPath:      cfg.ComicPath,
		DeleteDelay:    delay,
		Client:         bot,
	}
	return
}

func (b *Bot) WithLogger(logger *logrus.Logger) *Bot {
	b.logger = logger
	return b
}

func (b *Bot) WithRedis(rds *redis.Client) *Bot {
	b.redis = rds
	return b
}

func (b *Bot) WithPixivImg(imgPath string) *Bot {
	b.PixivPath = imgPath
	return b
}

func (b *Bot) WithTwitterImg(imgPath string) *Bot {
	b.TwitterImgPath = imgPath
	return b
}

func (b *Bot) WithQueue(queue *bt.Pool) *Bot {
	b.Queue = queue
	return b
}

func (b *Bot) WithES(es *elasticsearch7.Client) *Bot {
	b.es = es
	return b
}

func (b *Bot) putQueue(msg []byte, tube string) {
	conn, err := b.Queue.Get()
	if err != nil {
		b.logger.Errorf("%+v: %s", err, string(msg))
		return
	}
	conn.Use(tube)
	_, err = conn.Put(msg, 1, b.DeleteDelay, time.Minute)
	if err != nil {
		b.logger.Errorf("%+v", err)
		return
	}
}

func (b *Bot) isAuthedChat(c *tgbotapi.Chat) bool {
	for _, w := range b.WhitelistChats {
		if c.ID == w {
			return true
		}
	}
	return false
}

func (b *Bot) Send(chat int64, msg string) (tgbotapi.Message, error) {
	b.logger.Debugf("[%d]%s", chat, msg)
	message := tgbotapi.NewMessage(chat, msg)
	message.DisableNotification = true
	return b.Client.Send(message)
}

func (b *Bot) GetUserName(chatID int64, userID int) (name string, err error) {
	cacheKey := fmt.Sprintf("tg:user:%d", userID)
	cache, err := b.redis.Get(cacheKey).Result()
	if err == nil {
		name = cache
		return
	} else {
		if err != redis.Nil {
			return
		}
	}
	member, err := b.Client.GetChatMember(tgbotapi.ChatConfigWithUser{
		ChatID: chatID,
		UserID: userID,
	})
	if err != nil {
		return
	}
	name = member.User.String()
	if name != "" {
		b.redis.Set(cacheKey, name, 0)
	}
	return
}

func (b *Bot) SendPixivCandidate(target int64, id uint64) {
	row := tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⭕", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "like")),
		tgbotapi.NewInlineKeyboardButtonData("❌", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "diss")),
	)
	msg := tgbotapi.NewMessage(target, pixiv.URLWithID(id))
	msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(row)
	msg.DisableNotification = true
	_, err := b.Client.Send(msg)
	if err != nil {
		b.logger.Errorf("%+v", err)
	}
}

func (b *Bot) startDownloadPixiv() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgPixivTube)
		job, err := conn.Reserve()
		if err != nil {
			b.logger.Warningf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		msg := &DownloadPixiv{}
		err = json.Unmarshal(job.Body, msg)
		if err != nil {
			b.logger.Errorf("%+v", err)
			err = conn.Bury(job.ID, 0)
			if err != nil {
				b.logger.Errorf("%+v", err)
			}
			time.Sleep(3 * time.Second)
			continue
		}

		sizes, errs := pixiv.Download(msg.PixivID, b.PixivPath)
		for i := range sizes {
			if errs[i] != nil {
				err = errs[i]
				break
			}
			if sizes[i] == 0 {
				continue
			}
			b.logger.Debugf("download pixiv %d_p%d: %d bytes", msg.PixivID, i, sizes[i])
		}
		if err != nil {
			b.logger.Errorf("%+v", err)
			continue
		}

		err = conn.Delete(job.ID)
		if err != nil {
			b.logger.Errorf("delete job error: %+v", err)
			time.Sleep(3 * time.Second)
		}
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) startDeleteMessage() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgDeleteTube)
		job, err := conn.Reserve()
		if err != nil {
			b.logger.Warningf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}

		func() {
			var err error
			defer func() {
				if err != nil {
					b.logger.Errorf("%+v", err)
					if e := conn.Bury(job.ID, 0); e != nil {
						b.logger.Errorf("%+v", err)
					}
					time.Sleep(3 * time.Second)
				} else {
					if e := conn.Delete(job.ID); e != nil {
						b.logger.Errorf("%+v", err)
						time.Sleep(3 * time.Second)
					}
				}
			}()

			msg := &tgbotapi.Message{}
			err = json.Unmarshal(job.Body, msg)
			if err != nil {
				return
			}

			if msg.Chat == nil {
				err = fmt.Errorf("err msg with no chat: %+v", msg)
				return
			}
			delMsg := tgbotapi.DeleteMessageConfig{
				ChatID:    msg.Chat.ID,
				MessageID: msg.MessageID,
			}
			b.logger.Infof("del:[%s]{%s}", getMsgTitle(msg), strconv.Quote(msg.Text))
			_, err = b.Client.DeleteMessage(delMsg)

		}()
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) Start() {
	go b.startDeleteMessage()
	go b.startDownloadPixiv()

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 30
	for {
		updates, err := b.Client.GetUpdatesChan(u)
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		var message *tgbotapi.Message
		for update := range updates {
			if update.Message != nil {
				message = update.Message
			} else if update.EditedMessage != nil {
				message = update.EditedMessage
			} else if update.CallbackQuery != nil {
				b.logger.Infof(
					"recv:(%d)[%s]reaction:{%s}",
					update.CallbackQuery.Message.Chat.ID,
					update.CallbackQuery.From.String(),
					update.CallbackQuery.Data,
				)
				data := strings.SplitN(update.CallbackQuery.Data, ":", 2)
				switch data[0] {
				case "comic", "pic", "pixiv":
					go onReaction(b, update.CallbackQuery)
				case "pixivCandidate":
					if !b.isAuthedChat(update.CallbackQuery.Message.Chat) {
						b.logger.Warning("reaction from illegal chat, ignore")
						break
					}
					go onReactionCandidate(b, update.CallbackQuery)
				case "search":
					go onReactionSearch(b, update.CallbackQuery)
				default:
				}
				continue
			} else {
				continue
			}

			if !b.checkInWhitelist(message.Chat.ID) {
				continue
			}
			if message.Chat.IsGroup() {
				b.logger.Infof(
					"recv:(%d)[%s:%s]{%s}",
					message.Chat.ID,
					message.Chat.Title,
					message.From.String(),
					strconv.Quote(message.Text))
			} else {
				b.logger.Infof(
					"recv:(%d)[%s]{%s}",
					message.Chat.ID,
					message.From.String(),
					strconv.Quote(message.Text),
				)
			}
			if message.IsCommand() {
				switch message.Command() {
				case "start":
					go onStart(b, message)
				case "roll":
					go onRoll(b, message)
				case "comic":
					go onComic(b, message)
				case "pic":
					go onPic(b, message)
				case "pixiv":
					go onPixiv(b, message)
				case "search":
					go onSearch(b, message)
				default:
					b.logger.Infof("ignore unknown cmd: %+v", message.Command())
					continue
				}
			} else {
				if message.Text == "" {
					continue
				}
				go checkRepeat(b, message)
				go checkPixiv(b, message)
				go checkSave(b, message)
			}
		}
		b.logger.Warning("tg bot restarted.")
		time.Sleep(3 * time.Second)
	}
}

func (b *Bot) checkInWhitelist(id int64) bool {
	for _, c := range b.WhitelistChats {
		if c == id {
			return true
		}
	}
	b.logger.Debugf("ignore msg from %d", id)
	return false
}

func (b *Bot) probate(_type, _id string) error {
	b.logger.Infof("%s: %s", _type, _id)
	switch _type {
	case "comic":
		fileName := "nhentai.net@" + _id + ".epub"
		return os.Rename(
			filepath.Join(b.ComicPath, fileName),
			filepath.Join(b.ComicPath, "probation", fileName),
		)
	case "pic":
		return os.Rename(
			filepath.Join(b.TwitterImgPath, _id),
			filepath.Join(b.TwitterImgPath, "probation", _id),
		)
	case "pixiv":
		return os.Rename(
			filepath.Join(b.PixivPath, _id),
			filepath.Join(b.PixivPath, "probation", _id),
		)
	default:
		return fmt.Errorf("prohibit unkown type")
	}
}

func (b *Bot) setChatAction(chatID int64, action string) error {
	a := tgbotapi.NewChatAction(chatID, action)
	_, err := b.Client.Send(a)
	if err != nil {
		b.logger.Errorf("set action %s failed: %+v", action, err)
	}
	return err
}

func getMsgTitle(m *tgbotapi.Message) string {
	if m.Chat.IsGroup() {
		return m.Chat.Title
	}
	return m.From.String()
}
<|endoftext|>


<commit_before>package template

import (
	"bytes"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"path"
	"regexp"
	"strconv"
	"strings"
	"text/template/parse"
)

type FuncMap map[string]interface{}

type ScriptType int

const (
	_ ScriptType = iota
	ScriptTypeStandard
	ScriptTypeAsync
	ScriptTypeOnload
)

const (
	leftDelim       = "{{"
	rightDelim      = "}}"
	stylesTmplName  = "__styles"
	scriptsTmplName = "__scripts"
	dataKey         = "Data"
)

var stylesBoilerplate = `
  {{ range __getstyles }}
    <link rel="stylesheet" type="text/css" href="{{ asset . }}">
  {{ end }}
`

var scriptsBoilerplate = `
  {{ range __getscripts }}
    {{ if .IsAsync }}

    {{ else }}

    {{ end }}
  {{ end }}
`

var (
	commentRe   = regexp.MustCompile(`(?s:\{\{\\*(.*?)\*/\}\})`)
	keyRe       = regexp.MustCompile(`(?s:\s*([\w\-_])+:)`)
	defineRe    = regexp.MustCompile(`(\{\{\s*?define.*?\}\})`)
	stylesTree  = compileTree(stylesTmplName, stylesBoilerplate)
	scriptsTree = compileTree(scriptsTmplName, scriptsBoilerplate)
)

type script struct {
	Name string
	Type ScriptType
}

func (s *script) IsAsync() bool {
	return s.Type == ScriptTypeAsync
}

type Template struct {
	*template.Template
	Trees   map[string]*parse.Tree
	funcMap FuncMap
	root    string
	scripts []*script
	styles  []string
	vars    []string
	renames map[string]string
}

func (t *Template) parseScripts(value string, st ScriptType) {
	for _, v := range strings.Split(value, ",") {
		name := strings.TrimSpace(v)
		t.scripts = append(t.scripts, &script{name, st})
	}
}

func (t *Template) parseComment(comment string, file string, prepend string, included bool) error {
	lines := strings.Split(comment, "\n")
	extended := false
	for _, v := range lines {
		m := keyRe.FindStringSubmatchIndex(v)
		if m != nil && m[0] == 0 && len(m) == 4 {
			start := m[1] - m[3]
			end := start + m[2]
			key := strings.TrimSpace(v[start:end])
			value := strings.TrimSpace(v[m[1]:])
			inc := true
			if value != "" {
				switch strings.ToLower(key) {
				case "script", "scripts":
					t.parseScripts(value, ScriptTypeStandard)
				case "ascript", "ascripts":
					t.parseScripts(value, ScriptTypeAsync)
				case "css", "style", "styles":
					for _, v := range strings.Split(value, ",") {
						style := strings.TrimSpace(v)
						t.styles = append(t.styles, style)
					}
				case "extend", "extends":
					extended = true
					inc = false
					fallthrough
				case "include", "includes":
					includedFile := path.Join(path.Dir(file), value)
					err := t.load(includedFile, prepend, inc)
					if err != nil {
						return err
					}
				}
			}
		}
	}
	if !extended && !included {
		t.root = file
	}
	return nil
}

func (t *Template) load(file string, prepend string, included bool) error {
	// TODO: Detect circular dependencies
	b, err := ioutil.ReadFile(file)
	if err != nil {
		return err
	}
	s := string(b)
	matches := commentRe.FindStringSubmatch(s)
	comment := ""
	if matches != nil && len(matches) > 0 {
		comment = matches[1]
	}
	err = t.parseComment(comment, file, prepend, included)
	if err != nil {
		return err
	}
	if idx := strings.Index(s, ""); idx >= 0 {
		s = s[:idx] + "{{ template \"__styles\" }}" + s[idx:]
	}
	if idx := strings.Index(s, ""); idx >= 0 {
		s = s[:idx] + "{{ template \"__scripts\" }}" + s[idx:]
	}
	if prepend != "" {
		// Prepend to the template and to any define nodes found
		s = prepend + defineRe.ReplaceAllString(s, "$0"+strings.Replace(prepend, "$", "$$", -1))
	}
	treeMap, err := parse.Parse(file, s, leftDelim, rightDelim, templateFuncs, t.funcMap)
	if err != nil {
		return err
	}
	for k, v := range treeMap {
		if _, contains := t.Trees[k]; contains {
			// Redefinition of a template, which is allowed
			// by gondola templates. Just rename this
			// template and change update any template
			// nodes referring to it in the final sweep
			if t.renames == nil {
				t.renames = make(map[string]string)
			}
			fk := k
			for {
				k += "_"
				if len(t.renames[fk]) < len(k) {
					t.renames[fk] = k
					break
				}
			}
		}
		err := t.AddParseTree(k, v)
		if err != nil {
			return err
		}
	}
	return nil
}

func (t *Template) walkNode(node parse.Node, nt parse.NodeType, f func(parse.Node)) {
	if node == nil {
		return
	}
	if node.Type() == nt {
		f(node)
	}
	switch x := node.(type) {
	case *parse.ListNode:
		for _, v := range x.Nodes {
			t.walkNode(v, nt, f)
		}
	case *parse.IfNode:
		if x.List != nil {
			t.walkNode(x.List, nt, f)
		}
		if x.ElseList != nil {
			t.walkNode(x.ElseList, nt, f)
		}
	case *parse.WithNode:
		if x.List != nil {
			t.walkNode(x.List, nt, f)
		}
		if x.ElseList != nil {
			t.walkNode(x.ElseList, nt, f)
		}
	case *parse.RangeNode:
		if x.List != nil {
			t.walkNode(x.List, nt, f)
		}
		if x.ElseList != nil {
			t.walkNode(x.ElseList, nt, f)
		}
	}
}

func (t *Template) walkTrees(nt parse.NodeType, f func(parse.Node)) {
	for _, v := range t.Trees {
		t.walkNode(v.Root, nt, f)
	}
}

func (t *Template) referencedTemplates() []string {
	var templates []string
	t.walkTrees(parse.NodeTemplate, func(n parse.Node) {
		templates = append(templates, n.(*parse.TemplateNode).Name)
	})
	return templates
}

func (t *Template) Funcs(funcs FuncMap) {
	if t.funcMap == nil {
		t.funcMap = make(FuncMap)
	}
	for k, v := range funcs {
		t.funcMap[k] = v
	}
	t.Template.Funcs(template.FuncMap(t.funcMap))
}

func (t *Template) Parse(file string) error {
	return t.ParseVars(file, nil)
}

func (t *Template) ParseVars(file string, vars []string) error {
	prepend := ""
	if len(vars) > 0 {
		t.vars = vars
		// The variable definitions must be present at parse
		// time, because otherwise the parser will throw an
		// error when it finds a variable which wasn't
		// previously defined
		var p []string
		for _, v := range vars {
			p = append(p, fmt.Sprintf("{{ $%s := .%s }}", v, v))
		}
		prepend = strings.Join(p, "")
	}
	err := t.load(file, prepend, false)
	if err != nil {
		return err
	}
	/* Add styles and scripts */
	err = t.AddParseTree(stylesTmplName, stylesTree)
	if err != nil {
		return err
	}
	err = t.AddParseTree(scriptsTmplName, scriptsTree)
	if err != nil {
		return err
	}
	// Fill any empty templates, so we allow templates
	// to be left undefined
	for _, v := range t.referencedTemplates() {
		if _, ok := t.Trees[v]; !ok {
			tree := compileTree(v, "")
			t.AddParseTree(v, tree)
		}
	}
	var templateArgs []parse.Node
	if n := len(vars); n > 0 {
		// Modify the parse trees to always define vars
		for _, tr := range t.Trees {
			if len(tr.Root.Nodes) < n {
			    /* Empty template */
			    continue
			}
			// Skip the first n nodes, since they set the variables.
			// Then wrap the rest of template in a WithNode, which sets
			// the dot to .Data
			field := &parse.FieldNode{
				NodeType: parse.NodeField,
				Ident:    []string{dataKey},
			}
			command := &parse.CommandNode{
				NodeType: parse.NodeCommand,
				Args:     []parse.Node{field},
			}
			pipe := &parse.PipeNode{
				NodeType: parse.NodePipe,
				Cmds:     []*parse.CommandNode{command},
			}
			var nodes []parse.Node
			nodes = append(nodes, tr.Root.Nodes[:n]...)
			root := tr.Root.Nodes[n:]
			newRoot := &parse.ListNode{
				NodeType: parse.NodeList,
				Nodes:    root,
			}
			// The list needs to be copied, otherwise the
			// html/template escaper complains that the
			// node is shared between templates
			with := &parse.WithNode{
				parse.BranchNode{
					NodeType: parse.NodeWith,
					Pipe:     pipe,
					List:     newRoot,
					ElseList: newRoot.CopyList(),
				},
			}
			nodes = append(nodes, with)
			tr.Root = &parse.ListNode{
				NodeType: parse.NodeList,
				Nodes:    nodes,
			}
		}
		// Rewrite any template nodes to pass also the variables, since
		// they are not inherited
		templateArgs = []parse.Node{parse.NewIdentifier("map")}
		for _, v := range vars {
			templateArgs = append(templateArgs, &parse.StringNode{
				NodeType: parse.NodeString,
				Quoted:   fmt.Sprintf("\"%s\"", v),
				Text:     v,
			})
			templateArgs = append(templateArgs, &parse.VariableNode{
				NodeType: parse.NodeVariable,
				Ident:    []string{fmt.Sprintf("$%s", v)},
			})
		}
		templateArgs = append(templateArgs, &parse.StringNode{
			NodeType: parse.NodeString,
			Quoted:   fmt.Sprintf("\"%s\"", dataKey),
			Text:     dataKey,
		})
	}

	if len(t.renames) > 0 || len(templateArgs) > 0 {
		t.walkTrees(parse.NodeTemplate, func(n parse.Node) {
			node := n.(*parse.TemplateNode)
			if rename, ok := t.renames[node.Name]; ok {
				node.Name = rename
			}
			if templateArgs != nil {
				pipe := node.Pipe
				if pipe != nil && len(pipe.Cmds) > 0 {
					command := pipe.Cmds[0]
					args := make([]parse.Node, len(templateArgs))
					copy(args, templateArgs)
					command.Args = append(args, command.Args...)
				}
			}
		})
	}
	return nil
}

func (t *Template) AddParseTree(name string, tree *parse.Tree) error {
	_, err := t.Template.AddParseTree(name, tree)
	if err != nil {
		return err
	}
	t.Trees[name] = tree
	return nil
}

func (t *Template) Execute(w io.Writer, data interface{}) error {
	return t.ExecuteVars(w, data, nil)
}

func (t *Template) ExecuteVars(w io.Writer, data interface{}, vars map[string]interface{}) error {
	// TODO: Make sure vars is the same as the vars that were compiled in
	var buf bytes.Buffer
	var templateData interface{}
	if len(vars) > 0 {
		combined := make(map[string]interface{})
		for k, v := range vars {
			combined[k] = v
		}
		combined[dataKey] = data
		templateData = combined
	} else {
		templateData = data
	}
	err := t.ExecuteTemplate(&buf, t.root, templateData)
	if err != nil {
		return err
	}
	if rw, ok := w.(http.ResponseWriter); ok {
		header := rw.Header()
		header.Set("Content-Type", "text/html; charset=utf-8")
		header.Set("Content-Length", strconv.Itoa(buf.Len()))
		rw.Write(buf.Bytes())
	}
	return nil
}

func (t *Template) MustExecute(w io.Writer, data interface{}) {
	err := t.Execute(w, data)
	if err != nil {
		log.Panicf("Error executing template: %s\n", err)
	}
}

func AddFunc(name string, f interface{}) {
	templateFuncs[name] = f
}

func New() *Template {
	t := &Template{
		Template: template.New(""),
		Trees:    make(map[string]*parse.Tree),
	}
	// This is required so text/template calls t.init()
	// and initializes the common data structure
	t.Template.New("")
	funcs := FuncMap{
		"__getstyles":  func() []string { return t.styles },
		"__getscripts": func() []*script { return t.scripts },
	}
	t.Funcs(funcs)
	t.Template.Funcs(template.FuncMap(funcs)).Funcs(templateFuncs)
	return t
}

func Parse(file string) (*Template, error) {
	t := New()
	err := t.Parse(file)
	if err != nil {
		return nil, err
	}
	return t, nil
}

func MustParse(file string) *Template {
	t, err := Parse(file)
	if err != nil {
		log.Fatalf("Error loading template %s: %s\n", file, err)
	}
	return t
}

func compileTree(name, text string) *parse.Tree {
	funcs := map[string]interface{}{
		"__getstyles":  func() {},
		"__getscripts": func() {},
		"asset":        func() {},
	}
	treeMap, err := parse.Parse(name, text, leftDelim, rightDelim, funcs)
	if err != nil {
		panic(err)
	}
	return treeMap[name]
}
<commit_msg>Correctly rewrite template nodes with more complicate pipelines<commit_after>package template

import (
	"bytes"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"path"
	"regexp"
	"strconv"
	"strings"
	"text/template/parse"
)

type FuncMap map[string]interface{}

type ScriptType int

const (
	_ ScriptType = iota
	ScriptTypeStandard
	ScriptTypeAsync
	ScriptTypeOnload
)

const (
	leftDelim       = "{{"
	rightDelim      = "}}"
	stylesTmplName  = "__styles"
	scriptsTmplName = "__scripts"
	dataKey         = "Data"
)

var stylesBoilerplate = `
  {{ range __getstyles }}
    <link rel="stylesheet" type="text/css" href="{{ asset . }}">
  {{ end }}
`

var scriptsBoilerplate = `
  {{ range __getscripts }}
    {{ if .IsAsync }}

    {{ else }}

    {{ end }}
  {{ end }}
`

var (
	commentRe   = regexp.MustCompile(`(?s:\{\{\\*(.*?)\*/\}\})`)
	keyRe       = regexp.MustCompile(`(?s:\s*([\w\-_])+:)`)
	defineRe    = regexp.MustCompile(`(\{\{\s*?define.*?\}\})`)
	stylesTree  = compileTree(stylesTmplName, stylesBoilerplate)
	scriptsTree = compileTree(scriptsTmplName, scriptsBoilerplate)
)

type script struct {
	Name string
	Type ScriptType
}

func (s *script) IsAsync() bool {
	return s.Type == ScriptTypeAsync
}

type Template struct {
	*template.Template
	Trees   map[string]*parse.Tree
	funcMap FuncMap
	root    string
	scripts []*script
	styles  []string
	vars    []string
	renames map[string]string
}

func (t *Template) parseScripts(value string, st ScriptType) {
	for _, v := range strings.Split(value, ",") {
		name := strings.TrimSpace(v)
		t.scripts = append(t.scripts, &script{name, st})
	}
}

func (t *Template) parseComment(comment string, file string, prepend string, included bool) error {
	lines := strings.Split(comment, "\n")
	extended := false
	for _, v := range lines {
		m := keyRe.FindStringSubmatchIndex(v)
		if m != nil && m[0] == 0 && len(m) == 4 {
			start := m[1] - m[3]
			end := start + m[2]
			key := strings.TrimSpace(v[start:end])
			value := strings.TrimSpace(v[m[1]:])
			inc := true
			if value != "" {
				switch strings.ToLower(key) {
				case "script", "scripts":
					t.parseScripts(value, ScriptTypeStandard)
				case "ascript", "ascripts":
					t.parseScripts(value, ScriptTypeAsync)
				case "css", "style", "styles":
					for _, v := range strings.Split(value, ",") {
						style := strings.TrimSpace(v)
						t.styles = append(t.styles, style)
					}
				case "extend", "extends":
					extended = true
					inc = false
					fallthrough
				case "include", "includes":
					includedFile := path.Join(path.Dir(file), value)
					err := t.load(includedFile, prepend, inc)
					if err != nil {
						return err
					}
				}
			}
		}
	}
	if !extended && !included {
		t.root = file
	}
	return nil
}

func (t *Template) load(file string, prepend string, included bool) error {
	// TODO: Detect circular dependencies
	b, err := ioutil.ReadFile(file)
	if err != nil {
		return err
	}
	s := string(b)
	matches := commentRe.FindStringSubmatch(s)
	comment := ""
	if matches != nil && len(matches) > 0 {
		comment = matches[1]
	}
	err = t.parseComment(comment, file, prepend, included)
	if err != nil {
		return err
	}
	if idx := strings.Index(s, ""); idx >= 0 {
		s = s[:idx] + "{{ template \"__styles\" }}" + s[idx:]
	}
	if idx := strings.Index(s, ""); idx >= 0 {
		s = s[:idx] + "{{ template \"__scripts\" }}" + s[idx:]
	}
	if prepend != "" {
		// Prepend to the template and to any define nodes found
		s = prepend + defineRe.ReplaceAllString(s, "$0"+strings.Replace(prepend, "$", "$$", -1))
	}
	treeMap, err := parse.Parse(file, s, leftDelim, rightDelim, templateFuncs, t.funcMap)
	if err != nil {
		return err
	}
	for k, v := range treeMap {
		if _, contains := t.Trees[k]; contains {
			// Redefinition of a template, which is allowed
			// by gondola templates. Just rename this
			// template and change update any template
			// nodes referring to it in the final sweep
			if t.renames == nil {
				t.renames = make(map[string]string)
			}
			fk := k
			for {
				k += "_"
				if len(t.renames[fk]) < len(k) {
					t.renames[fk] = k
					break
				}
			}
		}
		err := t.AddParseTree(k, v)
		if err != nil {
			return err
		}
	}
	return nil
}

func (t *Template) walkNode(node parse.Node, nt parse.NodeType, f func(parse.Node)) {
	if node == nil {
		return
	}
	if node.Type() == nt {
		f(node)
	}
	switch x := node.(type) {
	case *parse.ListNode:
		for _, v := range x.Nodes {
			t.walkNode(v, nt, f)
		}
	case *parse.IfNode:
		if x.List != nil {
			t.walkNode(x.List, nt, f)
		}
		if x.ElseList != nil {
			t.walkNode(x.ElseList, nt, f)
		}
	case *parse.WithNode:
		if x.List != nil {
			t.walkNode(x.List, nt, f)
		}
		if x.ElseList != nil {
			t.walkNode(x.ElseList, nt, f)
		}
	case *parse.RangeNode:
		if x.List != nil {
			t.walkNode(x.List, nt, f)
		}
		if x.ElseList != nil {
			t.walkNode(x.ElseList, nt, f)
		}
	}
}

func (t *Template) walkTrees(nt parse.NodeType, f func(parse.Node)) {
	for _, v := range t.Trees {
		t.walkNode(v.Root, nt, f)
	}
}

func (t *Template) referencedTemplates() []string {
	var templates []string
	t.walkTrees(parse.NodeTemplate, func(n parse.Node) {
		templates = append(templates, n.(*parse.TemplateNode).Name)
	})
	return templates
}

func (t *Template) Funcs(funcs FuncMap) {
	if t.funcMap == nil {
		t.funcMap = make(FuncMap)
	}
	for k, v := range funcs {
		t.funcMap[k] = v
	}
	t.Template.Funcs(template.FuncMap(t.funcMap))
}

func (t *Template) Parse(file string) error {
	return t.ParseVars(file, nil)
}

func (t *Template) ParseVars(file string, vars []string) error {
	prepend := ""
	if len(vars) > 0 {
		t.vars = vars
		// The variable definitions must be present at parse
		// time, because otherwise the parser will throw an
		// error when it finds a variable which wasn't
		// previously defined
		var p []string
		for _, v := range vars {
			p = append(p, fmt.Sprintf("{{ $%s := .%s }}", v, v))
		}
		prepend = strings.Join(p, "")
	}
	err := t.load(file, prepend, false)
	if err != nil {
		return err
	}
	/* Add styles and scripts */
	err = t.AddParseTree(stylesTmplName, stylesTree)
	if err != nil {
		return err
	}
	err = t.AddParseTree(scriptsTmplName, scriptsTree)
	if err != nil {
		return err
	}
	// Fill any empty templates, so we allow templates
	// to be left undefined
	for _, v := range t.referencedTemplates() {
		if _, ok := t.Trees[v]; !ok {
			tree := compileTree(v, "")
			t.AddParseTree(v, tree)
		}
	}
	var templateArgs []parse.Node
	if n := len(vars); n > 0 {
		// Modify the parse trees to always define vars
		for _, tr := range t.Trees {
			if len(tr.Root.Nodes) < n {
				/* Empty template */
				continue
			}
			// Skip the first n nodes, since they set the variables.
			// Then wrap the rest of template in a WithNode, which sets
			// the dot to .Data
			field := &parse.FieldNode{
				NodeType: parse.NodeField,
				Ident:    []string{dataKey},
			}
			command := &parse.CommandNode{
				NodeType: parse.NodeCommand,
				Args:     []parse.Node{field},
			}
			pipe := &parse.PipeNode{
				NodeType: parse.NodePipe,
				Cmds:     []*parse.CommandNode{command},
			}
			var nodes []parse.Node
			nodes = append(nodes, tr.Root.Nodes[:n]...)
			root := tr.Root.Nodes[n:]
			newRoot := &parse.ListNode{
				NodeType: parse.NodeList,
				Nodes:    root,
			}
			// The list needs to be copied, otherwise the
			// html/template escaper complains that the
			// node is shared between templates
			with := &parse.WithNode{
				parse.BranchNode{
					NodeType: parse.NodeWith,
					Pipe:     pipe,
					List:     newRoot,
					ElseList: newRoot.CopyList(),
				},
			}
			nodes = append(nodes, with)
			tr.Root = &parse.ListNode{
				NodeType: parse.NodeList,
				Nodes:    nodes,
			}
		}
		// Rewrite any template nodes to pass also the variables, since
		// they are not inherited
		templateArgs = []parse.Node{parse.NewIdentifier("map")}
		for _, v := range vars {
			templateArgs = append(templateArgs, &parse.StringNode{
				NodeType: parse.NodeString,
				Quoted:   fmt.Sprintf("\"%s\"", v),
				Text:     v,
			})
			templateArgs = append(templateArgs, &parse.VariableNode{
				NodeType: parse.NodeVariable,
				Ident:    []string{fmt.Sprintf("$%s", v)},
			})
		}
		templateArgs = append(templateArgs, &parse.StringNode{
			NodeType: parse.NodeString,
			Quoted:   fmt.Sprintf("\"%s\"", dataKey),
			Text:     dataKey,
		})
	}

	if len(t.renames) > 0 || len(templateArgs) > 0 {
		t.walkTrees(parse.NodeTemplate, func(n parse.Node) {
			node := n.(*parse.TemplateNode)
			if rename, ok := t.renames[node.Name]; ok {
				node.Name = rename
			}
			if templateArgs != nil {
				if node.Pipe == nil {
					// No data, just pass variables
					command := &parse.CommandNode{
						NodeType: parse.NodeCommand,
						Args:     templateArgs[:len(templateArgs)-1],
					}
					node.Pipe = &parse.PipeNode{
						NodeType: parse.NodePipe,
						Cmds:     []*parse.CommandNode{command},
					}
				} else {
					newPipe := &parse.PipeNode{
						NodeType: parse.NodePipe,
						Cmds:     node.Pipe.Cmds,
					}
					args := make([]parse.Node, len(templateArgs))
					copy(args, templateArgs)
					command := &parse.CommandNode{
						NodeType: parse.NodeCommand,
						Args:     append(args, newPipe),
					}
					node.Pipe.Cmds = []*parse.CommandNode{command}
				}
			}
		})
	}
	return nil
}

func (t *Template) AddParseTree(name string, tree *parse.Tree) error {
	_, err := t.Template.AddParseTree(name, tree)
	if err != nil {
		return err
	}
	t.Trees[name] = tree
	return nil
}

func (t *Template) Execute(w io.Writer, data interface{}) error {
	return t.ExecuteVars(w, data, nil)
}

func (t *Template) ExecuteVars(w io.Writer, data interface{}, vars map[string]interface{}) error {
	// TODO: Make sure vars is the same as the vars that were compiled in
	var buf bytes.Buffer
	var templateData interface{}
	if len(vars) > 0 {
		combined := make(map[string]interface{})
		for k, v := range vars {
			combined[k] = v
		}
		combined[dataKey] = data
		templateData = combined
	} else {
		templateData = data
	}
	err := t.ExecuteTemplate(&buf, t.root, templateData)
	if err != nil {
		return err
	}
	if rw, ok := w.(http.ResponseWriter); ok {
		header := rw.Header()
		header.Set("Content-Type", "text/html; charset=utf-8")
		header.Set("Content-Length", strconv.Itoa(buf.Len()))
		rw.Write(buf.Bytes())
	}
	return nil
}

func (t *Template) MustExecute(w io.Writer, data interface{}) {
	err := t.Execute(w, data)
	if err != nil {
		log.Panicf("Error executing template: %s\n", err)
	}
}

func AddFunc(name string, f interface{}) {
	templateFuncs[name] = f
}

func New() *Template {
	t := &Template{
		Template: template.New(""),
		Trees:    make(map[string]*parse.Tree),
	}
	// This is required so text/template calls t.init()
	// and initializes the common data structure
	t.Template.New("")
	funcs := FuncMap{
		"__getstyles":  func() []string { return t.styles },
		"__getscripts": func() []*script { return t.scripts },
	}
	t.Funcs(funcs)
	t.Template.Funcs(template.FuncMap(funcs)).Funcs(templateFuncs)
	return t
}

func Parse(file string) (*Template, error) {
	t := New()
	err := t.Parse(file)
	if err != nil {
		return nil, err
	}
	return t, nil
}

func MustParse(file string) *Template {
	t, err := Parse(file)
	if err != nil {
		log.Fatalf("Error loading template %s: %s\n", file, err)
	}
	return t
}

func compileTree(name, text string) *parse.Tree {
	funcs := map[string]interface{}{
		"__getstyles":  func() {},
		"__getscripts": func() {},
		"asset":        func() {},
	}
	treeMap, err := parse.Parse(name, text, leftDelim, rightDelim, funcs)
	if err != nil {
		panic(err)
	}
	return treeMap[name]
}
<|endoftext|>


<commit_before>// Copyright © 2016 Aaron Longwell
//
// Use of this source code is governed by an MIT licese.
// Details in the LICENSE file.

package trello

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
)

type Card struct {
	client *Client

	// Key metadata
	ID               string     `json:"id"`
	IDShort          int        `json:"idShort"`
	Name             string     `json:"name"`
	Pos              float64    `json:"pos"`
	Email            string     `json:"email"`
	ShortLink        string     `json:"shortLink"`
	ShortUrl         string     `json:"shortUrl"`
	Url              string     `json:"url"`
	Desc             string     `json:"desc"`
	Due              *time.Time `json:"due"`
	Closed           bool       `json:"closed"`
	Subscribed       bool       `json:"subscribed"`
	DateLastActivity *time.Time `json:"dateLastActivity"`

	// Board
	Board   *Board
	IDBoard string `json:"idBoard"`

	// List
	List   *List
	IDList string `json:"idList"`

	// Badges
	Badges struct {
		Votes              int        `json:"votes"`
		ViewingMemberVoted bool       `json:"viewingMemberVoted"`
		Subscribed         bool       `json:"subscribed"`
		Fogbugz            string     `json:"fogbugz,omitempty"`
		CheckItems         int        `json:"checkItems"`
		CheckItemsChecked  int        `json:"checkItemsChecked"`
		Comments           int        `json:"comments"`
		Attachments        int        `json:"attachments"`
		Description        bool       `json:"description"`
		Due                *time.Time `json:"due,omitempty"`
	} `json:"badges"`

	// Actions
	Actions []*Action `json:"actions,omitempty"`

	// Checklists
	IDCheckLists    []string          `json:"idCheckLists"`
	Checklists      []*Checklist      `json:"checklists,omitempty"`
	CheckItemStates []*CheckItemState `json:"checkItemStates,omitempty"`

	// Members
	IDMembers      []string  `json:"idMembers,omitempty"`
	IDMembersVoted []string  `json:"idMembersVoted,omitempty"`
	Members        []*Member `json:"members,omitempty"`

	// Attachments
	IDAttachmentCover     string        `json:"idAttachmentCover"`
	ManualCoverAttachment bool          `json:"manualCoverAttachment"`
	Attachments           []*Attachment `json:attachments,omitempty"`

	// Labels
	Labels []*Label `json:"labels,omitempty"`
}

func (c *Card) CreatedAt() time.Time {
	t, err := IDToTime(c.ID)
	if err != nil {
		return time.Time{}
	} else {
		return t
	}
}

func (c *Client) CreateCard(card *Card, extraArgs Arguments) error {
	path := "cards"
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"pos":       strconv.FormatFloat(card.Pos, 'g', -1, 64),
		"idList":    card.IDList,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overriding the creation position with 'top' or 'botttom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := c.Post(path, args, &card)
	if err == nil {
		card.client = c
	}
	return err
}

func (l *List) AddCard(card *Card, extraArgs Arguments) error {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overwriting the creation position with 'top' or 'bottom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := l.client.Post(path, args, &card)
	if err == nil {
		card.client = l.client
	} else {
		err = errors.Wrapf(err, "Error adding card to list %s", l.ID)
	}
	return err
}

// Try these Arguments
//
// 	Arguments["keepFromSource"] = "all"
//  Arguments["keepFromSource"] = "none"
// 	Arguments["keepFromSource"] = "attachments,checklists,comments"
//
func (c *Card) CopyToList(listID string, args Arguments) (*Card, error) {
	path := "cards"
	args["idList"] = listID
	args["idCardSource"] = c.ID
	newCard := Card{}
	err := c.client.Post(path, args, &newCard)
	if err == nil {
		newCard.client = c.client
	} else {
		err = errors.Wrapf(err, "Error copying card '%s' to list '%s'.", c.ID, listID)
	}
	return &newCard, err
}

// If this Card was created from a copy of another Card, this func retrieves
// the originating Card. Returns an error only when a low-level failure occurred.
// If this Card has no parent, nil, nil is returned.
//
func (c *Card) GetParentCard(args Arguments) (*Card, error) {
	actions, err := c.GetActions(Arguments{"filter": "copyCard,createCard"})
	if err != nil {
		err = errors.Wrapf(err, "ParentCard() failed to GetActions() for card '%s'", c.ID)
		return nil, err
	}
	if len(actions) == 0 {
		return nil, nil
	}
	for _, action := range actions {
		if action.Data.CardSource != nil && action.Data.CardSource.ID != c.ID {
			card, err := c.client.GetCard(action.Data.CardSource.ID, args)
			return card, err
		}
	}
	return nil, nil
}

func (c *Card) GetAncestorCards(args Arguments) (ancestors []*Card, err error) {

	// Get the first parent
	parent, err := c.GetParentCard(args)
	if IsNotFound(err) || IsPermissionDenied(err) {
		return ancestors, nil
	}

	for parent != nil {
		ancestors = append(ancestors, parent)
		parent, err = parent.GetParentCard(args)
		if IsNotFound(err) && !IsPermissionDenied(err) {
			return ancestors, nil
		} else if err != nil {
			return ancestors, err
		}
	}

	return ancestors, err
}

func (c *Card) GetOriginatingCard(args Arguments) (*Card, error) {
	ancestors, err := c.GetAncestorCards(args)
	if err != nil {
		return c, err
	}
	if len(ancestors) > 0 {
		return ancestors[len(ancestors)-1], nil
	} else {
		return c, nil
	}
}

func (c *Card) CreatorMemberID() (string, error) {
	if len(c.Actions) > 0 {
		if c.Actions[0].IDMemberCreator != "" {
			return c.Actions[0].IDMemberCreator, nil
		}
	}

	actions, err := c.GetActions(Arguments{"filter": "emailCard,createCard,copyCard,moveCardToBoard,convertToCardFromCheckItem"})
	if len(actions) > 0 {
		return actions[0].IDMemberCreator, err
	}

	return "", errors.Wrapf(err, "No Actions on card '%s' could be used to find its creator.", c.ID)
}

func (b *Board) ContainsCopyOfCard(cardID string, args Arguments) (bool, error) {
	args["filter"] = "copyCard"
	actions, err := b.GetActions(args)
	if err != nil {
		err := errors.Wrapf(err, "GetCards() failed inside ContainsCopyOf() for board '%s' and card '%s'.", b.ID, cardID)
		return false, err
	}
	for _, action := range actions {
		if action.Data != nil && action.Data.CardSource != nil && action.Data.CardSource.ID == cardID {
			return true, nil
		}
	}
	return false, nil
}

func (c *Client) GetCard(cardID string, args Arguments) (card *Card, err error) {
	path := fmt.Sprintf("cards/%s", cardID)
	err = c.Get(path, args, &card)
	if card != nil {
		card.client = c
	}
	return
}

/**
 * Retrieves all Cards on a Board
 *
 * If before
 */
func (b *Board) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("boards/%s/cards", b.ID)

	err = b.client.Get(path, args, &cards)

	// Naive implementation would return here. To make sure we get all cards, we begin
	if len(cards) > 0 {
		moreCards := true
		for moreCards == true {
			nextCardBatch := make([]*Card, 0)
			args["before"] = EarliestCardID(cards)
			err = b.client.Get(path, args, &nextCardBatch)
			if len(nextCardBatch) > 0 {
				cards = append(cards, nextCardBatch...)
			} else {
				moreCards = false
			}
		}
	}

	for i := range cards {
		cards[i].client = b.client
	}

	return
}

/**
 * Retrieves all Cards in a List
 */
func (l *List) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	err = l.client.Get(path, args, &cards)
	for i := range cards {
		cards[i].client = l.client
	}
	return
}

func EarliestCardID(cards []*Card) string {
	if len(cards) == 0 {
		return ""
	}
	earliest := cards[0].ID
	for _, card := range cards {
		if card.ID < earliest {
			earliest = card.ID
		}
	}
	return earliest
}
<commit_msg>Performance refactorings in GetParentCard() GetAncestorCards() and CreatorMemberID(). When called on a Card{} that has .Actions = card.GetActions{trello.Arguments{filter: all, limit: 1000}), these functions no longer do a second GetActions() API call. If you want the API call, you need to make sure Card.Actions = [] before calling.<commit_after>// Copyright © 2016 Aaron Longwell
//
// Use of this source code is governed by an MIT licese.
// Details in the LICENSE file.

package trello

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
)

type Card struct {
	client *Client

	// Key metadata
	ID               string     `json:"id"`
	IDShort          int        `json:"idShort"`
	Name             string     `json:"name"`
	Pos              float64    `json:"pos"`
	Email            string     `json:"email"`
	ShortLink        string     `json:"shortLink"`
	ShortUrl         string     `json:"shortUrl"`
	Url              string     `json:"url"`
	Desc             string     `json:"desc"`
	Due              *time.Time `json:"due"`
	Closed           bool       `json:"closed"`
	Subscribed       bool       `json:"subscribed"`
	DateLastActivity *time.Time `json:"dateLastActivity"`

	// Board
	Board   *Board
	IDBoard string `json:"idBoard"`

	// List
	List   *List
	IDList string `json:"idList"`

	// Badges
	Badges struct {
		Votes              int        `json:"votes"`
		ViewingMemberVoted bool       `json:"viewingMemberVoted"`
		Subscribed         bool       `json:"subscribed"`
		Fogbugz            string     `json:"fogbugz,omitempty"`
		CheckItems         int        `json:"checkItems"`
		CheckItemsChecked  int        `json:"checkItemsChecked"`
		Comments           int        `json:"comments"`
		Attachments        int        `json:"attachments"`
		Description        bool       `json:"description"`
		Due                *time.Time `json:"due,omitempty"`
	} `json:"badges"`

	// Actions
	Actions ActionCollection `json:"actions,omitempty"`

	// Checklists
	IDCheckLists    []string          `json:"idCheckLists"`
	Checklists      []*Checklist      `json:"checklists,omitempty"`
	CheckItemStates []*CheckItemState `json:"checkItemStates,omitempty"`

	// Members
	IDMembers      []string  `json:"idMembers,omitempty"`
	IDMembersVoted []string  `json:"idMembersVoted,omitempty"`
	Members        []*Member `json:"members,omitempty"`

	// Attachments
	IDAttachmentCover     string        `json:"idAttachmentCover"`
	ManualCoverAttachment bool          `json:"manualCoverAttachment"`
	Attachments           []*Attachment `json:attachments,omitempty"`

	// Labels
	Labels []*Label `json:"labels,omitempty"`
}

func (c *Card) CreatedAt() time.Time {
	t, err := IDToTime(c.ID)
	if err != nil {
		return time.Time{}
	} else {
		return t
	}
}

func (c *Client) CreateCard(card *Card, extraArgs Arguments) error {
	path := "cards"
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"pos":       strconv.FormatFloat(card.Pos, 'g', -1, 64),
		"idList":    card.IDList,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overriding the creation position with 'top' or 'botttom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := c.Post(path, args, &card)
	if err == nil {
		card.client = c
	}
	return err
}

func (l *List) AddCard(card *Card, extraArgs Arguments) error {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	args := Arguments{
		"name":      card.Name,
		"desc":      card.Desc,
		"idMembers": strings.Join(card.IDMembers, ","),
	}
	if card.Due != nil {
		args["due"] = card.Due.Format(time.RFC3339)
	}
	// Allow overwriting the creation position with 'top' or 'bottom'
	if pos, ok := extraArgs["pos"]; ok {
		args["pos"] = pos
	}
	err := l.client.Post(path, args, &card)
	if err == nil {
		card.client = l.client
	} else {
		err = errors.Wrapf(err, "Error adding card to list %s", l.ID)
	}
	return err
}

// Try these Arguments
//
// 	Arguments["keepFromSource"] = "all"
//  Arguments["keepFromSource"] = "none"
// 	Arguments["keepFromSource"] = "attachments,checklists,comments"
//
func (c *Card) CopyToList(listID string, args Arguments) (*Card, error) {
	path := "cards"
	args["idList"] = listID
	args["idCardSource"] = c.ID
	newCard := Card{}
	err := c.client.Post(path, args, &newCard)
	if err == nil {
		newCard.client = c.client
	} else {
		err = errors.Wrapf(err, "Error copying card '%s' to list '%s'.", c.ID, listID)
	}
	return &newCard, err
}

// If this Card was created from a copy of another Card, this func retrieves
// the originating Card. Returns an error only when a low-level failure occurred.
// If this Card has no parent, a nil card and nil error are returned. In other words, the
// non-existence of a parent is not treated as an error.
//
func (c *Card) GetParentCard(args Arguments) (*Card, error) {

	// Hopefully the card came pre-loaded with Actions including the card creation
	action := c.Actions.FirstCardCreateAction()

	if action == nil {
		// No luck. Go get copyCard actions for this card.
		c.client.Logger.Debugf("Creation action wasn't supplied before GetParentCard() on '%s'. Getting copyCard actions.", c.ID)
		actions, err := c.GetActions(Arguments{"filter": "copyCard"})
		if err != nil {
			err = errors.Wrapf(err, "GetParentCard() failed to GetActions() for card '%s'", c.ID)
			return nil, err
		}
		action = actions.FirstCardCreateAction()
	}

	if action != nil && action.Data != nil && action.Data.CardSource != nil {
		card, err := c.client.GetCard(action.Data.CardSource.ID, args)
		return card, err
	}

	return nil, nil
}

func (c *Card) GetAncestorCards(args Arguments) (ancestors []*Card, err error) {

	// Get the first parent
	parent, err := c.GetParentCard(args)
	if IsNotFound(err) || IsPermissionDenied(err) {
		c.client.Logger.Debugf("Can't get details about the parent of card '%s' due to lack of permissions or card deleted.", c.ID)
		return ancestors, nil
	}

	for parent != nil {
		ancestors = append(ancestors, parent)
		parent, err = parent.GetParentCard(args)
		if IsNotFound(err) || IsPermissionDenied(err) {
			c.client.Logger.Debugf("Can't get details about the parent of card '%s' due to lack of permissions or card deleted.", c.ID)
			return ancestors, nil
		} else if err != nil {
			return ancestors, err
		}
	}

	return ancestors, err
}

func (c *Card) GetOriginatingCard(args Arguments) (*Card, error) {
	ancestors, err := c.GetAncestorCards(args)
	if err != nil {
		return c, err
	}
	if len(ancestors) > 0 {
		return ancestors[len(ancestors)-1], nil
	} else {
		return c, nil
	}
}

func (c *Card) CreatorMemberID() (string, error) {

	var actions ActionCollection
	var err error

	if len(c.Actions) == 0 {
		c.client.Logger.Debugf("CreatorMemberID() called on card '%s' without any Card.Actions. Fetching fresh.", c.ID)
		actions, err = c.GetActions(Defaults())
		if err != nil {
			err = errors.Wrapf(err, "GetActions() call failed.")
		}
	} else {
		actions = c.Actions.FilterToCardCreationActions()
	}

	if len(actions) > 0 {
		if actions[0].IDMemberCreator != "" {
			return actions[0].IDMemberCreator, err
		}
	}

	return "", errors.Wrapf(err, "No Actions on card '%s' could be used to find its creator.", c.ID)
}

func (b *Board) ContainsCopyOfCard(cardID string, args Arguments) (bool, error) {
	args["filter"] = "copyCard"
	actions, err := b.GetActions(args)
	if err != nil {
		err := errors.Wrapf(err, "GetCards() failed inside ContainsCopyOf() for board '%s' and card '%s'.", b.ID, cardID)
		return false, err
	}
	for _, action := range actions {
		if action.Data != nil && action.Data.CardSource != nil && action.Data.CardSource.ID == cardID {
			return true, nil
		}
	}
	return false, nil
}

func (c *Client) GetCard(cardID string, args Arguments) (card *Card, err error) {
	path := fmt.Sprintf("cards/%s", cardID)
	err = c.Get(path, args, &card)
	if card != nil {
		card.client = c
	}
	return
}

/**
 * Retrieves all Cards on a Board
 *
 * If before
 */
func (b *Board) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("boards/%s/cards", b.ID)

	err = b.client.Get(path, args, &cards)

	// Naive implementation would return here. To make sure we get all cards, we begin
	if len(cards) > 0 {
		moreCards := true
		for moreCards == true {
			nextCardBatch := make([]*Card, 0)
			args["before"] = EarliestCardID(cards)
			err = b.client.Get(path, args, &nextCardBatch)
			if len(nextCardBatch) > 0 {
				cards = append(cards, nextCardBatch...)
			} else {
				moreCards = false
			}
		}
	}

	for i := range cards {
		cards[i].client = b.client
	}

	return
}

/**
 * Retrieves all Cards in a List
 */
func (l *List) GetCards(args Arguments) (cards []*Card, err error) {
	path := fmt.Sprintf("lists/%s/cards", l.ID)
	err = l.client.Get(path, args, &cards)
	for i := range cards {
		cards[i].client = l.client
	}
	return
}

func EarliestCardID(cards []*Card) string {
	if len(cards) == 0 {
		return ""
	}
	earliest := cards[0].ID
	for _, card := range cards {
		if card.ID < earliest {
			earliest = card.ID
		}
	}
	return earliest
}
<|endoftext|>


<commit_before>package selenium

import (
	//	"strings"
	"testing"
)

var caps = &Capabilities {
	"browserName": "firefox",
}

func newRemote() WebDriver {
	wd, _ := NewRemote(caps, "", "")
	return wd
}
/*
func TestStatus(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	status, err := wd.Status()
	if err != nil {
		t.Error(err.String())
	}

	if len(status.OS.Name) == 0 {
		t.Error("No OS")
	}
}

func TestNewSession(t *testing.T) {
	wd := &remoteWD{capabilities: caps, executor: DEFAULT_EXECUTOR}
	sid, err := wd.NewSession()
	defer wd.Quit()

	if err != nil {
		t.Errorf("error in new session - %s", err)
	}

	if len(sid) == 0 {
		t.Error("Empty session id")
	}

	if wd.id != sid {
		t.Error("Session id mismatch")
	}
}

func TestCurrentWindowHandle(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	handle, err := wd.CurrentWindowHandle()

	if err != nil {
		t.Error(err.String())
	}

	if len(handle) == 0 {
		t.Error("Empty handle")
	}
}

func TestWindowHandles(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	handles, err := wd.CurrentWindowHandle()
	if err != nil {
		t.Error(err.String())
	}

	if len(handles) == 0 {
		t.Error("No handles")
	}
}

func TestGet(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	url := "http://www.google.com/"
	err := wd.Get(url)
	if err != nil {
		t.Error(err.String())
	}

	newUrl, err := wd.CurrentURL()
	if err != nil {
		t.Error(err.String())
	}

	if newUrl != url {
		t.Error("%s != %s", newUrl, url)
	}
}

func TestNavigation(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	url1 := "http://www.google.com/"
	err := wd.Get(url1)
	if err != nil {
		t.Error(err.String())
	}

	url2 := "http://golang.org/"
	err = wd.Get(url2)
	if err != nil {
		t.Error(err.String())
	}

	err = wd.Back()
	if err != nil {
		t.Error(err.String())
	}
	url, _ := wd.CurrentURL()
	if url != url1 {
		t.Error("back go me to %s (expected %s)", url, url1)
	}
	err = wd.Forward()
	if err != nil {
		t.Error(err.String())
	}
	url, _ = wd.CurrentURL()
	if url != url2 {
		t.Error("back go me to %s (expected %s)", url, url2)
	}

	err = wd.Refresh()
	if err != nil {
		t.Error(err.String())
	}
	url, _ = wd.CurrentURL()
	if url != url2 {
		t.Error("back go me to %s (expected %s)", url, url2)
	}
}

func TestTitle(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	_, err := wd.Title()
	if err != nil {
		t.Error(err.String())
	}
}

func TestPageSource(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	_, err := wd.PageSource()
	if err != nil {
		t.Error(err.String())
	}
}

func TestFindElement(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	elem, err := wd.FindElement(ByName, "btnK")
	if err != nil {
		t.Error(err.String())
	}

	we, ok := elem.(*remoteWE)
	if !ok {
		t.Error("Can't convert to *remoteWE")
	}

	if len(we.id) == 0 {
		t.Error("Empty element")
	}

	if we.parent != wd {
		t.Error("Bad parent")
	}
}

func TestFindElements(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	elems, err := wd.FindElements(ByName, "btnK")
	if err != nil {
		t.Error(err.String())
	}

	if len(elems) != 1 {
		t.Error("Wrong number of elements %d (should be 1)", len(elems))
	}

	we, ok := elems[0].(*remoteWE)
	if !ok {
		t.Error("Can't convert to *remoteWE")
	}

	if len(we.id) == 0 {
		t.Error("Empty element")
	}

	if we.parent != wd {
		t.Error("Bad parent")
	}
}

func TestSendKeys(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	input, err := wd.FindElement(ByName, "p")
	if err != nil {
		t.Error(err.String())
	}
	err = input.SendKeys("golang\n")
	if err != nil {
		t.Error(err.String())
	}

	source, err := wd.PageSource()
	if err != nil {
		t.Error(err.String())
	}

	if !strings.Contains(source, "The Go Programming Language") {
		t.Error("Google can't find Go")
	}

}

func TestClick(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	input, err := wd.FindElement(ByName, "p")
	if err != nil {
		t.Error(err.String())
	}
	err = input.SendKeys("golang")
	if err != nil {
		t.Error(err.String())
	}

	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}
	err = button.Click()
	if err != nil {
		t.Error(err.String())
	}

	source, err := wd.PageSource()
	if err != nil {
		t.Error(err.String())
	}

	if !strings.Contains(source, "The Go Programming Language") {
		t.Error("Google can't find Go")
	}
}

func TestGetCookies(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	cookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}

	if len(cookies) == 0 {
		t.Error("No cookies")
	}

	if len(cookies[0].Name) == 0 {
		t.Error("Empty cookie")
	}
}

func TestAddCookie(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	cookie := &Cookie{Name: "the nameless cookie", Value: "I have nothing"}
	err := wd.AddCookie(cookie)
	if err != nil {
		t.Error(err.String())
	}

	cookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}
	for _, c := range(cookies) {
		if (c.Name == cookie.Name) && (c.Value == cookie.Value) {
			return
		}
	}

	t.Error("Can't find new cookie")
}

func TestDeleteCookie(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	cookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}
	err = wd.DeleteCookie(cookies[0].Name)
	if err != nil {
		t.Error(err.String())
	}
	newCookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}
	if len(newCookies) != len(cookies) - 1 {
		t.Error("Cookie not deleted")
	}

	for _, c := range(newCookies) {
		if c.Name == cookies[0].Name {
			t.Error("Deleted cookie found")
		}
	}

}
func TestLocation(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}

	loc, err := button.Location()
	if err != nil {
		t.Error(err.String())
	}

	if (loc.X == 0) || (loc.Y == 0) {
		t.Errorf("Bad location: %v\n", loc)
	}
}

func TestLocationInView(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}

	loc, err := button.LocationInView()
	if err != nil {
		t.Error(err.String())
	}

	if (loc.X == 0) || (loc.Y == 0) {
		t.Errorf("Bad location: %v\n", loc)
	}
}

func TestSize(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}

	size, err := button.Size()
	if err != nil {
		t.Error(err.String())
	}

	if (size.Width == 0) || (size.Height == 0) {
		t.Errorf("Bad size: %v\n", size)
	}
}
*/

func TestExecuteScript(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	script := "return arguments[0] + arguments[1]"
	args := []interface{}{1, 2}
	reply, err := wd.ExecuteScript(script, args)
	if err != nil {
		t.Error(err.String())
	}

	result, ok := reply.(float64)
	if !ok {
		t.Error("Not an int reply")
	}

	if result != 3 {
		t.Error("Bad result %d (expected 3)", result)
	}
}
<commit_msg>Enable all tests<commit_after>package selenium

import (
	"strings"
	"testing"
)

var caps = &Capabilities {
	"browserName": "firefox",
}

func newRemote() WebDriver {
	wd, _ := NewRemote(caps, "", "")
	return wd
}

func TestStatus(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	status, err := wd.Status()
	if err != nil {
		t.Error(err.String())
	}

	if len(status.OS.Name) == 0 {
		t.Error("No OS")
	}
}

func TestNewSession(t *testing.T) {
	wd := &remoteWD{capabilities: caps, executor: DEFAULT_EXECUTOR}
	sid, err := wd.NewSession()
	defer wd.Quit()

	if err != nil {
		t.Errorf("error in new session - %s", err)
	}

	if len(sid) == 0 {
		t.Error("Empty session id")
	}

	if wd.id != sid {
		t.Error("Session id mismatch")
	}
}

func TestCurrentWindowHandle(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	handle, err := wd.CurrentWindowHandle()

	if err != nil {
		t.Error(err.String())
	}

	if len(handle) == 0 {
		t.Error("Empty handle")
	}
}

func TestWindowHandles(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	handles, err := wd.CurrentWindowHandle()
	if err != nil {
		t.Error(err.String())
	}

	if len(handles) == 0 {
		t.Error("No handles")
	}
}

func TestGet(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	url := "http://www.google.com/"
	err := wd.Get(url)
	if err != nil {
		t.Error(err.String())
	}

	newUrl, err := wd.CurrentURL()
	if err != nil {
		t.Error(err.String())
	}

	if newUrl != url {
		t.Error("%s != %s", newUrl, url)
	}
}

func TestNavigation(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	url1 := "http://www.google.com/"
	err := wd.Get(url1)
	if err != nil {
		t.Error(err.String())
	}

	url2 := "http://golang.org/"
	err = wd.Get(url2)
	if err != nil {
		t.Error(err.String())
	}

	err = wd.Back()
	if err != nil {
		t.Error(err.String())
	}
	url, _ := wd.CurrentURL()
	if url != url1 {
		t.Error("back go me to %s (expected %s)", url, url1)
	}
	err = wd.Forward()
	if err != nil {
		t.Error(err.String())
	}
	url, _ = wd.CurrentURL()
	if url != url2 {
		t.Error("back go me to %s (expected %s)", url, url2)
	}

	err = wd.Refresh()
	if err != nil {
		t.Error(err.String())
	}
	url, _ = wd.CurrentURL()
	if url != url2 {
		t.Error("back go me to %s (expected %s)", url, url2)
	}
}

func TestTitle(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	_, err := wd.Title()
	if err != nil {
		t.Error(err.String())
	}
}

func TestPageSource(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	_, err := wd.PageSource()
	if err != nil {
		t.Error(err.String())
	}
}

func TestFindElement(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	elem, err := wd.FindElement(ByName, "btnK")
	if err != nil {
		t.Error(err.String())
	}

	we, ok := elem.(*remoteWE)
	if !ok {
		t.Error("Can't convert to *remoteWE")
	}

	if len(we.id) == 0 {
		t.Error("Empty element")
	}

	if we.parent != wd {
		t.Error("Bad parent")
	}
}

func TestFindElements(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	elems, err := wd.FindElements(ByName, "btnK")
	if err != nil {
		t.Error(err.String())
	}

	if len(elems) != 1 {
		t.Error("Wrong number of elements %d (should be 1)", len(elems))
	}

	we, ok := elems[0].(*remoteWE)
	if !ok {
		t.Error("Can't convert to *remoteWE")
	}

	if len(we.id) == 0 {
		t.Error("Empty element")
	}

	if we.parent != wd {
		t.Error("Bad parent")
	}
}

func TestSendKeys(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	input, err := wd.FindElement(ByName, "p")
	if err != nil {
		t.Error(err.String())
	}
	err = input.SendKeys("golang\n")
	if err != nil {
		t.Error(err.String())
	}

	source, err := wd.PageSource()
	if err != nil {
		t.Error(err.String())
	}

	if !strings.Contains(source, "The Go Programming Language") {
		t.Error("Google can't find Go")
	}

}

func TestClick(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	input, err := wd.FindElement(ByName, "p")
	if err != nil {
		t.Error(err.String())
	}
	err = input.SendKeys("golang")
	if err != nil {
		t.Error(err.String())
	}

	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}
	err = button.Click()
	if err != nil {
		t.Error(err.String())
	}

	source, err := wd.PageSource()
	if err != nil {
		t.Error(err.String())
	}

	if !strings.Contains(source, "The Go Programming Language") {
		t.Error("Google can't find Go")
	}
}

func TestGetCookies(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	cookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}

	if len(cookies) == 0 {
		t.Error("No cookies")
	}

	if len(cookies[0].Name) == 0 {
		t.Error("Empty cookie")
	}
}

func TestAddCookie(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	cookie := &Cookie{Name: "the nameless cookie", Value: "I have nothing"}
	err := wd.AddCookie(cookie)
	if err != nil {
		t.Error(err.String())
	}

	cookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}
	for _, c := range(cookies) {
		if (c.Name == cookie.Name) && (c.Value == cookie.Value) {
			return
		}
	}

	t.Error("Can't find new cookie")
}

func TestDeleteCookie(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.google.com")
	cookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}
	err = wd.DeleteCookie(cookies[0].Name)
	if err != nil {
		t.Error(err.String())
	}
	newCookies, err := wd.GetCookies()
	if err != nil {
		t.Error(err.String())
	}
	if len(newCookies) != len(cookies) - 1 {
		t.Error("Cookie not deleted")
	}

	for _, c := range(newCookies) {
		if c.Name == cookies[0].Name {
			t.Error("Deleted cookie found")
		}
	}

}
func TestLocation(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}

	loc, err := button.Location()
	if err != nil {
		t.Error(err.String())
	}

	if (loc.X == 0) || (loc.Y == 0) {
		t.Errorf("Bad location: %v\n", loc)
	}
}

func TestLocationInView(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}

	loc, err := button.LocationInView()
	if err != nil {
		t.Error(err.String())
	}

	if (loc.X == 0) || (loc.Y == 0) {
		t.Errorf("Bad location: %v\n", loc)
	}
}

func TestSize(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	wd.Get("http://www.yahoo.com")
	button, err := wd.FindElement(ById, "search-submit")
	if err != nil {
		t.Error(err.String())
	}

	size, err := button.Size()
	if err != nil {
		t.Error(err.String())
	}

	if (size.Width == 0) || (size.Height == 0) {
		t.Errorf("Bad size: %v\n", size)
	}
}

func TestExecuteScript(t *testing.T) {
	wd := newRemote()
	defer wd.Quit()

	script := "return arguments[0] + arguments[1]"
	args := []interface{}{1, 2}
	reply, err := wd.ExecuteScript(script, args)
	if err != nil {
		t.Error(err.String())
	}

	result, ok := reply.(float64)
	if !ok {
		t.Error("Not an int reply")
	}

	if result != 3 {
		t.Error("Bad result %d (expected 3)", result)
	}
}
<|endoftext|>


<commit_before>package goczmq

/*
#include "czmq.h"

void Set_meta(zcert_t *self, const char *key, const char *value) {zcert_set_meta(self, key, value);}
*/
import "C"

import (
	"fmt"
	"os"
	"unsafe"
)

// Cert wraps the CZMQ zcert class. It provides tools for
// creating and working with ZMQ CURVE security certs.
// The certs can be used as a temporary object in memory
// or persisted to disk. Certs are made up of a public
// and secret keypair + metadata.
type Cert struct {
	zcertT *C.struct__zcert_t
}

// NewCert creates a new empty Cert instance
func NewCert() *Cert {
	return &Cert{
		zcertT: C.zcert_new(),
	}
}

// NewCertFromKeys creates a new Cert from a public and private key
func NewCertFromKeys(public []byte, secret []byte) (*Cert, error) {
	if len(public) != 32 {
		return nil, fmt.Errorf("invalid public key")
	}

	if len(secret) != 32 {
		return nil, fmt.Errorf("invalid private key")
	}

	return &Cert{
		zcertT: C.zcert_new_from(
			(*C.byte)(unsafe.Pointer(&public[0])),
			(*C.byte)(unsafe.Pointer(&secret[0]))),
	}, nil
}

// NewCertFromFile Load loads a Cert from files
func NewCertFromFile(filename string) (*Cert, error) {
	_, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return nil, ErrCertNotFound
	}

	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	cert := C.zcert_load(cFilename)
	return &Cert{
		zcertT: cert,
	}, nil
}

// SetMeta sets meta data for a Cert
func (c *Cert) SetMeta(key string, value string) {
	cKey := C.CString(key)
	defer C.free(unsafe.Pointer(cKey))

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	C.Set_meta(c.zcertT, cKey, cValue)
}

// Meta returns a meta data item from a Cert given a key
func (c *Cert) Meta(key string) string {
	cKey := C.CString(key)
	defer C.free(unsafe.Pointer(cKey))

	val := C.zcert_meta(c.zcertT, cKey)
	return C.GoString(val)
}

// PublicText returns the public key as a string
func (c *Cert) PublicText() string {
	val := C.zcert_public_txt(c.zcertT)
	return C.GoString(val)
}

// Apply sets the public and private keys for a socket
func (c *Cert) Apply(s *Sock) {
	handle := C.zsock_resolve(unsafe.Pointer(s.zsockT))
	C.zsocket_set_curve_secretkey_bin(handle, C.zcert_secret_key(c.zcertT))
	C.zsocket_set_curve_publickey_bin(handle, C.zcert_public_key(c.zcertT))
}

// Dup duplicates a Cert
func (c *Cert) Dup() *Cert {
	return &Cert{
		zcertT: C.zcert_dup(c.zcertT),
	}
}

// Equal checks two Certs for equality
func (c *Cert) Equal(compare *Cert) bool {
	check := C.zcert_eq(c.zcertT, compare.zcertT)
	if check == C.bool(true) {
		return true
	}
	return false
}

// Print prints a Cert to stdout
func (c *Cert) Print() {
	C.zcert_print(c.zcertT)
}

// SavePublic saves the public key to a file
func (c *Cert) SavePublic(filename string) error {
	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	rc := C.zcert_save_public(c.zcertT, cFilename)
	if rc == C.int(-1) {
		return fmt.Errorf("SavePublic error")
	}
	return nil
}

// SaveSecret saves the secret key to a file
func (c *Cert) SaveSecret(filename string) error {
	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	rc := C.zcert_save_secret(c.zcertT, cFilename)
	if rc == C.int(-1) {
		return fmt.Errorf("SaveSecret error")
	}
	return nil
}

// Save saves the public and secret key to filename and filename_secret
func (c *Cert) Save(filename string) error {
	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	rc := C.zcert_save(c.zcertT, cFilename)
	if rc == C.int(-1) {
		return fmt.Errorf("SavePublic: error")
	}
	return nil
}

// Destroy destroys Cert instance
func (c *Cert) Destroy() {
	C.zcert_destroy(&c.zcertT)
}
<commit_msg>problem: Set_meta in cert producing compiler warning<commit_after>package goczmq

/*
#include "czmq.h"

void Set_meta(zcert_t *self, const char *key, const char *value) {zcert_set_meta(self, key, "%s", value);}
*/
import "C"

import (
	"fmt"
	"os"
	"unsafe"
)

// Cert wraps the CZMQ zcert class. It provides tools for
// creating and working with ZMQ CURVE security certs.
// The certs can be used as a temporary object in memory
// or persisted to disk. Certs are made up of a public
// and secret keypair + metadata.
type Cert struct {
	zcertT *C.struct__zcert_t
}

// NewCert creates a new empty Cert instance
func NewCert() *Cert {
	return &Cert{
		zcertT: C.zcert_new(),
	}
}

// NewCertFromKeys creates a new Cert from a public and private key
func NewCertFromKeys(public []byte, secret []byte) (*Cert, error) {
	if len(public) != 32 {
		return nil, fmt.Errorf("invalid public key")
	}

	if len(secret) != 32 {
		return nil, fmt.Errorf("invalid private key")
	}

	return &Cert{
		zcertT: C.zcert_new_from(
			(*C.byte)(unsafe.Pointer(&public[0])),
			(*C.byte)(unsafe.Pointer(&secret[0]))),
	}, nil
}

// NewCertFromFile Load loads a Cert from files
func NewCertFromFile(filename string) (*Cert, error) {
	_, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return nil, ErrCertNotFound
	}

	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	cert := C.zcert_load(cFilename)
	return &Cert{
		zcertT: cert,
	}, nil
}

// SetMeta sets meta data for a Cert
func (c *Cert) SetMeta(key string, value string) {
	cKey := C.CString(key)
	defer C.free(unsafe.Pointer(cKey))

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	C.Set_meta(c.zcertT, cKey, cValue)
}

// Meta returns a meta data item from a Cert given a key
func (c *Cert) Meta(key string) string {
	cKey := C.CString(key)
	defer C.free(unsafe.Pointer(cKey))

	val := C.zcert_meta(c.zcertT, cKey)
	return C.GoString(val)
}

// PublicText returns the public key as a string
func (c *Cert) PublicText() string {
	val := C.zcert_public_txt(c.zcertT)
	return C.GoString(val)
}

// Apply sets the public and private keys for a socket
func (c *Cert) Apply(s *Sock) {
	handle := C.zsock_resolve(unsafe.Pointer(s.zsockT))
	C.zsocket_set_curve_secretkey_bin(handle, C.zcert_secret_key(c.zcertT))
	C.zsocket_set_curve_publickey_bin(handle, C.zcert_public_key(c.zcertT))
}

// Dup duplicates a Cert
func (c *Cert) Dup() *Cert {
	return &Cert{
		zcertT: C.zcert_dup(c.zcertT),
	}
}

// Equal checks two Certs for equality
func (c *Cert) Equal(compare *Cert) bool {
	check := C.zcert_eq(c.zcertT, compare.zcertT)
	if check == C.bool(true) {
		return true
	}
	return false
}

// Print prints a Cert to stdout
func (c *Cert) Print() {
	C.zcert_print(c.zcertT)
}

// SavePublic saves the public key to a file
func (c *Cert) SavePublic(filename string) error {
	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	rc := C.zcert_save_public(c.zcertT, cFilename)
	if rc == C.int(-1) {
		return fmt.Errorf("SavePublic error")
	}
	return nil
}

// SaveSecret saves the secret key to a file
func (c *Cert) SaveSecret(filename string) error {
	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	rc := C.zcert_save_secret(c.zcertT, cFilename)
	if rc == C.int(-1) {
		return fmt.Errorf("SaveSecret error")
	}
	return nil
}

// Save saves the public and secret key to filename and filename_secret
func (c *Cert) Save(filename string) error {
	cFilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cFilename))

	rc := C.zcert_save(c.zcertT, cFilename)
	if rc == C.int(-1) {
		return fmt.Errorf("SavePublic: error")
	}
	return nil
}

// Destroy destroys Cert instance
func (c *Cert) Destroy() {
	C.zcert_destroy(&c.zcertT)
}
<|endoftext|>


<commit_before>package cert

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
)

var SkipVerify = false

var userTempl string

func SetUserTempl(templ string) error {
	if templ == "" {
		return nil
	}

	path, err := filepath.Abs(templ)
	if err != nil {
		return err
	}

	content, err := ioutil.ReadFile(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		userTempl = templ
		return nil
	}

	userTempl = string(content)

	return nil
}

const defaultPort = "443"

func SplitHostPort(hostport string) (string, string, error) {
	if !strings.Contains(hostport, ":") {
		return hostport, defaultPort, nil
	}

	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", "", err
	}

	if port == "" {
		port = defaultPort
	}

	return host, port, nil
}

type Cert struct {
	DomainName string   `json:"domainName"`
	IP         string   `json:"ip"`
	Issuer     string   `json:"issuer"`
	CommonName string   `json:"commonName"`
	SANs       []string `json:"sans"`
	NotBefore  string   `json:"notBefore"`
	NotAfter   string   `json:"notAfter"`
	Error      string   `json:"error"`
	certChain  []*x509.Certificate
}

var serverCert = func(host, port string) ([]*x509.Certificate, string, error) {
	conn, err := tls.Dial("tcp", host+":"+port, &tls.Config{
		InsecureSkipVerify: SkipVerify,
	})
	if err != nil {
		return []*x509.Certificate{&x509.Certificate{}}, "", err
	}
	defer conn.Close()
	addr := conn.RemoteAddr()
	ip, _, _ := net.SplitHostPort(addr.String())
	cert := conn.ConnectionState().PeerCertificates

	return cert, ip, nil
}

func NewCert(hostport string) *Cert {
	host, port, err := SplitHostPort(hostport)
	if err != nil {
		return &Cert{DomainName: host, Error: err.Error()}
	}
	certChain, ip, err := serverCert(host, port)
	if err != nil {
		return &Cert{DomainName: host, Error: err.Error()}
	}
	cert := certChain[0]
	return &Cert{
		DomainName: host,
		IP:         ip,
		Issuer:     cert.Issuer.CommonName,
		CommonName: cert.Subject.CommonName,
		SANs:       cert.DNSNames,
		NotBefore:  cert.NotBefore.In(time.Local).String(),
		NotAfter:   cert.NotAfter.In(time.Local).String(),
		Error:      "",
		certChain:  certChain,
	}
}

func (c *Cert) Detail() *x509.Certificate {
	return c.certChain[0]
}

func (c *Cert) CertChain() []*x509.Certificate {
	return c.certChain
}

type Certs []*Cert

var tokens = make(chan struct{}, 128)

func validate(s []string) error {
	if len(s) < 1 {
		return fmt.Errorf("Input at least one domain name.")
	}
	return nil
}

func NewCerts(s []string) (Certs, error) {
	if err := validate(s); err != nil {
		return nil, err
	}

	type indexer struct {
		index int
		cert  *Cert
	}

	certs := make(Certs, len(s))
	ch := make(chan *indexer, len(s))
	for i, d := range s {
		go func(i int, d string) {
			tokens <- struct{}{}
			ch <- &indexer{i, NewCert(d)}
			<-tokens
		}(i, d)
	}

	for range s {
		i := <-ch
		certs[i.index] = i.cert
	}
	return certs, nil
}

const defaultTempl = `{{range .}}DomainName: {{.DomainName}}
IP:         {{.IP}}
Issuer:     {{.Issuer}}
NotBefore:  {{.NotBefore}}
NotAfter:   {{.NotAfter}}
CommonName: {{.CommonName}}
SANs:       {{.SANs}}
Error:      {{.Error}}

{{end}}
`

func (certs Certs) String() string {
	var b bytes.Buffer

	templ := defaultTempl
	if userTempl != "" {
		templ = userTempl
	}

	t := template.Must(template.New("default").Parse(templ))
	if err := t.Execute(&b, certs); err != nil {
		panic(err)
	}
	return b.String()
}

const markdownTempl = `DomainName | IP | Issuer | NotBefore | NotAfter | CN | SANs | Error
--- | --- | --- | --- | --- | --- | --- | ---
{{range .}}{{.DomainName}} | {{.IP}} | {{.Issuer}} | {{.NotBefore}} | {{.NotAfter}} | {{.CommonName}} | {{range .SANs}}{{.}}
{{end}} | {{.Error}}
{{end}}
`

func (certs Certs) escapeStar() Certs {
	for _, cert := range certs {
		for i, san := range cert.SANs {
			cert.SANs[i] = strings.Replace(san, "*", "\\*", -1)
		}
	}
	return certs
}

func (certs Certs) Markdown() string {
	var b bytes.Buffer
	t := template.Must(template.New("markdown").Parse(markdownTempl))
	if err := t.Execute(&b, certs.escapeStar()); err != nil {
		panic(err)
	}
	return b.String()
}

func (certs Certs) JSON() []byte {
	data, err := json.Marshal(certs)
	if err != nil {
		panic(err)
	}
	return data
}
<commit_msg>Remove unnecessary buffer of chan<commit_after>package cert

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
)

var SkipVerify = false

var userTempl string

func SetUserTempl(templ string) error {
	if templ == "" {
		return nil
	}

	path, err := filepath.Abs(templ)
	if err != nil {
		return err
	}

	content, err := ioutil.ReadFile(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		userTempl = templ
		return nil
	}

	userTempl = string(content)

	return nil
}

const defaultPort = "443"

func SplitHostPort(hostport string) (string, string, error) {
	if !strings.Contains(hostport, ":") {
		return hostport, defaultPort, nil
	}

	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", "", err
	}

	if port == "" {
		port = defaultPort
	}

	return host, port, nil
}

type Cert struct {
	DomainName string   `json:"domainName"`
	IP         string   `json:"ip"`
	Issuer     string   `json:"issuer"`
	CommonName string   `json:"commonName"`
	SANs       []string `json:"sans"`
	NotBefore  string   `json:"notBefore"`
	NotAfter   string   `json:"notAfter"`
	Error      string   `json:"error"`
	certChain  []*x509.Certificate
}

var serverCert = func(host, port string) ([]*x509.Certificate, string, error) {
	conn, err := tls.Dial("tcp", host+":"+port, &tls.Config{
		InsecureSkipVerify: SkipVerify,
	})
	if err != nil {
		return []*x509.Certificate{&x509.Certificate{}}, "", err
	}
	defer conn.Close()
	addr := conn.RemoteAddr()
	ip, _, _ := net.SplitHostPort(addr.String())
	cert := conn.ConnectionState().PeerCertificates

	return cert, ip, nil
}

func NewCert(hostport string) *Cert {
	host, port, err := SplitHostPort(hostport)
	if err != nil {
		return &Cert{DomainName: host, Error: err.Error()}
	}
	certChain, ip, err := serverCert(host, port)
	if err != nil {
		return &Cert{DomainName: host, Error: err.Error()}
	}
	cert := certChain[0]
	return &Cert{
		DomainName: host,
		IP:         ip,
		Issuer:     cert.Issuer.CommonName,
		CommonName: cert.Subject.CommonName,
		SANs:       cert.DNSNames,
		NotBefore:  cert.NotBefore.In(time.Local).String(),
		NotAfter:   cert.NotAfter.In(time.Local).String(),
		Error:      "",
		certChain:  certChain,
	}
}

func (c *Cert) Detail() *x509.Certificate {
	return c.certChain[0]
}

func (c *Cert) CertChain() []*x509.Certificate {
	return c.certChain
}

type Certs []*Cert

var tokens = make(chan struct{}, 128)

func validate(s []string) error {
	if len(s) < 1 {
		return fmt.Errorf("Input at least one domain name.")
	}
	return nil
}

func NewCerts(s []string) (Certs, error) {
	if err := validate(s); err != nil {
		return nil, err
	}

	type indexer struct {
		index int
		cert  *Cert
	}

	ch := make(chan *indexer)
	for i, d := range s {
		go func(i int, d string) {
			tokens <- struct{}{}
			ch <- &indexer{i, NewCert(d)}
			<-tokens
		}(i, d)
	}

	certs := make(Certs, len(s))
	for range s {
		i := <-ch
		certs[i.index] = i.cert
	}
	return certs, nil
}

const defaultTempl = `{{range .}}DomainName: {{.DomainName}}
IP:         {{.IP}}
Issuer:     {{.Issuer}}
NotBefore:  {{.NotBefore}}
NotAfter:   {{.NotAfter}}
CommonName: {{.CommonName}}
SANs:       {{.SANs}}
Error:      {{.Error}}

{{end}}
`

func (certs Certs) String() string {
	var b bytes.Buffer

	templ := defaultTempl
	if userTempl != "" {
		templ = userTempl
	}

	t := template.Must(template.New("default").Parse(templ))
	if err := t.Execute(&b, certs); err != nil {
		panic(err)
	}
	return b.String()
}

const markdownTempl = `DomainName | IP | Issuer | NotBefore | NotAfter | CN | SANs | Error
--- | --- | --- | --- | --- | --- | --- | ---
{{range .}}{{.DomainName}} | {{.IP}} | {{.Issuer}} | {{.NotBefore}} | {{.NotAfter}} | {{.CommonName}} | {{range .SANs}}{{.}}
{{end}} | {{.Error}}
{{end}}
`

func (certs Certs) escapeStar() Certs {
	for _, cert := range certs {
		for i, san := range cert.SANs {
			cert.SANs[i] = strings.Replace(san, "*", "\\*", -1)
		}
	}
	return certs
}

func (certs Certs) Markdown() string {
	var b bytes.Buffer
	t := template.Must(template.New("markdown").Parse(markdownTempl))
	if err := t.Execute(&b, certs.escapeStar()); err != nil {
		panic(err)
	}
	return b.String()
}

func (certs Certs) JSON() []byte {
	data, err := json.Marshal(certs)
	if err != nil {
		panic(err)
	}
	return data
}
<|endoftext|>


<commit_before>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package git

import (
	"fmt"
	"strings"

	"github.com/mcuadros/go-version"
)

// BranchPrefix base dir of the branch information file store on git
const BranchPrefix = "refs/heads/"

// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(repoPath, name string) bool {
	_, err := NewCommand("show-ref", "--verify", name).RunInDir(repoPath)
	return err == nil
}

// IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(repoPath, name string) bool {
	return IsReferenceExist(repoPath, BranchPrefix+name)
}

// IsBranchExist returns true if given branch exists in current repository.
func (repo *Repository) IsBranchExist(name string) bool {
	return IsBranchExist(repo.Path, name)
}

// Branch represents a Git branch.
type Branch struct {
	Name string
	Path string
}

// GetHEADBranch returns corresponding branch of HEAD.
func (repo *Repository) GetHEADBranch() (*Branch, error) {
	stdout, err := NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path)
	if err != nil {
		return nil, err
	}
	stdout = strings.TrimSpace(stdout)

	if !strings.HasPrefix(stdout, BranchPrefix) {
		return nil, fmt.Errorf("invalid HEAD branch: %v", stdout)
	}

	return &Branch{
		Name: stdout[len(BranchPrefix):],
		Path: stdout,
	}, nil
}

// SetDefaultBranch sets default branch of repository.
func (repo *Repository) SetDefaultBranch(name string) error {
	if version.Compare(gitVersion, "1.7.10", "<") {
		return ErrUnsupportedVersion{"1.7.10"}
	}

	_, err := NewCommand("symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path)
	return err
}

// GetBranches returns all branches of the repository.
func (repo *Repository) GetBranches() ([]string, error) {
	stdout, err := NewCommand("for-each-ref", "--format=%(refname)", BranchPrefix).RunInDir(repo.Path)
	if err != nil {
		return nil, err
	}

	refs := strings.Split(stdout, "\n")
	branches := make([]string, len(refs)-1)
	for i, ref := range refs[:len(refs)-1] {
		branches[i] = strings.TrimPrefix(ref, BranchPrefix)
	}
	return branches, nil
}

// DeleteBranchOptions Option(s) for delete branch
type DeleteBranchOptions struct {
	Force bool
}

// DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
	cmd := NewCommand("branch", "-d")

	if opts.Force {
		cmd.AddArguments("-f")
	}

	cmd.AddArguments(name)
	_, err := cmd.RunInDir(repo.Path)

	return err
}

// CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, newBranch string) error {
	cmd := NewCommand("branch")
	cmd.AddArguments(branch, newBranch)

	_, err := cmd.RunInDir(repo.Path)

	return err
}

// AddRemote adds a new remote to repository.
func (repo *Repository) AddRemote(name, url string, fetch bool) error {
	cmd := NewCommand("remote", "add")
	if fetch {
		cmd.AddArguments("-f")
	}
	cmd.AddArguments(name, url)

	_, err := cmd.RunInDir(repo.Path)
	return err
}

// RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error {
	_, err := NewCommand("remote", "remove", name).RunInDir(repo.Path)
	return err
}
<commit_msg>Fix branch deletion with git < 2.5.0 (#77)<commit_after>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package git

import (
	"fmt"
	"strings"

	"github.com/mcuadros/go-version"
)

// BranchPrefix base dir of the branch information file store on git
const BranchPrefix = "refs/heads/"

// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(repoPath, name string) bool {
	_, err := NewCommand("show-ref", "--verify", name).RunInDir(repoPath)
	return err == nil
}

// IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(repoPath, name string) bool {
	return IsReferenceExist(repoPath, BranchPrefix+name)
}

// IsBranchExist returns true if given branch exists in current repository.
func (repo *Repository) IsBranchExist(name string) bool {
	return IsBranchExist(repo.Path, name)
}

// Branch represents a Git branch.
type Branch struct {
	Name string
	Path string
}

// GetHEADBranch returns corresponding branch of HEAD.
func (repo *Repository) GetHEADBranch() (*Branch, error) {
	stdout, err := NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path)
	if err != nil {
		return nil, err
	}
	stdout = strings.TrimSpace(stdout)

	if !strings.HasPrefix(stdout, BranchPrefix) {
		return nil, fmt.Errorf("invalid HEAD branch: %v", stdout)
	}

	return &Branch{
		Name: stdout[len(BranchPrefix):],
		Path: stdout,
	}, nil
}

// SetDefaultBranch sets default branch of repository.
func (repo *Repository) SetDefaultBranch(name string) error {
	if version.Compare(gitVersion, "1.7.10", "<") {
		return ErrUnsupportedVersion{"1.7.10"}
	}

	_, err := NewCommand("symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path)
	return err
}

// GetBranches returns all branches of the repository.
func (repo *Repository) GetBranches() ([]string, error) {
	stdout, err := NewCommand("for-each-ref", "--format=%(refname)", BranchPrefix).RunInDir(repo.Path)
	if err != nil {
		return nil, err
	}

	refs := strings.Split(stdout, "\n")
	branches := make([]string, len(refs)-1)
	for i, ref := range refs[:len(refs)-1] {
		branches[i] = strings.TrimPrefix(ref, BranchPrefix)
	}
	return branches, nil
}

// DeleteBranchOptions Option(s) for delete branch
type DeleteBranchOptions struct {
	Force bool
}

// DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
	cmd := NewCommand("branch")

	if opts.Force {
		cmd.AddArguments("-D")
	} else {
		cmd.AddArguments("-d")
	}

	cmd.AddArguments(name)
	_, err := cmd.RunInDir(repo.Path)

	return err
}

// CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, newBranch string) error {
	cmd := NewCommand("branch")
	cmd.AddArguments(branch, newBranch)

	_, err := cmd.RunInDir(repo.Path)

	return err
}

// AddRemote adds a new remote to repository.
func (repo *Repository) AddRemote(name, url string, fetch bool) error {
	cmd := NewCommand("remote", "add")
	if fetch {
		cmd.AddArguments("-f")
	}
	cmd.AddArguments(name, url)

	_, err := cmd.RunInDir(repo.Path)
	return err
}

// RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error {
	_, err := NewCommand("remote", "remove", name).RunInDir(repo.Path)
	return err
}
<|endoftext|>


<commit_before>package terraform

// The main version number that is being run at the moment.
const Version = "0.6.8"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = ""
<commit_msg>release: clean up after v0.6.8<commit_after>package terraform

// The main version number that is being run at the moment.
const Version = "0.6.9"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = "dev"
<|endoftext|>