<commit_before>package busetabot

import (
	"context"
	"encoding/json"
	"errors"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/yi-jiayu/datamall/v2"
	"github.com/yi-jiayu/telegram-bot-api"
	"google.golang.org/appengine/log"

	"github.com/yi-jiayu/bus-eta-bot/v4/telegram"
)

// ResponseBufferSize is the size of the channel used to queue responses to be sent via the Telegram Bot API.
const ResponseBufferSize = 10

var handlers = Handlers{
	CommandHandlers:           commandHandlers,
	FallbackCommandHandler:    FallbackCommandHandler,
	TextHandler:               TextHandler,
	LocationHandler:           LocationHandler,
	CallbackQueryHandlers:     callbackQueryHandlers,
	InlineQueryHandler:        InlineQueryHandler,
	ChosenInlineResultHandler: ChosenInlineResultHandler,
	MessageErrorHandler:       messageErrorHandler,
	CallbackErrorHandler:      callbackErrorHandler,
}

// BusStopRepository provides bus stop information.
type BusStopRepository interface {
	Get(ID string) *BusStop
	Nearby(ctx context.Context, lat, lon, radius float64, limit int) (nearby []NearbyBusStop)
	Search(ctx context.Context, query string, limit int) []BusStop
}

type UserRepository interface {
	UpdateUserLastSeenTime(ctx context.Context, userID int, t time.Time) error
	GetUserFavourites(ctx context.Context, userID int) (favourites []string, err error)
	SetUserFavourites(ctx context.Context, userID int, favourites []string) error
}

type ETAService interface {
	GetBusArrival(busStopCode string, serviceNo string) (datamall.BusArrival, error)
}

type TelegramService interface {
	Do(request telegram.Request) error
}

// BusEtaBot contains all the bot's dependencies
type BusEtaBot struct {
	Handlers            Handlers
	Telegram            *tgbotapi.BotAPI
	Datamall            ETAService
	StreetView          *StreetViewAPI
	MeasurementProtocol *MeasurementProtocolClient
	NowFunc             func() time.Time
	BusStops            BusStopRepository
	Users               UserRepository
	TelegramService     TelegramService
}

// Handlers contains all the handlers used by the bot.
type Handlers struct {
	CommandHandlers           map[string]CommandHandler
	FallbackCommandHandler    MessageHandler
	TextHandler               MessageHandler
	LocationHandler           MessageHandler
	CallbackQueryHandlers     map[string]CallbackQueryHandler
	InlineQueryHandler        func(ctx context.Context, bot *BusEtaBot, ilq *tgbotapi.InlineQuery) error
	ChosenInlineResultHandler func(ctx context.Context, bot *BusEtaBot, cir *tgbotapi.ChosenInlineResult) error
	MessageErrorHandler       func(ctx context.Context, bot *BusEtaBot, message *tgbotapi.Message, err error)
	CallbackErrorHandler      func(ctx context.Context, bot *BusEtaBot, query *tgbotapi.CallbackQuery, err error)
}

type Response struct {
	Request telegram.Request
	Error   error
}

func ok(r telegram.Request) Response {
	return Response{
		Request: r,
	}
}

func notOk(err error) Response {
	return Response{
		Error: err,
	}
}

// DefaultHandlers returns a default set of handlers.
func DefaultHandlers() Handlers {
	return handlers
}

// NewBusEtaBot creates a new Bus Eta Bot with the provided tgbotapi.BotAPI and datamall.APIClient.
func NewBusEtaBot(handlers Handlers, tg *tgbotapi.BotAPI, dm ETAService, sv *StreetViewAPI, mp *MeasurementProtocolClient) BusEtaBot {
	bot := BusEtaBot{
		Handlers:            handlers,
		Telegram:            tg,
		Datamall:            dm,
		StreetView:          sv,
		MeasurementProtocol: mp,
	}

	bot.NowFunc = time.Now

	return bot
}

// Dispatch makes requests to the Telegram Bot API for each response in responses.
func (bot *BusEtaBot) Dispatch(ctx context.Context, responses <-chan Response) {
	var wg sync.WaitGroup
	for r := range responses {
		err := r.Error
		if err != nil {
			log.Errorf(ctx, "%+v", err)
			raven.CaptureError(err, nil)
		}
		wg.Add(1)
		go func(request telegram.Request) {
			defer wg.Done()
			err := bot.TelegramService.Do(request)
			if err != nil {
				log.Errorf(ctx, "%+v", err)
				raven.CaptureError(err, nil)
			}
		}(r.Request)
	}
}

// HandleUpdate dispatches an incoming update to the corresponding handler depending on the update type
func (bot *BusEtaBot) HandleUpdate(ctx context.Context, update *tgbotapi.Update) {
	var wg sync.WaitGroup
	defer wg.Wait()

	if message := update.Message; message != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, message.From.ID, time.Now())
				if err != nil {
					log.Warningf(ctx, "%+v", err)
				}
			}()
		}
		bot.handleMessage(ctx, message)
		return
	}

	if cbq := update.CallbackQuery; cbq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, cbq.From.ID, time.Now())
				if err != nil {
					log.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.CallbackQueryHandlers != nil {
			bot.handleCallbackQuery(ctx, cbq)
		}
		return
	}

	if ilq := update.InlineQuery; ilq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, ilq.From.ID, time.Now())
				if err != nil {
					log.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.InlineQueryHandler != nil {
			bot.handleInlineQuery(ctx, ilq)
		}
		return
	}

	if cir := update.ChosenInlineResult; cir != nil {
		bot.handleChosenInlineResult(ctx, cir)
		return
	}
}

func (bot *BusEtaBot) handleMessage(ctx context.Context, message *tgbotapi.Message) {
	if command := message.Command(); command != "" {
		bot.handleCommand(ctx, command, message)
		return
	}

	if text := message.Text; text != "" {
		bot.handleText(ctx, message)
		return
	}

	if location := message.Location; location != nil {
		bot.handleLocation(ctx, message)
		return
	}
}

func (bot *BusEtaBot) handleCommand(ctx context.Context, command string, message *tgbotapi.Message) {
	if handler, exists := bot.Handlers.CommandHandlers[command]; exists {
		responses := make(chan Response, ResponseBufferSize)
		go bot.Dispatch(ctx, responses)
		handler(ctx, bot, message, responses)
	} else {
		err := bot.Handlers.FallbackCommandHandler(ctx, bot, message)
		if err != nil {
			messageErrorHandler(ctx, bot, message, err)
		}
	}
}

func (bot *BusEtaBot) handleText(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.TextHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleLocation(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.LocationHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleCallbackQuery(ctx context.Context, cbq *tgbotapi.CallbackQuery) {
	var data map[string]interface{}
	err := json.Unmarshal([]byte(cbq.Data), &data)
	if err != nil {
		callbackErrorHandler(ctx, bot, cbq, err)
		return
	}

	if cbqType, ok := data["t"].(string); ok {
		if handler, ok := bot.Handlers.CallbackQueryHandlers[cbqType]; ok {
			responses := make(chan Response, ResponseBufferSize)
			go bot.Dispatch(ctx, responses)
			handler(ctx, bot, cbq, responses)
		}
	} else {
		callbackErrorHandler(ctx, bot, cbq, errors.New("unrecognised callback query"))
	}
}

func (bot *BusEtaBot) handleInlineQuery(ctx context.Context, ilq *tgbotapi.InlineQuery) {
	err := bot.Handlers.InlineQueryHandler(ctx, bot, ilq)
	if err != nil {
		log.Errorf(ctx, "%+v", err)
	}
}

func (bot *BusEtaBot) handleChosenInlineResult(ctx context.Context, cir *tgbotapi.ChosenInlineResult) {
	err := bot.Handlers.ChosenInlineResultHandler(ctx, bot, cir)
	if err != nil {
		log.Errorf(ctx, "%+v", err)
	}
}

// LogEvent logs an event to the Measurement Protocol if a MeasurementProtocolClient is set on the bot.
func (bot *BusEtaBot) LogEvent(ctx context.Context, user *tgbotapi.User, category, action, label string) {
	if bot.MeasurementProtocol != nil {
		_, err := bot.MeasurementProtocol.LogEvent(user.ID, user.LanguageCode, category, action, label)
		if err != nil {
			log.Errorf(ctx, "error while logging event: %v", err)
		}
	}
}
<commit_msg>bot: Fix race condition with how handlers and Dispatch were called<commit_after>package busetabot

import (
	"context"
	"encoding/json"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/pkg/errors"
	"github.com/yi-jiayu/datamall/v2"
	"github.com/yi-jiayu/telegram-bot-api"
	"google.golang.org/appengine/log"

	"github.com/yi-jiayu/bus-eta-bot/v4/telegram"
)

// ResponseBufferSize is the size of the channel used to queue responses to be sent via the Telegram Bot API.
const ResponseBufferSize = 10

var handlers = Handlers{
	CommandHandlers:           commandHandlers,
	FallbackCommandHandler:    FallbackCommandHandler,
	TextHandler:               TextHandler,
	LocationHandler:           LocationHandler,
	CallbackQueryHandlers:     callbackQueryHandlers,
	InlineQueryHandler:        InlineQueryHandler,
	ChosenInlineResultHandler: ChosenInlineResultHandler,
	MessageErrorHandler:       messageErrorHandler,
	CallbackErrorHandler:      callbackErrorHandler,
}

// BusStopRepository provides bus stop information.
type BusStopRepository interface {
	Get(ID string) *BusStop
	Nearby(ctx context.Context, lat, lon, radius float64, limit int) (nearby []NearbyBusStop)
	Search(ctx context.Context, query string, limit int) []BusStop
}

type UserRepository interface {
	UpdateUserLastSeenTime(ctx context.Context, userID int, t time.Time) error
	GetUserFavourites(ctx context.Context, userID int) (favourites []string, err error)
	SetUserFavourites(ctx context.Context, userID int, favourites []string) error
}

type ETAService interface {
	GetBusArrival(busStopCode string, serviceNo string) (datamall.BusArrival, error)
}

type TelegramService interface {
	Do(request telegram.Request) error
}

// BusEtaBot contains all the bot's dependencies
type BusEtaBot struct {
	Handlers            Handlers
	Telegram            *tgbotapi.BotAPI
	Datamall            ETAService
	StreetView          *StreetViewAPI
	MeasurementProtocol *MeasurementProtocolClient
	NowFunc             func() time.Time
	BusStops            BusStopRepository
	Users               UserRepository
	TelegramService     TelegramService
}

// Handlers contains all the handlers used by the bot.
type Handlers struct {
	CommandHandlers           map[string]CommandHandler
	FallbackCommandHandler    MessageHandler
	TextHandler               MessageHandler
	LocationHandler           MessageHandler
	CallbackQueryHandlers     map[string]CallbackQueryHandler
	InlineQueryHandler        func(ctx context.Context, bot *BusEtaBot, ilq *tgbotapi.InlineQuery) error
	ChosenInlineResultHandler func(ctx context.Context, bot *BusEtaBot, cir *tgbotapi.ChosenInlineResult) error
	MessageErrorHandler       func(ctx context.Context, bot *BusEtaBot, message *tgbotapi.Message, err error)
	CallbackErrorHandler      func(ctx context.Context, bot *BusEtaBot, query *tgbotapi.CallbackQuery, err error)
}

type Response struct {
	Request telegram.Request
	Error   error
}

func ok(r telegram.Request) Response {
	return Response{
		Request: r,
	}
}

func notOk(err error) Response {
	return Response{
		Error: err,
	}
}

// DefaultHandlers returns a default set of handlers.
func DefaultHandlers() Handlers {
	return handlers
}

// NewBusEtaBot creates a new Bus Eta Bot with the provided tgbotapi.BotAPI and datamall.APIClient.
func NewBusEtaBot(handlers Handlers, tg *tgbotapi.BotAPI, dm ETAService, sv *StreetViewAPI, mp *MeasurementProtocolClient) BusEtaBot {
	bot := BusEtaBot{
		Handlers:            handlers,
		Telegram:            tg,
		Datamall:            dm,
		StreetView:          sv,
		MeasurementProtocol: mp,
	}

	bot.NowFunc = time.Now

	return bot
}

// Dispatch makes requests to the Telegram Bot API for each response in responses.
func (bot *BusEtaBot) Dispatch(ctx context.Context, responses <-chan Response) {
	var wg sync.WaitGroup
	for r := range responses {
		err := r.Error
		if err != nil {
			log.Errorf(ctx, "%+v", err)
			raven.CaptureError(err, nil)
		}
		wg.Add(1)
		go func(request telegram.Request) {
			defer wg.Done()
			err := bot.TelegramService.Do(request)
			if err != nil {
				log.Errorf(ctx, "%+v", err)
				raven.CaptureError(err, nil)
			}
		}(r.Request)
	}
}

// HandleUpdate dispatches an incoming update to the corresponding handler depending on the update type
func (bot *BusEtaBot) HandleUpdate(ctx context.Context, update *tgbotapi.Update) {
	var wg sync.WaitGroup
	defer wg.Wait()

	if message := update.Message; message != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, message.From.ID, time.Now())
				if err != nil {
					log.Warningf(ctx, "%+v", err)
				}
			}()
		}
		bot.handleMessage(ctx, message)
		return
	}

	if cbq := update.CallbackQuery; cbq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, cbq.From.ID, time.Now())
				if err != nil {
					log.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.CallbackQueryHandlers != nil {
			bot.handleCallbackQuery(ctx, cbq)
		}
		return
	}

	if ilq := update.InlineQuery; ilq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, ilq.From.ID, time.Now())
				if err != nil {
					log.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.InlineQueryHandler != nil {
			bot.handleInlineQuery(ctx, ilq)
		}
		return
	}

	if cir := update.ChosenInlineResult; cir != nil {
		bot.handleChosenInlineResult(ctx, cir)
		return
	}
}

