<commit_before>package user

import (
	"context"
	htmlTemplate "html/template"
	"net/http"
	"strconv"
	"time"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/kvstore"
	"xorkevin.dev/governor/service/mail"
	"xorkevin.dev/governor/service/msgqueue"
	"xorkevin.dev/governor/service/user/apikey"
	"xorkevin.dev/governor/service/user/approval/model"
	"xorkevin.dev/governor/service/user/gate"
	"xorkevin.dev/governor/service/user/model"
	"xorkevin.dev/governor/service/user/reset/model"
	"xorkevin.dev/governor/service/user/role"
	"xorkevin.dev/governor/service/user/role/invitation/model"
	"xorkevin.dev/governor/service/user/session/model"
	"xorkevin.dev/governor/service/user/token"
	"xorkevin.dev/governor/util/rank"
)

const (
	authRoutePrefix = "/auth"
)

const (
	// NewUserQueueID is emitted when a new user is created
	NewUserQueueID = "gov.user.new"
	// DeleteUserQueueID is emitted when a user is deleted
	DeleteUserQueueID = "gov.user.delete"
)

const (
	time5m     int64 = int64(5 * time.Minute / time.Second)
	time24h    int64 = int64(24 * time.Hour / time.Second)
	time6month int64 = time24h * 365 / 2
)

type (
	// User is a user management service
	User interface {
		GetByID(userid string) (*ResUserGet, error)
		CheckUserExists(userid string) (bool, error)
	}

	Service interface {
		governor.Service
		User
	}

	service struct {
		users             usermodel.Repo
		sessions          sessionmodel.Repo
		approvals         approvalmodel.Repo
		invitations       invitationmodel.Repo
		resets            resetmodel.Repo
		roles             role.Role
		apikeys           apikey.Apikey
		kvusers           kvstore.KVStore
		kvsessions        kvstore.KVStore
		queue             msgqueue.Msgqueue
		mailer            mail.Mail
		gate              gate.Gate
		tokenizer         token.Tokenizer
		logger            governor.Logger
		baseURL           string
		authURL           string
		accessTime        int64
		refreshTime       int64
		refreshCacheTime  int64
		confirmTime       int64
		passwordResetTime int64
		invitationTime    int64
		userCacheTime     int64
		newLoginEmail     bool
		passwordMinSize   int
		userApproval      bool
		rolesummary       rank.Rank
		emailurlbase      string
		tplemailchange    *htmlTemplate.Template
		tplforgotpass     *htmlTemplate.Template
		tplnewuser        *htmlTemplate.Template
	}

	router struct {
		s service
	}

	// NewUserProps are properties of a newly created user
	NewUserProps struct {
		Userid       string `json:"userid"`
		Username     string `json:"username"`
		Email        string `json:"email"`
		FirstName    string `json:"first_name"`
		LastName     string `json:"last_name"`
		CreationTime int64  `json:"creation_time"`
	}

	// DeleteUserProps are properties of a deleted user
	DeleteUserProps struct {
		Userid string `json:"userid"`
	}

	ctxKeyUser struct{}
)

// GetCtxUser returns a User service from the context
func GetCtxUser(inj governor.Injector) User {
	v := inj.Get(ctxKeyUser{})
	if v == nil {
		return nil
	}
	return v.(User)
}

// setCtxUser sets a User service in the context
func setCtxUser(inj governor.Injector, u User) {
	inj.Set(ctxKeyUser{}, u)
}

// NewCtx creates a new User service from a context
func NewCtx(inj governor.Injector) Service {
	users := usermodel.GetCtxRepo(inj)
	sessions := sessionmodel.GetCtxRepo(inj)
	approvals := approvalmodel.GetCtxRepo(inj)
	invitations := invitationmodel.GetCtxRepo(inj)
	resets := resetmodel.GetCtxRepo(inj)
	roles := role.GetCtxRole(inj)
	apikeys := apikey.GetCtxApikey(inj)
	kv := kvstore.GetCtxKVStore(inj)
	queue := msgqueue.GetCtxMsgqueue(inj)
	mailer := mail.GetCtxMail(inj)
	tokenizer := token.GetCtxTokenizer(inj)
	g := gate.GetCtxGate(inj)

	return New(
		users,
		sessions,
		approvals,
		invitations,
		resets,
		roles,
		apikeys,
		kv,
		queue,
		mailer,
		tokenizer,
		g,
	)
}

// New creates a new User service
func New(
	users usermodel.Repo,
	sessions sessionmodel.Repo,
	approvals approvalmodel.Repo,
	invitations invitationmodel.Repo,
	resets resetmodel.Repo,
	roles role.Role,
	apikeys apikey.Apikey,
	kv kvstore.KVStore,
	queue msgqueue.Msgqueue,
	mailer mail.Mail,
	tokenizer token.Tokenizer,
	g gate.Gate,
) Service {
	return &service{
		users:             users,
		sessions:          sessions,
		approvals:         approvals,
		invitations:       invitations,
		resets:            resets,
		roles:             roles,
		apikeys:           apikeys,
		kvusers:           kv.Subtree("users"),
		kvsessions:        kv.Subtree("sessions"),
		queue:             queue,
		mailer:            mailer,
		gate:              g,
		tokenizer:         tokenizer,
		accessTime:        time5m,
		refreshTime:       time6month,
		refreshCacheTime:  time24h,
		confirmTime:       time24h,
		passwordResetTime: time24h,
		invitationTime:    time24h,
		userCacheTime:     time24h,
	}
}

func (s *service) Register(inj governor.Injector, r governor.ConfigRegistrar, jr governor.JobRegistrar) {
	setCtxUser(inj, s)

	r.SetDefault("accesstime", "5m")
	r.SetDefault("refreshtime", "4380h")
	r.SetDefault("refreshcache", "24h")
	r.SetDefault("confirmtime", "24h")
	r.SetDefault("passwordresettime", "24h")
	r.SetDefault("invitationtime", "24h")
	r.SetDefault("usercachetime", "24h")
	r.SetDefault("newloginemail", true)
	r.SetDefault("passwordminsize", 8)
	r.SetDefault("userapproval", false)
	r.SetDefault("rolesummary", []string{rank.TagUser, rank.TagAdmin})
	r.SetDefault("email.url.base", "http://localhost:8080")
	r.SetDefault("email.url.emailchange", "/a/confirm/email?key={{.Userid}}.{{.Key}}")
	r.SetDefault("email.url.forgotpass", "/x/resetpass?key={{.Userid}}.{{.Key}}")
	r.SetDefault("email.url.newuser", "/x/confirm?userid={{.Userid}}&key={{.Key}}")
}

func (s *service) router() *router {
	return &router{
		s: *s,
	}
}

func (s *service) Init(ctx context.Context, c governor.Config, r governor.ConfigReader, l governor.Logger, m governor.Router) error {
	s.logger = l
	l = s.logger.WithData(map[string]string{
		"phase": "init",
	})

	s.baseURL = c.BaseURL
	s.authURL = c.BaseURL + r.URL() + authRoutePrefix
	if t, err := time.ParseDuration(r.GetStr("accesstime")); err != nil {
		return governor.NewError("Failed to parse access time", http.StatusBadRequest, err)
	} else {
		s.accessTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("refreshtime")); err != nil {
		return governor.NewError("Failed to parse refresh time", http.StatusBadRequest, err)
	} else {
		s.refreshTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("refreshcache")); err != nil {
		return governor.NewError("Failed to parse refresh cache", http.StatusBadRequest, err)
	} else {
		s.refreshCacheTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("confirmtime")); err != nil {
		return governor.NewError("Failed to parse confirm time", http.StatusBadRequest, err)
	} else {
		s.confirmTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("passwordresettime")); err != nil {
		return governor.NewError("Failed to parse password reset time", http.StatusBadRequest, err)
	} else {
		s.passwordResetTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("invitationtime")); err != nil {
		return governor.NewError("Failed to parse role invitation time", http.StatusBadRequest, err)
	} else {
		s.invitationTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("usercachetime")); err != nil {
		return governor.NewError("Failed to parse user cache time", http.StatusBadRequest, err)
	} else {
		s.userCacheTime = int64(t / time.Second)
	}
	s.newLoginEmail = r.GetBool("newloginemail")
	s.passwordMinSize = r.GetInt("passwordminsize")
	s.userApproval = r.GetBool("userapproval")
	s.rolesummary = rank.FromSlice(r.GetStrSlice("rolesummary"))

	s.emailurlbase = r.GetStr("email.url.base")
	if t, err := htmlTemplate.New("email.url.emailchange").Parse(r.GetStr("email.url.emailchange")); err != nil {
		return governor.NewError("Failed to parse email change url template", http.StatusBadRequest, err)
	} else {
		s.tplemailchange = t
	}
	if t, err := htmlTemplate.New("email.url.forgotpass").Parse(r.GetStr("email.url.forgotpass")); err != nil {
		return governor.NewError("Failed to parse forgot pass url template", http.StatusBadRequest, err)
	} else {
		s.tplforgotpass = t
	}
	if t, err := htmlTemplate.New("email.url.newuser").Parse(r.GetStr("email.url.newuser")); err != nil {
		return governor.NewError("Failed to parse new user url template", http.StatusBadRequest, err)
	} else {
		s.tplnewuser = t
	}

	l.Info("loaded config", map[string]string{
		"accesstime (s)":        strconv.FormatInt(s.accessTime, 10),
		"refreshtime (s)":       strconv.FormatInt(s.refreshTime, 10),
		"refreshcache (s)":      strconv.FormatInt(s.refreshCacheTime, 10),
		"confirmtime (s)":       strconv.FormatInt(s.confirmTime, 10),
		"passwordresettime (s)": strconv.FormatInt(s.passwordResetTime, 10),
		"invitationtime (s)":    strconv.FormatInt(s.invitationTime, 10),
		"usercachetime (s)":     strconv.FormatInt(s.userCacheTime, 10),
		"newloginemail":         strconv.FormatBool(s.newLoginEmail),
		"passwordminsize":       strconv.Itoa(s.passwordMinSize),
		"issuer":                r.GetStr("issuer"),
		"userapproval":          strconv.FormatBool(s.userApproval),
		"rolesummary":           s.rolesummary.String(),
		"tplemailchange":        r.GetStr("email.url.emailchange"),
		"tplforgotpass":         r.GetStr("email.url.forgotpass"),
		"tplnewuser":            r.GetStr("email.url.newuser"),
	})

	sr := s.router()
	sr.mountRoute(m.Group("/user"))
	sr.mountAuth(m.Group(authRoutePrefix))
	sr.mountApikey(m.Group("/apikey"))
	l.Info("mounted http routes", nil)
	return nil
}

func (s *service) Setup(req governor.ReqSetup) error {
	l := s.logger.WithData(map[string]string{
		"phase": "setup",
	})

	madmin, err := s.users.New(req.Username, req.Password, req.Email, req.Firstname, req.Lastname)
	if err != nil {
		return err
	}

	if err := s.users.Setup(); err != nil {
		return err
	}
	l.Info("created user table", nil)

	if err := s.sessions.Setup(); err != nil {
		return err
	}
	l.Info("created usersession table", nil)

	if err := s.approvals.Setup(); err != nil {
		return err
	}
	l.Info("created userapprovals table", nil)

	if err := s.invitations.Setup(); err != nil {
		return err
	}
	l.Info("created userroleinvitations table", nil)

	if err := s.users.Insert(madmin); err != nil {
		return err
	}
	if err := s.roles.InsertRoles(madmin.Userid, rank.Admin()); err != nil {
		return err
	}
	l.Info("inserted new setup admin", map[string]string{
		"username": madmin.Username,
		"userid":   madmin.Userid,
	})
	return nil
}

