<commit_before>// Copyright (C) 2014 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package lastfm

import (
	"errors"
	"fmt"
	"sort"
	"sync"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/xconstruct/stark/proto"
	"github.com/xconstruct/stark/services"
)

var Module = &services.Module{
	Name:        "lastfm",
	Version:     "1.0",
	NewInstance: NewService,
}

type Config struct {
	User   string
	ApiKey string
}

type Dependencies struct {
	DB     *gorm.DB
	Config services.Config
	Log    proto.Logger
	Client *proto.Client
}

type Service struct {
	cfg Config
	DB  *gorm.DB
	Log proto.Logger
	*proto.Client

	importing  sync.WaitGroup
	refreshing bool
}

func NewService(deps *Dependencies) *Service {
	s := &Service{
		Config{},
		deps.DB,
		deps.Log,
		deps.Client,

		sync.WaitGroup{},
		false,
	}
	deps.Config.Get(&s.cfg)
	return s
}

func (s *Service) Enable() error {
	createIndizes := !s.DB.HasTable(&Track{})
	if err := s.DB.AutoMigrate(&Track{}, &Artist{}).Error; err != nil {
		return err
	}
	if createIndizes {
		if err := s.DB.Model(&Track{}).AddIndex("album_artist_title", "album", "artist", "title").Error; err != nil {
			return err
		}
	}

	if s.cfg.User != "" {
		go func() {
			for _ = range time.Tick(30 * time.Minute) {
				if err := s.ImportAll(); err != nil {
					s.Log.Errorln("[lastfm] import err:", err)
				}
			}
		}()
	}

	s.Subscribe("lastfm/force_import", "", func(msg proto.Message) {
		if err := s.ImportAll(); err != nil {
			s.ReplyInternalError(msg, err)
		}
	})
	s.Subscribe("lastfm/tag", "", s.handleTag)
	s.Subscribe("cmd/genre", "", s.handleTag)
	return nil
}

func (s *Service) ImportAll() error {
	s.importing.Wait()
	s.importing.Add(1)
	defer s.importing.Done()

	if s.cfg.User == "" {
		return errors.New("No user specified in config!")
	}
	api := NewApi(s.cfg.ApiKey)
	var last Track
	err := s.DB.Order("time DESC").First(&last).Error
	if err != nil && err != gorm.RecordNotFound {
		return err
	}

	initial, err := api.UserGetRecentTracks(s.cfg.User, 1, last.Time.Unix()+60)
	if err != nil {
		return err
	}
	page := initial.RecentTracks.Attr.TotalPages
	for {
		aresult, err := api.UserGetRecentTracks(s.cfg.User, page, last.Time.Unix()+60)
		if err != nil {
			return err
		}
		result := aresult.RecentTracks
		s.Log.Infof("[lastfm] import page %d/%d", result.Attr.Page, result.Attr.TotalPages)
		if len(result.Tracks) == 0 {
			break
		}

		tracks := make([]Track, len(result.Tracks))
		for i, track := range result.Tracks {
			if track.Attr.NowPlaying {
				continue
			}
			if track.Name == "" {
				continue
			}
			tracks[i].Artist = track.Artist.Text
			tracks[i].Album = track.Album.Text
			tracks[i].Title = track.Name
			tracks[i].Time, err = track.ParseDate()
			if err != nil {
				return err
			}
		}

		if err := s.storeTracks(tracks); err != nil {
			return err
		}
		page = result.Attr.Page - 1
		if page == 0 {
			break
		}
	}

	go s.RefreshArtistInfo()

	return nil
}

func (s *Service) storeTracks(ts []Track) error {
	sort.Sort(ByDate(ts))
	tx := s.DB.Begin()
	for _, t := range ts {
		if err := tx.Save(&t).Error; err != nil {
			tx.Rollback()
			return err
		}
	}
	tx.Commit()
	return nil
}

func (s *Service) RefreshArtistInfo() {
	if s.refreshing || s.cfg.ApiKey == "" {
		return
	}
	defer func() {
		s.refreshing = false
	}()
	s.refreshing = true

	api := NewApi(s.cfg.ApiKey)
	for {
		var name string
		err := s.DB.Raw(`
			SELECT t.artist FROM lastfm_tracks t
			LEFT JOIN lastfm_artists a ON a.name = t.artist
			WHERE a.name IS NULL
			ORDER BY t.time DESC
			LIMIT 1
		`).Row().Scan(&name)
		if err != nil {
			if err != gorm.RecordNotFound {
				s.Log.Errorln("[lastfm] refresh artist err:", err)
			}
			return
		}
		if name == "" {
			return
		}

		tags, err := api.ArtistGetTopTags(name)
		if err != nil {
			s.Log.Errorln("[lastfm] refresh artist api err:", name, err)
			return
		}
		genre, broad := FindGenre(tags.TopTags.Tags)
		artist := &Artist{
			Name:       name,
			Genre:      genre,
			BroadGenre: broad,
		}
		if err = s.DB.Save(artist).Error; err != nil {
			s.Log.Errorln("[lastfm] refresh artist save err:", err)
			return
		}
	}
}

type tagPayload struct {
	Artist     string   `json:"artist"`
	Genre      string   `json:"genre"`
	BroadGenre string   `json:"broad_genre"`
	Tags       []string `json:"tags"`
}

func (p tagPayload) Text() string {
	return fmt.Sprintf("%s is genre %s, %s.", p.Artist, p.Genre, p.BroadGenre)
}

func (s *Service) handleTag(msg proto.Message) {
	var p tagPayload
	p.Artist = msg.Text
	if err := msg.DecodePayload(&p); err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	api := NewApi(s.cfg.ApiKey)
	tags, err := api.ArtistGetTopTags(p.Artist)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}

	p.Artist = tags.TopTags.Attr.Artist
	p.Genre, p.BroadGenre = FindGenre(tags.TopTags.Tags)
	for _, t := range tags.TopTags.Tags {
		p.Tags = append(p.Tags, t.Name)
	}
	s.Reply(msg, proto.CreateMessage("lastfm/tagged", p))
}
<commit_msg>lastfm: Fix null tracks.<commit_after>// Copyright (C) 2014 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package lastfm

import (
	"errors"
	"fmt"
	"sort"
	"sync"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/xconstruct/stark/proto"
	"github.com/xconstruct/stark/services"
)

var Module = &services.Module{
	Name:        "lastfm",
	Version:     "1.0",
	NewInstance: NewService,
}

type Config struct {
	User   string
	ApiKey string
}

type Dependencies struct {
	DB     *gorm.DB
	Config services.Config
	Log    proto.Logger
	Client *proto.Client
}

type Service struct {
	cfg Config
	DB  *gorm.DB
	Log proto.Logger
	*proto.Client

	importing  sync.WaitGroup
	refreshing bool
}

func NewService(deps *Dependencies) *Service {
	s := &Service{
		Config{},
		deps.DB,
		deps.Log,
		deps.Client,

		sync.WaitGroup{},
		false,
	}
	deps.Config.Get(&s.cfg)
	return s
}

func (s *Service) Enable() error {
	createIndizes := !s.DB.HasTable(&Track{})
	if err := s.DB.AutoMigrate(&Track{}, &Artist{}).Error; err != nil {
		return err
	}
	if createIndizes {
		if err := s.DB.Model(&Track{}).AddIndex("album_artist_title", "album", "artist", "title").Error; err != nil {
			return err
		}
	}

	if s.cfg.User != "" {
		go func() {
			for _ = range time.Tick(30 * time.Minute) {
				if err := s.ImportAll(); err != nil {
					s.Log.Errorln("[lastfm] import err:", err)
				}
			}
		}()
	}

	s.Subscribe("lastfm/force_import", "", func(msg proto.Message) {
		if err := s.ImportAll(); err != nil {
			s.ReplyInternalError(msg, err)
		}
	})
	s.Subscribe("lastfm/tag", "", s.handleTag)
	s.Subscribe("cmd/genre", "", s.handleTag)
	return nil
}

func (s *Service) ImportAll() error {
	s.importing.Wait()
	s.importing.Add(1)
	defer s.importing.Done()

	if s.cfg.User == "" {
		return errors.New("No user specified in config!")
	}
	api := NewApi(s.cfg.ApiKey)
	var last Track
	err := s.DB.Order("time DESC").First(&last).Error
	if err != nil && err != gorm.RecordNotFound {
		return err
	}

	initial, err := api.UserGetRecentTracks(s.cfg.User, 1, last.Time.Unix()+60)
	if err != nil {
		return err
	}
	page := initial.RecentTracks.Attr.TotalPages
	for {
		aresult, err := api.UserGetRecentTracks(s.cfg.User, page, last.Time.Unix()+60)
		if err != nil {
			return err
		}
		result := aresult.RecentTracks
		s.Log.Infof("[lastfm] import page %d/%d", result.Attr.Page, result.Attr.TotalPages)
		if len(result.Tracks) == 0 {
			break
		}

		tracks := make([]Track, 0, len(result.Tracks))
		for _, track := range result.Tracks {
			if track.Attr.NowPlaying || track.Name == "" {
				continue
			}
			t, err := track.ParseDate()
			if err != nil {
				return err
			}
			tracks = append(tracks, Track{
				Artist: track.Artist.Text,
				Album:  track.Album.Text,
				Title:  track.Name,
				Time:   t,
			})
		}

		if err := s.storeTracks(tracks); err != nil {
			return err
		}
		page = result.Attr.Page - 1
		if page == 0 {
			break
		}
	}

	go s.RefreshArtistInfo()

	return nil
}

func (s *Service) storeTracks(ts []Track) error {
	sort.Sort(ByDate(ts))
	tx := s.DB.Begin()
	for _, t := range ts {
		if err := tx.Save(&t).Error; err != nil {
			tx.Rollback()
			return err
		}
	}
	tx.Commit()
	return nil
}

func (s *Service) RefreshArtistInfo() {
	if s.refreshing || s.cfg.ApiKey == "" {
		return
	}
	defer func() {
		s.refreshing = false
	}()
	s.refreshing = true

	api := NewApi(s.cfg.ApiKey)
	for {
		var name string
		err := s.DB.Raw(`
			SELECT t.artist FROM lastfm_tracks t
			LEFT JOIN lastfm_artists a ON a.name = t.artist
			WHERE a.name IS NULL
			ORDER BY t.time DESC
			LIMIT 1
		`).Row().Scan(&name)
		if err != nil {
			if err != gorm.RecordNotFound {
				s.Log.Errorln("[lastfm] refresh artist err:", err)
			}
			return
		}
		if name == "" {
			return
		}

		tags, err := api.ArtistGetTopTags(name)
		if err != nil {
			s.Log.Errorln("[lastfm] refresh artist api err:", name, err)
			return
		}
		genre, broad := FindGenre(tags.TopTags.Tags)
		artist := &Artist{
			Name:       name,
			Genre:      genre,
			BroadGenre: broad,
		}
		if err = s.DB.Save(artist).Error; err != nil {
			s.Log.Errorln("[lastfm] refresh artist save err:", err)
			return
		}
	}
}

type tagPayload struct {
	Artist     string   `json:"artist"`
	Genre      string   `json:"genre"`
	BroadGenre string   `json:"broad_genre"`
	Tags       []string `json:"tags"`
}

func (p tagPayload) Text() string {
	return fmt.Sprintf("%s is genre %s, %s.", p.Artist, p.Genre, p.BroadGenre)
}

func (s *Service) handleTag(msg proto.Message) {
	var p tagPayload
	p.Artist = msg.Text
	if err := msg.DecodePayload(&p); err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	api := NewApi(s.cfg.ApiKey)
	tags, err := api.ArtistGetTopTags(p.Artist)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}

	p.Artist = tags.TopTags.Attr.Artist
	p.Genre, p.BroadGenre = FindGenre(tags.TopTags.Tags)
	for _, t := range tags.TopTags.Tags {
		p.Tags = append(p.Tags, t.Name)
	}
	s.Reply(msg, proto.CreateMessage("lastfm/tagged", p))
}
<|endoftext|>


<commit_before>package reuseport

import (
	"errors"
	"net"
)

// ErrUnsuportedProtocol signals that the protocol is not currently
// supported by this package. This package currently only supports TCP.
var ErrUnsupportedProtocol = errors.New("protocol not yet supported")

// ErrReuseFailed is returned if a reuse attempt was unsuccessful.
var ErrReuseFailed = errors.New("protocol not yet supported")