func (bot *BusEtaBot) handleMessage(ctx context.Context, message *tgbotapi.Message) {
	if command := message.Command(); command != "" {
		bot.handleCommand(ctx, command, message)
		return
	}

	if text := message.Text; text != "" {
		bot.handleText(ctx, message)
		return
	}

	if location := message.Location; location != nil {
		bot.handleLocation(ctx, message)
		return
	}
}

func (bot *BusEtaBot) handleCommand(ctx context.Context, command string, message *tgbotapi.Message) {
	if handler, exists := bot.Handlers.CommandHandlers[command]; exists {
		responses := make(chan Response, ResponseBufferSize)
		go handler(ctx, bot, message, responses)
		bot.Dispatch(ctx, responses)
	} else {
		err := bot.Handlers.FallbackCommandHandler(ctx, bot, message)
		if err != nil {
			messageErrorHandler(ctx, bot, message, err)
		}
	}
}

func (bot *BusEtaBot) handleText(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.TextHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleLocation(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.LocationHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleCallbackQuery(ctx context.Context, cbq *tgbotapi.CallbackQuery) {
	var data map[string]interface{}
	err := json.Unmarshal([]byte(cbq.Data), &data)
	if err != nil {
		callbackErrorHandler(ctx, bot, cbq, err)
		return
	}

	if cbqType, ok := data["t"].(string); ok {
		if handler, ok := bot.Handlers.CallbackQueryHandlers[cbqType]; ok {
			responses := make(chan Response, ResponseBufferSize)
			go handler(ctx, bot, cbq, responses)
			bot.Dispatch(ctx, responses)
		}
	} else {
		callbackErrorHandler(ctx, bot, cbq, errors.New("unrecognised callback query"))
	}
}

func (bot *BusEtaBot) handleInlineQuery(ctx context.Context, ilq *tgbotapi.InlineQuery) {
	err := bot.Handlers.InlineQueryHandler(ctx, bot, ilq)
	if err != nil {
		log.Errorf(ctx, "%+v", err)
	}
}

func (bot *BusEtaBot) handleChosenInlineResult(ctx context.Context, cir *tgbotapi.ChosenInlineResult) {
	err := bot.Handlers.ChosenInlineResultHandler(ctx, bot, cir)
	if err != nil {
		log.Errorf(ctx, "%+v", err)
	}
}

// LogEvent logs an event to the Measurement Protocol if a MeasurementProtocolClient is set on the bot.
func (bot *BusEtaBot) LogEvent(ctx context.Context, user *tgbotapi.User, category, action, label string) {
	if bot.MeasurementProtocol != nil {
		_, err := bot.MeasurementProtocol.LogEvent(user.ID, user.LanguageCode, category, action, label)
		if err != nil {
			log.Errorf(ctx, "error while logging event: %v", err)
		}
	}
}
<|endoftext|>


<commit_before>package rice

import (
	"errors"
	"fmt"
	"github.com/GeertJohan/go.rice/embedded"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

// Box abstracts a directory for resources/files.
// It can either load files from disk, or from embedded code (when `rice --embed` was ran).
type Box struct {
	name         string
	absolutePath string
	embed        *embedded.EmbeddedBox
}

func findBox(name string) (*Box, error) {
	b := &Box{
		name: name,
	}

	// find if box is embedded
	if embed := embedded.EmbeddedBoxes[name]; embed != nil {
		b.embed = embed
		return b, nil
	}
	// box was not embedded

	// when given name is an absolute path, set it as absolute path.
	// otherwise calculate absolute path from caller source location
	if filepath.IsAbs(name) {
		return nil, errors.New("given name/path is aboslute")
	}

	// resolve absolute directory path
	err := b.resolveAbsolutePathFromCaller()
	if err != nil {
		return nil, err
	}

	// check if absolutePath exists on filesystem
	info, err := os.Stat(b.absolutePath)
	if err != nil {
		return nil, err
	}
	// check if absolutePath is actually a directory
	if !info.IsDir() {
		return nil, errors.New("given name/path is not a directory")
	}

	// all done
	return b, nil
}

// FindBox returns a Box instance for given name.
// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root.
// When the given name is absolute, it's absolute. derp.
// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded).
func FindBox(name string) (*Box, error) {
	return findBox(name)
}

// MustFindBox returns a Box instance for given name, like FindBox does.
// It does not return an error, instead it panics when an error occurs.
func MustFindBox(name string) *Box {
	box, err := findBox(name)
	if err != nil {
		panic(err)
	}
	return box
}

func (b *Box) resolveAbsolutePathFromCaller() error {
	_, callingGoFile, _, ok := runtime.Caller(3)
	if !ok {
		return errors.New("couldn't find caller on stack")
	}

	// resolve to proper path
	pkgDir := filepath.Dir(callingGoFile)
	b.absolutePath = filepath.Join(pkgDir, b.name)
	return nil
}

// IsEmbedded indicates wether this box was embedded into the application
func (b *Box) IsEmbedded() bool {
	return b.embed != nil
}

// Time returns how actual the box is.
// When the box is embedded, it's value is saved in the embedding code.
// When the box is live, this methods returns time.Now()
func (b *Box) Time() time.Time {
	if b.IsEmbedded() {
		return b.embed.Time
	}

	return time.Now()
}

// Open opens a File from the box
// If there is an error, it will be of type *os.PathError.
func (b *Box) Open(name string) (*File, error) {
	if Debug {
		fmt.Printf("Open(%s)\n", name)
	}
	if b.IsEmbedded() {
		if Debug {
			fmt.Println("Box is embedded")
		}

		// // fast return for root
		// if name == "/" {
		// 	return &File{virtualD: newVirtualDir(b.embed.RootDir)}, nil
		// }

		// trim prefix (paths are relative to box)
		name = strings.TrimLeft(name, "/")
		if Debug {
			fmt.Printf("Trying %s\n", name)
		}

		// search for file
		ef := b.embed.Files[name]
		if ef == nil {
			if Debug {
				fmt.Println("Didn't find file in embed")
			}
			// file not found, try dir
			ed := b.embed.Dirs[name]
			if ed == nil {
				if Debug {
					fmt.Println("Didn't find dir in embed")
				}
				// dir not found, error out
				return nil, &os.PathError{
					Op:   "open",
					Path: name,
					Err:  os.ErrNotExist,
				}
			}
			if Debug {
				fmt.Println("Found dir. Returning virtual dir")
			}
			vd := newVirtualDir(ed)
			return &File{virtualD: vd}, nil
		}

		// box is embedded
		if Debug {
			fmt.Println("Found file. Returning virtual file")
		}
		vf := newVirtualFile(ef)
		return &File{virtualF: vf}, nil
	}

	// perform os open
	if Debug {
		fmt.Println("Using os.Open(%s)", filepath.Join(b.absolutePath, name))
	}
	file, err := os.Open(filepath.Join(b.absolutePath, name))
	if err != nil {
		return nil, err
	}
	return &File{realF: file}, nil
}

// Bytes returns the content of the file with given name as []byte
func (b *Box) Bytes(name string) ([]byte, error) {
	// check if box is embedded
	if b.IsEmbedded() {
		// find file in embed
		ef := b.embed.Files[name]
		if ef == nil {
			return nil, os.ErrNotExist
		}
		// clone byteSlice
		cpy := make([]byte, 0, len(ef.Content))
		cpy = append(cpy, ef.Content...)
		// return copied bytes
		return cpy, nil
	}

	// open actual file from disk
	file, err := os.Open(filepath.Join(b.absolutePath, name))
	if err != nil {
		return nil, err
	}
	// read complete content
	bts, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}
	// return result
	return bts, nil
}

// String returns the content of the file with given name as string
func (b *Box) String(name string) (string, error) {
	// check if box is embedded
	if b.IsEmbedded() {
		// find file in embed
		ef := b.embed.Files[name]
		if ef == nil {
			return "", os.ErrNotExist
		}
		// return as string
		return ef.Content, nil
	}

	// open actual file from disk
	file, err := os.Open(filepath.Join(b.absolutePath, name))
	if err != nil {
		return "", err
	}
	// read complete content
	bts, err := ioutil.ReadAll(file)
	if err != nil {
		return "", err
	}
	// return result as string
	return string(bts), nil
}
<commit_msg>Cleanup<commit_after>package rice

import (
	"errors"
	"fmt"
	"github.com/GeertJohan/go.rice/embedded"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

// Box abstracts a directory for resources/files.
// It can either load files from disk, or from embedded code (when `rice --embed` was ran).
type Box struct {
	name         string
	absolutePath string
	embed        *embedded.EmbeddedBox
}

func findBox(name string) (*Box, error) {
	b := &Box{
		name: name,
	}

	// find if box is embedded
	if embed := embedded.EmbeddedBoxes[name]; embed != nil {
		b.embed = embed
		return b, nil
	}
	// box was not embedded

	// when given name is an absolute path, set it as absolute path.
	// otherwise calculate absolute path from caller source location
	if filepath.IsAbs(name) {
		return nil, errors.New("given name/path is aboslute")
	}

	// resolve absolute directory path
	err := b.resolveAbsolutePathFromCaller()
	if err != nil {
		return nil, err
	}

	// check if absolutePath exists on filesystem
	info, err := os.Stat(b.absolutePath)
	if err != nil {
		return nil, err
	}
	// check if absolutePath is actually a directory
	if !info.IsDir() {
		return nil, errors.New("given name/path is not a directory")
	}

	// all done
	return b, nil
}

// FindBox returns a Box instance for given name.
// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root.
// When the given name is absolute, it's absolute. derp.
// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded).
func FindBox(name string) (*Box, error) {
	return findBox(name)
}

// MustFindBox returns a Box instance for given name, like FindBox does.
// It does not return an error, instead it panics when an error occurs.
func MustFindBox(name string) *Box {
	box, err := findBox(name)
	if err != nil {
		panic(err)
	}
	return box
}

func (b *Box) resolveAbsolutePathFromCaller() error {
	_, callingGoFile, _, ok := runtime.Caller(3)
	if !ok {
		return errors.New("couldn't find caller on stack")
	}

	// resolve to proper path
	pkgDir := filepath.Dir(callingGoFile)
	b.absolutePath = filepath.Join(pkgDir, b.name)
	return nil
}

// IsEmbedded indicates wether this box was embedded into the application
func (b *Box) IsEmbedded() bool {
	return b.embed != nil
}

// Time returns how actual the box is.
// When the box is embedded, it's value is saved in the embedding code.
// When the box is live, this methods returns time.Now()
func (b *Box) Time() time.Time {
	if b.IsEmbedded() {
		return b.embed.Time
	}

	return time.Now()
}

// Open opens a File from the box
// If there is an error, it will be of type *os.PathError.
func (b *Box) Open(name string) (*File, error) {
	if Debug {
		fmt.Printf("Open(%s)\n", name)
	}
	if b.IsEmbedded() {
		if Debug {
			fmt.Println("Box is embedded")
		}

		// trim prefix (paths are relative to box)
		name = strings.TrimLeft(name, "/")
		if Debug {
			fmt.Printf("Trying %s\n", name)
		}

		// search for file
		ef := b.embed.Files[name]
		if ef == nil {
			if Debug {
				fmt.Println("Didn't find file in embed")
			}
			// file not found, try dir
			ed := b.embed.Dirs[name]
			if ed == nil {
				if Debug {
					fmt.Println("Didn't find dir in embed")
				}
				// dir not found, error out
				return nil, &os.PathError{
					Op:   "open",
					Path: name,
					Err:  os.ErrNotExist,
				}
			}
			if Debug {
				fmt.Println("Found dir. Returning virtual dir")
			}
			vd := newVirtualDir(ed)
			return &File{virtualD: vd}, nil
		}

		// box is embedded
		if Debug {
			fmt.Println("Found file. Returning virtual file")
		}
		vf := newVirtualFile(ef)
		return &File{virtualF: vf}, nil
	}

	// perform os open
	if Debug {
		fmt.Println("Using os.Open(%s)", filepath.Join(b.absolutePath, name))
	}
	file, err := os.Open(filepath.Join(b.absolutePath, name))
	if err != nil {
		return nil, err
	}
	return &File{realF: file}, nil
}

// Bytes returns the content of the file with given name as []byte
func (b *Box) Bytes(name string) ([]byte, error) {
	// check if box is embedded
	if b.IsEmbedded() {
		// find file in embed
		ef := b.embed.Files[name]
		if ef == nil {
			return nil, os.ErrNotExist
		}
		// clone byteSlice
		cpy := make([]byte, 0, len(ef.Content))
		cpy = append(cpy, ef.Content...)
		// return copied bytes
		return cpy, nil
	}

	// open actual file from disk
	file, err := os.Open(filepath.Join(b.absolutePath, name))
	if err != nil {
		return nil, err
	}
	// read complete content
	bts, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}
	// return result
	return bts, nil
}

// String returns the content of the file with given name as string
func (b *Box) String(name string) (string, error) {
	// check if box is embedded
	if b.IsEmbedded() {
		// find file in embed
		ef := b.embed.Files[name]
		if ef == nil {
			return "", os.ErrNotExist
		}
		// return as string
		return ef.Content, nil
	}

	// open actual file from disk
	file, err := os.Open(filepath.Join(b.absolutePath, name))
	if err != nil {
		return "", err
	}
	// read complete content
	bts, err := ioutil.ReadAll(file)
	if err != nil {
		return "", err
	}
	// return result as string
	return string(bts), nil
}
<|endoftext|>


<commit_before>package ghch

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"strings"
	"text/template"
	"time"

	"github.com/jessevdk/go-flags"
	"github.com/octokit/go-octokit/octokit"
)

type ghOpts struct {
	RepoPath    string `short:"r" long:"repo" default:"." description:"git repository path"`
	GitPath     string `short:"g" long:"git" default:"git" description:"git path"`
	From        string `short:"f" long:"from" description:"git commit revision range start from"`
	To          string `short:"t" long:"to" description:"git commit revision range end to"`
	Token       string `          long:"token" description:"github token"`
	Verbose     bool   `short:"v" long:"verbose"`
	Remote      string `          long:"remote" default:"origin" description:"default remote name"`
	Format      string `short:"F" long:"format" default:"json" description:"json or markdown"`
	All         bool   `short:"A" long:"all" description:"output all changes"`
	NextVersion string `short:"N" long:"next-version"`
	// Tmpl string
}

const (
	exitCodeOK = iota
	exitCodeParseFlagError
	exitCodeErr
)

// CLI is struct for command line tool
type CLI struct {
	OutStream, ErrStream io.Writer
}

// Run the ghch
func (cli *CLI) Run(argv []string) int {
	log.SetOutput(cli.ErrStream)
	p, opts, err := parseArgs(argv)
	if err != nil {
		if ferr, ok := err.(*flags.Error); !ok || ferr.Type != flags.ErrHelp {
			p.WriteHelp(cli.ErrStream)
		}
		return exitCodeParseFlagError
	}

	gh := (&ghch{
		remote:   opts.Remote,
		repoPath: opts.RepoPath,
		gitPath:  opts.GitPath,
		verbose:  opts.Verbose,
		token:    opts.Token,
	}).initialize()

	if opts.All {
		chlog := Changelog{}
		vers := append(gh.versions(), "")
		prevRev := ""
		for _, rev := range vers {
			r := gh.getSection(rev, prevRev)
			if prevRev == "" && opts.NextVersion != "" {
				r.ToRevision = opts.NextVersion
			}
			chlog.Sections = append(chlog.Sections, r)
			prevRev = rev
		}

		if opts.Format == "markdown" {
			results := make([]string, len(chlog.Sections))
			for i, v := range chlog.Sections {
				results[i], _ = v.toMkdn()
			}
			fmt.Fprintln(cli.OutStream, strings.Join(results, "\n\n"))
		} else {
			jsn, _ := json.MarshalIndent(chlog, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	} else {
		if opts.From == "" && opts.To == "" {
			opts.From = gh.getLatestSemverTag()
		}
		r := gh.getSection(opts.From, opts.To)
		if r.ToRevision == "" && opts.NextVersion != "" {
			r.ToRevision = opts.NextVersion
		}
		if opts.Format == "markdown" {
			str, err := r.toMkdn()
			if err != nil {
				log.Print(err)
			} else {
				fmt.Fprintln(cli.OutStream, str)
			}
		} else {
			jsn, _ := json.MarshalIndent(r, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	}
	return exitCodeOK
}

func parseArgs(args []string) (*flags.Parser, *ghOpts, error) {
	opts := &ghOpts{}
	p := flags.NewParser(opts, flags.Default)
	p.Usage = "[OPTIONS]\n\nVersion: " + version
	_, err := p.ParseArgs(args)
	return p, opts, err
}

func (gh *ghch) getSection(from, to string) Section {
	r := gh.mergedPRs(from, to)
	t, err := gh.getChangedAt(to)
	if err != nil {
		log.Print(err)
	}
	owner, repo := gh.ownerAndRepo()
	return Section{
		PullRequests: r,
		FromRevision: from,
		ToRevision:   to,
		ChangedAt:    t,
		Owner:        owner,
		Repo:         repo,
	}
}

// Changelog contains Sectionst
type Changelog struct {
	Sections []Section `json:"Sections"`
}

// Section contains changes between two revisions
type Section struct {
	PullRequests []*octokit.PullRequest `json:"pull_requests"`
	FromRevision string                 `json:"from_revision"`
	ToRevision   string                 `json:"to_revision"`
	ChangedAt    time.Time              `json:"changed_at"`
	Owner        string                 `json:"owner"`
	Repo         string                 `json:"repo"`
}

var tmplStr = `{{$ret := . -}}
## [{{.ToRevision}}](https://github.com/{{.Owner}}/{{.Repo}}/releases/tag/{{.ToRevision}}) ({{.ChangedAt.Format "2006-01-02"}})
{{range .PullRequests}}
* {{.Title}} [#{{.Number}}](https://github.com/{{$ret.Owner}}/{{$ret.Repo}}/pull/{{.Number}}) ([{{.User.Login}}](https://github.com/{{.User.Login}}))
{{- end}}`

var mdTmpl *template.Template

func init() {
	var err error
	mdTmpl, err = template.New("md-changelog").Parse(tmplStr)
	if err != nil {
		log.Fatal(err)
	}
}

func (rs Section) toMkdn() (string, error) {
	var b bytes.Buffer
	err := mdTmpl.Execute(&b, rs)
	if err != nil {
		return "", err
	}
	return b.String(), nil
}
<commit_msg>add -w option<commit_after>package ghch

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/template"
	"time"

	"github.com/jessevdk/go-flags"
	"github.com/octokit/go-octokit/octokit"
)

type ghOpts struct {
	RepoPath    string `short:"r" long:"repo" default:"." description:"git repository path"`
	GitPath     string `short:"g" long:"git" default:"git" description:"git path"`
	From        string `short:"f" long:"from" description:"git commit revision range start from"`
	To          string `short:"t" long:"to" description:"git commit revision range end to"`
	Token       string `          long:"token" description:"github token"`
	Verbose     bool   `short:"v" long:"verbose"`
	Remote      string `          long:"remote" default:"origin" description:"default remote name"`
	Format      string `short:"F" long:"format" description:"json or markdown"`
	All         bool   `short:"A" long:"all" description:"output all changes"`
	NextVersion string `short:"N" long:"next-version"`
	Write       bool   `short:"w" description:"write result to file"`
	changelogMd string
	// Tmpl string
}

const (
	exitCodeOK = iota
	exitCodeParseFlagError
	exitCodeErr
)

// CLI is struct for command line tool
type CLI struct {
	OutStream, ErrStream io.Writer
}

// Run the ghch
func (cli *CLI) Run(argv []string) int {
	log.SetOutput(cli.ErrStream)
	p, opts, err := parseArgs(argv)
	if err != nil {
		if ferr, ok := err.(*flags.Error); !ok || ferr.Type != flags.ErrHelp {
			p.WriteHelp(cli.ErrStream)
		}
		return exitCodeParseFlagError
	}

	gh := (&ghch{
		remote:   opts.Remote,
		repoPath: opts.RepoPath,
		gitPath:  opts.GitPath,
		verbose:  opts.Verbose,
		token:    opts.Token,
	}).initialize()

	if opts.All {
		chlog := Changelog{}
		vers := append(gh.versions(), "")
		prevRev := ""
		for _, rev := range vers {
			r := gh.getSection(rev, prevRev)
			if prevRev == "" && opts.NextVersion != "" {
				r.ToRevision = opts.NextVersion
			}
			chlog.Sections = append(chlog.Sections, r)
			prevRev = rev
		}

		if opts.Format == "markdown" {
			results := make([]string, len(chlog.Sections))
			for i, v := range chlog.Sections {
				results[i], _ = v.toMkdn()
			}

			if opts.Write {
				content := "# Changelog\n\n" + strings.Join(results, "\n\n")
				err := ioutil.WriteFile(opts.changelogMd, []byte(content), 0644)
				if err != nil {
					log.Print(err)
					return exitCodeErr
				}
			} else {
				fmt.Fprintln(cli.OutStream, strings.Join(results, "\n\n"))
			}
		} else {
			jsn, _ := json.MarshalIndent(chlog, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	} else {
		if opts.From == "" && opts.To == "" {
			opts.From = gh.getLatestSemverTag()
		}
		r := gh.getSection(opts.From, opts.To)
		if r.ToRevision == "" && opts.NextVersion != "" {
			r.ToRevision = opts.NextVersion
		}
		if opts.Format == "markdown" {
			str, err := r.toMkdn()
			if err != nil {
				log.Print(err)
				return exitCodeErr
			}
			if opts.Write {
				content := ""
				if exists(opts.changelogMd) {
					byt, err := ioutil.ReadFile(opts.changelogMd)
					if err != nil {
						log.Print(err)
						return exitCodeErr
					}
					content = insertNewChangelog(byt, str)
				} else {
					content = "# Changelog\n\n" + str
				}
				err = ioutil.WriteFile(opts.changelogMd, []byte(content), 0644)
				if err != nil {
					log.Print(err)
					return exitCodeErr
				}
			} else {
				fmt.Fprintln(cli.OutStream, str)
			}
		} else {
			jsn, _ := json.MarshalIndent(r, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	}
	return exitCodeOK
}

func insertNewChangelog(orig []byte, section string) string {
	var bf bytes.Buffer
	lineSnr := bufio.NewScanner(bytes.NewReader(orig))
	inserted := false
	for lineSnr.Scan() {
		line := lineSnr.Text()
		if !inserted && strings.HasPrefix(line, "## ") {
			bf.WriteString(section)
			bf.WriteString("\n\n")
			inserted = true
		}
		bf.WriteString(line)
		bf.WriteString("\n")
	}
	if !inserted {
		bf.WriteString(section)
	}
	return bf.String()
}

func exists(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil
}

func parseArgs(args []string) (*flags.Parser, *ghOpts, error) {
	opts := &ghOpts{}
	p := flags.NewParser(opts, flags.Default)
	p.Usage = "[OPTIONS]\n\nVersion: " + version
	rest, err := p.ParseArgs(args)
	if opts.Write {
		opts.Format = "markdown"
		opts.changelogMd = "CHANGELOG.md"
		if len(rest) > 0 {
			opts.changelogMd = rest[0]
		}
	}
	return p, opts, err
}

func (gh *ghch) getSection(from, to string) Section {
	r := gh.mergedPRs(from, to)
	t, err := gh.getChangedAt(to)
	if err != nil {
		log.Print(err)
	}
	owner, repo := gh.ownerAndRepo()
	return Section{
		PullRequests: r,
		FromRevision: from,
		ToRevision:   to,
		ChangedAt:    t,
		Owner:        owner,
		Repo:         repo,
	}
}

// Changelog contains Sectionst
type Changelog struct {
	Sections []Section `json:"Sections"`
}

// Section contains changes between two revisions
type Section struct {
	PullRequests []*octokit.PullRequest `json:"pull_requests"`
	FromRevision string                 `json:"from_revision"`
	ToRevision   string                 `json:"to_revision"`
	ChangedAt    time.Time              `json:"changed_at"`
	Owner        string                 `json:"owner"`
	Repo         string                 `json:"repo"`
}

var tmplStr = `{{$ret := . -}}
## [{{.ToRevision}}](https://github.com/{{.Owner}}/{{.Repo}}/releases/tag/{{.ToRevision}}) ({{.ChangedAt.Format "2006-01-02"}})
{{range .PullRequests}}
* {{.Title}} [#{{.Number}}](https://github.com/{{$ret.Owner}}/{{$ret.Repo}}/pull/{{.Number}}) ([{{.User.Login}}](https://github.com/{{.User.Login}}))
{{- end}}`

var mdTmpl *template.Template

func init() {
	var err error
	mdTmpl, err = template.New("md-changelog").Parse(tmplStr)
	if err != nil {
		log.Fatal(err)
	}
}

func (rs Section) toMkdn() (string, error) {
	var b bytes.Buffer
	err := mdTmpl.Execute(&b, rs)
	if err != nil {
		return "", err
	}
	return b.String(), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"time"

	"github.com/urfave/cli"
	"gopkg.in/yaml.v2"
)

type Cli struct {
	Config       *Config
	Notifier     *Notifier
	Dbclient     *DBClient
	Garoonclient *GaroonClient
}

type Config struct {
	NotificationTypes *NotificationTypes `yaml:"notification_types"`
	Username          string
	Password          string
	Endpoint          string
	Userid            string
	Configfile        string
}

type NotificationTypes struct {
	Desktop bool `yaml:"desktop"`
}

func NewCli() *Cli {
	return &Cli{
		Notifier: NewNotifier(),
		Notifier: &Notifier{},
		Config: &Config{
			NotificationTypes: &NotificationTypes{
				Desktop: true,
			},
		},
	}
}

func (c *Cli) Run(args []string) error {
	var err error
	app := cli.NewApp()
	app.Name = "goroon"
	app.Usage = "garoon schedule notifier"
	app.Commands = []cli.Command{
		{
			Name:    "db:create",
			Aliases: []string{"c"},
			Usage:   "create database",
			Action: func(ctx *cli.Context) error {
				dbClient, _ := NewDBClient("./data.db")
				dbClient.CreateTable()
				return nil
			},
		},
		{
			Name:    "start",
			Aliases: []string{"s"},
			Usage:   "start check schedule and notifiy you",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "username, u",
					Destination: &c.Config.Username,
					EnvVar:      "GAROON_USERNAME",
				},
				cli.StringFlag{
					Name:        "password, p",
					Destination: &c.Config.Password,
					EnvVar:      "GAROON_PASSWORD",
				},
				cli.StringFlag{
					Name:        "endpoint, e",
					Destination: &c.Config.Endpoint,
					EnvVar:      "GAROON_ENDPOINT",
				},
				cli.StringFlag{
					Name:        "userid",
					Destination: &c.Config.Userid,
				},
				cli.StringFlag{
					Name:        "config, c",
					Destination: &c.Config.Configfile,
				},
			},
			Action: func(ctx *cli.Context) error {
				if c.Config.Configfile != "" {
					err := c.loadYaml(c.Config.Configfile)
					if err != nil {
						return err
					}
				}

				c.loop()
				return nil
			},
		},
		{
			Name:    "get",
			Aliases: []string{"g"},
			Usage:   "get today's your schedule",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "username, u",
					Destination: &c.Config.Username,
					EnvVar:      "GAROON_USERNAME",
				},
				cli.StringFlag{
					Name:        "password, p",
					Destination: &c.Config.Password,
					EnvVar:      "GAROON_PASSWORD",
				},
				cli.StringFlag{
					Name:        "endpoint, e",
					Destination: &c.Config.Endpoint,
					EnvVar:      "GAROON_ENDPOINT",
				},
				cli.StringFlag{
					Name:        "userid",
					Destination: &c.Config.Userid,
				},
				cli.StringFlag{
					Name:        "config, c",
					Destination: &c.Config.Configfile,
				},
			},
			Action: func(ctx *cli.Context) error {
				now := time.Now()
				c.Garoonclient = NewGaroonClient(c.Config.Username, c.Config.Password, c.Config.Endpoint)
				response, err := c.Garoonclient.Request(
					c.Config.Userid,
					time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local),
					time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local),
				)
				if err != nil {
					return err
				}

				for _, event := range response.ResponseBody.ScheduleEvents {
					fmt.Println("%s: %s - %s", event.Detail,
						event.When.Datetime.Start.Format("2006-01-02T15:04:05"),
						event.When.Datetime.End.Format("2006-01-02T15:04:05"))
					dt := event.When.Datetime
					if dt.Start.Add(-10 * time.Minute).Before(time.Now()) {
						if c.Config.NotificationTypes.Desktop {
							c.Notifier.Notify(event.Detail, "")
							c.Dbclient.UpdateRecord(event, true)
						}
					}
				}
				return nil
			},
		},
	}
	app.Run(args)
	return err
}

func (c *Cli) loadYaml(filename string) (err error) {
	c.Config = &Config{}
	readBody, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = yaml.Unmarshal([]byte(readBody), &c.Config)
	if err != nil {
		return err
	}
	return nil
}

func (c *Cli) loop() error {
	dbClient, err := NewDBClient("./data.db")
	if err != nil {
		return err
	}
	now := time.Now()
	c.Garoonclient = NewGaroonClient(c.Config.Username, c.Config.Password, c.Config.Endpoint)

	for {

		response, err := c.Garoonclient.Request(
			c.Config.Userid,
			time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local),
			time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local),
		)
		if err != nil {
			return err
		}

		events := []ScheduleEventStorage{}
		dbClient.Db.Select(&events, "SELECT * FROM schedule_events")

		for _, event := range response.ResponseBody.ScheduleEvents {
			isCreate := true
			isNotify := false
			for _, event_temp := range events {
				if event_temp.Id == event.Id {
					isCreate = false
					isNotify = event_temp.IsNotify
					break
				}
			}
			if isNotify == true {
				continue
			}
			if isCreate {
				dbClient.CreateRecord(event)
			} else {
				dbClient.UpdateRecord(event, false)
			}

			dt := event.When.Datetime
			if dt.Start.Add(-10 * time.Minute).Before(time.Now()) {
				if c.Config.NotificationTypes.Desktop {
					c.Notifier.Notify(event.Detail, "", c.Config.Endpoint + "/schedule/view?event=" + event.Id)
					dbClient.UpdateRecord(event, true)
				}
			}
		}

		//処理
		time.Sleep(60000 * time.Millisecond)
		fmt.Println(time.Now().Format("2006-01-02T15:04:05"))
	}
	return nil
}
<commit_msg>Remove notifier from get subcommand<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"time"

	"github.com/urfave/cli"
	"gopkg.in/yaml.v2"
)