func (s *service) Start(ctx context.Context) error {
	return nil
}

func (s *service) Stop(ctx context.Context) {
}

func (s *service) Health() error {
	return nil
}
<commit_msg>Add userresets setup to user setup<commit_after>package user

import (
	"context"
	htmlTemplate "html/template"
	"net/http"
	"strconv"
	"time"
	"xorkevin.dev/governor"
	"xorkevin.dev/governor/service/kvstore"
	"xorkevin.dev/governor/service/mail"
	"xorkevin.dev/governor/service/msgqueue"
	"xorkevin.dev/governor/service/user/apikey"
	"xorkevin.dev/governor/service/user/approval/model"
	"xorkevin.dev/governor/service/user/gate"
	"xorkevin.dev/governor/service/user/model"
	"xorkevin.dev/governor/service/user/reset/model"
	"xorkevin.dev/governor/service/user/role"
	"xorkevin.dev/governor/service/user/role/invitation/model"
	"xorkevin.dev/governor/service/user/session/model"
	"xorkevin.dev/governor/service/user/token"
	"xorkevin.dev/governor/util/rank"
)

const (
	authRoutePrefix = "/auth"
)

const (
	// NewUserQueueID is emitted when a new user is created
	NewUserQueueID = "gov.user.new"
	// DeleteUserQueueID is emitted when a user is deleted
	DeleteUserQueueID = "gov.user.delete"
)

const (
	time5m     int64 = int64(5 * time.Minute / time.Second)
	time24h    int64 = int64(24 * time.Hour / time.Second)
	time6month int64 = time24h * 365 / 2
)

type (
	// User is a user management service
	User interface {
		GetByID(userid string) (*ResUserGet, error)
		CheckUserExists(userid string) (bool, error)
	}

	Service interface {
		governor.Service
		User
	}

	service struct {
		users             usermodel.Repo
		sessions          sessionmodel.Repo
		approvals         approvalmodel.Repo
		invitations       invitationmodel.Repo
		resets            resetmodel.Repo
		roles             role.Role
		apikeys           apikey.Apikey
		kvusers           kvstore.KVStore
		kvsessions        kvstore.KVStore
		queue             msgqueue.Msgqueue
		mailer            mail.Mail
		gate              gate.Gate
		tokenizer         token.Tokenizer
		logger            governor.Logger
		baseURL           string
		authURL           string
		accessTime        int64
		refreshTime       int64
		refreshCacheTime  int64
		confirmTime       int64
		passwordResetTime int64
		invitationTime    int64
		userCacheTime     int64
		newLoginEmail     bool
		passwordMinSize   int
		userApproval      bool
		rolesummary       rank.Rank
		emailurlbase      string
		tplemailchange    *htmlTemplate.Template
		tplforgotpass     *htmlTemplate.Template
		tplnewuser        *htmlTemplate.Template
	}

	router struct {
		s service
	}

	// NewUserProps are properties of a newly created user
	NewUserProps struct {
		Userid       string `json:"userid"`
		Username     string `json:"username"`
		Email        string `json:"email"`
		FirstName    string `json:"first_name"`
		LastName     string `json:"last_name"`
		CreationTime int64  `json:"creation_time"`
	}

	// DeleteUserProps are properties of a deleted user
	DeleteUserProps struct {
		Userid string `json:"userid"`
	}

	ctxKeyUser struct{}
)

// GetCtxUser returns a User service from the context
func GetCtxUser(inj governor.Injector) User {
	v := inj.Get(ctxKeyUser{})
	if v == nil {
		return nil
	}
	return v.(User)
}

// setCtxUser sets a User service in the context
func setCtxUser(inj governor.Injector, u User) {
	inj.Set(ctxKeyUser{}, u)
}

// NewCtx creates a new User service from a context
func NewCtx(inj governor.Injector) Service {
	users := usermodel.GetCtxRepo(inj)
	sessions := sessionmodel.GetCtxRepo(inj)
	approvals := approvalmodel.GetCtxRepo(inj)
	invitations := invitationmodel.GetCtxRepo(inj)
	resets := resetmodel.GetCtxRepo(inj)
	roles := role.GetCtxRole(inj)
	apikeys := apikey.GetCtxApikey(inj)
	kv := kvstore.GetCtxKVStore(inj)
	queue := msgqueue.GetCtxMsgqueue(inj)
	mailer := mail.GetCtxMail(inj)
	tokenizer := token.GetCtxTokenizer(inj)
	g := gate.GetCtxGate(inj)

	return New(
		users,
		sessions,
		approvals,
		invitations,
		resets,
		roles,
		apikeys,
		kv,
		queue,
		mailer,
		tokenizer,
		g,
	)
}

// New creates a new User service
func New(
	users usermodel.Repo,
	sessions sessionmodel.Repo,
	approvals approvalmodel.Repo,
	invitations invitationmodel.Repo,
	resets resetmodel.Repo,
	roles role.Role,
	apikeys apikey.Apikey,
	kv kvstore.KVStore,
	queue msgqueue.Msgqueue,
	mailer mail.Mail,
	tokenizer token.Tokenizer,
	g gate.Gate,
) Service {
	return &service{
		users:             users,
		sessions:          sessions,
		approvals:         approvals,
		invitations:       invitations,
		resets:            resets,
		roles:             roles,
		apikeys:           apikeys,
		kvusers:           kv.Subtree("users"),
		kvsessions:        kv.Subtree("sessions"),
		queue:             queue,
		mailer:            mailer,
		gate:              g,
		tokenizer:         tokenizer,
		accessTime:        time5m,
		refreshTime:       time6month,
		refreshCacheTime:  time24h,
		confirmTime:       time24h,
		passwordResetTime: time24h,
		invitationTime:    time24h,
		userCacheTime:     time24h,
	}
}

func (s *service) Register(inj governor.Injector, r governor.ConfigRegistrar, jr governor.JobRegistrar) {
	setCtxUser(inj, s)

	r.SetDefault("accesstime", "5m")
	r.SetDefault("refreshtime", "4380h")
	r.SetDefault("refreshcache", "24h")
	r.SetDefault("confirmtime", "24h")
	r.SetDefault("passwordresettime", "24h")
	r.SetDefault("invitationtime", "24h")
	r.SetDefault("usercachetime", "24h")
	r.SetDefault("newloginemail", true)
	r.SetDefault("passwordminsize", 8)
	r.SetDefault("userapproval", false)
	r.SetDefault("rolesummary", []string{rank.TagUser, rank.TagAdmin})
	r.SetDefault("email.url.base", "http://localhost:8080")
	r.SetDefault("email.url.emailchange", "/a/confirm/email?key={{.Userid}}.{{.Key}}")
	r.SetDefault("email.url.forgotpass", "/x/resetpass?key={{.Userid}}.{{.Key}}")
	r.SetDefault("email.url.newuser", "/x/confirm?userid={{.Userid}}&key={{.Key}}")
}

func (s *service) router() *router {
	return &router{
		s: *s,
	}
}

func (s *service) Init(ctx context.Context, c governor.Config, r governor.ConfigReader, l governor.Logger, m governor.Router) error {
	s.logger = l
	l = s.logger.WithData(map[string]string{
		"phase": "init",
	})

	s.baseURL = c.BaseURL
	s.authURL = c.BaseURL + r.URL() + authRoutePrefix
	if t, err := time.ParseDuration(r.GetStr("accesstime")); err != nil {
		return governor.NewError("Failed to parse access time", http.StatusBadRequest, err)
	} else {
		s.accessTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("refreshtime")); err != nil {
		return governor.NewError("Failed to parse refresh time", http.StatusBadRequest, err)
	} else {
		s.refreshTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("refreshcache")); err != nil {
		return governor.NewError("Failed to parse refresh cache", http.StatusBadRequest, err)
	} else {
		s.refreshCacheTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("confirmtime")); err != nil {
		return governor.NewError("Failed to parse confirm time", http.StatusBadRequest, err)
	} else {
		s.confirmTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("passwordresettime")); err != nil {
		return governor.NewError("Failed to parse password reset time", http.StatusBadRequest, err)
	} else {
		s.passwordResetTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("invitationtime")); err != nil {
		return governor.NewError("Failed to parse role invitation time", http.StatusBadRequest, err)
	} else {
		s.invitationTime = int64(t / time.Second)
	}
	if t, err := time.ParseDuration(r.GetStr("usercachetime")); err != nil {
		return governor.NewError("Failed to parse user cache time", http.StatusBadRequest, err)
	} else {
		s.userCacheTime = int64(t / time.Second)
	}
	s.newLoginEmail = r.GetBool("newloginemail")
	s.passwordMinSize = r.GetInt("passwordminsize")
	s.userApproval = r.GetBool("userapproval")
	s.rolesummary = rank.FromSlice(r.GetStrSlice("rolesummary"))

	s.emailurlbase = r.GetStr("email.url.base")
	if t, err := htmlTemplate.New("email.url.emailchange").Parse(r.GetStr("email.url.emailchange")); err != nil {
		return governor.NewError("Failed to parse email change url template", http.StatusBadRequest, err)
	} else {
		s.tplemailchange = t
	}
	if t, err := htmlTemplate.New("email.url.forgotpass").Parse(r.GetStr("email.url.forgotpass")); err != nil {
		return governor.NewError("Failed to parse forgot pass url template", http.StatusBadRequest, err)
	} else {
		s.tplforgotpass = t
	}
	if t, err := htmlTemplate.New("email.url.newuser").Parse(r.GetStr("email.url.newuser")); err != nil {
		return governor.NewError("Failed to parse new user url template", http.StatusBadRequest, err)
	} else {
		s.tplnewuser = t
	}

	l.Info("loaded config", map[string]string{
		"accesstime (s)":        strconv.FormatInt(s.accessTime, 10),
		"refreshtime (s)":       strconv.FormatInt(s.refreshTime, 10),
		"refreshcache (s)":      strconv.FormatInt(s.refreshCacheTime, 10),
		"confirmtime (s)":       strconv.FormatInt(s.confirmTime, 10),
		"passwordresettime (s)": strconv.FormatInt(s.passwordResetTime, 10),
		"invitationtime (s)":    strconv.FormatInt(s.invitationTime, 10),
		"usercachetime (s)":     strconv.FormatInt(s.userCacheTime, 10),
		"newloginemail":         strconv.FormatBool(s.newLoginEmail),
		"passwordminsize":       strconv.Itoa(s.passwordMinSize),
		"issuer":                r.GetStr("issuer"),
		"userapproval":          strconv.FormatBool(s.userApproval),
		"rolesummary":           s.rolesummary.String(),
		"tplemailchange":        r.GetStr("email.url.emailchange"),
		"tplforgotpass":         r.GetStr("email.url.forgotpass"),
		"tplnewuser":            r.GetStr("email.url.newuser"),
	})

	sr := s.router()
	sr.mountRoute(m.Group("/user"))
	sr.mountAuth(m.Group(authRoutePrefix))
	sr.mountApikey(m.Group("/apikey"))
	l.Info("mounted http routes", nil)
	return nil
}