// Listen listens at the given network and address. see net.Listen
// Returns a net.Listener created from a file discriptor for a socket
// with SO_REUSEPORT and SO_REUSEADDR option set.
func Listen(network, address string) (net.Listener, error) {
	return listen(network, address)
}

// Dialer is used to specify the Dial options, much like net.Dialer.
// We simply wrap a net.Dialer.
type Dialer struct {
	D net.Dialer
}

// Dial dials the given network and address. see net.Dialer.Dial
// Returns a net.Conn created from a file discriptor for a socket
// with SO_REUSEPORT and SO_REUSEADDR option set.
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
	return dial(d.D, network, address)
}
<commit_msg>Dial() func<commit_after>package reuseport

import (
	"errors"
	"net"

	resolve "github.com/jbenet/go-net-resolve-addr"
)

// ErrUnsuportedProtocol signals that the protocol is not currently
// supported by this package. This package currently only supports TCP.
var ErrUnsupportedProtocol = errors.New("protocol not yet supported")

// ErrReuseFailed is returned if a reuse attempt was unsuccessful.
var ErrReuseFailed = errors.New("protocol not yet supported")

// Listen listens at the given network and address. see net.Listen
// Returns a net.Listener created from a file discriptor for a socket
// with SO_REUSEPORT and SO_REUSEADDR option set.
func Listen(network, address string) (net.Listener, error) {
	return listen(network, address)
}

// Dial dials the given network and address. see net.Dialer.Dial
// Returns a net.Conn created from a file discriptor for a socket
// with SO_REUSEPORT and SO_REUSEADDR option set.
func Dial(network, laddr, raddr string) (net.Conn, error) {

	var d Dialer
	if laddr != "" {
		netladdr, err := resolve.ResolveAddr("dial", network, laddr)
		if err != nil {
			return nil, err
		}
		d.D.LocalAddr = netladdr
	}

	return dial(d.D, network, raddr)
}

// Dialer is used to specify the Dial options, much like net.Dialer.
// We simply wrap a net.Dialer.
type Dialer struct {
	D net.Dialer
}

// Dial dials the given network and address. see net.Dialer.Dial
// Returns a net.Conn created from a file discriptor for a socket
// with SO_REUSEPORT and SO_REUSEADDR option set.
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
	return dial(d.D, network, address)
}
<|endoftext|>


<commit_before>package dockerclient

import (
	"io"
)

type Callback func(*Event, chan error, ...interface{})

type StatCallback func(string, *Stats, chan error, ...interface{})

type Client interface {
	Info() (*Info, error)
	ListContainers(all, size bool, filters string) ([]Container, error)
	InspectContainer(id string) (*ContainerInfo, error)
	CreateContainer(config *ContainerConfig, name string) (string, error)
	ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error)
	ContainerChanges(id string) ([]*ContainerChanges, error)
	Exec(config *ExecConfig) (string, error)
	StartContainer(id string, config *HostConfig) error
	StopContainer(id string, timeout int) error
	RestartContainer(id string, timeout int) error
	KillContainer(id, signal string) error
	// MonitorEvents returns an EventOrError channel and a close channel. If an
	// error is ever sent, then no more stats will be sent. Users must
	// always close the close channel when they are done reading stats, even
	// if an error was sent.
	MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error)
	StartMonitorEvents(cb Callback, ec chan error, args ...interface{})
	StopAllMonitorEvents()
	StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{})
	StopAllMonitorStats()
	Version() (*Version, error)
	PullImage(name string, auth *AuthConfig) error
	RemoveContainer(id string, force, volumes bool) error
	ListImages() ([]*Image, error)
	RemoveImage(name string) ([]*ImageDelete, error)
	PauseContainer(name string) error
	UnpauseContainer(name string) error
}
<commit_msg>fix comments<commit_after>package dockerclient

import (
	"io"
)

type Callback func(*Event, chan error, ...interface{})

type StatCallback func(string, *Stats, chan error, ...interface{})

type Client interface {
	Info() (*Info, error)
	ListContainers(all, size bool, filters string) ([]Container, error)
	InspectContainer(id string) (*ContainerInfo, error)
	CreateContainer(config *ContainerConfig, name string) (string, error)
	ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error)
	ContainerChanges(id string) ([]*ContainerChanges, error)
	Exec(config *ExecConfig) (string, error)
	StartContainer(id string, config *HostConfig) error
	StopContainer(id string, timeout int) error
	RestartContainer(id string, timeout int) error
	KillContainer(id, signal string) error
	// MonitorEvents returns an EventOrError channel and a close channel. If an
	// error is ever sent, then no more eventswill be sent. Users must
	// always close the close channel when they are done reading events, even
	// if an error was sent.
	MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error)
	StartMonitorEvents(cb Callback, ec chan error, args ...interface{})
	StopAllMonitorEvents()
	StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{})
	StopAllMonitorStats()
	Version() (*Version, error)
	PullImage(name string, auth *AuthConfig) error
	RemoveContainer(id string, force, volumes bool) error
	ListImages() ([]*Image, error)
	RemoveImage(name string) ([]*ImageDelete, error)
	PauseContainer(name string) error
	UnpauseContainer(name string) error
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 RightScale, Inc. - see LICENSE

package persist

// LogClient is the interface the application needs to implement so the persist can call it back
type LogClient interface {
	// Replay is called by the persistence layer during log replay in order to replay
	// an individual log event. If Replay returns an error the replay is aborted and
	// produces the error.
	// Beware that it it possible that log events are replayed that contain mutations to
	// resources that have not been created yet, i.e. for which the event produced by
	// Enumerate has not been replayed yet. In those cases, Replay must ignore the event
	// because a subsequent create will be Replayed with the correct value for the
	// resource. Note that if events contain updates to multiple resources then some
	// of them may have been created and need updating while others may not have been created
	// and shouldn't be created/updated.
	Replay(logEvent interface{}) error

	// Enumerate is called by the persistence layer in order to enumerate all live resources
	// by making calls to Log.Write(). (If enumerate encounters an erorr it's time to panic.)
	// Enumerate can run in parallel with new updates to resources however the application
	// must ensure that calls to Log.Write() are in the same order as Enumerate's reads
	// and other update's writes.
	Enumerate()
}

type Log interface {
	// Write an event to the log, this uses gob serialization internally. If an error
	// occurs there is a serious problem with the log, for example, disk full or socket
	// disconnected from the log destination. If the application can unroll the mutations
	// it has performed it should do so and return its client and error. If, however, the
	// application cannot unroll it is recommended not to check for error here and continue
	// to operate optimistically. Once the log problem gets repaired the persist layer will
	// do a log rotation to ensure all live data is captured.
	Write(logEvent interface{}) error

	// SetSizeLimit determines when the persist layer should rotate logs. The default is
	// 10MB
	SetSizeLimit(bytes int64)

	// AddDestination adds additional destinations to the Log (not yet implemented)
	AddDestination(dest LogDestination) error

	// HealthCheck returns any persistent error encountered in persist that prevents it
	// from logging. If HealthCheck() returns an error then all Write() calls will return
	// the same error. If the problem is fixed the error will eventually go away again and
	// the log will be "repaired" by doing a rotation. The intent of the HealthCheck call
	// is for the application to be able to reject requests early if the logging is broken.
	HealthCheck() error
}

// CreateLog creates a new log and errors if a pre-existing log is found.
func CreateLog(dest LogDestination, client LogClient) (Log, error) {
	return &pLog{}, nil
}

// OpenLog reopens an existing log, replays all log entries, and then prepares to append
// to it. The call to OpenLog completes once any necessary replay has completed. The
// okToCreate flag indicates whether it's ok to start a completely new log (or whether an
// error shold be produced if no pre-existing log is found).
func OpenLog(dest LogDestination, client LogClient, okToCreate bool) (Log, error) {
	return &pLog{}, nil
}

type LogDestination interface {
}

// ===== Stub implementation of do-nothing persistence log

type pLog struct {
}

func (pl *pLog) Write(logEvent interface{}) error {
	return nil
}
func (pl *pLog) SetSizeLimit(bytes int64)                 {}
func (pl *pLog) AddDestination(dest LogDestination) error { return nil }
func (pl *pLog) HealthCheck() error                       { return nil }
<commit_msg>reworked interface<commit_after>// Copyright (c) 2015 RightScale, Inc. - see LICENSE

package persist

import "encoding/gob"

// LogClient is the interface the application needs to implement so the persist can call it back
type LogClient interface {
	// Replay is called by the persistence layer during log replay in order to replay
	// an individual log event. If Replay returns an error the replay is aborted and
	// produces the error.
	// Beware that it it possible that log events are replayed that contain mutations to
	// resources that have not been created yet, i.e. for which the event produced by
	// Enumerate has not been replayed yet. In those cases, Replay must ignore the event
	// because a subsequent create will be Replayed with the correct value for the
	// resource. Note that if events contain updates to multiple resources then some
	// of them may have been created and need updating while others may not have been created
	// and shouldn't be created/updated.
	Replay(logEvent interface{}) error

	// PersistAll is called by the persistence layer in order to enumerate all live resources
	// and persist them by making calls to Log.Write().
	// (If PersistAll encounters an erorr it's time to panic.)
	// PersistAll can run in parallel with new updates to resources however the application
	// must ensure that calls to Log.Write() are in the same order as PersistAll's reads
	// and other update's writes.
	PersistAll()
}

type Log interface {
	// Write an event to the log, this uses gob serialization internally. If an error
	// occurs there is a serious problem with the log, for example, disk full or socket
	// disconnected from the log destination. If the application can unroll the mutations
	// it has performed it should do so and return its client and error. If, however, the
	// application cannot unroll it is recommended not to check for error here and continue
	// to operate optimistically. Once the log problem gets repaired the persist layer will
	// do a log rotation to ensure all live data is captured.
	Write(logEvent interface{}) error

	// SetSizeLimit determines when the persist layer should rotate logs. The default is
	// 10MB
	SetSizeLimit(bytes int64)

	// AddDestination adds additional destinations to the Log (not yet implemented)
	AddDestination(dest LogDestination) error

	// HealthCheck returns any persistent error encountered in persist that prevents it
	// from logging. If HealthCheck() returns an error then all Write() calls will return
	// the same error. If the problem is fixed the error will eventually go away again and
	// the log will be "repaired" by doing a rotation. The intent of the HealthCheck call
	// is for the application to be able to reject requests early if the logging is broken.
	HealthCheck() error

	// Register a type being written to the log, this must be called for each type passed
	// to Write and for any type expected in an interface type inside an event. This calls
	// gob.Register() internally, please see the gob docs
	Register(value interface{})
}

// CreateLog creates a new log and errors if a pre-existing log is found.
func CreateLog(dest LogDestination, client LogClient) (Log, error) {
	return &pLog{}, nil
}

// OpenLog reopens an existing log, replays all log entries, and then prepares to append
// to it. The call to OpenLog completes once any necessary replay has completed. The
// okToCreate flag indicates whether it's ok to start a completely new log (or whether an
// error shold be produced if no pre-existing log is found).
func OpenLog(dest LogDestination, client LogClient, okToCreate bool) (Log, error) {
	return &pLog{}, nil
}

type LogDestination interface {
}

// ===== Stub implementation of do-nothing persistence log

type pLog struct {
}

func (pl *pLog) Write(logEvent interface{}) error {
	return nil
}
func (pl *pLog) SetSizeLimit(bytes int64)                 {}
func (pl *pLog) AddDestination(dest LogDestination) error { return nil }
func (pl *pLog) HealthCheck() error                       { return nil }
func (pl *pLog) Register(value interface{})               { gob.Register(value) }

// ===== Stub implementation for do-nothing persistence log destination

type NoopDest struct {
}
<|endoftext|>


<commit_before>// My mods to the Go implementation of Conway's Game of Life.
//
// based on https://golang.org/doc/play/life.go
//
package main

import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"
)

const (
	deadcell = "  "
)

type LocationSource interface {
	NextLocation() (x, y int)
	HasNext() bool
}

var (
	Seeder LocationSource

	seed     int64
	gens     int
	startGen int
	iconName string
	livecell []byte
	icon     map[string]string
)

type RandomLocationProvider struct {
	i    int
	w, h int
}

func (r *RandomLocationProvider) NextLocation() (x, y int) {
	if !r.HasNext() {
		log.Fatal("NextLocation(): no next location available")
	}
	r.i++
	return rand.Intn(r.w), rand.Intn(r.h)
}