type Cli struct {
	Config       *Config
	Notifier     *Notifier
	Dbclient     *DBClient
	Garoonclient *GaroonClient
}

type Config struct {
	NotificationTypes *NotificationTypes `yaml:"notification_types"`
	Username          string
	Password          string
	Endpoint          string
	Userid            string
	Configfile        string
}

type NotificationTypes struct {
	Desktop bool `yaml:"desktop"`
}

func NewCli() *Cli {
	return &Cli{
		Notifier: NewNotifier(),
		Notifier: &Notifier{},
		Config: &Config{
			NotificationTypes: &NotificationTypes{
				Desktop: true,
			},
		},
	}
}

func (c *Cli) Run(args []string) error {
	var err error
	app := cli.NewApp()
	app.Name = "goroon"
	app.Usage = "garoon schedule notifier"
	app.Commands = []cli.Command{
		{
			Name:    "db:create",
			Aliases: []string{"c"},
			Usage:   "create database",
			Action: func(ctx *cli.Context) error {
				dbClient, _ := NewDBClient("./data.db")
				dbClient.CreateTable()
				return nil
			},
		},
		{
			Name:    "start",
			Aliases: []string{"s"},
			Usage:   "start check schedule and notifiy you",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "username, u",
					Destination: &c.Config.Username,
					EnvVar:      "GAROON_USERNAME",
				},
				cli.StringFlag{
					Name:        "password, p",
					Destination: &c.Config.Password,
					EnvVar:      "GAROON_PASSWORD",
				},
				cli.StringFlag{
					Name:        "endpoint, e",
					Destination: &c.Config.Endpoint,
					EnvVar:      "GAROON_ENDPOINT",
				},
				cli.StringFlag{
					Name:        "userid",
					Destination: &c.Config.Userid,
				},
				cli.StringFlag{
					Name:        "config, c",
					Destination: &c.Config.Configfile,
				},
			},
			Action: func(ctx *cli.Context) error {
				if c.Config.Configfile != "" {
					err := c.loadYaml(c.Config.Configfile)
					if err != nil {
						return err
					}
				}

				c.loop()
				return nil
			},
		},
		{
			Name:    "get",
			Aliases: []string{"g"},
			Usage:   "get today's your schedule",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "username, u",
					Destination: &c.Config.Username,
					EnvVar:      "GAROON_USERNAME",
				},
				cli.StringFlag{
					Name:        "password, p",
					Destination: &c.Config.Password,
					EnvVar:      "GAROON_PASSWORD",
				},
				cli.StringFlag{
					Name:        "endpoint, e",
					Destination: &c.Config.Endpoint,
					EnvVar:      "GAROON_ENDPOINT",
				},
				cli.StringFlag{
					Name:        "userid",
					Destination: &c.Config.Userid,
				},
				cli.StringFlag{
					Name:        "config, c",
					Destination: &c.Config.Configfile,
				},
			},
			Action: func(ctx *cli.Context) error {
				now := time.Now()
				c.Garoonclient = NewGaroonClient(c.Config.Username, c.Config.Password, c.Config.Endpoint)
				response, err := c.Garoonclient.Request(
					c.Config.Userid,
					time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local),
					time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local),
				)
				if err != nil {
					return err
				}

				for _, event := range response.ResponseBody.ScheduleEvents {
					fmt.Println("%s: %s - %s", event.Detail,
						event.When.Datetime.Start.Format("2006-01-02T15:04:05"),
						event.When.Datetime.End.Format("2006-01-02T15:04:05"))
				}
				return nil
			},
		},
	}
	app.Run(args)
	return err
}