func (s *service) Setup(req governor.ReqSetup) error {
	l := s.logger.WithData(map[string]string{
		"phase": "setup",
	})

	madmin, err := s.users.New(req.Username, req.Password, req.Email, req.Firstname, req.Lastname)
	if err != nil {
		return err
	}

	if err := s.users.Setup(); err != nil {
		return err
	}
	l.Info("created user table", nil)

	if err := s.sessions.Setup(); err != nil {
		return err
	}
	l.Info("created usersessions table", nil)

	if err := s.approvals.Setup(); err != nil {
		return err
	}
	l.Info("created userapprovals table", nil)

	if err := s.invitations.Setup(); err != nil {
		return err
	}
	l.Info("created userroleinvitations table", nil)

	if err := s.resets.Setup(); err != nil {
		return err
	}
	l.Info("created userresets table", nil)

	if err := s.users.Insert(madmin); err != nil {
		return err
	}
	if err := s.roles.InsertRoles(madmin.Userid, rank.Admin()); err != nil {
		return err
	}
	l.Info("inserted new setup admin", map[string]string{
		"username": madmin.Username,
		"userid":   madmin.Userid,
	})
	return nil
}

func (s *service) Start(ctx context.Context) error {
	return nil
}

func (s *service) Stop(ctx context.Context) {
}

func (s *service) Health() error {
	return nil
}
<|endoftext|>


<commit_before>/*
The following AWS services are available:

- Auto Scaling
- Amazon CloudWatch
- Amazon Cognito
- AWS Data Pipeline
- Amazon DynamoDB
- Amazon EC2
- Amazon Kinesis
- Amazon Simple Storage Service (S3)
- Amazon Simple Email Service (SES)
- Amazon SimpleDB
- Amazon Simple Notification Service (SNS)
- Amazon Simple Queue Service (SQS)
- Amazon Simple Workflow Service (SWF)
*/
package services

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"github.com/twhello/aws-to-go/interfaces"
	"io"
	"log"
	"net/http"
	"sort"
	"strings"
	"time"
)

var httpClient = &http.Client{
	Transport: &http.Transport{
		MaxIdleConnsPerHost:   25,
		ResponseHeaderTimeout: 30 * time.Second,
	},
}

// Returns the shared http.Client.
// Default settings: 25 MaxIdleConnsPerHost and 30 second ResponseHeaderTimeout.
func HttpClient() *http.Client {
	configSetting.m.RLock()
	defer configSetting.m.RUnlock()
	return httpClient
}

// Submits the signed request to AWS and, if not nil, unmarshals the XML
// response to the 'dto' interface, or returns an error or ServiceError.
func DoRequest(awsreq interfaces.IAWSRequest, dto interface{}, eval *EvalServiceResponse) (resp *http.Response, err interfaces.IServiceError) {

	config := Config()
	isDebug := config.IsDebugging()
	resp = nil
	err = nil
	req := awsreq.BuildRequest()

	if isDebug {
		log.Printf("\nREQUEST  > %+v \n", req)
	}

	RETRY_ATTEMPTS := config.RetryAttempts()
	retries := uint(0)

RETRY:

	resp, e := HttpClient().Do(req)
	if e != nil {
		err = NewServiceError(100, "100 HTTP Error", "", e.Error())
		return nil, err
	}

	resp, err = evalResponse(resp, eval)

	if isDebug {
		log.Printf("\nRESPONSE > %+v \n", resp)
	}

	if err == nil {

		if dto != nil {
			defer resp.Body.Close()
			e := eval.Decode(resp.Body, dto)
			if e != nil {
				err = NewServiceError(101, "101 IO Read Error", "Decode", e.Error())
			}
		}

	} else {

		if isDebug {
			log.Printf("\nERROR    > %+v \n", err)
		}

		if err.IsRetry() && retries < RETRY_ATTEMPTS {

			if isDebug {
				log.Printf("\nRETRY   > %s of %s in %s milliseconds.\n", (retries + 1), RETRY_ATTEMPTS, (1 << retries * 100))
			}
			time.Sleep(time.Millisecond * (1 << retries * 100))
			retries++
			goto RETRY
		}
	}

	return
}

func evalResponse(response *http.Response, eval *EvalServiceResponse) (*http.Response, interfaces.IServiceError) {

	if response.StatusCode >= 400 {

		srvErr := NewServiceError(response.StatusCode, response.Status, "", "")
		eval.Decode(response.Body, srvErr)
		response.Body.Close()
		srvErr.SetRetry(eval.Matches(response.StatusCode, srvErr.ErrorType()))

		return response, srvErr
	}

	return response, nil
}

/*****************************************************************************/

// A collection of retriable codes and errors.
type EvalServiceResponse struct {
	Codes   []int
	Errors  []string
	Decoder func(io.Reader, interface{}) error
}

// Creates the default EvalServiceResponse for XML responses:
//	services.NewEvalServiceResponse(
//		func(r io.Reader, v interface{})error { return xml.NewDecoder(r).Decode(v) },
//		[]int{500, 503},
//		[]string{"Throttling"},
//	)
func NewEvalXmlServiceResponse() *EvalServiceResponse {
	return NewEvalServiceResponse(
		func(r io.Reader, v interface{}) error { return xml.NewDecoder(r).Decode(v) },
		[]int{500, 503},
		[]string{"Throttling"},
	)
}

// Creates the default EvalServiceResponse for JSON responses:
//	services.NewEvalServiceResponse(
//		func(r io.Reader, v interface{})error { return json.NewDecoder(r).Decode(v) },
//		[]int{500, 503},
//		[]string{"Throttling"},
//	)
func NewEvalJsonServiceResponse() *EvalServiceResponse {
	return NewEvalServiceResponse(
		func(r io.Reader, v interface{}) error { return json.NewDecoder(r).Decode(v) },
		[]int{500, 503},
		[]string{"Throttling"},
	)
}

// Creates a new EvalServiceResponse struct.
func NewEvalServiceResponse(decoder func(io.Reader, interface{}) error, codes []int, errors []string) *EvalServiceResponse {
	sort.Ints(codes)
	sort.Strings(errors)
	return &EvalServiceResponse{codes, errors, decoder}
}

// Decodes the service response.Body into the given response struct.
func (e EvalServiceResponse) Decode(r io.Reader, v interface{}) error {
	return e.Decoder(r, v)
}

// Returns true if the collection contains the code or error.
// Note: Errors match using strings.Contains().
func (r *EvalServiceResponse) Matches(code int, errorType string) bool {

	if r.Codes != nil {
		for _, v := range r.Codes {
			if v == code {
				return true
			}
		}
	}

	if r.Errors != nil {
		for _, e := range r.Errors {
			if strings.Contains(errorType, e) {
				return true
			}
		}
	}

	return false
}

/*****************************************************************************/

// General Service Error.
type ServiceError struct {
	ErrCode    int    `xml:"-" json:"-"`
	ErrStatus  string `xml:"-" json:"-"`
	ErrType    string `xml:"Error>Code" json:"__type"`
	ErrMessage string `xml:"Error>Message" json:"message"`
	isRetry    bool
}

// Creates a new ServiceError.
func NewServiceError(code int, status, errType, errMessage string) *ServiceError {
	return &ServiceError{code, status, errType, errMessage, false}
}

func (err ServiceError) SetRetry(val bool) {
	err.isRetry = val
}

func (err ServiceError) Code() int {
	return err.ErrCode
}

func (err ServiceError) Status() string {
	return err.ErrStatus
}

func (err ServiceError) ErrorType() string {
	return err.ErrType
}

func (err ServiceError) ErrorMessage() string {
	return err.ErrMessage
}

func (err ServiceError) IsRetry() bool {
	return err.isRetry
}

func (err ServiceError) Error() string {
	return fmt.Sprintf("Code: %d, Status: %s, Type: %s, Message: %s, Retry: %s \n",
		err.ErrCode, err.ErrStatus, err.ErrType, err.ErrMessage, err.isRetry)
}
<commit_msg>Log info.<commit_after>/*
The following AWS services are available:

- Auto Scaling
- Amazon CloudWatch
- Amazon Cognito
- AWS Data Pipeline
- Amazon DynamoDB
- Amazon EC2
- Amazon Kinesis
- Amazon Simple Storage Service (S3)
- Amazon Simple Email Service (SES)
- Amazon SimpleDB
- Amazon Simple Notification Service (SNS)
- Amazon Simple Queue Service (SQS)
- Amazon Simple Workflow Service (SWF)
*/
package services

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"github.com/twhello/aws-to-go/interfaces"
	"io"
	"log"
	"net/http"
	"sort"
	"strings"
	"time"
)

var httpClient = &http.Client{
	Transport: &http.Transport{
		MaxIdleConnsPerHost:   25,
		ResponseHeaderTimeout: 30 * time.Second,
	},
}

// Returns the shared http.Client.
// Default settings: 25 MaxIdleConnsPerHost and 30 second ResponseHeaderTimeout.
func HttpClient() *http.Client {
	configSetting.m.RLock()
	defer configSetting.m.RUnlock()
	return httpClient
}

// Submits the signed request to AWS and, if not nil, unmarshals the XML
// response to the 'dto' interface, or returns an error or ServiceError.
func DoRequest(awsreq interfaces.IAWSRequest, dto interface{}, eval *EvalServiceResponse) (resp *http.Response, err interfaces.IServiceError) {

	config := Config()
	isDebug := config.IsDebugging()
	resp = nil
	err = nil
	req := awsreq.BuildRequest()

	if isDebug {
		log.Printf("\nREQUEST  > %+v \n", req)
	}

	RETRY_ATTEMPTS := config.RetryAttempts()
	retries := uint(0)

RETRY:

	resp, e := HttpClient().Do(req)
	if e != nil {
		err = NewServiceError(100, "100 HTTP Error", "", e.Error())
		return nil, err
	}

	resp, err = evalResponse(resp, eval)

	if isDebug {
		log.Printf("\nRESPONSE > %+v \n", resp)
	}

	if err == nil {

		if dto != nil {
			defer resp.Body.Close()
			e := eval.Decode(resp.Body, dto)
			if e != nil {
				err = NewServiceError(101, "101 IO Read Error", "Decode", e.Error())
			}
		}

	} else {

		if isDebug {
			log.Printf("\nERROR    > %+v \n", err)
		}

		if err.IsRetry() && retries < RETRY_ATTEMPTS {

			if isDebug {
				log.Printf("\nRETRY   > %s of %s in %s milliseconds.\n", (retries + 1), RETRY_ATTEMPTS, (1 << retries * 100))
			}
			time.Sleep(time.Millisecond * (1 << retries * 100))
			retries++
			goto RETRY
		}
	}

	return
}

func evalResponse(response *http.Response, eval *EvalServiceResponse) (*http.Response, interfaces.IServiceError) {

	if response.StatusCode >= 400 {

		srvErr := NewServiceError(response.StatusCode, response.Status, "", "")
		eval.Decode(response.Body, srvErr)
		response.Body.Close()
		srvErr.SetRetry(eval.Matches(response.StatusCode, srvErr.ErrorType()))

		return response, srvErr
	}

	return response, nil
}

/*****************************************************************************/

// A collection of retriable codes and errors.
type EvalServiceResponse struct {
	Codes   []int
	Errors  []string
	Decoder func(io.Reader, interface{}) error
}

// Creates the default EvalServiceResponse for XML responses:
//	services.NewEvalServiceResponse(
//		func(r io.Reader, v interface{})error { return xml.NewDecoder(r).Decode(v) },
//		[]int{500, 503},
//		[]string{"Throttling"},
//	)
func NewEvalXmlServiceResponse() *EvalServiceResponse {
	return NewEvalServiceResponse(
		func(r io.Reader, v interface{}) error { return xml.NewDecoder(r).Decode(v) },
		[]int{500, 503},
		[]string{"Throttling"},
	)
}