func (r *RandomLocationProvider) HasNext() bool {
	return r.i < r.w*r.h/4
}

func NewRandomLocationProvider(w, h int) *RandomLocationProvider {
	return &RandomLocationProvider{w: w, h: h}
}

// Field represents a two-dimensional field of cells.
type Field struct {
	s    [][]bool
	w, h int
}

// NewField returns an empty field of the specified width and height.
func NewField(w, h int) *Field {
	s := make([][]bool, h)
	for i := range s {
		s[i] = make([]bool, w)
	}
	return &Field{s: s, w: w, h: h}
}

// Set sets the state of the specified cell to the given value.
func (f *Field) Set(x, y int, b bool) {
	f.s[y][x] = b
}

// Alive reports whether the specified cell is alive.
// If the x or y coordinates are outside the field boundaries they are wrapped
// toroidally. For instance, an x value of -1 is treated as width-1.
func (f *Field) Alive(x, y int) bool {
	x += f.w
	x %= f.w
	y += f.h
	y %= f.h
	return f.s[y][x]
}

// Next returns the state of the specified cell at the next time step.
func (f *Field) Next(x, y int) bool {
	// Count the adjacent cells that are alive.
	alive := 0
	for i := -1; i <= 1; i++ {
		for j := -1; j <= 1; j++ {
			if (j != 0 || i != 0) && f.Alive(x+i, y+j) {
				alive++
			}
		}
	}
	// Return next state according to the game rules:
	//   exactly 3 neighbors: on,
	//   exactly 2 neighbors: maintain current state,
	//   otherwise: off.
	return alive == 3 || alive == 2 && f.Alive(x, y)
}

// Life stores the state of a round of Conway's Game of Life.
type Life struct {
	a, b    *Field
	w, h, g int
}

// NewLife returns a new Life game state with initial state provided by Seeder
func NewLife(w, h int) *Life {
	a := NewField(w, h)
	for Seeder.HasNext() {
		x, y := Seeder.NextLocation()
		a.Set(x, y, true)
	}
	return &Life{
		a: a, b: NewField(w, h),
		w: w, h: h,
	}
}

// Step advances the game by one instant, recomputing and updating all cells.
func (l *Life) step() {
	// Update the state of the next field (b) from the current field (a).
	for y := 0; y < l.h; y++ {
		for x := 0; x < l.w; x++ {
			l.b.Set(x, y, l.a.Next(x, y))
		}
	}
	// Swap fields a and b.
	l.a, l.b = l.b, l.a

	// increment generation count
	l.g++
}

// String returns the game board as a string.
func (l *Life) String() string {
	var buf bytes.Buffer
	for y := 0; y < l.h; y++ {
		for x := 0; x < l.w; x++ {
			cell := []byte(deadcell)
			if l.a.Alive(x, y) {
				cell = livecell
			}
			buf.Write(cell)
		}
		buf.WriteByte('\n')
	}
	return buf.String()
}

func (l *Life) showGeneration(nth int) {
	fmt.Printf("\n\nGeneration %v (%v of %v):\n\n%v", l.g, nth-startGen+1, gens, l)
}

func (l *Life) showSummary() {
	fmt.Printf("%v generations calculated.\n\n", l.g)
	fmt.Printf("To continue: %v -y %v -x %v -seed %v -icon %v -s %v -n %v\n", os.Args[0],
		l.h, l.w, seed, iconName, l.g, gens,
	)
}

func (l *Life) simulate(gens int, delay time.Duration) {

	fmt.Printf("\nConway's Game of Life\n")

	if startGen > 1 {
		fmt.Printf("\nStarting from generation %v...", startGen)
		startGen--
	} else {
		startGen = 0
	}

	maxgen := gens + startGen
	for i := 0; i < maxgen; i++ {
		l.step()
		if startGen <= i {
			l.showGeneration(i)
			time.Sleep(delay)
		}
	}

	l.showSummary()
}

func initSeed() {
	if seed == 0 {
		seed = time.Now().UnixNano()
	}
	rand.Seed(seed)
}

func initDisplay() {
	ding, ok := icon[iconName]
	if !ok {
		iconName = "blue-circle"
		ding = icon[iconName]
	}
	livecell = []byte(" " + ding)
}

func checkStartGeneration() {
	if startGen < 0 {
		startGen = 0
	}
}

func parseflags() (width, height, stepsPerSecond int) {

	flag.Int64Var(&seed, "seed", 0,
		"seed for initial population (default random)")

	flag.IntVar(&height, "y", 30, "height of simulation field")
	flag.IntVar(&width, "x", 30, "width of simulation field")
	flag.IntVar(&gens, "n", 20, "display up to `N` generations")
	flag.IntVar(&stepsPerSecond, "r", 5, "display `N` generations per second")
	flag.IntVar(&startGen, "s", 0, "start displaying from generation `N`")
	flag.StringVar(&iconName, "icon", "", "`name` of icon to use for live cells (default blue-circle)")

	flag.Parse()

	initSeed()
	initDisplay()
	checkStartGeneration()

	return
}

func usage() {

	icon = make(map[string]string)
	icon["aster-1"] = "\u2731"
	icon["aster-2"] = "\u2749"
	icon["blue-circle"] = "\u23FA"
	icon["blue-square"] = "\u23F9"
	icon["bug"] = "\u2603"
	icon["circle-plus"] = "\u2A01"
	icon["circle-x"] = "\u2A02"
	icon["dot-star"] = "\u272A"
	icon["fat-x"] = "\u2716"
	icon["flower"] = "\u273F"
	icon["green-x"] = "\u274E"
	icon["man-dribble"] = "\u26F9"
	icon["man-yellow"] = "\u26B1"
	icon["no-entry"] = "\u26D4"
	icon["redhat"] = "\u26D1"
	icon["skull-x"] = "\u2620"
	icon["snowflake"] = "\u274A"
	icon["snowman"] = "\u26C4"
	icon["square-big"] = "\u2B1C"
	icon["square-small"] = "\u25A9"
	icon["star-yellow"] = "\u2B50"
	icon["star-white"] = "\u2605"
	icon["star-6pt"] = "\u2736"
	icon["star-8pt"] = "\u2738"
	icon["whitedot"] = "\u26AA"

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [-x] [-y] [-r] [-n] [-s] [-seed] [-icon]\n\nOptions:\n\n",
			os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr,
			"\nAvailable icons for live cells:\n\n"+
				"Icon\tName\t\tDescription\n"+
				"----\t--------\t-----------\n"+
				icon["aster-1"]+"\taster-1\t\tAsterisk 1\n"+
				icon["aster-2"]+"\taster-2\t\tAsterisk 2\n"+
				icon["blue-circle"]+"\tblue-circle\tBlue tile, white circle (default)\n"+
				icon["blue-square"]+"\tblue-square\tBlue tile, white square\n"+
				icon["bug"]+"\tbug\t\tBug\n"+
				icon["circle-plus"]+"\tcircle-plus\tCircle with a '+'\n"+
				icon["circle-x"]+"\tcircle-x\tCircle with an 'x'\n"+
				icon["dot-star"]+"\tdot-star\tDot with star\n"+
				icon["fat-x"]+"\tfat-x\t\tFat white X\n"+
				icon["flower"]+"\tflower\t\tFlower\n"+
				icon["green-x"]+"\tgreen-x\t\tGreen tile with white X\n"+
				icon["man-dribble"]+"\tman-dribble\tMan dribbling ball\n"+
				icon["man-yellow"]+"\tman-yellow\tLittle yellow man\n"+
				icon["no-entry"]+"\tno-entry\tNo entry sign\n"+
				icon["redhat"]+"\tredhat\t\tRed hardhat with white cross\n"+
				icon["skull-x"]+"\tskull-x\t\tSkull and crossbones\n"+
				icon["snowflake"]+"\tsnowflake\tSnowflake\n"+
				icon["snowman"]+"\tsnowman\t\tSnowman\n"+
				icon["square-big"]+"\tsquare-big\tBig square\n"+
				icon["square-small"]+"\tsquare-small\tSmall square\n"+
				icon["star-yellow"]+"\tstar-yellow\tYellow 5-point star\n"+
				icon["star-white"]+"\tstar-white\tWhite 5-point star\n"+
				icon["star-6pt"]+"\tstar-6pt\t6-point star\n"+
				icon["star-8pt"]+"\tstar-8pt\t8-point star\n"+
				icon["whitedot"]+"\twhitedot\tWhite dot\n",
		)
	}
}

func main() {

	usage()

	width, height, stepsPerSecond := parseflags()

	if Seeder == nil {
		Seeder = NewRandomLocationProvider(width, height)
	}

	NewLife(width, height).simulate(
		gens,
		time.Second/time.Duration(stepsPerSecond),
	)
}
<commit_msg>Added HasNext() to LocationSource interface, changed seeding loop to use HasNext()<commit_after>// My mods to the Go implementation of Conway's Game of Life.
//
// based on https://golang.org/doc/play/life.go
//
package main

import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"
)

const (
	deadcell = "  "
)

type LocationSource interface {
	NextLocation() (x, y int)
	HasNext() bool
}

var (
	Seeder LocationSource

	seed     int64
	gens     int
	startGen int
	iconName string
	livecell []byte
	icon     map[string]string
)

type RandomLocationProvider struct {
	i    int
	w, h int
}

func (r *RandomLocationProvider) NextLocation() (x, y int) {
	if !r.HasNext() {
		log.Fatal("NextLocation(): no next location available")
	}
	r.i++
	return rand.Intn(r.w), rand.Intn(r.h)
}

func (r *RandomLocationProvider) HasNext() bool {
	return r.i < r.w*r.h/4
}

func NewRandomLocationProvider(w, h int) *RandomLocationProvider {
	return &RandomLocationProvider{w: w, h: h}
}

// Field represents a two-dimensional field of cells.
type Field struct {
	s    [][]bool
	w, h int
}

// NewField returns an empty field of the specified width and height.
func NewField(w, h int) *Field {
	s := make([][]bool, h)
	for i := range s {
		s[i] = make([]bool, w)
	}
	return &Field{s: s, w: w, h: h}
}

// Set sets the state of the specified cell to the given value.
func (f *Field) Set(x, y int, b bool) {
	f.s[y][x] = b
}

// Alive reports whether the specified cell is alive.
// If the x or y coordinates are outside the field boundaries they are wrapped
// toroidally. For instance, an x value of -1 is treated as width-1.
func (f *Field) Alive(x, y int) bool {
	x += f.w
	x %= f.w
	y += f.h
	y %= f.h
	return f.s[y][x] // && !f.BlackHoled(y, x)
}

// Next returns the state of the specified cell at the next time step.
func (f *Field) Next(x, y int) bool {
	// Count the adjacent cells that are alive.
	alive := 0
	for i := -1; i <= 1; i++ {
		for j := -1; j <= 1; j++ {
			if (j != 0 || i != 0) && f.Alive(x+i, y+j) {
				alive++
			}
		}
	}
	// Return next state according to the game rules:
	//   exactly 3 neighbors: on,
	//   exactly 2 neighbors: maintain current state,
	//   otherwise: off.
	return alive == 3 || alive == 2 && f.Alive(x, y)
}

// Life stores the state of a round of Conway's Game of Life.
type Life struct {
	a, b    *Field
	w, h, g int
}

// NewLife returns a new Life game state with initial state provided by Seeder
func NewLife(w, h int) *Life {
	a := NewField(w, h)
	for Seeder.HasNext() {
		x, y := Seeder.NextLocation()
		a.Set(x, y, true)
	}
	return &Life{
		a: a, b: NewField(w, h),
		w: w, h: h,
	}
}

// Step advances the game by one instant, recomputing and updating all cells.
func (l *Life) step() {
	// Update the state of the next field (b) from the current field (a).
	for y := 0; y < l.h; y++ {
		for x := 0; x < l.w; x++ {
			l.b.Set(x, y, l.a.Next(x, y))
		}
	}
	// Swap fields a and b.
	l.a, l.b = l.b, l.a

	// increment generation count
	l.g++
}

// String returns the game board as a string.
func (l *Life) String() string {
	var buf bytes.Buffer
	for y := 0; y < l.h; y++ {
		for x := 0; x < l.w; x++ {
			cell := []byte(deadcell)
			if l.a.Alive(x, y) {
				cell = livecell
			}
			buf.Write(cell)
		}
		buf.WriteByte('\n')
	}
	return buf.String()
}