func (c *Cli) loadYaml(filename string) (err error) {
	c.Config = &Config{}
	readBody, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = yaml.Unmarshal([]byte(readBody), &c.Config)
	if err != nil {
		return err
	}
	return nil
}

func (c *Cli) loop() error {
	dbClient, err := NewDBClient("./data.db")
	if err != nil {
		return err
	}
	now := time.Now()
	c.Garoonclient = NewGaroonClient(c.Config.Username, c.Config.Password, c.Config.Endpoint)

	for {

		response, err := c.Garoonclient.Request(
			c.Config.Userid,
			time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local),
			time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local),
		)
		if err != nil {
			return err
		}

		events := []ScheduleEventStorage{}
		dbClient.Db.Select(&events, "SELECT * FROM schedule_events")

		for _, event := range response.ResponseBody.ScheduleEvents {
			isCreate := true
			isNotify := false
			for _, event_temp := range events {
				if event_temp.Id == event.Id {
					isCreate = false
					isNotify = event_temp.IsNotify
					break
				}
			}
			if isNotify == true {
				continue
			}
			if isCreate {
				dbClient.CreateRecord(event)
			} else {
				dbClient.UpdateRecord(event, false)
			}

			dt := event.When.Datetime
			if dt.Start.Add(-10 * time.Minute).Before(time.Now()) {
				if c.Config.NotificationTypes.Desktop {
					c.Notifier.Notify(event.Detail, "", c.Config.Endpoint + "/schedule/view?event=" + event.Id)
					dbClient.UpdateRecord(event, true)
				}
			}
		}

		//処理
		time.Sleep(60000 * time.Millisecond)
		fmt.Println(time.Now().Format("2006-01-02T15:04:05"))
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/fatih/color"
	"os"
	"strconv"
	"time"
)