// Creates the default EvalServiceResponse for JSON responses:
//	services.NewEvalServiceResponse(
//		func(r io.Reader, v interface{})error { return json.NewDecoder(r).Decode(v) },
//		[]int{500, 503},
//		[]string{"Throttling"},
//	)
func NewEvalJsonServiceResponse() *EvalServiceResponse {
	return NewEvalServiceResponse(
		func(r io.Reader, v interface{}) error { return json.NewDecoder(r).Decode(v) },
		[]int{500, 503},
		[]string{"Throttling"},
	)
}

// Creates a new EvalServiceResponse struct.
func NewEvalServiceResponse(decoder func(io.Reader, interface{}) error, codes []int, errors []string) *EvalServiceResponse {
	sort.Ints(codes)
	sort.Strings(errors)
	return &EvalServiceResponse{codes, errors, decoder}
}

// Decodes the service response.Body into the given response struct.
func (e EvalServiceResponse) Decode(r io.Reader, v interface{}) error {
	return e.Decoder(r, v)
}

// Returns true if the collection contains the code or error.
// Note: Errors match using strings.Contains().
func (r *EvalServiceResponse) Matches(code int, errorType string) bool {

	if r.Codes != nil {
		for _, v := range r.Codes {
			if v == code {
				return true
			}
		}
	}

	log.Printf(">>> Errors: %s %s", code, errorType)
	if r.Errors != nil {
		log.Printf(">>> %s \n", r.Errors)
		for _, e := range r.Errors {
			log.Printf(">>> strings.Contains(%s, %s) = %s", errorType, e, strings.Contains(errorType, e))
			if strings.Contains(errorType, e) {
				return true
			}
		}
	}

	return false
}

/*****************************************************************************/

// General Service Error.
type ServiceError struct {
	ErrCode    int    `xml:"-" json:"-"`
	ErrStatus  string `xml:"-" json:"-"`
	ErrType    string `xml:"Error>Code" json:"__type"`
	ErrMessage string `xml:"Error>Message" json:"message"`
	isRetry    bool
}

// Creates a new ServiceError.
func NewServiceError(code int, status, errType, errMessage string) *ServiceError {
	return &ServiceError{code, status, errType, errMessage, false}
}

func (err ServiceError) SetRetry(val bool) {
	err.isRetry = val
}

func (err ServiceError) Code() int {
	return err.ErrCode
}

func (err ServiceError) Status() string {
	return err.ErrStatus
}

func (err ServiceError) ErrorType() string {
	return err.ErrType
}

func (err ServiceError) ErrorMessage() string {
	return err.ErrMessage
}

func (err ServiceError) IsRetry() bool {
	return err.isRetry
}

func (err ServiceError) Error() string {
	return fmt.Sprintf("Code: %d, Status: %s, Type: %s, Message: %s, Retry: %s \n",
		err.ErrCode, err.ErrStatus, err.ErrType, err.ErrMessage, err.isRetry)
}
<|endoftext|>


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

package main

import (
	"fmt"
	"log"
	"path"
	"sort"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/minio-io/mc/pkg/s3"
)

const (
	recvFormat  = "2006-01-02T15:04:05.000Z"
	printFormat = "2006-01-02 15:04:05"
)

const (
	delimiter = "/"
)

func parseTime(t string) string {
	ti, _ := time.Parse(recvFormat, t)
	return ti.Format(printFormat)
}

func parseLastModified(t string) string {
	ti, _ := time.Parse(time.RFC1123, t)
	return ti.Format(printFormat)
}

func printBuckets(v []*s3.Bucket) {
	for _, b := range v {
		fmt.Printf("%s   %s\n", parseTime(b.CreationDate), b.Name)
	}
}

func printObjects(v []*s3.Item) {
	if len(v) > 0 {
		sort.Sort(s3.BySize(v))
		for _, b := range v {
			fmt.Printf("%s   %d %s\n", parseTime(b.LastModified), b.Size, b.Key)
		}
	}
}

func printPrefixes(v []*s3.Prefix) {
	if len(v) > 0 {
		for _, b := range v {
			fmt.Printf("                      PRE %s\n", b.Prefix)
		}
	}
}

func printObject(v int64, date, key string) {
	fmt.Printf("%s   %d %s\n", parseLastModified(date), v, key)
}

func getBucketAndObject(p string) (bucket, object string) {
	i := strings.Index(p, delimiter)
	if i == -1 {
		bucket = p
	} else if i != -1 {
		bucket = p[:i]
		object = path.Base(p)
		if bucket == object {
			object = ""
		}
	}
	return
}