func (l *Life) showGeneration(nth int) {
	fmt.Printf("\n\nGeneration %v (%v of %v):\n\n%v", l.g, nth-startGen+1, gens, l)
}

func (l *Life) showSummary() {
	fmt.Printf("%v generations calculated.\n\n", l.g)
	fmt.Printf("To continue: %v -y %v -x %v -seed %v -icon %v -s %v -n %v\n", os.Args[0],
		l.h, l.w, seed, iconName, l.g, gens,
	)
}

func (l *Life) simulate(gens int, delay time.Duration) {

	fmt.Printf("\nConway's Game of Life\n")

	if startGen > 1 {
		fmt.Printf("\nStarting from generation %v...", startGen)
		startGen--
	} else {
		startGen = 0
	}

	maxgen := gens + startGen
	for i := 0; i < maxgen; i++ {
		l.step()
		if startGen <= i {
			l.showGeneration(i)
			time.Sleep(delay)
		}
	}

	l.showSummary()
}

func initSeed(w, h int) {
	// default to random location seeder
	if Seeder == nil {
		if seed == 0 {
			seed = time.Now().UnixNano()
		}
		rand.Seed(seed)
		Seeder = NewRandomLocationProvider(w, h)
	}
}

func initDisplay() {
	ding, ok := icon[iconName]
	if !ok {
		iconName = "blue-circle"
		ding = icon[iconName]
	}
	livecell = []byte(" " + ding)
}

func checkStartGeneration() {
	if startGen < 0 {
		startGen = 0
	}
}

func parseflags() (width, height, stepsPerSecond int) {

	flag.Int64Var(&seed, "seed", 0,
		"seed for initial population (default random)")

	flag.IntVar(&height, "y", 30, "height of simulation field")
	flag.IntVar(&width, "x", 30, "width of simulation field")
	flag.IntVar(&gens, "n", 20, "display up to `N` generations")
	flag.IntVar(&stepsPerSecond, "r", 5, "display `N` generations per second")
	flag.IntVar(&startGen, "s", 0, "start displaying from generation `N`")
	flag.StringVar(&iconName, "icon", "", "`name` of icon to use for live cells (default blue-circle)")

	flag.Parse()

	initSeed(width, height)
	initDisplay()
	checkStartGeneration()

	return
}

func usage() {

	icon = make(map[string]string)
	icon["aster-1"] = "\u2731"
	icon["aster-2"] = "\u2749"
	icon["blue-circle"] = "\u23FA"
	icon["blue-square"] = "\u23F9"
	icon["bug"] = "\u2603"
	icon["circle-plus"] = "\u2A01"
	icon["circle-x"] = "\u2A02"
	icon["dot-star"] = "\u272A"
	icon["fat-x"] = "\u2716"
	icon["flower"] = "\u273F"
	icon["green-x"] = "\u274E"
	icon["man-dribble"] = "\u26F9"
	icon["man-yellow"] = "\u26B1"
	icon["no-entry"] = "\u26D4"
	icon["redhat"] = "\u26D1"
	icon["skull-x"] = "\u2620"
	icon["snowflake"] = "\u274A"
	icon["snowman"] = "\u26C4"
	icon["square-big"] = "\u2B1C"
	icon["square-small"] = "\u25A9"
	icon["star-yellow"] = "\u2B50"
	icon["star-white"] = "\u2605"
	icon["star-6pt"] = "\u2736"
	icon["star-8pt"] = "\u2738"
	icon["whitedot"] = "\u26AA"

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [-x] [-y] [-r] [-n] [-s] [-seed] [-icon]\n\nOptions:\n\n",
			os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr,
			"\nAvailable icons for live cells:\n\n"+
				"Icon\tName\t\tDescription\n"+
				"----\t--------\t-----------\n"+
				icon["aster-1"]+"\taster-1\t\tAsterisk 1\n"+
				icon["aster-2"]+"\taster-2\t\tAsterisk 2\n"+
				icon["blue-circle"]+"\tblue-circle\tBlue tile, white circle (default)\n"+
				icon["blue-square"]+"\tblue-square\tBlue tile, white square\n"+
				icon["bug"]+"\tbug\t\tBug\n"+
				icon["circle-plus"]+"\tcircle-plus\tCircle with a '+'\n"+
				icon["circle-x"]+"\tcircle-x\tCircle with an 'x'\n"+
				icon["dot-star"]+"\tdot-star\tDot with star\n"+
				icon["fat-x"]+"\tfat-x\t\tFat white X\n"+
				icon["flower"]+"\tflower\t\tFlower\n"+
				icon["green-x"]+"\tgreen-x\t\tGreen tile with white X\n"+
				icon["man-dribble"]+"\tman-dribble\tMan dribbling ball\n"+
				icon["man-yellow"]+"\tman-yellow\tLittle yellow man\n"+
				icon["no-entry"]+"\tno-entry\tNo entry sign\n"+
				icon["redhat"]+"\tredhat\t\tRed hardhat with white cross\n"+
				icon["skull-x"]+"\tskull-x\t\tSkull and crossbones\n"+
				icon["snowflake"]+"\tsnowflake\tSnowflake\n"+
				icon["snowman"]+"\tsnowman\t\tSnowman\n"+
				icon["square-big"]+"\tsquare-big\tBig square\n"+
				icon["square-small"]+"\tsquare-small\tSmall square\n"+
				icon["star-yellow"]+"\tstar-yellow\tYellow 5-point star\n"+
				icon["star-white"]+"\tstar-white\tWhite 5-point star\n"+
				icon["star-6pt"]+"\tstar-6pt\t6-point star\n"+
				icon["star-8pt"]+"\tstar-8pt\t8-point star\n"+
				icon["whitedot"]+"\twhitedot\tWhite dot\n",
		)
	}
}

func main() {

	usage()

	width, height, stepsPerSecond := parseflags()

	NewLife(width, height).simulate(
		gens,
		time.Second/time.Duration(stepsPerSecond),
	)
}
<|endoftext|>


<commit_before>/*
Package lint implements Vale's syntax-aware linting functionality.

The package is split into core linting logic (this file), source code
(code.go), and markup (markup.go). The general flow is as follows:

    Lint (files and directories)     LintString (stdin)
                \                   /
                 lintFiles         /
                         \        /
                          +      +
    +-------------------+ lintFile ------+|lintMarkdown|lintADoc|lintRST
    |                    /    |    \       |            |       /
    |                   /     |     \      |           /       /
    |                  /      |      \     |          +--------
    |                 /       |       \    |         /
    |                +        +        +   +        +
    |               lintCode  lintLines  lintHTML
    |               |         |              |
    |               |         |              +
    |                \        |         lintProse
    |                 \       |        /
    |                  +      +       +
    |                      lintText
    |   <= add Alerts{}       |
    +-------------------------+
*/
package lint

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/errata-ai/vale/check"
	"github.com/errata-ai/vale/core"
	"github.com/remeh/sizedwaitgroup"
)

// A Linter lints a File.
type Linter struct {
	Config       *core.Config   // command-line and config file settings
	CheckManager *check.Manager // loaded checks
}

// A Block represents a section of text.
type Block struct {
	Context string        // parent content - e.g., sentence -> paragraph
	Text    string        // text content
	Raw     string        // text content without any processing
	Scope   core.Selector // section selector
}

// NewBlock makes a new Block with prepared text and a Selector.
func NewBlock(ctx, txt, raw, sel string) Block {
	if ctx == "" {
		ctx = txt
	}
	return Block{
		Context: ctx, Text: txt, Raw: raw, Scope: core.Selector{Value: sel}}
}

// LintString src according to its format.
func (l Linter) LintString(src string) ([]*core.File, error) {
	return []*core.File{l.lintFile(src)}, nil
}

// Lint src according to its format.
func (l Linter) Lint(input []string, pat string) ([]*core.File, error) {
	var linted []*core.File

	done := make(chan core.File)
	defer close(done)

	for _, src := range input {
		if !(core.IsDir(src) || core.FileExists(src)) {
			continue
		}
		filesChan, errc := l.lintFiles(done, src, core.NewGlob(pat))
		for f := range filesChan {
			if l.Config.Normalize {
				f.Path = filepath.ToSlash(f.Path)
			}
			linted = append(linted, f)
		}
		if err := <-errc; err != nil {
			return nil, err
		}
	}

	if l.Config.Sorted {
		sort.Sort(core.ByName(linted))
	}

	return linted, nil
}

// lintFiles walks the `root` directory, creating a new goroutine to lint any
// file that matches the given glob pattern.
func (l Linter) lintFiles(done <-chan core.File, root string, glob core.Glob) (<-chan *core.File, <-chan error) {
	filesChan := make(chan *core.File)
	errc := make(chan error, 1)
	ignore := []string{".", "_"}
	go func() {
		wg := sizedwaitgroup.New(5)
		err := filepath.Walk(root, func(fp string, fi os.FileInfo, err error) error {
			if err != nil || fi.IsDir() {
				return nil
			} else if !glob.Match(fp) || core.HasAnyPrefix(fi.Name(), ignore) {
				return nil
			}
			wg.Add()
			go func(fp string) {
				select {
				case filesChan <- l.lintFile(fp):
				case <-done:
				}
				wg.Done()
			}(fp)
			// Abort the walk if done is closed.
			select {
			case <-done:
				return errors.New("walk canceled")
			default:
				return nil
			}
		})
		// Walk has returned, so all calls to wg.Add are done.  Start a
		// goroutine to close c once all the sends are done.
		go func() {
			wg.Wait()
			close(filesChan)
		}()
		errc <- err
	}()
	return filesChan, errc
}

// lintFile creates a new `File` from the path `src` and selects a linter based
// on its format.
//
// TODO: remove dependencies on `asciidoctor` and `rst2html`.
func (l Linter) lintFile(src string) *core.File {
	file := core.NewFile(src, l.Config)
	if file.Command != "" {
		// We've been given a custom command to execute.
		parts := strings.Split(file.Command, " ")
		exe := core.Which([]string{parts[0]})
		if exe != "" {
			l.lintCustom(file, exe, parts[1:])
		} else {
			fmt.Printf("%s not found!\n", parts[0])
		}
	} else if file.Format == "markup" && !l.Config.Simple {
		switch file.NormedExt {
		case ".adoc":
			cmd := core.Which([]string{"asciidoctor"})
			if cmd != "" {
				l.lintADoc(file, cmd)
			} else {
				fmt.Println("asciidoctor not found!")
			}
		case ".md":
			l.lintMarkdown(file)
		case ".rst":
			cmd := core.Which([]string{"rst2html", "rst2html.py"})
			runtime := core.Which([]string{"python", "py", "python.exe"})
			if cmd != "" && runtime != "" {
				l.lintRST(file, runtime, cmd)
			} else {
				fmt.Println(fmt.Sprintf("can't run rst2html: (%s, %s)!", runtime, cmd))
			}
		case ".html":
			l.lintHTML(file)
		}
	} else if file.Format == "code" && !l.Config.Simple {
		l.lintCode(file)
	} else {
		l.lintLines(file)
	}
	return file
}

func (l Linter) lintProse(f *core.File, ctx, txt, raw string, lnTotal, lnLength int) {
	var b Block
	text := core.PrepText(txt)
	rawText := core.PrepText(raw)
	senScope := "sentence" + f.RealExt
	parScope := "paragraph" + f.RealExt
	txtScope := "text" + f.RealExt
	hasCtx := ctx != ""
	for _, p := range strings.SplitAfter(text, "\n\n") {
		for _, s := range core.SentenceTokenizer.Tokenize(p) {
			sent := strings.TrimSpace(s)
			if hasCtx {
				b = NewBlock(ctx, sent, "", senScope)
			} else {
				b = NewBlock(p, sent, "", senScope)
			}
			l.lintText(f, b, lnTotal, lnLength)
		}
		l.lintText(f, NewBlock(ctx, p, "", parScope), lnTotal, lnLength)
	}
	l.lintText(f, NewBlock(ctx, text, rawText, txtScope), lnTotal, lnLength)
}

func (l Linter) lintLines(f *core.File) {
	var line string
	lines := 1
	for f.Scanner.Scan() {
		line = core.PrepText(f.Scanner.Text() + "\n")
		l.lintText(f, NewBlock("", line, "", "text"+f.RealExt), lines+1, 0)
		lines++
	}
}