const green = color.New(color.FgGreen).SprintFunc()

func getRequestedStorage(c *cli.Context) *Storage {
	storage, err := NewStorage(os.ExpandEnv(c.GlobalString("ctxfile")))

	if err != nil {
		panic(err)
	}
	return storage
}

func stopContext(c *cli.Context) {
	storage := getRequestedStorage(c)

	currentContext := storage.GetCurrentContext()

	if currentContext == nil {
		fmt.Println("No current context. Start a context first!")
		return
	}

	currentContext.Stop()
	storage.Save()
}

func switchContext(c *cli.Context) {
	if len(c.Args()) != 1 {
		fmt.Println("You must provide the id of the context")
		return
	}

	contextId := c.Args()[0]
	storage := getRequestedStorage(c)

	err := storage.SwitchContext(contextId)
	if err != nil {
		fmt.Printf("%s", err)
		return
	}

	fmt.Printf("You're working on %s", green(contextId))
}

func fmtDuration(duration time.Duration) string {
	return fmt.Sprintf("%sm", strconv.FormatInt(int64(duration/time.Minute), 10))
}

func info(c *cli.Context) {
	storage := getRequestedStorage(c)
	context := storage.GetCurrentContext()
	fmt.Printf("%s    %s", context.Id, fmtDuration(context.GetTotalDuration()))
}

func list(c *cli.Context) {
	storage := getRequestedStorage(c)
	for _, contextId := range storage.GetContextIds() {
		fmt.Println(contextId)
	}
}
<commit_msg>Fix syntax error<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/fatih/color"
	"os"
	"strconv"
	"time"
)

func getRequestedStorage(c *cli.Context) *Storage {
	storage, err := NewStorage(os.ExpandEnv(c.GlobalString("ctxfile")))

	if err != nil {
		panic(err)
	}
	return storage
}

func stopContext(c *cli.Context) {
	storage := getRequestedStorage(c)

	currentContext := storage.GetCurrentContext()

	if currentContext == nil {
		fmt.Println("No current context. Start a context first!")
		return
	}

	currentContext.Stop()
	storage.Save()
}

func switchContext(c *cli.Context) {
	if len(c.Args()) != 1 {
		fmt.Println("You must provide the id of the context")
		return
	}

	contextId := c.Args()[0]
	storage := getRequestedStorage(c)

	err := storage.SwitchContext(contextId)
	if err != nil {
		fmt.Printf("%s", err)
		return
	}

	green := color.New(color.FgGreen).SprintFunc()
	fmt.Printf("You're working on %s", green(contextId))
}

func fmtDuration(duration time.Duration) string {
	return fmt.Sprintf("%sm", strconv.FormatInt(int64(duration/time.Minute), 10))
}

func info(c *cli.Context) {
	storage := getRequestedStorage(c)
	context := storage.GetCurrentContext()
	fmt.Printf("%s    %s", context.Id, fmtDuration(context.GetTotalDuration()))
}

func list(c *cli.Context) {
	storage := getRequestedStorage(c)
	for _, contextId := range storage.GetContextIds() {
		fmt.Println(contextId)
	}
}
<|endoftext|>


<commit_before>/*
 This package is used to implement a "line oriented command interpreter", inspired by the python package with
 the same name http://docs.python.org/2/library/cmd.html

 Usage:

	 commander := &Cmd{...}
	 commander.Init()

         cmd := NewCommand(name,option...)

	 commander.Add(cmd)

	 commander.CmdLoop()
*/
package cmd

import (
	"github.com/calgaryscientific/args"
	"github.com/calgaryscientific/pretty"
	"github.com/calgaryscientific/readline"

	"fmt"
	"os"
	"os/exec"
	"path"
	"sort"
	"strconv"
	"strings"
	"sync"
	"runtime"
	"flag"
)

//
// This is used to describe a new command
//
type Command struct {
	// command name
	name string
	// command description
	help string
	// the function to call to execute the command
	call func(*Command, string) bool
	// list of possible sub commands
	subCommands map[string]*Command
	flags *flag.FlagSet

}

type option func(command *Command)

func SetHelp(help string) option {
	return func(command* Command) {
		command.help = help
	}
}

func SetFlag(flag string, value string, help string) option {
	return func(command *Command) {
		command.flags.String(flag,value,help)
	}
}

func SetCmd(cmd func(command *Command, line string)(stop bool)) option {
	return func(command* Command) {
		command.call = cmd
	}
}

func NewCommand(name string, opts ...option) (*Command){

	command := &Command{
		name: name,
		help: "",
		call:func(*Command, string) bool { return false },
		subCommands: make(map[string]*Command),
		flags: flag.NewFlagSet(name,flag.ContinueOnError) }

	for _, opt := range opts {
		opt(command)
	}

	command.flags.Usage = func() {
		fmt.Fprintf(os.Stderr,"%s -%s", command.name, command.help + "\n")
		command.flags.PrintDefaults()
	}

	return command
}

func (command *Command) AddSubCommand(name string, opts ...option) {

	subcommand := NewCommand(name,opts...)
	command.subCommands[name] = subcommand

	subcommand.flags.Usage = func() {
		fmt.Fprintf(os.Stderr,"%s %s -%s", command.name, subcommand.name, subcommand.help + "\n")
		subcommand.flags.PrintDefaults()
	}

}

func (command* Command) GetFlag(name string)(string) {
	flag := command.flags.Lookup(name)

	if flag != nil {
		return flag.Value.String()
	}

	return ""
}

func (command *Command) Usage() {
	command.flags.Usage()

	for _, subcommand := range command.subCommands {
		fmt.Println()
		subcommand.flags.Usage()
	}
}

//
// The context for command completion
//
type Completer struct {
	// the list of words to match on
	Words []string
	// the list of current matches
	Matches []string
}

//
// Return a word matching the prefix
// If there are multiple matches, index selects which one to pick
//
func (c *Completer) Complete(prefix string, index int) string {
	if index == 0 {
		c.Matches = c.Matches[:0]

		for _, w := range c.Words {
			if strings.HasPrefix(w, prefix) {
				c.Matches = append(c.Matches, w)
			}
		}
	}

	if index < len(c.Matches) {
		return c.Matches[index]
	} else {
		return ""
	}
}

//
// Create a Completer and initialize with list of words
//
func NewCompleter(words []string) (c *Completer) {
	c = new(Completer)
	c.Words = words
	c.Matches = make([]string, 0, len(c.Words))
	return
}

//
// This the the "context" for the command interpreter
//
type Cmd struct {
	// the prompt string
	Prompt string

	// the history file
	HistoryFile string

	// this function is called before starting the command loop
	PreLoop func()

	// this function is called before terminating the command loop
	PostLoop func()

	// this function is called before executing the selected command
	PreCmd func(string)

	// this function is called after a command has been executed
	// return true to terminate the interpreter, false to continue
	PostCmd func(string, bool) bool

	// this function is called if the last typed command was an empty line
	EmptyLine func()

	// this function is called if the command line doesn't match any existing command
	// by default it displays an error message
	Default func(string)

	// this function is called to implement command completion.
	// it should return a list of words that match the input text
	Complete func(string, string, int, int) []string

	// if true, enable shell commands
	EnableShell bool

	// this is the list of available commands indexed by command name
	Commands map[string]*Command

	///////// private stuff /////////////
	completer    *Completer
	commandNames []string

	waitGroup          *sync.WaitGroup
	waitMax, waitCount int
}

func (cmd *Cmd) readHistoryFile() {
	if len(cmd.HistoryFile) == 0 {
		// no history file
		return
	}

	filepath := cmd.HistoryFile // start with current directory
	if _, err := os.Stat(filepath); err == nil {
		if err := readline.ReadHistoryFile(filepath); err != nil {
			fmt.Println(err)
		}

		return
	}

	filepath = path.Join(os.Getenv("HOME"), filepath) // then check home directory
	if _, err := os.Stat(filepath); err == nil {
		if err := readline.ReadHistoryFile(filepath); err != nil {
			fmt.Println(err)
		}
	}

	// update HistoryFile with home path
	cmd.HistoryFile = filepath
}