func doFsList(c *cli.Context) {
	var err error
	var auth *s3.Auth
	var items []*s3.Item
	var prefixes []*s3.Prefix

	auth, err = getAWSEnvironment()
	if err != nil {
		log.Fatal(err)
	}

	var buckets []*s3.Bucket
	s3c := s3.NewS3Client(auth)
	switch len(c.Args()) {
	case 1:
		input := c.Args().Get(0)
		if path.IsAbs(input) {
			log.Fatal("Invalid bucket style")
		}
		bucket, object := getBucketAndObject(input)
		if object == "" {
			items, prefixes, err = s3c.GetBucket(bucket, "", "", "/", s3.MAX_OBJECT_LIST)
			if err != nil {
				log.Fatal(err)
			}
			printPrefixes(prefixes)
			printObjects(items)
		} else {
			var date string
			var size int64
			size, date, err = s3c.Stat(object, bucket)
			if err != nil {
				items, prefixes, err = s3c.GetBucket(bucket, "", object, "/", s3.MAX_OBJECT_LIST)
				if err != nil {
					log.Fatal(err)
				}
				printPrefixes(prefixes)
				printObjects(items)
			} else {
				printObject(size, date, object)
			}
		}
	default:
		buckets, err = s3c.Buckets()
		if err != nil {
			log.Fatal(err)
		}
		printBuckets(buckets)
	}
}
<commit_msg>Simplify path prefix and delimiters<commit_after>/*
 * Mini Object Storage, (C) 2014,2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"path"
	"sort"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/minio-io/mc/pkg/s3"
)

const (
	recvFormat  = "2006-01-02T15:04:05.000Z"
	printFormat = "2006-01-02 15:04:05"
)

const (
	delimiter = '/'
)

func parseTime(t string) string {
	ti, _ := time.Parse(recvFormat, t)
	return ti.Format(printFormat)
}

func parseLastModified(t string) string {
	ti, _ := time.Parse(time.RFC1123, t)
	return ti.Format(printFormat)
}

func printBuckets(v []*s3.Bucket) {
	for _, b := range v {
		fmt.Printf("%s   %s\n", parseTime(b.CreationDate), b.Name)
	}
}

func printObjects(v []*s3.Item) {
	if len(v) > 0 {
		sort.Sort(s3.BySize(v))
		for _, b := range v {
			fmt.Printf("%s   %d %s\n", parseTime(b.LastModified), b.Size, b.Key)
		}
	}
}

func printPrefixes(v []*s3.Prefix) {
	if len(v) > 0 {
		for _, b := range v {
			fmt.Printf("                      PRE %s\n", b.Prefix)
		}
	}
}

func printObject(v int64, date, key string) {
	fmt.Printf("%s   %d %s\n", parseLastModified(date), v, key)
}

func getBucketAndObject(p string) (bucket, object string) {
	readBuffer := bytes.NewBufferString(p)
	reader := bufio.NewReader(readBuffer)
	pathPrefix, _ := reader.ReadString(byte(delimiter))
	bucket = path.Clean(pathPrefix)
	object = strings.TrimPrefix(p, pathPrefix)
	// if object is equal to bucket, set object to be empty
	if path.Clean(object) == bucket {
		object = ""
		return
	}
	return
}

func doFsList(c *cli.Context) {
	var err error
	var auth *s3.Auth
	var items []*s3.Item
	var prefixes []*s3.Prefix

	auth, err = getAWSEnvironment()
	if err != nil {
		log.Fatal(err)
	}

	var buckets []*s3.Bucket
	s3c := s3.NewS3Client(auth)
	switch len(c.Args()) {
	case 1:
		input := c.Args().Get(0)
		if path.IsAbs(input) {
			log.Fatal("Invalid bucket style")
		}
		bucket, object := getBucketAndObject(input)
		if object == "" {
			items, prefixes, err = s3c.GetBucket(bucket, "", "", string(delimiter), s3.MAX_OBJECT_LIST)
			if err != nil {
				log.Fatal(err)
			}
			printPrefixes(prefixes)
			printObjects(items)
		} else {
			var date string
			var size int64
			size, date, err = s3c.Stat(object, bucket)
			if err != nil {
				items, prefixes, err = s3c.GetBucket(bucket, "", object, string(delimiter), s3.MAX_OBJECT_LIST)
				if err != nil {
					log.Fatal(err)
				}
				printPrefixes(prefixes)
				printObjects(items)
			} else {
				printObject(size, date, object)
			}
		}
	default:
		buckets, err = s3c.Buckets()
		if err != nil {
			log.Fatal(err)
		}
		printBuckets(buckets)
	}
}
<|endoftext|>


<commit_before>package settings

import (
	"encoding/json"
	"os"
)

// GetSettings attempts to retrieve
// application settings from a config file
func GetSettings() (p PaoSettings, e error) {
	p = PaoSettings{}
	file, err := os.Open("conf/paoSettings.json")
	if err != nil {
		e = err
		return
	}
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&p)
	if err != nil {
		e = err
	}
	return
}

type dbConfig struct {
	Driver, ConnectionString string
}
type authConfig struct {
	EncryptionKey string
}

// PaoSettings for pao as specified
// in conf/paoSettings.json
type PaoSettings struct {
	DbConfig   dbConfig
	AuthConfig authConfig
}
<commit_msg>Try to get config file from environment variable.<commit_after>package settings

import (
	"encoding/json"
	"os"
)

// GetSettings attempts to retrieve
// application settings from a config file
func GetSettings() (p PaoSettings, e error) {
	p = PaoSettings{}
	path := os.Getenv("PAO_CONF")
	if path == "" {
		path = "conf/paoSettings.json"
	}
	file, err := os.Open(path)
	if err != nil {
		e = err
		return
	}
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&p)
	if err != nil {
		e = err
	}
	return
}

type dbConfig struct {
	Driver, ConnectionString string
}
type authConfig struct {
	EncryptionKey string
}

// PaoSettings for pao as specified
// in conf/paoSettings.json
type PaoSettings struct {
	DbConfig   dbConfig
	AuthConfig authConfig
}
<|endoftext|>


<commit_before>package gfmxr

import (
	"fmt"
	"os"
	"regexp"
	"strings"
)

var (
	DefaultFrobs = map[string]Frob{
		"bash":       NewSimpleInterpretedFrob("bash", "bash"),
		"go":         &GoFrob{},
		"java":       &JavaFrob{},
		"javascript": NewSimpleInterpretedFrob("js", "node"),
		"json":       NewSimpleInterpretedFrob("json", "node"),
		"python":     NewSimpleInterpretedFrob("py", "python"),
		"ruby":       NewSimpleInterpretedFrob("rb", "ruby"),
		"shell":      NewSimpleInterpretedFrob("bash", "bash"),
		"sh":         NewSimpleInterpretedFrob("sh", "sh"),
		"zsh":        NewSimpleInterpretedFrob("zsh", "zsh"),
	}

	errEmptySource = fmt.Errorf("empty source")

	javaPublicClassRe = regexp.MustCompile("public +class +([^ ]+)")
)

type Frob interface {
	Extension() string
	CanExecute(*Runnable) error
	TempFileName(*Runnable) string
	Environ(*Runnable) []string
	Commands(*Runnable) []*command
}

type command struct {
	Main bool
	Args []string
}

func NewSimpleInterpretedFrob(ext, interpreter string) Frob {
	return &InterpretedFrob{
		ext:  ext,
		env:  []string{},
		tmpl: []string{interpreter, "--", "{{.FILE}}"},
	}
}

type InterpretedFrob struct {
	ext  string
	env  []string
	tmpl []string
}

func (e *InterpretedFrob) Extension() string {
	return e.ext
}

func (e *InterpretedFrob) CanExecute(rn *Runnable) error {
	if len(rn.Lines) < 1 {
		return errEmptySource
	}
	return nil
}

func (e *InterpretedFrob) TempFileName(_ *Runnable) string {
	return fmt.Sprintf("example.%s", e.ext)
}

func (e *InterpretedFrob) Environ(_ *Runnable) []string {
	return e.env
}

func (e *InterpretedFrob) Commands(_ *Runnable) []*command {
	return []*command{
		&command{
			Main: true,
			Args: e.tmpl,
		},
	}
}

type GoFrob struct{}

func (e *GoFrob) Extension() string {
	return "go"
}

func (e *GoFrob) TempFileName(_ *Runnable) string {
	return "example.go"
}

func (e *GoFrob) CanExecute(rn *Runnable) error {
	if len(rn.Lines) < 1 {
		return errEmptySource
	}

	trimmedLine0 := strings.TrimSpace(rn.Lines[0])

	if trimmedLine0 != "package main" {
		return fmt.Errorf("first line is not \"package main\": %q", trimmedLine0)
	}

	return nil
}

func (e *GoFrob) Environ(_ *Runnable) []string {
	return []string{}
}

func (e *GoFrob) Commands(_ *Runnable) []*command {
	return []*command{
		&command{
			Args: []string{"go", "build", "-o", "{{.NAMEBASE}}" + os.Getenv("GOEXE"), "{{.FILE}}"},
		},
		&command{
			Main: true,
			Args: []string{"{{.NAMEBASE}}" + os.Getenv("GOEXE")},
		},
	}
}

type JavaFrob struct{}

func (e *JavaFrob) Extension() string {
	return "java"
}

func (e *JavaFrob) CanExecute(rn *Runnable) error {
	if len(rn.Lines) < 1 {
		return errEmptySource
	}

	for _, line := range rn.Lines {
		if javaPublicClassRe.MatchString(line) {
			return nil
		}
	}

	return fmt.Errorf("no public class found")
}

func (e *JavaFrob) TempFileName(rn *Runnable) string {
	return fmt.Sprintf("%s.java", e.getClassName(rn.String()))
}

func (e *JavaFrob) Environ(_ *Runnable) []string {
	return []string{}
}

func (e *JavaFrob) Commands(rn *Runnable) []*command {
	return []*command{
		&command{
			Args: []string{"javac", "{{.BASENAME}}"},
		},
		&command{
			Main: true,
			Args: []string{"java", e.getClassName(rn.String())},
		},
	}
}

func (e *JavaFrob) getClassName(source string) string {
	if m := javaPublicClassRe.FindStringSubmatch(source); len(m) > 1 {
		return m[1]
	}

	return "Unknown"
}
<commit_msg>Include '.exe' on windows betterer<commit_after>package gfmxr

import (
	"fmt"
	"regexp"
	"runtime"
	"strings"
)

var (
	DefaultFrobs = map[string]Frob{
		"bash":       NewSimpleInterpretedFrob("bash", "bash"),
		"go":         &GoFrob{},
		"java":       &JavaFrob{},
		"javascript": NewSimpleInterpretedFrob("js", "node"),
		"json":       NewSimpleInterpretedFrob("json", "node"),
		"python":     NewSimpleInterpretedFrob("py", "python"),
		"ruby":       NewSimpleInterpretedFrob("rb", "ruby"),
		"shell":      NewSimpleInterpretedFrob("bash", "bash"),
		"sh":         NewSimpleInterpretedFrob("sh", "sh"),
		"zsh":        NewSimpleInterpretedFrob("zsh", "zsh"),
	}

	errEmptySource = fmt.Errorf("empty source")

	javaPublicClassRe = regexp.MustCompile("public +class +([^ ]+)")
)

type Frob interface {
	Extension() string
	CanExecute(*Runnable) error
	TempFileName(*Runnable) string
	Environ(*Runnable) []string
	Commands(*Runnable) []*command
}

type command struct {
	Main bool
	Args []string
}

func NewSimpleInterpretedFrob(ext, interpreter string) Frob {
	return &InterpretedFrob{
		ext:  ext,
		env:  []string{},
		tmpl: []string{interpreter, "--", "{{.FILE}}"},
	}
}

type InterpretedFrob struct {
	ext  string
	env  []string
	tmpl []string
}

func (e *InterpretedFrob) Extension() string {
	return e.ext
}

func (e *InterpretedFrob) CanExecute(rn *Runnable) error {
	if len(rn.Lines) < 1 {
		return errEmptySource
	}
	return nil
}

func (e *InterpretedFrob) TempFileName(_ *Runnable) string {
	return fmt.Sprintf("example.%s", e.ext)
}

func (e *InterpretedFrob) Environ(_ *Runnable) []string {
	return e.env
}

func (e *InterpretedFrob) Commands(_ *Runnable) []*command {
	return []*command{
		&command{
			Main: true,
			Args: e.tmpl,
		},
	}
}

type GoFrob struct{}

func (e *GoFrob) Extension() string {
	return "go"
}

func (e *GoFrob) TempFileName(_ *Runnable) string {
	return "example.go"
}

func (e *GoFrob) CanExecute(rn *Runnable) error {
	if len(rn.Lines) < 1 {
		return errEmptySource
	}

	trimmedLine0 := strings.TrimSpace(rn.Lines[0])

	if trimmedLine0 != "package main" {
		return fmt.Errorf("first line is not \"package main\": %q", trimmedLine0)
	}

	return nil
}

func (e *GoFrob) Environ(_ *Runnable) []string {
	return []string{}
}

func (e *GoFrob) Commands(_ *Runnable) []*command {
	goExe := ""
	if runtime.GOOS == "windows" {
		goExe = ".exe"
	}

	return []*command{
		&command{
			Args: []string{"go", "build", "-o", "{{.NAMEBASE}}" + goExe, "{{.FILE}}"},
		},
		&command{
			Main: true,
			Args: []string{"{{.NAMEBASE}}" + goExe},
		},
	}
}

type JavaFrob struct{}

func (e *JavaFrob) Extension() string {
	return "java"
}

func (e *JavaFrob) CanExecute(rn *Runnable) error {
	if len(rn.Lines) < 1 {
		return errEmptySource
	}

	for _, line := range rn.Lines {
		if javaPublicClassRe.MatchString(line) {
			return nil
		}
	}

	return fmt.Errorf("no public class found")
}

func (e *JavaFrob) TempFileName(rn *Runnable) string {
	return fmt.Sprintf("%s.java", e.getClassName(rn.String()))
}

func (e *JavaFrob) Environ(_ *Runnable) []string {
	return []string{}
}

func (e *JavaFrob) Commands(rn *Runnable) []*command {
	return []*command{
		&command{
			Args: []string{"javac", "{{.BASENAME}}"},
		},
		&command{
			Main: true,
			Args: []string{"java", e.getClassName(rn.String())},
		},
	}
}

func (e *JavaFrob) getClassName(source string) string {
	if m := javaPublicClassRe.FindStringSubmatch(source); len(m) > 1 {
		return m[1]
	}

	return "Unknown"
}
<|endoftext|>


<commit_before>package xpath

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

// The XPath function list.

func predicate(q query) func(NodeNavigator) bool {
	type Predicater interface {
		Test(NodeNavigator) bool
	}
	if p, ok := q.(Predicater); ok {
		return p.Test
	}
	return func(NodeNavigator) bool { return true }
}

// positionFunc is a XPath Node Set functions position().
func positionFunc(q query, t iterator) interface{} {
	var (
		count = 1
		node  = t.Current()
	)
	test := predicate(q)
	for node.MoveToPrevious() {
		if test(node) {
			count++
		}
	}
	return float64(count)
}

// lastFunc is a XPath Node Set functions last().
func lastFunc(q query, t iterator) interface{} {
	var (
		count = 0
		node  = t.Current()
	)
	node.MoveToFirst()
	test := predicate(q)
	for {
		if test(node) {
			count++
		}
		if !node.MoveToNext() {
			break
		}
	}
	return float64(count)
}

// countFunc is a XPath Node Set functions count(node-set).
func countFunc(q query, t iterator) interface{} {
	var count = 0
	test := predicate(q)
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if test(node) {
				count++
			}
		}
	}
	return float64(count)
}

// sumFunc is a XPath Node Set functions sum(node-set).
func sumFunc(q query, t iterator) interface{} {
	var sum float64
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
				sum += v
			}
		}
	case float64:
		sum = typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("sum() function argument type must be a node-set or number"))
		}
		sum = v
	}
	return sum
}

func asNumber(t iterator, o interface{}) float64 {
	switch typ := o.(type) {
	case query:
		node := typ.Select(t)
		if node == nil {
			return float64(0)
		}
		if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
			return v
		}
	case float64:
		return typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("ceiling() function argument type must be a node-set or number"))
		}
		return v
	}
	return 0
}

// ceilingFunc is a XPath Node Set functions ceiling(node-set).
func ceilingFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Ceil(val)
}

// floorFunc is a XPath Node Set functions floor(node-set).
func floorFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Floor(val)
}

// roundFunc is a XPath Node Set functions round(node-set).
func roundFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Round(val)
}

// nameFunc is a XPath functions name([node-set]).
func nameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	ns := v.Prefix()
	if ns == "" {
		return v.LocalName()
	}
	return ns + ":" + v.LocalName()
}

// localNameFunc is a XPath functions local-name([node-set]).
func localNameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.LocalName()
}

// namespaceFunc is a XPath functions namespace-uri([node-set]).
func namespaceFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.Prefix()
}

func asBool(t iterator, v interface{}) bool {
	switch v := v.(type) {
	case nil:
		return false
	case *NodeIterator:
		return v.MoveNext()
	case bool:
		return bool(v)
	case float64:
		return v == 0
	case string:
		return v != ""
	case query:
		return v.Select(t) != nil
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

func asString(t iterator, v interface{}) string {
	switch v := v.(type) {
	case nil:
		return ""
	case bool:
		if v {
			return "true"
		}
		return "false"
	case float64:
		return strconv.FormatFloat(v, 'g', -1, 64)
	case string:
		return v
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

// booleanFunc is a XPath functions boolean([node-set]).
func booleanFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asBool(t, v)
}

// numberFunc is a XPath functions number([node-set]).
func numberFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asNumber(t, v)
}

// stringFunc is a XPath functions string([node-set]).
func stringFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asString(t, v)
}

// startwithFunc is a XPath functions starts-with(string, string).
func startwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("starts-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("starts-with() function argument type must be string"))
		}
		return strings.HasPrefix(m, n)
	}
}

// endwithFunc is a XPath functions ends-with(string, string).
func endwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("ends-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("ends-with() function argument type must be string"))
		}
		return strings.HasSuffix(m, n)
	}
}

// containsFunc is a XPath functions contains(string or @attr, string).
func containsFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)

		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("contains() function argument type must be string"))
		}

		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("contains() function argument type must be string"))
		}

		return strings.Contains(m, n)
	}
}

// normalizespaceFunc is XPath functions normalize-space(string?)
func normalizespaceFunc(q query, t iterator) interface{} {
	var m string
	switch typ := q.Evaluate(t).(type) {
	case string:
		m = typ
	case query:
		node := typ.Select(t)
		if node == nil {
			return false
		}
		m = node.Value()
	}
	return strings.TrimSpace(m)
}

// substringFunc is XPath functions substring function returns a part of a given string.
func substringFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var m string
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return ""
			}
			m = node.Value()
		}

		var start, length float64
		var ok bool

		if start, ok = arg2.Evaluate(t).(float64); !ok {
			panic(errors.New("substring() function first argument type must be int"))
		} else if start < 1 {
			panic(errors.New("substring() function first argument type must be >= 1"))
		}
		start--
		if arg3 != nil {
			if length, ok = arg3.Evaluate(t).(float64); !ok {
				panic(errors.New("substring() function second argument type must be int"))
			}
		}
		if (len(m) - int(start)) < int(length) {
			panic(errors.New("substring() function start and length argument out of range"))
		}
		if length > 0 {
			return m[int(start):int(length+start)]
		}
		return m[int(start):]
	}
}

// substringIndFunc is XPath functions substring-before/substring-after function returns a part of a given string.
func substringIndFunc(arg1, arg2 query, after bool) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var str string
		switch v := arg1.Evaluate(t).(type) {
		case string:
			str = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			str = node.Value()
		}
		var word string
		switch v := arg2.Evaluate(t).(type) {
		case string:
			word = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			word = node.Value()
		}
		if word == "" {
			return ""
		}

		i := strings.Index(str, word)
		if i < 0 {
			return ""
		}
		if after {
			return str[i+len(word):]
		}
		return str[:i]
	}
}

// stringLengthFunc is XPATH string-length( [string] ) function that returns a number
// equal to the number of characters in a given string.
func stringLengthFunc(arg1 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		switch v := arg1.Evaluate(t).(type) {
		case string:
			return float64(len(v))
		case query:
			node := v.Select(t)
			if node == nil {
				break
			}
			return float64(len(node.Value()))
		}
		return float64(0)
	}
}

// translateFunc is XPath functions translate() function returns a replaced string.
func translateFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		str := asString(t, arg1.Evaluate(t))
		src := asString(t, arg2.Evaluate(t))
		dst := asString(t, arg3.Evaluate(t))

		var replace []string
		for i, s := range src {
			d := ""
			if i < len(dst) {
				d = string(dst[i])
			}
			replace = append(replace, string(s), d)
		}
		return strings.NewReplacer(replace...).Replace(str)
	}
}

// notFunc is XPATH functions not(expression) function operation.
func notFunc(q query, t iterator) interface{} {
	switch v := q.Evaluate(t).(type) {
	case bool:
		return !v
	case query:
		node := v.Select(t)
		return node == nil
	default:
		return false
	}
}

// concatFunc is the concat function concatenates two or more
// strings and returns the resulting string.
// concat( string1 , string2 [, stringn]* )
func concatFunc(args ...query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var a []string
		for _, v := range args {
			switch v := v.Evaluate(t).(type) {
			case string:
				a = append(a, v)
			case query:
				node := v.Select(t)
				if node != nil {
					a = append(a, node.Value())
				}
			}
		}
		return strings.Join(a, "")
	}
}
<commit_msg>fix roundFunc(), compatible with Go<1.9x.<commit_after>package xpath

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

// The XPath function list.

func predicate(q query) func(NodeNavigator) bool {
	type Predicater interface {
		Test(NodeNavigator) bool
	}
	if p, ok := q.(Predicater); ok {
		return p.Test
	}
	return func(NodeNavigator) bool { return true }
}

// positionFunc is a XPath Node Set functions position().
func positionFunc(q query, t iterator) interface{} {
	var (
		count = 1
		node  = t.Current()
	)
	test := predicate(q)
	for node.MoveToPrevious() {
		if test(node) {
			count++
		}
	}
	return float64(count)
}

// lastFunc is a XPath Node Set functions last().
func lastFunc(q query, t iterator) interface{} {
	var (
		count = 0
		node  = t.Current()
	)
	node.MoveToFirst()
	test := predicate(q)
	for {
		if test(node) {
			count++
		}
		if !node.MoveToNext() {
			break
		}
	}
	return float64(count)
}

// countFunc is a XPath Node Set functions count(node-set).
func countFunc(q query, t iterator) interface{} {
	var count = 0
	test := predicate(q)
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if test(node) {
				count++
			}
		}
	}
	return float64(count)
}

// sumFunc is a XPath Node Set functions sum(node-set).
func sumFunc(q query, t iterator) interface{} {
	var sum float64
	switch typ := q.Evaluate(t).(type) {
	case query:
		for node := typ.Select(t); node != nil; node = typ.Select(t) {
			if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
				sum += v
			}
		}
	case float64:
		sum = typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("sum() function argument type must be a node-set or number"))
		}
		sum = v
	}
	return sum
}

func asNumber(t iterator, o interface{}) float64 {
	switch typ := o.(type) {
	case query:
		node := typ.Select(t)
		if node == nil {
			return float64(0)
		}
		if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
			return v
		}
	case float64:
		return typ
	case string:
		v, err := strconv.ParseFloat(typ, 64)
		if err != nil {
			panic(errors.New("ceiling() function argument type must be a node-set or number"))
		}
		return v
	}
	return 0
}

// ceilingFunc is a XPath Node Set functions ceiling(node-set).
func ceilingFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Ceil(val)
}

// floorFunc is a XPath Node Set functions floor(node-set).
func floorFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	return math.Floor(val)
}

// math.Round() is supported by Go 1.9x,
// This method just compatible for version <1.9x.
// https://github.com/golang/go/issues/4594
func round(f float64) int {
	if math.Abs(f) < 0.5 {
		return 0
	}
	return int(f + math.Copysign(0.5, f))
}

// roundFunc is a XPath Node Set functions round(node-set).
func roundFunc(q query, t iterator) interface{} {
	val := asNumber(t, q.Evaluate(t))
	//return math.Round(val)
	return round(val)
}

// nameFunc is a XPath functions name([node-set]).
func nameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	ns := v.Prefix()
	if ns == "" {
		return v.LocalName()
	}
	return ns + ":" + v.LocalName()
}

// localNameFunc is a XPath functions local-name([node-set]).
func localNameFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.LocalName()
}

// namespaceFunc is a XPath functions namespace-uri([node-set]).
func namespaceFunc(q query, t iterator) interface{} {
	v := q.Select(t)
	if v == nil {
		return ""
	}
	return v.Prefix()
}

func asBool(t iterator, v interface{}) bool {
	switch v := v.(type) {
	case nil:
		return false
	case *NodeIterator:
		return v.MoveNext()
	case bool:
		return bool(v)
	case float64:
		return v == 0
	case string:
		return v != ""
	case query:
		return v.Select(t) != nil
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

func asString(t iterator, v interface{}) string {
	switch v := v.(type) {
	case nil:
		return ""
	case bool:
		if v {
			return "true"
		}
		return "false"
	case float64:
		return strconv.FormatFloat(v, 'g', -1, 64)
	case string:
		return v
	default:
		panic(fmt.Errorf("unexpected type: %T", v))
	}
}

// booleanFunc is a XPath functions boolean([node-set]).
func booleanFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asBool(t, v)
}

// numberFunc is a XPath functions number([node-set]).
func numberFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asNumber(t, v)
}

// stringFunc is a XPath functions string([node-set]).
func stringFunc(q query, t iterator) interface{} {
	v := q.Evaluate(t)
	return asString(t, v)
}

// startwithFunc is a XPath functions starts-with(string, string).
func startwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("starts-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("starts-with() function argument type must be string"))
		}
		return strings.HasPrefix(m, n)
	}
}

// endwithFunc is a XPath functions ends-with(string, string).
func endwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("ends-with() function argument type must be string"))
		}
		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("ends-with() function argument type must be string"))
		}
		return strings.HasSuffix(m, n)
	}
}

// containsFunc is a XPath functions contains(string or @attr, string).
func containsFunc(arg1, arg2 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var (
			m, n string
			ok   bool
		)

		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return false
			}
			m = node.Value()
		default:
			panic(errors.New("contains() function argument type must be string"))
		}

		n, ok = arg2.Evaluate(t).(string)
		if !ok {
			panic(errors.New("contains() function argument type must be string"))
		}

		return strings.Contains(m, n)
	}
}

// normalizespaceFunc is XPath functions normalize-space(string?)
func normalizespaceFunc(q query, t iterator) interface{} {
	var m string
	switch typ := q.Evaluate(t).(type) {
	case string:
		m = typ
	case query:
		node := typ.Select(t)
		if node == nil {
			return false
		}
		m = node.Value()
	}
	return strings.TrimSpace(m)
}

// substringFunc is XPath functions substring function returns a part of a given string.
func substringFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var m string
		switch typ := arg1.Evaluate(t).(type) {
		case string:
			m = typ
		case query:
			node := typ.Select(t)
			if node == nil {
				return ""
			}
			m = node.Value()
		}

		var start, length float64
		var ok bool

		if start, ok = arg2.Evaluate(t).(float64); !ok {
			panic(errors.New("substring() function first argument type must be int"))
		} else if start < 1 {
			panic(errors.New("substring() function first argument type must be >= 1"))
		}
		start--
		if arg3 != nil {
			if length, ok = arg3.Evaluate(t).(float64); !ok {
				panic(errors.New("substring() function second argument type must be int"))
			}
		}
		if (len(m) - int(start)) < int(length) {
			panic(errors.New("substring() function start and length argument out of range"))
		}
		if length > 0 {
			return m[int(start):int(length+start)]
		}
		return m[int(start):]
	}
}

// substringIndFunc is XPath functions substring-before/substring-after function returns a part of a given string.
func substringIndFunc(arg1, arg2 query, after bool) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var str string
		switch v := arg1.Evaluate(t).(type) {
		case string:
			str = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			str = node.Value()
		}
		var word string
		switch v := arg2.Evaluate(t).(type) {
		case string:
			word = v
		case query:
			node := v.Select(t)
			if node == nil {
				return ""
			}
			word = node.Value()
		}
		if word == "" {
			return ""
		}

		i := strings.Index(str, word)
		if i < 0 {
			return ""
		}
		if after {
			return str[i+len(word):]
		}
		return str[:i]
	}
}

// stringLengthFunc is XPATH string-length( [string] ) function that returns a number
// equal to the number of characters in a given string.
func stringLengthFunc(arg1 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		switch v := arg1.Evaluate(t).(type) {
		case string:
			return float64(len(v))
		case query:
			node := v.Select(t)
			if node == nil {
				break
			}
			return float64(len(node.Value()))
		}
		return float64(0)
	}
}

// translateFunc is XPath functions translate() function returns a replaced string.
func translateFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		str := asString(t, arg1.Evaluate(t))
		src := asString(t, arg2.Evaluate(t))
		dst := asString(t, arg3.Evaluate(t))

		var replace []string
		for i, s := range src {
			d := ""
			if i < len(dst) {
				d = string(dst[i])
			}
			replace = append(replace, string(s), d)
		}
		return strings.NewReplacer(replace...).Replace(str)
	}
}

// notFunc is XPATH functions not(expression) function operation.
func notFunc(q query, t iterator) interface{} {
	switch v := q.Evaluate(t).(type) {
	case bool:
		return !v
	case query:
		node := v.Select(t)
		return node == nil
	default:
		return false
	}
}

// concatFunc is the concat function concatenates two or more
// strings and returns the resulting string.
// concat( string1 , string2 [, stringn]* )
func concatFunc(args ...query) func(query, iterator) interface{} {
	return func(q query, t iterator) interface{} {
		var a []string
		for _, v := range args {
			switch v := v.Evaluate(t).(type) {
			case string:
				a = append(a, v)
			case query:
				node := v.Select(t)
				if node != nil {
					a = append(a, node.Value())
				}
			}
		}
		return strings.Join(a, "")
	}
}
<|endoftext|>


<commit_before>package keyshareserver

import (
	"database/sql"
	"encoding/json"
	"time"

	"github.com/go-errors/errors"
	_ "github.com/jackc/pgx/stdlib"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/privacybydesign/irmago/server/keyshare"
)

// postgresDB provides a postgres-backed implementation of KeyshareDB
// database access is done through the database/sql mechanisms, using
// pgx as database driver

type keysharePostgresDatabase struct {
	db keyshare.DB
}

const MAX_PIN_TRIES = 3         // Number of tries allowed on pin before we start with exponential backoff
const EMAIL_TOKEN_VALIDITY = 24 // amount of time user's email validation token is valid (in hours)

// Initial amount of time user is forced to back off when having multiple pin failures (in seconds).
// var so that tests may change it.
var BACKOFF_START int64 = 30

func NewPostgresDatabase(connstring string) (KeyshareDB, error) {
	db, err := sql.Open("pgx", connstring)
	if err != nil {
		return nil, err
	}
	if err = db.Ping(); err != nil {
		return nil, errors.Errorf("failed to connect to database: %v", err)
	}
	return &keysharePostgresDatabase{
		db: keyshare.DB{DB: db},
	}, nil
}

func (db *keysharePostgresDatabase) NewUser(user *KeyshareUser) error {
	res, err := db.db.Query("INSERT INTO irma.users (username, language, coredata, last_seen, pin_counter, pin_block_date) VALUES ($1, $2, $3, $4, 0, 0) RETURNING id",
		user.Username,
		user.Language,
		user.Coredata[:],
		time.Now().Unix())
	if err != nil {
		return err
	}
	defer common.Close(res)
	if !res.Next() {
		return ErrUserAlreadyExists
	}
	var id int64
	err = res.Scan(&id)
	if err != nil {
		return err
	}
	user.id = id
	return nil
}

func (db *keysharePostgresDatabase) User(username string) (*KeyshareUser, error) {
	var result KeyshareUser
	var ep []byte
	err := db.db.QueryUser(
		"SELECT id, username, language, coredata FROM irma.users WHERE username = $1 AND coredata IS NOT NULL",
		[]interface{}{&result.id, &result.Username, &result.Language, &ep},
		username,
	)
	if err != nil {
		return nil, err
	}
	if len(ep) != len(result.Coredata[:]) {
		return nil, ErrInvalidRecord
	}
	copy(result.Coredata[:], ep)
	return &result, nil
}

func (db *keysharePostgresDatabase) UpdateUser(user *KeyshareUser) error {
	return db.db.ExecUser(
		"UPDATE irma.users SET username=$1, language=$2, coredata=$3 WHERE id=$4",
		user.Username,
		user.Language,
		user.Coredata[:],
		user.id,
	)
}

func (db *keysharePostgresDatabase) ReservePincheck(user *KeyshareUser) (bool, int, int64, error) {
	// Check that account is not blocked already, and if not,
	//  update pinCounter and pinBlockDate
	uprows, err := db.db.Query(`
		UPDATE irma.users
		SET pin_counter = pin_counter+1,
			pin_block_date = $1 + CASE WHEN pin_counter-$3 < 0 THEN 0
			                           ELSE $2*2^GREATEST(0, pin_counter-$3)
			                      END
		WHERE id=$4 AND pin_block_date<=$1 AND coredata IS NOT NULL
		RETURNING pin_counter, pin_block_date`,
		time.Now().Unix(),
		BACKOFF_START,
		MAX_PIN_TRIES-1,
		user.id)
	if err != nil {
		return false, 0, 0, err
	}
	defer common.Close(uprows)

	var (
		allowed bool
		wait    int64
		tries   int
	)
	if !uprows.Next() {
		// if no results, then account either does not exist (which would be weird here) or is blocked
		// so request wait timeout
		pinrows, err := db.db.Query("SELECT pin_block_date FROM irma.users WHERE id=$1 AND coredata IS NOT NULL", user.id)
		if err != nil {
			return false, 0, 0, err
		}
		defer common.Close(pinrows)
		if !pinrows.Next() {
			return false, 0, 0, keyshare.ErrUserNotFound
		}
		err = pinrows.Scan(&wait)
		if err != nil {
			return false, 0, 0, err
		}
	} else {
		// Pin check is allowed (implied since there is a result, so pinBlockDate <= now)
		//  calculate tries remaining and wait time
		allowed = true
		err = uprows.Scan(&tries, &wait)
		if err != nil {
			return false, 0, 0, err
		}
		tries = MAX_PIN_TRIES - tries
		if tries < 0 {
			tries = 0
		}
	}

	wait = wait - time.Now().Unix()
	if wait < 0 {
		wait = 0
	}
	return allowed, tries, wait, nil
}

func (db *keysharePostgresDatabase) ClearPincheck(user *KeyshareUser) error {
	return db.db.ExecUser(
		"UPDATE irma.users SET pin_counter=0, pin_block_date=0 WHERE id=$1",
		user.id,
	)
}

func (db *keysharePostgresDatabase) SetSeen(user *KeyshareUser) error {
	return db.db.ExecUser(
		"UPDATE irma.users SET last_seen = $1 WHERE id = $2",
		time.Now().Unix(),
		user.id,
	)
}

func (db *keysharePostgresDatabase) AddLog(user *KeyshareUser, eventType LogEntryType, param interface{}) error {
	var encodedParamString *string
	if param != nil {
		encodedParam, err := json.Marshal(param)
		if err != nil {
			return err
		}
		encodedParams := string(encodedParam)
		encodedParamString = &encodedParams
	}

	_, err := db.db.Exec("INSERT INTO irma.log_entry_records (time, event, param, user_id) VALUES ($1, $2, $3, $4)",
		time.Now().Unix(),
		eventType,
		encodedParamString,
		user.id)
	return err
}

func (db *keysharePostgresDatabase) AddEmailVerification(user *KeyshareUser, emailAddress, token string) error {
	_, err := db.db.Exec("INSERT INTO irma.email_verification_tokens (token, email, user_id, expiry) VALUES ($1, $2, $3, $4)",
		token,
		emailAddress,
		user.id,
		time.Now().Add(EMAIL_TOKEN_VALIDITY*time.Hour).Unix())
	return err
}
<commit_msg>refactor: consistently use spaces around = sign in SQL queries<commit_after>package keyshareserver

import (
	"database/sql"
	"encoding/json"
	"time"

	"github.com/go-errors/errors"
	_ "github.com/jackc/pgx/stdlib"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/privacybydesign/irmago/server/keyshare"
)

// postgresDB provides a postgres-backed implementation of KeyshareDB
// database access is done through the database/sql mechanisms, using
// pgx as database driver

type keysharePostgresDatabase struct {
	db keyshare.DB
}

const MAX_PIN_TRIES = 3         // Number of tries allowed on pin before we start with exponential backoff
const EMAIL_TOKEN_VALIDITY = 24 // amount of time user's email validation token is valid (in hours)

// Initial amount of time user is forced to back off when having multiple pin failures (in seconds).
// var so that tests may change it.
var BACKOFF_START int64 = 30

func NewPostgresDatabase(connstring string) (KeyshareDB, error) {
	db, err := sql.Open("pgx", connstring)
	if err != nil {
		return nil, err
	}
	if err = db.Ping(); err != nil {
		return nil, errors.Errorf("failed to connect to database: %v", err)
	}
	return &keysharePostgresDatabase{
		db: keyshare.DB{DB: db},
	}, nil
}

func (db *keysharePostgresDatabase) NewUser(user *KeyshareUser) error {
	res, err := db.db.Query("INSERT INTO irma.users (username, language, coredata, last_seen, pin_counter, pin_block_date) VALUES ($1, $2, $3, $4, 0, 0) RETURNING id",
		user.Username,
		user.Language,
		user.Coredata[:],
		time.Now().Unix())
	if err != nil {
		return err
	}
	defer common.Close(res)
	if !res.Next() {
		return ErrUserAlreadyExists
	}
	var id int64
	err = res.Scan(&id)
	if err != nil {
		return err
	}
	user.id = id
	return nil
}

func (db *keysharePostgresDatabase) User(username string) (*KeyshareUser, error) {
	var result KeyshareUser
	var ep []byte
	err := db.db.QueryUser(
		"SELECT id, username, language, coredata FROM irma.users WHERE username = $1 AND coredata IS NOT NULL",
		[]interface{}{&result.id, &result.Username, &result.Language, &ep},
		username,
	)
	if err != nil {
		return nil, err
	}
	if len(ep) != len(result.Coredata[:]) {
		return nil, ErrInvalidRecord
	}
	copy(result.Coredata[:], ep)
	return &result, nil
}

func (db *keysharePostgresDatabase) UpdateUser(user *KeyshareUser) error {
	return db.db.ExecUser(
		"UPDATE irma.users SET username = $1, language = $2, coredata = $3 WHERE id=$4",
		user.Username,
		user.Language,
		user.Coredata[:],
		user.id,
	)
}

func (db *keysharePostgresDatabase) ReservePincheck(user *KeyshareUser) (bool, int, int64, error) {
	// Check that account is not blocked already, and if not,
	//  update pinCounter and pinBlockDate
	uprows, err := db.db.Query(`
		UPDATE irma.users
		SET pin_counter = pin_counter+1,
			pin_block_date = $1 + CASE WHEN pin_counter-$3 < 0 THEN 0
			                           ELSE $2*2^GREATEST(0, pin_counter-$3)
			                      END
		WHERE id=$4 AND pin_block_date<=$1 AND coredata IS NOT NULL
		RETURNING pin_counter, pin_block_date`,
		time.Now().Unix(),
		BACKOFF_START,
		MAX_PIN_TRIES-1,
		user.id)
	if err != nil {
		return false, 0, 0, err
	}
	defer common.Close(uprows)

	var (
		allowed bool
		wait    int64
		tries   int
	)
	if !uprows.Next() {
		// if no results, then account either does not exist (which would be weird here) or is blocked
		// so request wait timeout
		pinrows, err := db.db.Query("SELECT pin_block_date FROM irma.users WHERE id=$1 AND coredata IS NOT NULL", user.id)
		if err != nil {
			return false, 0, 0, err
		}
		defer common.Close(pinrows)
		if !pinrows.Next() {
			return false, 0, 0, keyshare.ErrUserNotFound
		}
		err = pinrows.Scan(&wait)
		if err != nil {
			return false, 0, 0, err
		}
	} else {
		// Pin check is allowed (implied since there is a result, so pinBlockDate <= now)
		//  calculate tries remaining and wait time
		allowed = true
		err = uprows.Scan(&tries, &wait)
		if err != nil {
			return false, 0, 0, err
		}
		tries = MAX_PIN_TRIES - tries
		if tries < 0 {
			tries = 0
		}
	}

	wait = wait - time.Now().Unix()
	if wait < 0 {
		wait = 0
	}
	return allowed, tries, wait, nil
}

func (db *keysharePostgresDatabase) ClearPincheck(user *KeyshareUser) error {
	return db.db.ExecUser(
		"UPDATE irma.users SET pin_counter = 0, pin_block_date = 0 WHERE id = $1",
		user.id,
	)
}

func (db *keysharePostgresDatabase) SetSeen(user *KeyshareUser) error {
	return db.db.ExecUser(
		"UPDATE irma.users SET last_seen = $1 WHERE id = $2",
		time.Now().Unix(),
		user.id,
	)
}

func (db *keysharePostgresDatabase) AddLog(user *KeyshareUser, eventType LogEntryType, param interface{}) error {
	var encodedParamString *string
	if param != nil {
		encodedParam, err := json.Marshal(param)
		if err != nil {
			return err
		}
		encodedParams := string(encodedParam)
		encodedParamString = &encodedParams
	}

	_, err := db.db.Exec("INSERT INTO irma.log_entry_records (time, event, param, user_id) VALUES ($1, $2, $3, $4)",
		time.Now().Unix(),
		eventType,
		encodedParamString,
		user.id)
	return err
}

func (db *keysharePostgresDatabase) AddEmailVerification(user *KeyshareUser, emailAddress, token string) error {
	_, err := db.db.Exec("INSERT INTO irma.email_verification_tokens (token, email, user_id, expiry) VALUES ($1, $2, $3, $4)",
		token,
		emailAddress,
		user.id,
		time.Now().Add(EMAIL_TOKEN_VALIDITY*time.Hour).Unix())
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"time"
)

type GaleWarning struct {
	Number int
	Date   time.Time
}

// Bulletin spécial: Avis de Grand frais à Coup de vent numéro 36
var (
	reWarning = regexp.MustCompile(`^\s*Bulletin spécial:.*(?:nr|numéro|n°)\s+(\d+)`)
)

// extractWarningNumber returns the gale warning number in supplied weatcher
// forecast. It returns zero if there is none.
func extractWarningNumber(path string) (int, error) {
	fp, err := os.Open(path)
	if err != nil {
		return 0, err
	}
	defer fp.Close()

	scanner := bufio.NewScanner(fp)
	for scanner.Scan() {
		m := reWarning.FindSubmatch(scanner.Bytes())
		if m != nil {
			n, err := strconv.ParseInt(string(m[1]), 10, 32)
			if err != nil {
				return 0, err
			}
			return int(n), nil
		}
	}
	return 0, scanner.Err()
}

var (
	rePath = regexp.MustCompile(`^.*(\d{4}_\d{2}_\d{2}T\d{2}_\d{2}_\d{2})\.txt$`)
)

// extractWarningNumbers returns the sequence of gale warnings extracted from
// weather forecasts in supplied directory.
func extractWarningNumbers(dir string) ([]GaleWarning, error) {

	warnings := []GaleWarning{}
	err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
		if err != nil || !fi.Mode().IsRegular() {
			return err
		}
		m := rePath.FindStringSubmatch(path)
		if m == nil {
			return nil
		}
		d, err := time.Parse("2006_01_02T15_04_05", m[1])
		if err != nil {
			return err
		}
		n, err := extractWarningNumber(path)
		if err != nil {
			return err
		}
		if n > 0 {
			warnings = append(warnings, GaleWarning{
				Number: n,
				Date:   d,
			})
		}
		return nil
	})
	return warnings, err
}

func serveGaleWarnings(galeDir string, template []byte, w http.ResponseWriter,
	req *http.Request) error {

	warnings, err := extractWarningNumbers(galeDir)
	if err != nil {
		return err
	}

	baseDate := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC)

	type warningOffset struct {
		X       float64 `json:"x"`
		Y       float64 `json:"y"`
		Date    string  `json:"date"`
		YearDay int     `json:"yearday"`
	}
	offsets := []warningOffset{}
	refs := []warningOffset{}
	for _, w := range warnings {
		deltaDays := w.Date.Sub(baseDate).Hours() / 24.
		offset := warningOffset{
			X:       deltaDays,
			Y:       float64(w.Number),
			Date:    w.Date.Format("2006-01-02 15:04:05"),
			YearDay: w.Date.YearDay(),
		}
		offsets = append(offsets, offset)
		offset.Y = float64(offset.YearDay)
		refs = append(refs, offset)
	}

	dataVar, err := json.Marshal(&offsets)
	if err != nil {
		return err
	}
	refVar, err := json.Marshal(&refs)
	if err != nil {
		return err
	}
	page := bytes.Replace(template, []byte("$DATA"), dataVar, -1)
	page = bytes.Replace(page, []byte("$REF"), refVar, -1)
	w.Header().Set("Content-Type", "text/html")
	_, err = w.Write(page)
	return err
}

func handleGaleWarnings(galeDir string, template []byte, w http.ResponseWriter,
	req *http.Request) {

	err := serveGaleWarnings(galeDir, template, w, req)
	if err != nil {
		log.Printf("error: %s\n", err)
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(500)
		w.Write([]byte(fmt.Sprintf("error: %s", err)))
	}
}

var (
	galeCmd = app.Command("gale", "display gale warning number vs day in the year")
	galeDir = galeCmd.Arg("forecastdir", "directory container weather forecasts").
		Required().String()
	galePrefix = galeCmd.Flag("prefix", "public URL prefix").String()
	galeHttp   = galeCmd.Flag("http", "HTTP host:port").Default(":5000").String()
)

func galeFn() error {
	prefix := *galePrefix
	addr := *galeHttp
	template, err := ioutil.ReadFile("scripts/main.html")
	if err != nil {
		return err
	}
	http.HandleFunc(prefix+"/", func(w http.ResponseWriter, req *http.Request) {
		handleGaleWarnings(*galeDir, template, w, req)
	})
	http.Handle(prefix+"/scripts/", http.StripPrefix(prefix+"/scripts/",
		http.FileServer(http.Dir("scripts"))))
	return http.ListenAndServe(addr, nil)
}
<commit_msg>gale: fix regexp again, the forecast is written by humans<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"time"
)

type GaleWarning struct {
	Number int
	Date   time.Time
}

// Bulletin spécial: Avis de Grand frais à Coup de vent numéro 36
var (
	reWarning = regexp.MustCompile(`^\s*Bulletin spécial:.*?(\d+)`)
)

// extractWarningNumber returns the gale warning number in supplied weatcher
// forecast. It returns zero if there is none.
func extractWarningNumber(path string) (int, error) {
	fp, err := os.Open(path)
	if err != nil {
		return 0, err
	}
	defer fp.Close()

	scanner := bufio.NewScanner(fp)
	for scanner.Scan() {
		m := reWarning.FindSubmatch(scanner.Bytes())
		if m != nil {
			n, err := strconv.ParseInt(string(m[1]), 10, 32)
			if err != nil {
				return 0, err
			}
			return int(n), nil
		}
	}
	return 0, scanner.Err()
}

var (
	rePath = regexp.MustCompile(`^.*(\d{4}_\d{2}_\d{2}T\d{2}_\d{2}_\d{2})\.txt$`)
)

// extractWarningNumbers returns the sequence of gale warnings extracted from
// weather forecasts in supplied directory.
func extractWarningNumbers(dir string) ([]GaleWarning, error) {

	warnings := []GaleWarning{}
	err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
		if err != nil || !fi.Mode().IsRegular() {
			return err
		}
		m := rePath.FindStringSubmatch(path)
		if m == nil {
			return nil
		}
		d, err := time.Parse("2006_01_02T15_04_05", m[1])
		if err != nil {
			return err
		}
		n, err := extractWarningNumber(path)
		if err != nil {
			return err
		}
		if n > 0 {
			warnings = append(warnings, GaleWarning{
				Number: n,
				Date:   d,
			})
		}
		return nil
	})
	return warnings, err
}

func serveGaleWarnings(galeDir string, template []byte, w http.ResponseWriter,
	req *http.Request) error {

	warnings, err := extractWarningNumbers(galeDir)
	if err != nil {
		return err
	}

	baseDate := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC)

	type warningOffset struct {
		X       float64 `json:"x"`
		Y       float64 `json:"y"`
		Date    string  `json:"date"`
		YearDay int     `json:"yearday"`
	}
	offsets := []warningOffset{}
	refs := []warningOffset{}
	for _, w := range warnings {
		deltaDays := w.Date.Sub(baseDate).Hours() / 24.
		offset := warningOffset{
			X:       deltaDays,
			Y:       float64(w.Number),
			Date:    w.Date.Format("2006-01-02 15:04:05"),
			YearDay: w.Date.YearDay(),
		}
		offsets = append(offsets, offset)
		offset.Y = float64(offset.YearDay)
		refs = append(refs, offset)
	}

	dataVar, err := json.Marshal(&offsets)
	if err != nil {
		return err
	}
	refVar, err := json.Marshal(&refs)
	if err != nil {
		return err
	}
	page := bytes.Replace(template, []byte("$DATA"), dataVar, -1)
	page = bytes.Replace(page, []byte("$REF"), refVar, -1)
	w.Header().Set("Content-Type", "text/html")
	_, err = w.Write(page)
	return err
}

func handleGaleWarnings(galeDir string, template []byte, w http.ResponseWriter,
	req *http.Request) {

	err := serveGaleWarnings(galeDir, template, w, req)
	if err != nil {
		log.Printf("error: %s\n", err)
		w.Header().Set("Content-Type", "text/plain")
		w.WriteHeader(500)
		w.Write([]byte(fmt.Sprintf("error: %s", err)))
	}
}

var (
	galeCmd = app.Command("gale", "display gale warning number vs day in the year")
	galeDir = galeCmd.Arg("forecastdir", "directory container weather forecasts").
		Required().String()
	galePrefix = galeCmd.Flag("prefix", "public URL prefix").String()
	galeHttp   = galeCmd.Flag("http", "HTTP host:port").Default(":5000").String()
)

func galeFn() error {
	prefix := *galePrefix
	addr := *galeHttp
	template, err := ioutil.ReadFile("scripts/main.html")
	if err != nil {
		return err
	}
	http.HandleFunc(prefix+"/", func(w http.ResponseWriter, req *http.Request) {
		handleGaleWarnings(*galeDir, template, w, req)
	})
	http.Handle(prefix+"/scripts/", http.StripPrefix(prefix+"/scripts/",
		http.FileServer(http.Dir("scripts"))))
	return http.ListenAndServe(addr, nil)
}
<|endoftext|>


<commit_before>package RemindMe

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"
)

func Remind(text string, paus time.Duration) {
	if paus == 0 {
		log.Fatal("Time must be greater than 0")
		os.Exit(1)
	}
	ticker := time.NewTicker(paus)
	for {
		select {
		case clock := <-ticker.C:
			fmt.Println("Time is now " + strconv.Itoa(clock.Hour()) + ":" + strconv.Itoa(clock.Minute()) + "." + strconv.Itoa(clock.Second()) + " " + text)
		default:
			//nothing to do.
		}
	}
}
<commit_msg>Fix time.format<commit_after>package RemindMe

import (
	"fmt"
	"log"
	"os"
	"time"
)

func Remind(text string, paus time.Duration) {
	if paus == 0 {
		log.Fatal("Time must be greater than 0")
		os.Exit(1)
	}
	ticker := time.NewTicker(paus)
	for {
		select {
		case clock := <-ticker.C:
			fmt.Println("Time is now " + clock.Format("15:04:05") + " " + text)
		default:
			//nothing to do.
		}
	}
}
<|endoftext|>