func (l Linter) lintText(f *core.File, blk Block, lines int, pad int) {
	var style, txt string
	var run bool

	ctx := blk.Context
	min := l.Config.MinAlertLevel
	hasCode := core.StringInSlice(f.NormedExt, []string{".md", ".adoc", ".rst"})
	f.ChkToCtx = make(map[string]string)
	for name, chk := range l.CheckManager.AllChecks {
		style = strings.Split(name, ".")[0]
		run = false

		if chk.Code && hasCode {
			txt = blk.Raw
		} else {
			txt = blk.Text
		}

		// It has been disabled via an in-text comment.
		if f.QueryComments(name) || txt == "" {
			continue
		}

		if chk.Level < min || !blk.Scope.Contains(chk.Scope) {
			continue
		}

		// Has the check been disabled for this extension?
		if val, ok := f.Checks[name]; ok && !run {
			if !val {
				continue
			}
			run = true
		}

		// Has the check been disabled for all extensions?
		if val, ok := l.Config.GChecks[name]; ok && !run {
			if !val {
				continue
			}
			run = true
		}

		if !run && !core.StringInSlice(style, f.BaseStyles) {
			continue
		}

		for _, a := range chk.Rule(txt, f) {
			f.AddAlert(a, ctx, txt, lines, pad)
		}
	}
}
<commit_msg>fix: try to bail early when there's nothing to do<commit_after>/*
Package lint implements Vale's syntax-aware linting functionality.

The package is split into core linting logic (this file), source code
(code.go), and markup (markup.go). The general flow is as follows:

    Lint (files and directories)     LintString (stdin)
                \                   /
                 lintFiles         /
                         \        /
                          +      +
    +-------------------+ lintFile ------+|lintMarkdown|lintADoc|lintRST
    |                    /    |    \       |            |       /
    |                   /     |     \      |           /       /
    |                  /      |      \     |          +--------
    |                 /       |       \    |         /
    |                +        +        +   +        +
    |               lintCode  lintLines  lintHTML
    |               |         |              |
    |               |         |              +
    |                \        |         lintProse
    |                 \       |        /
    |                  +      +       +
    |                      lintText
    |   <= add Alerts{}       |
    +-------------------------+
*/
package lint

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/errata-ai/vale/check"
	"github.com/errata-ai/vale/core"
	"github.com/remeh/sizedwaitgroup"
)

// A Linter lints a File.
type Linter struct {
	Config       *core.Config   // command-line and config file settings
	CheckManager *check.Manager // loaded checks
}

// A Block represents a section of text.
type Block struct {
	Context string        // parent content - e.g., sentence -> paragraph
	Text    string        // text content
	Raw     string        // text content without any processing
	Scope   core.Selector // section selector
}

// NewBlock makes a new Block with prepared text and a Selector.
func NewBlock(ctx, txt, raw, sel string) Block {
	if ctx == "" {
		ctx = txt
	}
	return Block{
		Context: ctx, Text: txt, Raw: raw, Scope: core.Selector{Value: sel}}
}

// LintString src according to its format.
func (l Linter) LintString(src string) ([]*core.File, error) {
	return []*core.File{l.lintFile(src)}, nil
}

// Lint src according to its format.
func (l Linter) Lint(input []string, pat string) ([]*core.File, error) {
	var linted []*core.File

	done := make(chan core.File)
	defer close(done)

	for _, src := range input {
		if !(core.IsDir(src) || core.FileExists(src)) {
			continue
		}
		filesChan, errc := l.lintFiles(done, src, core.NewGlob(pat))
		for f := range filesChan {
			if l.Config.Normalize {
				f.Path = filepath.ToSlash(f.Path)
			}
			linted = append(linted, f)
		}
		if err := <-errc; err != nil {
			return nil, err
		}
	}

	if l.Config.Sorted {
		sort.Sort(core.ByName(linted))
	}

	return linted, nil
}

// lintFiles walks the `root` directory, creating a new goroutine to lint any
// file that matches the given glob pattern.
func (l Linter) lintFiles(done <-chan core.File, root string, glob core.Glob) (<-chan *core.File, <-chan error) {
	filesChan := make(chan *core.File)
	errc := make(chan error, 1)
	ignore := []string{".", "_"}
	go func() {
		wg := sizedwaitgroup.New(5)
		err := filepath.Walk(root, func(fp string, fi os.FileInfo, err error) error {
			if err != nil || fi.IsDir() {
				return nil
			} else if !glob.Match(fp) || core.HasAnyPrefix(fi.Name(), ignore) {
				return nil
			}
			wg.Add()
			go func(fp string) {
				select {
				case filesChan <- l.lintFile(fp):
				case <-done:
				}
				wg.Done()
			}(fp)
			// Abort the walk if done is closed.
			select {
			case <-done:
				return errors.New("walk canceled")
			default:
				return nil
			}
		})
		// Walk has returned, so all calls to wg.Add are done.  Start a
		// goroutine to close c once all the sends are done.
		go func() {
			wg.Wait()
			close(filesChan)
		}()
		errc <- err
	}()
	return filesChan, errc
}

// lintFile creates a new `File` from the path `src` and selects a linter based
// on its format.
//
// TODO: remove dependencies on `asciidoctor` and `rst2html`.
func (l Linter) lintFile(src string) *core.File {
	file := core.NewFile(src, l.Config)
	if len(file.Checks) == 0 && len(file.BaseStyles) == 0 {
		if len(l.Config.GBaseStyles) == 0 && len(l.Config.GChecks) == 0 {
			// There's nothing to do; bail early.
			return file
		}
	}

	if file.Command != "" {
		// We've been given a custom command to execute.
		parts := strings.Split(file.Command, " ")
		exe := core.Which([]string{parts[0]})
		if exe != "" {
			l.lintCustom(file, exe, parts[1:])
		} else {
			fmt.Printf("%s not found!\n", parts[0])
		}
	} else if file.Format == "markup" && !l.Config.Simple {
		switch file.NormedExt {
		case ".adoc":
			cmd := core.Which([]string{"asciidoctor"})
			if cmd != "" {
				l.lintADoc(file, cmd)
			} else {
				fmt.Println("asciidoctor not found!")
			}
		case ".md":
			l.lintMarkdown(file)
		case ".rst":
			cmd := core.Which([]string{"rst2html", "rst2html.py"})
			runtime := core.Which([]string{"python", "py", "python.exe"})
			if cmd != "" && runtime != "" {
				l.lintRST(file, runtime, cmd)
			} else {
				fmt.Println(fmt.Sprintf("can't run rst2html: (%s, %s)!", runtime, cmd))
			}
		case ".html":
			l.lintHTML(file)
		}
	} else if file.Format == "code" && !l.Config.Simple {
		l.lintCode(file)
	} else {
		l.lintLines(file)
	}

	return file
}

func (l Linter) lintProse(f *core.File, ctx, txt, raw string, lnTotal, lnLength int) {
	var b Block
	text := core.PrepText(txt)
	rawText := core.PrepText(raw)
	senScope := "sentence" + f.RealExt
	parScope := "paragraph" + f.RealExt
	txtScope := "text" + f.RealExt
	hasCtx := ctx != ""
	for _, p := range strings.SplitAfter(text, "\n\n") {
		for _, s := range core.SentenceTokenizer.Tokenize(p) {
			sent := strings.TrimSpace(s)
			if hasCtx {
				b = NewBlock(ctx, sent, "", senScope)
			} else {
				b = NewBlock(p, sent, "", senScope)
			}
			l.lintText(f, b, lnTotal, lnLength)
		}
		l.lintText(f, NewBlock(ctx, p, "", parScope), lnTotal, lnLength)
	}
	l.lintText(f, NewBlock(ctx, text, rawText, txtScope), lnTotal, lnLength)
}

func (l Linter) lintLines(f *core.File) {
	var line string
	lines := 1
	for f.Scanner.Scan() {
		line = core.PrepText(f.Scanner.Text() + "\n")
		l.lintText(f, NewBlock("", line, "", "text"+f.RealExt), lines+1, 0)
		lines++
	}
}