func (cmd *Cmd) writeHistoryFile() {
	if len(cmd.HistoryFile) == 0 {
		// no history file
		return
	}

	if err := readline.WriteHistoryFile(cmd.HistoryFile); err != nil {
		fmt.Println(err)
	}
}

//
// Initialize the command interpreter context
//
func (cmd *Cmd) Init() {
	if cmd.PreLoop == nil {
		cmd.PreLoop = func() {}
	}
	if cmd.PostLoop == nil {
		cmd.PostLoop = func() {}
	}
	if cmd.PreCmd == nil {
		cmd.PreCmd = func(string) {}
	}
	if cmd.PostCmd == nil {
		cmd.PostCmd = func(line string, stop bool) bool { return stop }
	}
	if cmd.EmptyLine == nil {
		cmd.EmptyLine = func() {}
	}
	if cmd.Default == nil {
		cmd.Default = func(line string) { fmt.Printf("invalid command: %v\n", line) }
	}

	cmd.Commands = make(map[string]*Command)

	help := NewCommand("help",
		SetHelp(`list available commands`),
		SetCmd(cmd.Help))

	cmd.Add(help)
	//cmd.Add(Command{"echo", `echo input line`, cmd.Echo})
	//cmd.Add(Command{"go", `go cmd: asynchronous execution of cmd, or 'go [--start|--wait]'`, cmd.Go})
}

//
// Add a completer that matches on command names
//
func (cmd *Cmd) addCommandCompleter() {
	cmd.commandNames = make([]string, 0, len(cmd.Commands))

	for n, _ := range cmd.Commands {
		cmd.commandNames = append(cmd.commandNames, n)
	}

	// sorting for Help()
	sort.Strings(cmd.commandNames)

	cmd.completer = NewCompleter(cmd.commandNames)
	//readline.SetCompletionEntryFunction(completer.Complete)

	readline.SetAttemptedCompletionFunction(cmd.attemptedCompletion)
}

func (cmd *Cmd) attemptedCompletion(text string, start, end int) []string {
	if start == 0 { // this is the command to match
		return readline.CompletionMatches(text, cmd.completer.Complete)
	} else if cmd.Complete != nil {
		return cmd.Complete(text, readline.GetLineBuffer(), start, end)
	} else {
		return nil
	}
}

//
// execute shell command
//
func shellExec(command string) {
	args := args.GetArgs(command)
	if len(args) < 1 {
		fmt.Println("No command to exec")
	} else {
		var cmd *exec.Cmd;

		if runtime.GOOS == "windows" {
			cmdArgs := []string{"cmd","/C"}
			cmdArgs = append(cmdArgs,args...)
			cmd = exec.Command(cmdArgs[0],cmdArgs[1:]...)
		} else {
			cmd = exec.Command(args[0])
			cmd.Args = args
		}

		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		if err := cmd.Run(); err != nil {
			fmt.Println(err)
		}
	}
}

// Add a command to the command interpreter.
// Overrides a command with the same name, if there was one
//
func (cmd *Cmd) Add(command *Command) {
	cmd.Commands[command.name] = command
}

//
// Default help command.
// It lists all available commands or it displays the help for the specified command
//
func (cmd *Cmd) Help(command* Command,line string) (stop bool) {
	fmt.Println("")

	if len(line) == 0 {
		fmt.Println("Available commands (use 'help <topic>'):")
		fmt.Println("================================================================")

		tp := pretty.NewTabPrinter(8)

		for _, c := range cmd.commandNames {
			tp.Print(c)
		}

		tp.Println()
	} else {

		args := strings.Split(line," ")

		if len(args) > 1 {

			if c, ok := cmd.Commands[args[0]]; ok {

				cm, ok := c.subCommands[args[1]]
				if ok {
					if len(cm.help) > 0 {
						cm.Usage()
					} else {
						fmt.Println("No help for ", line)
					}
				} else {
					fmt.Println("unknown command")
				}
			}

		} else {

			c, ok := cmd.Commands[line]
			if ok {
				if len(c.help) > 0 {
					c.Usage()
				} else {
					fmt.Println("No help for ", line)
				}
			} else {
				fmt.Println("unknown command")
			}
		}
	}

	fmt.Println("")
	return
}

func (cmd *Cmd) Echo(line string) (stop bool) {
	fmt.Println(line)
	return
}

func (cmd *Cmd) Go(line string) (stop bool) {
	if strings.HasPrefix(line, "-") {
		// should be --start or --wait

		args := args.ParseArgs(line)

		if _, ok := args.Options["start"]; ok {
			cmd.waitGroup = new(sync.WaitGroup)
			cmd.waitCount = 0
			cmd.waitMax = 0

			if len(args.Arguments) > 0 {
				cmd.waitMax, _ = strconv.Atoi(args.Arguments[0])
			}

			return
		}

		if _, ok := args.Options["wait"]; ok {
			if cmd.waitGroup == nil {
				fmt.Println("nothing to wait on")
			} else {
				cmd.waitGroup.Wait()
				cmd.waitGroup = nil
			}

			return
		}
	}

	if strings.HasPrefix(line, "go ") {
		fmt.Println("Don't go go me!")
	} else {
		if cmd.waitGroup == nil {
			go cmd.OneCmd(line)
		} else {
			if cmd.waitMax > 0 {
				if cmd.waitCount >= cmd.waitMax {
					cmd.waitGroup.Wait()
					cmd.waitCount = 0
				}
			}

			cmd.waitCount++
			cmd.waitGroup.Add(1)
			go func() {
				defer cmd.waitGroup.Done()
				cmd.OneCmd(line)
			}()
		}
	}

	return
}

//
// This method executes one command
//
func (cmd *Cmd) OneCmd(line string) (stop bool) {

	if cmd.EnableShell && strings.HasPrefix(line, "!") {
		shellExec(line[1:])
		return
	}

	parts := strings.SplitN(line, " ", 2)
	cname := parts[0]

	command, ok := cmd.Commands[cname]

	if ok {
		var params string

		if len(parts) > 1 {
			splitLine := strings.SplitN(parts[1], " ", 2)
			subcmd := splitLine[0]

			subcommand, ok := command.subCommands[subcmd]
			if ok {
				if len(splitLine) > 2 {

					params = strings.TrimSpace(splitLine[1])
				}

				args := strings.Split(line," ")
				subcommand.flags.Parse(args[2:])

				stop = subcommand.call(subcommand,params)

				subcommand.flags.VisitAll(func(flag *flag.Flag) {
					flag.Value.Set(flag.DefValue)
				})
				return
			}

			params = strings.TrimSpace(parts[1])
		}

		args := strings.Split(line," ")
		command.flags.Parse(args[1:])
		stop = command.call(command,params)

		command.flags.VisitAll(func(flag *flag.Flag) {
			flag.Value.Set(flag.DefValue)
		})

	} else {
		cmd.Default(line)
	}

	return
}

//
// This is the command interpreter entry point.
// It displays a prompt, waits for a command and executes it until the selected command returns true
//
func (cmd *Cmd) CmdLoop() {
	if len(cmd.Prompt) == 0 {
		cmd.Prompt = "> "
	}

	cmd.addCommandCompleter()

	cmd.PreLoop()

	cmd.readHistoryFile()

	// loop until ReadLine returns nil (signalling EOF)
	for {
		result := readline.ReadLine(&cmd.Prompt)
		if result == nil {
			//break
			continue
		}

		line := strings.TrimSpace(*result)
		if line == "" {
			cmd.EmptyLine()
			continue
		}

		readline.AddHistory(*result) // allow user to recall this line

		cmd.PreCmd(line)

		stop := cmd.OneCmd(line)
		stop = cmd.PostCmd(line, stop)

		if stop {
			break
		}
	}

	cmd.writeHistoryFile()

	cmd.PostLoop()
}
<commit_msg>Make the AddCommandCompleter function exported so run help with out entering the command prompt will work<commit_after>/*
 This package is used to implement a "line oriented command interpreter", inspired by the python package with
 the same name http://docs.python.org/2/library/cmd.html

 Usage:

	 commander := &Cmd{...}
	 commander.Init()

         cmd := NewCommand(name,option...)

	 commander.Add(cmd)

	 commander.CmdLoop()
*/
package cmd

import (
	"github.com/calgaryscientific/args"
	"github.com/calgaryscientific/pretty"
	"github.com/calgaryscientific/readline"

	"fmt"
	"os"
	"os/exec"
	"path"
	"sort"
	"strconv"
	"strings"
	"sync"
	"runtime"
	"flag"
)

//
// This is used to describe a new command
//
type Command struct {
	// command name
	name string
	// command description
	help string
	// the function to call to execute the command
	call func(*Command, string) bool
	// list of possible sub commands
	subCommands map[string]*Command
	flags *flag.FlagSet

}

type option func(command *Command)

func SetHelp(help string) option {
	return func(command* Command) {
		command.help = help
	}
}

func SetFlag(flag string, value string, help string) option {
	return func(command *Command) {
		command.flags.String(flag,value,help)
	}
}

func SetCmd(cmd func(command *Command, line string)(stop bool)) option {
	return func(command* Command) {
		command.call = cmd
	}
}

func NewCommand(name string, opts ...option) (*Command){

	command := &Command{
		name: name,
		help: "",
		call:func(*Command, string) bool { return false },
		subCommands: make(map[string]*Command),
		flags: flag.NewFlagSet(name,flag.ContinueOnError) }

	for _, opt := range opts {
		opt(command)
	}

	command.flags.Usage = func() {
		fmt.Fprintf(os.Stderr,"%s -%s", command.name, command.help + "\n")
		command.flags.PrintDefaults()
	}

	return command
}

func (command *Command) AddSubCommand(name string, opts ...option) {

	subcommand := NewCommand(name,opts...)
	command.subCommands[name] = subcommand

	subcommand.flags.Usage = func() {
		fmt.Fprintf(os.Stderr,"%s %s -%s", command.name, subcommand.name, subcommand.help + "\n")
		subcommand.flags.PrintDefaults()
	}

}

func (command* Command) GetFlag(name string)(string) {
	flag := command.flags.Lookup(name)

	if flag != nil {
		return flag.Value.String()
	}

	return ""
}

func (command *Command) Usage() {
	command.flags.Usage()

	for _, subcommand := range command.subCommands {
		fmt.Println()
		subcommand.flags.Usage()
	}
}

//
// The context for command completion
//
type Completer struct {
	// the list of words to match on
	Words []string
	// the list of current matches
	Matches []string
}

//
// Return a word matching the prefix
// If there are multiple matches, index selects which one to pick
//
func (c *Completer) Complete(prefix string, index int) string {
	if index == 0 {
		c.Matches = c.Matches[:0]

		for _, w := range c.Words {
			if strings.HasPrefix(w, prefix) {
				c.Matches = append(c.Matches, w)
			}
		}
	}

	if index < len(c.Matches) {
		return c.Matches[index]
	} else {
		return ""
	}
}

//
// Create a Completer and initialize with list of words
//
func NewCompleter(words []string) (c *Completer) {
	c = new(Completer)
	c.Words = words
	c.Matches = make([]string, 0, len(c.Words))
	return
}

//
// This the the "context" for the command interpreter
//
type Cmd struct {
	// the prompt string
	Prompt string

	// the history file
	HistoryFile string

	// this function is called before starting the command loop
	PreLoop func()

	// this function is called before terminating the command loop
	PostLoop func()

	// this function is called before executing the selected command
	PreCmd func(string)

	// this function is called after a command has been executed
	// return true to terminate the interpreter, false to continue
	PostCmd func(string, bool) bool

	// this function is called if the last typed command was an empty line
	EmptyLine func()

	// this function is called if the command line doesn't match any existing command
	// by default it displays an error message
	Default func(string)

	// this function is called to implement command completion.
	// it should return a list of words that match the input text
	Complete func(string, string, int, int) []string

	// if true, enable shell commands
	EnableShell bool

	// this is the list of available commands indexed by command name
	Commands map[string]*Command

	///////// private stuff /////////////
	completer    *Completer
	commandNames []string

	waitGroup          *sync.WaitGroup
	waitMax, waitCount int
}