func (l Linter) lintText(f *core.File, blk Block, lines int, pad int) {
	var style, txt string
	var run bool

	ctx := blk.Context
	min := l.Config.MinAlertLevel
	hasCode := core.StringInSlice(f.NormedExt, []string{".md", ".adoc", ".rst"})
	f.ChkToCtx = make(map[string]string)
	for name, chk := range l.CheckManager.AllChecks {
		style = strings.Split(name, ".")[0]
		run = false

		if chk.Code && hasCode {
			txt = blk.Raw
		} else {
			txt = blk.Text
		}

		// It has been disabled via an in-text comment.
		if f.QueryComments(name) || txt == "" {
			continue
		}

		if chk.Level < min || !blk.Scope.Contains(chk.Scope) {
			continue
		}

		// Has the check been disabled for this extension?
		if val, ok := f.Checks[name]; ok && !run {
			if !val {
				continue
			}
			run = true
		}

		// Has the check been disabled for all extensions?
		if val, ok := l.Config.GChecks[name]; ok && !run {
			if !val {
				continue
			}
			run = true
		}

		if !run && !core.StringInSlice(style, f.BaseStyles) {
			continue
		}

		for _, a := range chk.Rule(txt, f) {
			f.AddAlert(a, ctx, txt, lines, pad)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
    "net/http"
    "log"
    iconv "github.com/djimenez/iconv-go"
    goquery "github.com/PuerkitoBio/goquery"
    "strings"
    "time"
    "encoding/json"
    "os"
)

type MealDay struct {
    Date        string  `json:"date"`
    Breakfast   string  `json:"breakfast"`
    Lunch       string  `json:"lunch"`
    Dinner      string  `json:"dinner"`
    Supper      string  `json:"supper"`
}

func main() {

    args := os.Args[1:]
    if (len(args) != 1) {
        log.Fatal("Invalid arguments")
    }

    res, err := http.Get("http://www.leijonacatering.fi/ruokalista_varuskunta.php")
    if err != nil {
		log.Fatal(err)
    }
	defer res.Body.Close()

    // convert to UTF-8
    utfBody, err := iconv.NewReader(res.Body, "iso-8859-1", "utf-8")
	if err != nil {
		log.Fatal(err)
	}

    // parse date
    doc, err := goquery.NewDocumentFromReader(utfBody)
    if err != nil {
        log.Fatal(err)
    }

    sel := doc.Find("table tr").First().Find("td").Last();
    dateSl := strings.Split(sel.Text(), "-")
    endDateStr := strings.TrimSpace(strings.Replace(dateSl[len(dateSl) - 1], ".", "/", -1))
    date, err := time.Parse("2/1/2006", endDateStr)
    if err != nil {
        log.Fatal(err)
    }
    weekDur, err := time.ParseDuration("-144h")
    date = date.Add(weekDur)

    // make slices for breakfast, lunch, dinner, and supper
    var bf, lunch, dinner, supper []string
    bf = make([]string, 7)
    lunch = make([]string, 7)
    dinner = make([]string, 7)
    supper = make([]string, 7)

    // parse rows for each meal
    bfSel := doc.Find("table tr").Eq(2).Find("td")
    lunchSel := doc.Find("table tr").Eq(3).Find("td")
    dinnerSel := doc.Find("table tr").Eq(4).Find("td")
    supperSel := doc.Find("table tr").Eq(5).Find("td")

    for i := 0; i < 7; i++ {
        bf[i] = bfSel.Eq(i+1).Text()
        lunch[i] = lunchSel.Eq(i+1).Text()
        dinner[i] = dinnerSel.Eq(i+1).Text()
        supper[i] = supperSel.Eq(i+1).Text()
    }

    mealdays := make([]MealDay, 7)
    dateFormat := "2-1-2006"
    for i := 0; i < 7; i++ {
        mealdays[i] = MealDay{  date.Format(dateFormat),
                                bf[i],
                                lunch[i],
                                dinner[i],
                                supper[i] }
        date = date.AddDate(0,0,1)
    }

    /* jsonMeals, err := json.Marshal(mealdays)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s \n", jsonMeals) */

    outFile, err := os.Create(args[0])
    if err != nil {
        log.Fatal(err)
    }
    defer outFile.Close()

    enc := json.NewEncoder(outFile)
    enc.Encode(mealdays)
}
<commit_msg>Parse both menu pages<commit_after>package main

import (
	"encoding/json"
	goquery "github.com/PuerkitoBio/goquery"
	iconv "github.com/djimenez/iconv-go"
	"log"
	"net/http"
	"os"
	"strings"
	"time"
)

type MealDay struct {
	Date      int64  `json:"date"`
	Breakfast string `json:"breakfast"`
	Lunch     string `json:"lunch"`
	Dinner    string `json:"dinner"`
	Supper    string `json:"supper"`
}

func parseMenu(url string) []MealDay {
	res, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	// convert to UTF-8
	utfBody, err := iconv.NewReader(res.Body, "iso-8859-1", "utf-8")
	if err != nil {
		log.Fatal(err)
	}

	// parse date
	doc, err := goquery.NewDocumentFromReader(utfBody)
	if err != nil {
		log.Fatal(err)
	}

	sel := doc.Find("table tr").First().Find("td").Last()
	dateSl := strings.Split(sel.Text(), "-")
	endDateStr := strings.TrimSpace(strings.Replace(dateSl[len(dateSl)-1], ".", "/", -1))
	date, err := time.Parse("2/1/2006", endDateStr)
	if err != nil {
		log.Fatal(err)
	}
	weekDur, err := time.ParseDuration("-144h")
	date = date.Add(weekDur)

	// make slices for breakfast, lunch, dinner, and supper
	var bf, lunch, dinner, supper []string
	bf = make([]string, 7)
	lunch = make([]string, 7)
	dinner = make([]string, 7)
	supper = make([]string, 7)

	// parse rows for each meal
	bfSel := doc.Find("table tr").Eq(2).Find("td")
	lunchSel := doc.Find("table tr").Eq(3).Find("td")
	dinnerSel := doc.Find("table tr").Eq(4).Find("td")
	supperSel := doc.Find("table tr").Eq(5).Find("td")

	for i := 0; i < 7; i++ {
		bf[i] = bfSel.Eq(i + 1).Text()
		lunch[i] = lunchSel.Eq(i + 1).Text()
		dinner[i] = dinnerSel.Eq(i + 1).Text()
		supper[i] = supperSel.Eq(i + 1).Text()
	}

	mealdays := make([]MealDay, 7)
	for i := 0; i < 7; i++ {
		mealdays[i] = MealDay{date.Unix(),
			bf[i],
			lunch[i],
			dinner[i],
			supper[i]}
		date = date.AddDate(0, 0, 1)
	}

	return mealdays
}

func main() {

	args := os.Args[1:]
	if len(args) != 1 {
		log.Fatal("Invalid arguments")
	}

	mealdays1 := parseMenu("http://www.leijonacatering.fi/ruokalista_varuskunta.php")
	mealdays2 := parseMenu("http://www.leijonacatering.fi/ruokalista_varuskunta_seur.php")

	alldays := append(mealdays1, mealdays2...)

	outFile, err := os.Create(args[0])
	if err != nil {
		log.Fatal(err)
	}
	defer outFile.Close()

	enc := json.NewEncoder(outFile)
	enc.Encode(alldays)
}
<|endoftext|>


<commit_before>package papaBot

// Full message handling routines.

import (
	"fmt"
	"github.com/pawelszydlo/papa-bot/events"
	"github.com/pawelszydlo/papa-bot/utils"
	"log"
	"mvdan.cc/xurls"
	"os"
	"regexp"
	"time"
)

var titleRe = regexp.MustCompile("(?is)<title.*?>(.+?)</title>")
var metaRe = regexp.MustCompile(`(?is)<\s*?meta.*?content\s*?=\s*?"(.*?)".*?>`)
var descRe = regexp.MustCompile(`(?is)(property|name)\s*?=.*?description`)

// messageListener looks for commands in messages.
func (bot *Bot) messageListener(message events.EventMessage) {
	// Increase lines count for all announcements.
	for k := range bot.lastURLAnnouncedLinesPassed {
		bot.lastURLAnnouncedLinesPassed[k] += 1
		// After 100 lines pass, forget it ever happened.
		if bot.lastURLAnnouncedLinesPassed[k] > 100 {
			delete(bot.lastURLAnnouncedLinesPassed, k)
			delete(bot.lastURLAnnouncedTime, k)
		}
	}

	// Handles the commands.
	if message.AtBot {
		bot.handleBotCommand(&message)
	}
}

// handleURLsListener finds all URLs in the message and fires URL events on them.
func (bot *Bot) handleURLsListener(message events.EventMessage) {

	// Find all URLs in the message.
	links := xurls.Strict().FindAllString(message.Message, -1)
	// Remove multiple same links from one message.
	links = utils.RemoveDuplicates(links)
	for i := range links {
		// Validate the url.
		bot.Log.Infof("Got link %s", links[i])
		link := utils.StandardizeURL(links[i])
		bot.Log.Debugf("Standardized to: %s", link)

		// Try to get the body of the page.
		err, finalLink, body := bot.GetPageBody(link, map[string]string{})
		if err != nil {
			bot.Log.Warningf("Could't fetch the body: %s", err)
		}

		// Iterate over meta tags to get the description
		description := ""
		metas := metaRe.FindAllStringSubmatch(string(body), -1)
		for i := range metas {
			if len(metas[i]) > 1 {
				isDesc := descRe.FindString(metas[i][0])
				if isDesc != "" && (len(metas[i][1]) > len(description)) {
					description = utils.CleanString(metas[i][1], true)
				}
			}
		}
		// Get the title
		title := ""
		match := titleRe.FindStringSubmatch(string(body))
		if len(match) > 1 {
			title = utils.CleanString(match[1], true)
		}

		// Insert URL into the db.
		bot.Log.Debugf("Storing URL info for: %s", finalLink)
		if _, err := bot.Db.Exec(`INSERT INTO urls(transport, channel, nick, link, quote, title) VALUES(?, ?, ?, ?, ?, ?)`,
			message.TransportName, message.Channel, message.Nick, finalLink, message.Message, title); err != nil {
			bot.Log.Warningf("Can't add url to database: %s", err)
		}

		// Trigger url found message.
		bot.EventDispatcher.Trigger(events.EventMessage{
			message.TransportName,
			message.TransportFormatting,
			events.EventURLFound,
			message.Nick,
			message.UserId,
			message.Channel,
			finalLink,
			message.Context,
			message.AtBot,
		})

		linkKey := finalLink + message.Channel
		// If we can't announce yet, skip this link.
		if time.Since(bot.lastURLAnnouncedTime[linkKey]) < bot.Config.UrlAnnounceIntervalMinutes*time.Minute {
			continue
		}
		if lines, exists := bot.lastURLAnnouncedLinesPassed[linkKey]; exists && lines < bot.Config.UrlAnnounceIntervalLines {
			continue
		}

		// On mattermost we can skip all link info display.
		if message.TransportName == "mattermost" {
			return
		}

		// Announce the title, save the description.
		if title != "" {
			if description != "" {
				bot.SendNotice(&message, title+" …")
			} else {
				bot.SendNotice(&message, title)
			}
			bot.lastURLAnnouncedTime[linkKey] = time.Now()
			bot.lastURLAnnouncedLinesPassed[linkKey] = 0
			// Keep the long info for later.
			bot.AddMoreInfo(message.TransportName, message.Channel, description)
		}
	}
}

// scribe saves the message into appropriate channel log file.
func (bot *Bot) scribeListener(message events.EventMessage) {
	if !bot.Config.ChatLogging {
		return
	}
	go func() {
		logFileName := fmt.Sprintf(
			"logs/%s_%s_%s.txt", message.TransportName, message.Channel, time.Now().Format("2006-01-02"))
		f, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			bot.Log.Errorf("Error opening log file: %s", err)
			return
		}
		defer f.Close()

		scribe := log.New(f, "", log.Ldate|log.Ltime)
		if message.EventCode == events.EventChatMessage {
			scribe.Println(fmt.Sprintf("%s: %s", message.Nick, message.Message))
		} else if message.EventCode == events.EventChatNotice {
			scribe.Println(fmt.Sprintf("Notice from %s: %s", message.Nick, message.Message))
		} else if message.EventCode == events.EventJoinedChannel {
			scribe.Println(fmt.Sprintf("* %s joined.", message.Nick))
		} else if message.EventCode == events.EventPartChannel {
			scribe.Println(fmt.Sprintf("* %s left.", message.Nick))
		} else { // Must be channel activity.
			scribe.Println(fmt.Sprintf("* %s %s", message.Nick, message.Message))
		}
	}()
}
<commit_msg>Fix URL handling when body can't be fetched.<commit_after>package papaBot

// Full message handling routines.

import (
	"fmt"
	"github.com/pawelszydlo/papa-bot/events"
	"github.com/pawelszydlo/papa-bot/utils"
	"log"
	"mvdan.cc/xurls"
	"os"
	"regexp"
	"time"
)

var titleRe = regexp.MustCompile("(?is)<title.*?>(.+?)</title>")
var metaRe = regexp.MustCompile(`(?is)<\s*?meta.*?content\s*?=\s*?"(.*?)".*?>`)
var descRe = regexp.MustCompile(`(?is)(property|name)\s*?=.*?description`)

// messageListener looks for commands in messages.
func (bot *Bot) messageListener(message events.EventMessage) {
	// Increase lines count for all announcements.
	for k := range bot.lastURLAnnouncedLinesPassed {
		bot.lastURLAnnouncedLinesPassed[k] += 1
		// After 100 lines pass, forget it ever happened.
		if bot.lastURLAnnouncedLinesPassed[k] > 100 {
			delete(bot.lastURLAnnouncedLinesPassed, k)
			delete(bot.lastURLAnnouncedTime, k)
		}
	}

	// Handles the commands.
	if message.AtBot {
		bot.handleBotCommand(&message)
	}
}

// handleURLsListener finds all URLs in the message and fires URL events on them.
func (bot *Bot) handleURLsListener(message events.EventMessage) {

	// Find all URLs in the message.
	links := xurls.Strict().FindAllString(message.Message, -1)
	// Remove multiple same links from one message.
	links = utils.RemoveDuplicates(links)
	for i := range links {
		// Validate the url.
		bot.Log.Infof("Got link %s", links[i])
		link := utils.StandardizeURL(links[i])
		bot.Log.Debugf("Standardized to: %s", link)

		// Try to get the body of the page.
		err, finalLink, body := bot.GetPageBody(link, map[string]string{})
		if err != nil {
			bot.Log.Warningf("Could't fetch the body: %s", err)
		}

		// Update link if needed.
		if finalLink != "" {
			link = finalLink
		}

		// Iterate over meta tags to get the description
		description := ""
		metas := metaRe.FindAllStringSubmatch(string(body), -1)
		for i := range metas {
			if len(metas[i]) > 1 {
				isDesc := descRe.FindString(metas[i][0])
				if isDesc != "" && (len(metas[i][1]) > len(description)) {
					description = utils.CleanString(metas[i][1], true)
				}
			}
		}
		// Get the title
		title := ""
		match := titleRe.FindStringSubmatch(string(body))
		if len(match) > 1 {
			title = utils.CleanString(match[1], true)
		}

		// Insert URL into the db.
		bot.Log.Debugf("Storing URL info for: %s", link)
		if _, err := bot.Db.Exec(`INSERT INTO urls(transport, channel, nick, link, quote, title) VALUES(?, ?, ?, ?, ?, ?)`,
			message.TransportName, message.Channel, message.Nick, link, message.Message, title); err != nil {
			bot.Log.Warningf("Can't add url to database: %s", err)
		}

		// Trigger url found message.
		bot.EventDispatcher.Trigger(events.EventMessage{
			message.TransportName,
			message.TransportFormatting,
			events.EventURLFound,
			message.Nick,
			message.UserId,
			message.Channel,
			link,
			message.Context,
			message.AtBot,
		})

		linkKey := link + message.Channel
		// If we can't announce yet, skip this link.
		if time.Since(bot.lastURLAnnouncedTime[linkKey]) < bot.Config.UrlAnnounceIntervalMinutes*time.Minute {
			continue
		}
		if lines, exists := bot.lastURLAnnouncedLinesPassed[linkKey]; exists && lines < bot.Config.UrlAnnounceIntervalLines {
			continue
		}

		// On mattermost we can skip all link info display.
		if message.TransportName == "mattermost" {
			return
		}

		// Announce the title, save the description.
		if title != "" {
			if description != "" {
				bot.SendNotice(&message, title+" …")
			} else {
				bot.SendNotice(&message, title)
			}
			bot.lastURLAnnouncedTime[linkKey] = time.Now()
			bot.lastURLAnnouncedLinesPassed[linkKey] = 0
			// Keep the long info for later.
			bot.AddMoreInfo(message.TransportName, message.Channel, description)
		}
	}
}

// scribe saves the message into appropriate channel log file.
func (bot *Bot) scribeListener(message events.EventMessage) {
	if !bot.Config.ChatLogging {
		return
	}
	go func() {
		logFileName := fmt.Sprintf(
			"logs/%s_%s_%s.txt", message.TransportName, message.Channel, time.Now().Format("2006-01-02"))
		f, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			bot.Log.Errorf("Error opening log file: %s", err)
			return
		}
		defer f.Close()

		scribe := log.New(f, "", log.Ldate|log.Ltime)
		if message.EventCode == events.EventChatMessage {
			scribe.Println(fmt.Sprintf("%s: %s", message.Nick, message.Message))
		} else if message.EventCode == events.EventChatNotice {
			scribe.Println(fmt.Sprintf("Notice from %s: %s", message.Nick, message.Message))
		} else if message.EventCode == events.EventJoinedChannel {
			scribe.Println(fmt.Sprintf("* %s joined.", message.Nick))
		} else if message.EventCode == events.EventPartChannel {
			scribe.Println(fmt.Sprintf("* %s left.", message.Nick))
		} else { // Must be channel activity.
			scribe.Println(fmt.Sprintf("* %s %s", message.Nick, message.Message))
		}
	}()
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "strings"
    "encoding/json"
    "time"
)