func (cmd *Cmd) readHistoryFile() {
	if len(cmd.HistoryFile) == 0 {
		// no history file
		return
	}

	filepath := cmd.HistoryFile // start with current directory
	if _, err := os.Stat(filepath); err == nil {
		if err := readline.ReadHistoryFile(filepath); err != nil {
			fmt.Println(err)
		}

		return
	}

	filepath = path.Join(os.Getenv("HOME"), filepath) // then check home directory
	if _, err := os.Stat(filepath); err == nil {
		if err := readline.ReadHistoryFile(filepath); err != nil {
			fmt.Println(err)
		}
	}

	// update HistoryFile with home path
	cmd.HistoryFile = filepath
}

func (cmd *Cmd) writeHistoryFile() {
	if len(cmd.HistoryFile) == 0 {
		// no history file
		return
	}

	if err := readline.WriteHistoryFile(cmd.HistoryFile); err != nil {
		fmt.Println(err)
	}
}

//
// Initialize the command interpreter context
//
func (cmd *Cmd) Init() {
	if cmd.PreLoop == nil {
		cmd.PreLoop = func() {}
	}
	if cmd.PostLoop == nil {
		cmd.PostLoop = func() {}
	}
	if cmd.PreCmd == nil {
		cmd.PreCmd = func(string) {}
	}
	if cmd.PostCmd == nil {
		cmd.PostCmd = func(line string, stop bool) bool { return stop }
	}
	if cmd.EmptyLine == nil {
		cmd.EmptyLine = func() {}
	}
	if cmd.Default == nil {
		cmd.Default = func(line string) { fmt.Printf("invalid command: %v\n", line) }
	}

	cmd.Commands = make(map[string]*Command)

	help := NewCommand("help",
		SetHelp(`list available commands`),
		SetCmd(cmd.Help))

	cmd.Add(help)
	//cmd.Add(Command{"echo", `echo input line`, cmd.Echo})
	//cmd.Add(Command{"go", `go cmd: asynchronous execution of cmd, or 'go [--start|--wait]'`, cmd.Go})
}

//
// Add a completer that matches on command names
//
func (cmd *Cmd) AddCommandCompleter() {
	cmd.commandNames = make([]string, 0, len(cmd.Commands))

	for n, _ := range cmd.Commands {
		cmd.commandNames = append(cmd.commandNames, n)
	}

	// sorting for Help()
	sort.Strings(cmd.commandNames)

	cmd.completer = NewCompleter(cmd.commandNames)
	//readline.SetCompletionEntryFunction(completer.Complete)

	readline.SetAttemptedCompletionFunction(cmd.attemptedCompletion)
}

func (cmd *Cmd) attemptedCompletion(text string, start, end int) []string {
	if start == 0 { // this is the command to match
		return readline.CompletionMatches(text, cmd.completer.Complete)
	} else if cmd.Complete != nil {
		return cmd.Complete(text, readline.GetLineBuffer(), start, end)
	} else {
		return nil
	}
}

//
// execute shell command
//
func shellExec(command string) {
	args := args.GetArgs(command)
	if len(args) < 1 {
		fmt.Println("No command to exec")
	} else {
		var cmd *exec.Cmd;

		if runtime.GOOS == "windows" {
			cmdArgs := []string{"cmd","/C"}
			cmdArgs = append(cmdArgs,args...)
			cmd = exec.Command(cmdArgs[0],cmdArgs[1:]...)
		} else {
			cmd = exec.Command(args[0])
			cmd.Args = args
		}

		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		if err := cmd.Run(); err != nil {
			fmt.Println(err)
		}
	}
}

// Add a command to the command interpreter.
// Overrides a command with the same name, if there was one
//
func (cmd *Cmd) Add(command *Command) {
	cmd.Commands[command.name] = command
}

//
// Default help command.
// It lists all available commands or it displays the help for the specified command
//
func (cmd *Cmd) Help(command* Command,line string) (stop bool) {
	fmt.Println("")

	if len(line) == 0 {
		fmt.Println("Available commands (use 'help <topic>'):")
		fmt.Println("================================================================")

		tp := pretty.NewTabPrinter(8)

		for _, c := range cmd.commandNames {
			tp.Print(c)
		}

		tp.Println()
	} else {

		args := strings.Split(line," ")

		if len(args) > 1 {

			if c, ok := cmd.Commands[args[0]]; ok {

				cm, ok := c.subCommands[args[1]]
				if ok {
					if len(cm.help) > 0 {
						cm.Usage()
					} else {
						fmt.Println("No help for ", line)
					}
				} else {
					fmt.Println("unknown command")
				}
			}

		} else {

			c, ok := cmd.Commands[line]
			if ok {
				if len(c.help) > 0 {
					c.Usage()
				} else {
					fmt.Println("No help for ", line)
				}
			} else {
				fmt.Println("unknown command")
			}
		}
	}

	fmt.Println("")
	return
}

func (cmd *Cmd) Echo(line string) (stop bool) {
	fmt.Println(line)
	return
}

func (cmd *Cmd) Go(line string) (stop bool) {
	if strings.HasPrefix(line, "-") {
		// should be --start or --wait

		args := args.ParseArgs(line)

		if _, ok := args.Options["start"]; ok {
			cmd.waitGroup = new(sync.WaitGroup)
			cmd.waitCount = 0
			cmd.waitMax = 0

			if len(args.Arguments) > 0 {
				cmd.waitMax, _ = strconv.Atoi(args.Arguments[0])
			}

			return
		}

		if _, ok := args.Options["wait"]; ok {
			if cmd.waitGroup == nil {
				fmt.Println("nothing to wait on")
			} else {
				cmd.waitGroup.Wait()
				cmd.waitGroup = nil
			}

			return
		}
	}

	if strings.HasPrefix(line, "go ") {
		fmt.Println("Don't go go me!")
	} else {
		if cmd.waitGroup == nil {
			go cmd.OneCmd(line)
		} else {
			if cmd.waitMax > 0 {
				if cmd.waitCount >= cmd.waitMax {
					cmd.waitGroup.Wait()
					cmd.waitCount = 0
				}
			}

			cmd.waitCount++
			cmd.waitGroup.Add(1)
			go func() {
				defer cmd.waitGroup.Done()
				cmd.OneCmd(line)
			}()
		}
	}

	return
}

//
// This method executes one command
//
func (cmd *Cmd) OneCmd(line string) (stop bool) {

	if cmd.EnableShell && strings.HasPrefix(line, "!") {
		shellExec(line[1:])
		return
	}

	parts := strings.SplitN(line, " ", 2)
	cname := parts[0]

	command, ok := cmd.Commands[cname]

	if ok {
		var params string

		if len(parts) > 1 {
			splitLine := strings.SplitN(parts[1], " ", 2)
			subcmd := splitLine[0]

			subcommand, ok := command.subCommands[subcmd]
			if ok {
				if len(splitLine) > 2 {

					params = strings.TrimSpace(splitLine[1])
				}

				args := strings.Split(line," ")
				subcommand.flags.Parse(args[2:])

				stop = subcommand.call(subcommand,params)

				subcommand.flags.VisitAll(func(flag *flag.Flag) {
					flag.Value.Set(flag.DefValue)
				})
				return
			}

			params = strings.TrimSpace(parts[1])
		}

		args := strings.Split(line," ")
		command.flags.Parse(args[1:])
		stop = command.call(command,params)

		command.flags.VisitAll(func(flag *flag.Flag) {
			flag.Value.Set(flag.DefValue)
		})

	} else {
		cmd.Default(line)
	}

	return
}

//
// This is the command interpreter entry point.
// It displays a prompt, waits for a command and executes it until the selected command returns true
//
func (cmd *Cmd) CmdLoop() {
	if len(cmd.Prompt) == 0 {
		cmd.Prompt = "> "
	}

	cmd.AddCommandCompleter()

	cmd.PreLoop()

	cmd.readHistoryFile()

	// loop until ReadLine returns nil (signalling EOF)
	for {
		result := readline.ReadLine(&cmd.Prompt)
		if result == nil {
			//break
			continue
		}

		line := strings.TrimSpace(*result)
		if line == "" {
			cmd.EmptyLine()
			continue
		}

		readline.AddHistory(*result) // allow user to recall this line

		cmd.PreCmd(line)

		stop := cmd.OneCmd(line)
		stop = cmd.PostCmd(line, stop)

		if stop {
			break
		}
	}

	cmd.writeHistoryFile()

	cmd.PostLoop()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"sync"
)

var initInput sync.Once
var input *bufio.Reader

func prompt() (string, error) {
	initInput.Do(func (){input = bufio.NewReader(os.Stdin)})

	fmt.Print("masc> ")
	line, _, err := input.ReadLine()
	return string(line), err
}

func isWhite(r rune) bool {
	return r == ' ' || r == '\t'
}

func isQuot(r rune) bool { return r == '"' }

func tokenize(line string) (toks []string) {
	// states
	const (
		READY = iota
		INTOK
		INQUOT
	)

	var tmp string
	state := READY
	for _, c := range line {
		switch state {
		case READY:
			if isWhite(c) { continue }
			tmp = string(c)
			state = INTOK
		case INTOK:
			if isWhite(c) {
				toks = append(toks, tmp)
				state = READY
				continue
			}
			tmp += string(c)
		case INQUOT:
		default:
			panic("Invalid state")
		}
	}
	if len(tmp) > 0 {
		toks = append(toks, tmp)
	}
	return
}

func getLine(prefix string, msg string) string {
	lines := strings.Split(msg, "\n")
	for _, line := range lines {
		l := strings.TrimSpace(line)
		if strings.Index(l, prefix) != -1 {
			return strings.TrimSpace(l[len(prefix):])
		}
	}
	return ""
}

func getFrom(msg string) string {
	return getLine("From:", msg)
}

func getTo(msg string) []string {
	return strings.Split(getLine("To:", msg), ",")
}

var actions map[string]func([]string)

func init() {
	actions = map[string]func([]string){
		"help": func(toks []string) {
			fmt.Println(`Commands:
`)
		},
		"h": alias("help"),
		"?": alias("help"),

		"mail": func(toks []string) {
			// create temporary file with this message
			f, err := ioutil.TempFile("", "masc")
			if err != nil {
				panic(err)
			}
			b := bufio.NewWriter(f)
			b.WriteString("Template message here")
			f.Close()

			runEditor(f.Name())

			// read file and remove
			c, err := ioutil.ReadFile(f.Name())
			if err != nil {
				panic(err)
			}
			os.Remove(f.Name())

			msg := string(c)

			// confirm send
			fmt.Print("Send (y/n)? ")
			lb, _, err := input.ReadLine()
			l := string(lb)
			for l != "y" && l != "n" && err == nil {
				fmt.Print("Send (y/n)? ")
				lb, _, err = input.ReadLine()
				l = string(lb)
			}
			if err != nil { panic(err) }
			if l == "n" {
				fmt.Println("Aborting")
				return
			}
			fmt.Println("Sending...")

			// send
			out := &Outgoing{getFrom(msg), getTo(msg), msg}
			doSend(config.Sends[getFrom(msg)], out)
		},
		"m": alias("mail"),

		"smtp": func(args []string) {
			if len(args) != 5 {
				panic("usage: smtp name server ident uname passwd")
			}
			login := &SMTPLogin{args[1], args[2], args[3], args[4]}
			config.Sends[args[0]] = login
		},
	}
}

func alias(cmd string) func([]string) {
	return actions[cmd]
}

func runEditor(filename string) {
	editor := os.Getenv("EDITOR")
	if editor == "" {
		editor = "nano"
	}

	path, err := exec.LookPath(editor)
	if err != nil { panic(err) }

	cmd := exec.Command(path, filename)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil { panic(err) }
}

func UIMain() {
	// Prompt loop
	line, err := prompt()
	for err == nil {
		// tokenize the line
		toks := tokenize(line)
		if len(toks) == 0 { goto nothing }
		if toks[0] == "exit" { goto exit }
		{
			action, ok := actions[toks[0]]
			if !ok {
				fmt.Println("?")
				goto nothing
			}
			func () {
				defer func() {
					err := recover()
					if err != nil {
						fmt.Printf("error: %s\n", err)
					}
				}()
				action(toks[1:])
			}()
		}
nothing:
		line, err = prompt()
	}
	if err != nil && err != io.EOF {
		panic(err)
	}
exit:
	fmt.Println("Bye!")
}
<commit_msg>Template message works<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"sync"
)

var initInput sync.Once
var input *bufio.Reader

func prompt() (string, error) {
	initInput.Do(func (){input = bufio.NewReader(os.Stdin)})

	fmt.Print("masc> ")
	line, _, err := input.ReadLine()
	return string(line), err
}

func isWhite(r rune) bool {
	return r == ' ' || r == '\t'
}

func isQuot(r rune) bool { return r == '"' }

func tokenize(line string) (toks []string) {
	// states
	const (
		READY = iota
		INTOK
		INQUOT
	)

	var tmp string
	state := READY
	for _, c := range line {
		switch state {
		case READY:
			if isWhite(c) { continue }
			tmp = string(c)
			state = INTOK
		case INTOK:
			if isWhite(c) {
				toks = append(toks, tmp)
				state = READY
				continue
			}
			tmp += string(c)
		case INQUOT:
		default:
			panic("Invalid state")
		}
	}
	if len(tmp) > 0 {
		toks = append(toks, tmp)
	}
	return
}

func getLine(prefix string, msg string) string {
	lines := strings.Split(msg, "\n")
	for _, line := range lines {
		l := strings.TrimSpace(line)
		if strings.Index(l, prefix) != -1 {
			return strings.TrimSpace(l[len(prefix):])
		}
	}
	return ""
}

func getFrom(msg string) string {
	return getLine("From:", msg)
}

func getTo(msg string) []string {
	return strings.Split(getLine("To:", msg), ",")
}

var actions map[string]func([]string)

func init() {
	actions = map[string]func([]string){
		"help": func(toks []string) {
			fmt.Println(`Commands:
`)
		},
		"h": alias("help"),
		"?": alias("help"),

		"mail": func(toks []string) {
			// create temporary file with this message
			f, err := ioutil.TempFile("", "masc")
			if err != nil {
				panic(err)
			}
			b := bufio.NewWriter(f)
			b.WriteString("From:\nTo:\nSubject:\n\n")
			b.Flush()
			f.Close()

			runEditor(f.Name())

			// read file and remove
			c, err := ioutil.ReadFile(f.Name())
			if err != nil {
				panic(err)
			}
			os.Remove(f.Name())

			msg := string(c)

			// confirm send
			fmt.Print("Send (y/n)? ")
			lb, _, err := input.ReadLine()
			l := string(lb)
			for l != "y" && l != "n" && err == nil {
				fmt.Print("Send (y/n)? ")
				lb, _, err = input.ReadLine()
				l = string(lb)
			}
			if err != nil { panic(err) }
			if l == "n" {
				fmt.Println("Aborting")
				return
			}
			fmt.Println("Sending...")

			// send
			out := &Outgoing{getFrom(msg), getTo(msg), msg}
			doSend(config.Sends[getFrom(msg)], out)
		},
		"m": alias("mail"),

		"smtp": func(args []string) {
			if len(args) != 5 {
				panic("usage: smtp name server ident uname passwd")
			}
			login := &SMTPLogin{args[1], args[2], args[3], args[4]}
			config.Sends[args[0]] = login
		},
	}
}

func alias(cmd string) func([]string) {
	return actions[cmd]
}

func runEditor(filename string) {
	editor := os.Getenv("EDITOR")
	if editor == "" {
		editor = "nano"
	}

	path, err := exec.LookPath(editor)
	if err != nil { panic(err) }

	cmd := exec.Command(path, filename)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil { panic(err) }
}

func UIMain() {
	// Prompt loop
	line, err := prompt()
	for err == nil {
		// tokenize the line
		toks := tokenize(line)
		if len(toks) == 0 { goto nothing }
		if toks[0] == "exit" { goto exit }
		{
			action, ok := actions[toks[0]]
			if !ok {
				fmt.Println("?")
				goto nothing
			}
			func () {
				defer func() {
					err := recover()
					if err != nil {
						fmt.Printf("error: %s\n", err)
					}
				}()
				action(toks[1:])
			}()
		}
nothing:
		line, err = prompt()
	}
	if err != nil && err != io.EOF {
		panic(err)
	}
exit:
	fmt.Println("Bye!")
}
<|endoftext|>


<commit_before>package gweb

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"reflect"
	"strings"

	. "github.com/eynstudio/gobreak"
	"github.com/eynstudio/gobreak/di"
)

var ctxType = reflect.TypeOf(&Ctx{})

type Ctx struct {
	*App
	di.Container
	*Req
	*Resp
	Scope   M
	isErr   bool
	afters  []Handler
	Handled bool
	Uid     GUID
}

func (p *Ctx) Error(code int) *Ctx {
	p.WriteHeader(code)
	p.isErr = true
	return p
}

func (p *Ctx) Set(k string, v T)   { p.Scope[k] = v }
func (p *Ctx) IsErr() bool         { return p.isErr }
func (p *Ctx) Get(k string) string { return p.Scope.GetStr(k) }

func (p *Ctx) IsGET() bool  { return p.Method == GET }
func (p *Ctx) IsPOST() bool { return p.Method == POST }
func (p *Ctx) IsPUT() bool  { return p.Method == PUT }
func (p *Ctx) IsDEL() bool  { return p.Method == DEL }

func (p *Ctx) SetHandled()         { p.Handled = true }
func (p *Ctx) OK()                 { p.WriteHeader(http.StatusOK) }
func (p *Ctx) NotFound()           { p.Error(http.StatusNotFound) }
func (p *Ctx) Forbidden()          { p.Error(http.StatusForbidden) }
func (p *Ctx) Redirect(url string) { http.Redirect(p.Resp, p.Request, url, http.StatusFound) }
func (p *Ctx) HandleStatusJson(s Status) {
	p.Json(s)
	p.Handled = true
}

func (p *Ctx) Json(m T) {
	if p.IsErr() {
		return
	}
	if b, err := json.Marshal(m); err != nil {
		p.Error(http.StatusInternalServerError)
	} else {
		p.Resp.Header().Set("Content-Type", "application/json; charset=utf-8")
		p.Resp.Write(b)
	}
}

func (p *Ctx) Text(str string) {
	if p.IsErr() {
		return
	}
	p.Resp.Write([]byte(str))
}

func (p *Ctx) SetCookie(c http.Cookie) { http.SetCookie(p.Resp, &c) }

func (p *Ctx) Tmpl(tpl string, o T) {
	p.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := p.App.Tmpl.Execute(p.Resp, tpl, o); err != nil {
		log.Println(err)
		p.Error(http.StatusInternalServerError)
	}
}

func (p *Ctx) ServeFile() bool {
	url := p.Url()
	for _, path := range p.Cfg.ServeFiles {
		if strings.HasPrefix(url, path) {
			if fi, err := os.Stat(url[1:]); err != nil || fi.IsDir() {
				p.NotFound()
			} else {
				http.ServeFile(p.Resp, p.Request, url[1:])
			}
			return true
		}
	}
	return false
}

func (p Ctx) IsAuth() bool {
	return !p.Uid.IsEmpty()
}

func (p *Ctx) UserId() GUID {
	if !p.HasToken() {
		return GUID("")
	}
	if p.Uid != "" {
		return p.Uid
	}
	uid, _ := p.Sessions.GetSessUid(p.Token)
	p.Uid = GUID(uid)
	return p.Uid
}
<commit_msg>add ctx.ReqIp<commit_after>package gweb

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"reflect"
	"strings"

	http2 "github.com/eynstudio/gobreak/net/http"

	. "github.com/eynstudio/gobreak"
	"github.com/eynstudio/gobreak/di"
)

var ctxType = reflect.TypeOf(&Ctx{})

type Ctx struct {
	*App
	di.Container
	*Req
	*Resp
	Scope   M
	isErr   bool
	afters  []Handler
	Handled bool
	Uid     GUID
}

func (p *Ctx) Error(code int) *Ctx {
	p.WriteHeader(code)
	p.isErr = true
	return p
}

func (p *Ctx) Set(k string, v T)   { p.Scope[k] = v }
func (p *Ctx) IsErr() bool         { return p.isErr }
func (p *Ctx) Get(k string) string { return p.Scope.GetStr(k) }

func (p *Ctx) IsGET() bool  { return p.Method == GET }
func (p *Ctx) IsPOST() bool { return p.Method == POST }
func (p *Ctx) IsPUT() bool  { return p.Method == PUT }
func (p *Ctx) IsDEL() bool  { return p.Method == DEL }

func (p *Ctx) SetHandled()         { p.Handled = true }
func (p *Ctx) OK()                 { p.WriteHeader(http.StatusOK) }
func (p *Ctx) NotFound()           { p.Error(http.StatusNotFound) }
func (p *Ctx) Forbidden()          { p.Error(http.StatusForbidden) }
func (p *Ctx) Redirect(url string) { http.Redirect(p.Resp, p.Request, url, http.StatusFound) }
func (p *Ctx) HandleStatusJson(s Status) {
	p.Json(s)
	p.Handled = true
}

func (p *Ctx) Json(m T) {
	if p.IsErr() {
		return
	}
	if b, err := json.Marshal(m); err != nil {
		p.Error(http.StatusInternalServerError)
	} else {
		p.Resp.Header().Set("Content-Type", "application/json; charset=utf-8")
		p.Resp.Write(b)
	}
}

func (p *Ctx) Text(str string) {
	if p.IsErr() {
		return
	}
	p.Resp.Write([]byte(str))
}

func (p *Ctx) SetCookie(c http.Cookie) { http.SetCookie(p.Resp, &c) }

func (p *Ctx) Tmpl(tpl string, o T) {
	p.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := p.App.Tmpl.Execute(p.Resp, tpl, o); err != nil {
		log.Println(err)
		p.Error(http.StatusInternalServerError)
	}
}

func (p *Ctx) ServeFile() bool {
	url := p.Url()
	for _, path := range p.Cfg.ServeFiles {
		if strings.HasPrefix(url, path) {
			if fi, err := os.Stat(url[1:]); err != nil || fi.IsDir() {
				p.NotFound()
			} else {
				http.ServeFile(p.Resp, p.Request, url[1:])
			}
			return true
		}
	}
	return false
}

func (p Ctx) IsAuth() bool {
	return !p.Uid.IsEmpty()
}

func (p *Ctx) UserId() GUID {
	if !p.HasToken() {
		return GUID("")
	}
	if p.Uid != "" {
		return p.Uid
	}
	uid, _ := p.Sessions.GetSessUid(p.Token)
	p.Uid = GUID(uid)
	return p.Uid
}

func (p *Ctx) ReqIp() string { return http2.GetReqIp(p.Request) }
<|endoftext|>