type WordConfiguration struct {
		TypeName string
		Values []string
		Script bool
	}

func main() {
    var str = "{every} {1} {dec 25} {starting dec 25 2016} {do nothing} = $christmas {every} {2} {weeks} {starting jan 1 2016} {send email to list} EXCEPT {$Christmas} = biweeklynewsletter"
    fmt.Println("Example : ")
    fmt.Printf("%v\n\n",str)
    fmt.Println("Initial parsing : ")
    var wordsCount = FieldsCount(str)
    var words = strings.SplitAfterN(str, "}", wordsCount)
    fmt.Printf("%q\n", words)

    fmt.Println("Correction : ")

    var scriptsCount = CorrectScriptPart(words)

    fmt.Printf("%q\n Corrected count %v\n", words, scriptsCount)

    Configuration()
}

func CorrectScriptPart(words []string) int {
    var scriptsCount = 0;
    for index,element := range words {
        // index is the index where we are
        // element is the element from someSlice for where we are
        if index > 0 {
            if strings.Index(element, "{") > 1 {
                words[index - 1] = words[index - 1] +  element[0:strings.Index(element, "{") - 1]
                words[index] = element[strings.Index(element, "{"):len(element)]
                scriptsCount = scriptsCount + 1
            }
        }
    }

    return scriptsCount
}

func Validate(str string) bool {
    return true
}

func FieldsCount(str string) int {
    return  strings.Count(str,"{")
}

func Configuration() {
    var jsonBlob = []byte(`[
		{"TypeName": "Platypus", "Values": ["Crimson","Red","Ruby","Maroon"], "Script": true},
		{"TypeName": "Quoll", "Values": ["Crimson","Red"], "Script": false}
	]`)

	var configs []WordConfiguration
	err := json.Unmarshal(jsonBlob, &configs)
	if err != nil {
		fmt.Println("error:", err)
	}
	fmt.Println("\nConfiguration:")
	fmt.Printf("%+v\n", configs)

}

//begin schedule
func IsOccuring(event string, aDate time.Time) bool{
	return false
}

func Dates(event string, fromDate time.Time, toDate time.Time) [] time.Time {
	return nil
}

func NextOccurance(event string, aDate time.Time) time.Time{
	return time.Now()
}

//end schedule <commit_msg>Scheduler element<commit_after>package main

import (
    "fmt"
    "strings"
    "encoding/json"
    "time"
)

type WordConfiguration struct {
		TypeName string
		Values []string
		Script bool
	}

const(
    every = 1
    once = 2
)

const(
    year = 1
    month = 2
    week = 3
    day = 4
)

type ScheduleElement struct {
		SeriesType int // every|once
		SeriesNumber int // 1, 2...
		SeriesDate time.Time() // dec 25
		SeriesPeriod int // week|day..
		fromDate time.Time() // starting dec 25 2016
		toDate time.Time() // ending dec 25 2017
		ExpressionName string //do nothing
		ExpressionText string //$Christmas
	}

func main() {
    var str = "{every} {1} {dec 25} {starting dec 25 2016} {do nothing} = $christmas {every} {2} {weeks} {starting jan 1 2016} {send email to list} EXCEPT {$Christmas} = biweeklynewsletter"
    fmt.Println("Example : ")
    fmt.Printf("%v\n\n",str)
    fmt.Println("Initial parsing : ")
    var wordsCount = FieldsCount(str)
    var words = strings.SplitAfterN(str, "}", wordsCount)
    fmt.Printf("%q\n", words)

    fmt.Println("Correction : ")

    var scriptsCount = CorrectScriptPart(words)

    fmt.Printf("%q\n Corrected count %v\n", words, scriptsCount)

    Configuration()
}

func CorrectScriptPart(words []string) int {
    var scriptsCount = 0;
    for index,element := range words {
        // index is the index where we are
        // element is the element from someSlice for where we are
        if index > 0 {
            if strings.Index(element, "{") > 1 {
                words[index - 1] = words[index - 1] +  element[0:strings.Index(element, "{") - 1]
                words[index] = element[strings.Index(element, "{"):len(element)]
                scriptsCount = scriptsCount + 1
            }
        }
    }

    return scriptsCount
}

func Validate(str string) bool {
    return true
}

func FieldsCount(str string) int {
    return  strings.Count(str,"{")
}

func Configuration() {
    var jsonBlob = []byte(`[
		{"TypeName": "Platypus", "Values": ["Crimson","Red","Ruby","Maroon"], "Script": true},
		{"TypeName": "Quoll", "Values": ["Crimson","Red"], "Script": false}
	]`)

	var configs []WordConfiguration
	err := json.Unmarshal(jsonBlob, &configs)
	if err != nil {
		fmt.Println("error:", err)
	}
	fmt.Println("\nConfiguration:")
	fmt.Printf("%+v\n", configs)

}

//begin schedule
func IsOccuring(event string, aDate time.Time) bool{
	return false
}

func Dates(event string, fromDate time.Time, toDate time.Time) [] time.Time {
	return nil
}

func NextOccurance(event string, aDate time.Time) time.Time{
	return time.Now()
}

//end schedule <|endoftext|>


<commit_before>package iwscanner

import (
        "os/exec"
        "strings"
        "fmt"
        "bytes"
        "strconv"
        "regexp"
)

func IsWifiOn() (ret bool, err error) {
        var out bytes.Buffer
        testcmd := exec.Command("ifconfig", "wlan0")
        testcmd.Stdout = &out
        if e := testcmd.Run(); e != nil {
                err = fmt.Errorf("Error with run: %v", e)
                return
        }

        contains_up := strings.Contains(out.String(), "UP")

        if (contains_up) {
                ret = true
        } else {
                ret = false
        }
        return
}

// in can have strings "on" or "off"
func TurnWifi(in string) error {
        var t string

        if in == "on" {
                t = "up"
        } else {
                t = "down"
        }

        cmd := exec.Command("ifconfig", "wlan0", t)
        if err := cmd.Run(); err != nil {
                return fmt.Errorf("Error with run: %v", err)
        } else {

                wifi_on, e := IsWifiOn()
                if e != nil {
                        return fmt.Errorf("Error with IsWifiOn: %v", e)
                }

                if in == "on" && wifi_on {
                        return nil
                }
                if in == "off" && !wifi_on {
                        return nil
                }
                return fmt.Errorf("Error: something is wrong. This should not happen.")

        }
}

type AP struct {
        address string
        quality int
        essid string
}

type APs []AP

func GetAPs () (aps APs , err error) {
        return
}

func parseIwlistOutput(in string) (aps APs) {
        splits := strings.Split(in, "Cell")
        address_regex, _ := regexp.Compile("Address: ([0-9A-Z:]*)")
        quality_regex, _ := regexp.Compile("Quality=([0-9]+)")
        essid_regex, _ := regexp.Compile("ESSID:\"(.*)\"")

        first := true
        for _, split := range splits {
                if first {
                        first = false
                        continue
                }

                ap := AP{}
                address_match := address_regex.FindStringSubmatch(split)
                ap.address = address_match[1]

                quality_match := quality_regex.FindStringSubmatch(split)
                i, _ := strconv.Atoi(quality_match[1])
                ap.quality = i

                ap.essid = essid_regex.FindStringSubmatch(split)[1]

                aps = append(aps, ap)
        }
        return
}
<commit_msg>wifi functions based on the device<commit_after>package iwscanner

import (
        "os/exec"
        "strings"
        "fmt"
        "bytes"
        "strconv"
        "regexp"
)

func IsWifiOn(device string) (ret bool, err error) {
        var out bytes.Buffer
        testcmd := exec.Command("ifconfig", device)
        testcmd.Stdout = &out
        if e := testcmd.Run(); e != nil {
                err = fmt.Errorf("Error with run: %v", e)
                return
        }

        contains_up := strings.Contains(out.String(), "UP")

        if (contains_up) {
                ret = true
        } else {
                ret = false
        }
        return
}

// in can have strings "on" or "off"
func TurnWifi(device string, in string) error {
        var t string

        if in == "on" {
                t = "up"
        } else {
                t = "down"
        }

        cmd := exec.Command("ifconfig", device, t)
        if err := cmd.Run(); err != nil {
                return fmt.Errorf("Error with run: %v", err)
        } else {

                wifi_on, e := IsWifiOn(device)
                if e != nil {
                        return fmt.Errorf("Error with IsWifiOn: %v", e)
                }

                if in == "on" && wifi_on {
                        return nil
                }
                if in == "off" && !wifi_on {
                        return nil
                }
                return fmt.Errorf("Error: something is wrong. This should not happen.")

        }
}

type AP struct {
        address string
        quality int
        essid string
}

type APs []AP

func GetAPs () (aps APs , err error) {
        return
}

func parseIwlistOutput(in string) (aps APs) {
        splits := strings.Split(in, "Cell")
        address_regex, _ := regexp.Compile("Address: ([0-9A-Z:]*)")
        quality_regex, _ := regexp.Compile("Quality=([0-9]+)")
        essid_regex, _ := regexp.Compile("ESSID:\"(.*)\"")

        first := true
        for _, split := range splits {
                if first {
                        first = false
                        continue
                }

                ap := AP{}
                address_match := address_regex.FindStringSubmatch(split)
                ap.address = address_match[1]

                quality_match := quality_regex.FindStringSubmatch(split)
                i, _ := strconv.Atoi(quality_match[1])
                ap.quality = i

                ap.essid = essid_regex.FindStringSubmatch(split)[1]

                aps = append(aps, ap)
        }
        return
}
<|endoftext|>


<commit_before>package digitalocean

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"time"

	"arvika.pulcy.com/iggi/droplets/providers"
	"github.com/dchest/uniuri"
	"github.com/digitalocean/godo"
)

const (
	cloudConfigTemplate = `#cloud-config

coreos:
  etcd:
    # generate a new token for each unique cluster from https://discovery.etcd.io/new
    discovery: %s
    # use $public_ipv4 if your datacenter of choice does not support private networking
    addr: $private_ipv4:4001
    peer-addr: $private_ipv4:7001
  fleet:
    public-ip: $private_ipv4   # used for fleetctl ssh command
  units:
    - name: etcd.service
      command: start
    - name: fleet.service
      command: start`
)

func (this *doProvider) CreateCluster(options *providers.CreateClusterOptions) error {
	discoveryUrl, err := newDiscoveryUrl()
	if err != nil {
		return err
	}

	wg := sync.WaitGroup{}
	var lastErr error
	for i := 1; i <= options.InstanceCount; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			prefix := strings.ToLower(uniuri.NewLen(8))
			instanceOptions := &providers.CreateInstanceOptions{
				Domain:       options.Domain,
				ClusterName:  fmt.Sprintf("%s.%s", options.Name, options.Domain),
				InstanceName: fmt.Sprintf("%s.%s.%s", prefix, options.Name, options.Domain),
				Region:       options.Region,
				Image:        options.Image,
				Size:         options.Size,
				DiscoveryUrl: discoveryUrl,
				SSHKeyNames:  options.SSHKeyNames,
			}
			err := this.CreateInstance(instanceOptions)
			if err != nil && lastErr != nil {
				lastErr = err
			}
		}(i)
	}
	wg.Wait()
	if lastErr != nil {
		return lastErr
	}

	return nil
}

func (this *doProvider) CreateInstance(options *providers.CreateInstanceOptions) error {
	client := NewDOClient(this.token)

	keys := []godo.DropletCreateSSHKey{}
	listedKeys, err := KeyList(client)
	if err != nil {
		return err
	}
	for _, key := range options.SSHKeyNames {
		k := findKeyId(key, listedKeys)
		if k == nil {
			return errors.New("Key not found")
		}
		keys = append(keys, godo.DropletCreateSSHKey{ID: k.ID})
	}

	userData := fmt.Sprintf(cloudConfigTemplate, options.DiscoveryUrl)

	request := &godo.DropletCreateRequest{
		Name:              options.InstanceName,
		Region:            options.Region,
		Size:              options.Size,
		Image:             godo.DropletCreateImage{Slug: options.Image},
		SSHKeys:           keys,
		Backups:           false,
		IPv6:              true,
		PrivateNetworking: true,
		UserData:          userData,
	}

	// Create droplet
	createDroplet, _, err := client.Droplets.Create(request)
	if err != nil {
		return err
	}

	// Wait for active
	droplet, err := this.waitUntilDropletActive(createDroplet.ID)
	if err != nil {
		return nil
	}

	publicIpv4 := getIpv4(*droplet, "public")
	publicIpv6 := getIpv6(*droplet, "public")
	fmt.Printf("%s: %s: %s\n", droplet.Name, publicIpv4, publicIpv6)

	// Create DNS record for the instance
	if err := this.createDnsRecord(options.Domain, "A", options.InstanceName, publicIpv4); err != nil {
		return err
	}
	if err := this.createDnsRecord(options.Domain, "A", options.ClusterName, publicIpv4); err != nil {
		return err
	}
	if publicIpv6 != "" {
		if err := this.createDnsRecord(options.Domain, "AAAA", options.InstanceName, publicIpv6); err != nil {
			return err
		}
		if err := this.createDnsRecord(options.Domain, "AAAA", options.ClusterName, publicIpv6); err != nil {
			return err
		}
	}

	return nil
}

func (this *doProvider) waitUntilDropletActive(id int) (*godo.Droplet, error) {
	client := NewDOClient(this.token)
	for {
		droplet, _, err := client.Droplets.Get(id)
		if err != nil {
			return nil, err
		}
		if droplet.Status != "new" {
			return droplet, nil
		}
		// Wait a while
		time.Sleep(time.Millisecond * 250)
	}
}

func findKeyId(key string, listedKeys []godo.Key) *godo.Key {
	for _, k := range listedKeys {
		if k.Name == key {
			return &k
		}
	}
	return nil
}

func newDiscoveryUrl() (string, error) {
	resp, err := http.Get("https://discovery.etcd.io/new")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", nil
	}
	return strings.TrimSpace(string(body)), nil
}
<commit_msg>Updated etcd to etcd2, improved cloud-config<commit_after>package digitalocean

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"time"

	"arvika.pulcy.com/iggi/droplets/providers"
	"github.com/dchest/uniuri"
	"github.com/digitalocean/godo"
)

const (
	cloudConfigTemplate = `#cloud-config

coreos:
  etcd2:
    # generate a new token for each unique cluster from https://discovery.etcd.io/new?size=3
    discovery: "%s"
    # multi-region and multi-cloud deployments need to use $public_ipv4
    advertise-client-urls: "http://$private_ipv4:2379"
    initial-advertise-peer-urls: "http://$private_ipv4:2380"
    # listen on both the official ports and the legacy ports
    # legacy ports can be omitted if your application doesn't depend on them
    listen-client-urls: "http://0.0.0.0:2379,http://0.0.0.0:4001"
    listen-peer-urls: "http://$private_ipv4:2380,http://$private_ipv4:7001"
  fleet:
    public-ip: $private_ipv4   # used for fleetctl ssh command
    metadata: "region=%s"
  update:
    reboot-strategy: "etcd-lock"
  units:
    - name: etcd2.service
      command: start
    - name: fleet.service
      command: start`
)

func (this *doProvider) CreateCluster(options *providers.CreateClusterOptions) error {
	discoveryUrl, err := newDiscoveryUrl()
	if err != nil {
		return err
	}

	wg := sync.WaitGroup{}
	var lastErr error
	for i := 1; i <= options.InstanceCount; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			prefix := strings.ToLower(uniuri.NewLen(8))
			instanceOptions := &providers.CreateInstanceOptions{
				Domain:       options.Domain,
				ClusterName:  fmt.Sprintf("%s.%s", options.Name, options.Domain),
				InstanceName: fmt.Sprintf("%s.%s.%s", prefix, options.Name, options.Domain),
				Region:       options.Region,
				Image:        options.Image,
				Size:         options.Size,
				DiscoveryUrl: discoveryUrl,
				SSHKeyNames:  options.SSHKeyNames,
			}
			err := this.CreateInstance(instanceOptions)
			if err != nil && lastErr != nil {
				lastErr = err
			}
		}(i)
	}
	wg.Wait()
	if lastErr != nil {
		return lastErr
	}

	return nil
}

func (this *doProvider) CreateInstance(options *providers.CreateInstanceOptions) error {
	client := NewDOClient(this.token)

	keys := []godo.DropletCreateSSHKey{}
	listedKeys, err := KeyList(client)
	if err != nil {
		return err
	}
	for _, key := range options.SSHKeyNames {
		k := findKeyId(key, listedKeys)
		if k == nil {
			return errors.New("Key not found")
		}
		keys = append(keys, godo.DropletCreateSSHKey{ID: k.ID})
	}

	userData := fmt.Sprintf(cloudConfigTemplate, options.DiscoveryUrl, options.Region)

	request := &godo.DropletCreateRequest{
		Name:              options.InstanceName,
		Region:            options.Region,
		Size:              options.Size,
		Image:             godo.DropletCreateImage{Slug: options.Image},
		SSHKeys:           keys,
		Backups:           false,
		IPv6:              true,
		PrivateNetworking: true,
		UserData:          userData,
	}

	// Create droplet
	createDroplet, _, err := client.Droplets.Create(request)
	if err != nil {
		return err
	}

	// Wait for active
	droplet, err := this.waitUntilDropletActive(createDroplet.ID)
	if err != nil {
		return nil
	}

	publicIpv4 := getIpv4(*droplet, "public")
	publicIpv6 := getIpv6(*droplet, "public")
	fmt.Printf("%s: %s: %s\n", droplet.Name, publicIpv4, publicIpv6)

	// Create DNS record for the instance
	if err := this.createDnsRecord(options.Domain, "A", options.InstanceName, publicIpv4); err != nil {
		return err
	}
	if err := this.createDnsRecord(options.Domain, "A", options.ClusterName, publicIpv4); err != nil {
		return err
	}
	if publicIpv6 != "" {
		if err := this.createDnsRecord(options.Domain, "AAAA", options.InstanceName, publicIpv6); err != nil {
			return err
		}
		if err := this.createDnsRecord(options.Domain, "AAAA", options.ClusterName, publicIpv6); err != nil {
			return err
		}
	}

	return nil
}

func (this *doProvider) waitUntilDropletActive(id int) (*godo.Droplet, error) {
	client := NewDOClient(this.token)
	for {
		droplet, _, err := client.Droplets.Get(id)
		if err != nil {
			return nil, err
		}
		if droplet.Status != "new" {
			return droplet, nil
		}
		// Wait a while
		time.Sleep(time.Millisecond * 250)
	}
}

func findKeyId(key string, listedKeys []godo.Key) *godo.Key {
	for _, k := range listedKeys {
		if k.Name == key {
			return &k
		}
	}
	return nil
}

func newDiscoveryUrl() (string, error) {
	resp, err := http.Get("https://discovery.etcd.io/new")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", nil
	}
	return strings.TrimSpace(string(body)), nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"

	"github.com/emembrives/dispotrains/dispotrains.webapp/src/storage"

	"gopkg.in/mgo.v2/bson"
)

func FulfillmentHandler(w http.ResponseWriter, req *http.Request) {
	c := session.DB("dispotrains").C("stations")
	jsonReader := json.NewDecoder(req.Body)
	data := make(map[string]interface{})
	jsonReader.Decode(&data)
	queryResult, ok := data["queryResult"].(map[string]interface{})
	if !ok {
		log.Println(data["queryResult"])
		return
	}
	action, ok := queryResult["action"].(string)
	if !ok {
		log.Println(queryResult["action"])
		return
	}
	if action != "get_station_info" {
		log.Println(errors.New("Unknown action: " + action))
		return
	}
	parameters, ok := queryResult["parameters"].(map[string]interface{})
	if !ok {
		log.Println(queryResult["parameters"])
		return
	}
	stationName, ok := parameters["station"].(string)
	if !ok {
		log.Println(parameters["station"])
		return
	}
	var station storage.Station
	if err := c.Find(bson.M{"name": stationName}).One(&station); err != nil {
		log.Println(err)
		return
	}
	json.NewEncoder(w).Encode(&station)
}
<commit_msg>Assistant response<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"

	"github.com/emembrives/dispotrains/dispotrains.webapp/src/storage"

	"gopkg.in/mgo.v2/bson"
)

type webhookResponse struct {
	FulfillmentText string `json:"fulfillmentText,omitempty"`
}

func brokenElevators(elevators []*storage.Elevator) []*storage.Elevator {
	broken := make([]*storage.Elevator, 0)
	for _, e := range elevators {
		state := e.GetLastStatus().State
		if state != "Disponible" && state != "Information non disponible" {
			broken = append(broken, e)
		}
	}
	return broken
}

func noInfoElevators(elevators []*storage.Elevator) []*storage.Elevator {
	noInfo := make([]*storage.Elevator, 0)
	for _, e := range elevators {
		state := e.GetLastStatus().State
		if state == "Information non disponible" {
			noInfo = append(noInfo, e)
		}
	}
	return noInfo
}

func FulfillmentHandler(w http.ResponseWriter, req *http.Request) {
	c := session.DB("dispotrains").C("stations")
	jsonReader := json.NewDecoder(req.Body)
	data := make(map[string]interface{})
	jsonReader.Decode(&data)
	queryResult, ok := data["queryResult"].(map[string]interface{})
	if !ok {
		log.Println(data["queryResult"])
		return
	}
	action, ok := queryResult["action"].(string)
	if !ok {
		log.Println(queryResult["action"])
		return
	}
	if action != "get_station_info" {
		log.Println(errors.New("Unknown action: " + action))
		return
	}
	parameters, ok := queryResult["parameters"].(map[string]interface{})
	if !ok {
		log.Println(queryResult["parameters"])
		return
	}
	stationName, ok := parameters["station"].(string)
	if !ok {
		log.Println(parameters["station"])
		return
	}
	var station storage.Station
	if err := c.Find(bson.M{"name": stationName}).One(&station); err != nil {
		log.Println(err)
		return
	}
	response := webhookResponse{}
	response.FulfillmentText = fmt.Sprintf("Au %s, la gare de %s", station.LastUpdate.Format("02/01 à 15 heures 04"), station.DisplayName)
	if station.Available() {
		response.FulfillmentText = response.FulfillmentText + "n'a aucun ascenseur en panne."
	} else {
		broken := brokenElevators(station.Elevators)
		noInfo := noInfoElevators(station.Elevators)
		var summary string
		var descriptions string
		if len(broken) == 0 && len(noInfo) == 0 {
			summary = fmt.Sprintf(" a %d sur %d ascenseurs sans relevé.", len(noInfo), len(station.Elevators))
		} else if len(broken) != 0 {
			summary = fmt.Sprintf(" a %d sur %d ascenseurs en panne et %d sans relevé. ", len(broken), len(station.Elevators), len(noInfo))
			descriptions = "Les ascenseurs en panne sont: "
			for i, e := range broken {
				if i != 0 {
					descriptions += " ; "
				}
				descriptions += fmt.Sprintf("%s", e.Situation)
				if len(e.Direction) != 0 {
					descriptions += ", direction " + e.Direction
				}
				if e.Status.Forecast != nil {
					descriptions += ", jusqu'au " + e.Status.Forecast.Format("02/01/2006")
				}
			}
		}
		response.FulfillmentText = response.FulfillmentText + summary + descriptions
	}
	json.NewEncoder(w).Encode(&response)
}
<|endoftext|>