<commit_before>package storage

import "io"

// ObjectInfo represents a Google Cloud Storage (GCS) object.
type ObjectInfo struct {
	// Bucket is the name of the bucket containing this GCS object.
	Bucket string `json:"bucket,omitempty"`

	// Name is the name of the object.
	Name string `json:"name,omitempty"`

	// ContentType is the MIME type of the object's content.
	ContentType string `json:"contentType,omitempty"`

	// Size is the length of the object's content.
	// Read-only.
	Size int64 `json:"size,omitempty"`

	// ContentEncoding is the encoding of the object's content.
	// Read-only.
	ContentEncoding string `json:"contentEncoding,omitempty"`

	// MD5 is the MD5 hash of the data.
	// Read-only.
	MD5 []byte `json:"md5Hash,omitempty"`

	// CRC32C is the CRC32C checksum of the object's content.
	// Read-only.
	CRC32C []byte `json:"crc32c,omitempty"`

	// MediaLink is an URL to the object's content.
	// Read-only.
	MediaLink string `json:"mediaLink,omitempty"`

	// Metadata represents user-provided metadata, in key/value pairs.
	// It can be nil if no metadata is provided.
	Metadata map[string]string `json:"metadata,omitempty"`

	// Generation is the generation version of the object's content.
	// Read-only.
	Generation int64 `json:"generation,omitempty"`

	// MetaGeneration is the version of the metadata for this
	// object at this generation. This field is used for preconditions
	// and for detecting changes in metadata. A metageneration number
	// is only meaningful in the context of a particular generation
	// of a particular object. Readonly.
	MetaGeneration int64 `json:"metageneration,omitempty"`

	// TODO(jbd): Add ACL and owner.
	// TODO(jbd): Add timeDelete and updated.
}

type BucketInfo struct {
	// Name is the name of the bucket.
	Name string `json:"name,omitempty"`
}

type Storage struct {
	// TODO(jbd): Add connection
}

type Bucket struct {
	name string
}

// TODO(jbd): Add storage.buckets.list.
// TODO(jbd): Add storage.buckets.insert.
// TODO(jbd): Add storage.buckets.update.
// TODO(jbd): Add storage.buckets.delete.
// TODO(jbd): Add storage.objects.list.
// TODO(jbd): Add storage.objects.watch.

func (s *Storage) NewBucket(name string) *Bucket {
	panic("not yet implemented")
}

// GetBucketInfo returns the specified bucket.
func (s *Storage) GetBucketInfo(name string) (*BucketInfo, error) {
	panic("not yet implemented")
}

// Stat returns the meta information of an object.
func (b *Bucket) Stat(name string) (*ObjectInfo, error) {
	panic("not yet impelemented")
}

// Put inserts/updates an object with the provided meta information.
func (b *Bucket) Put(name string, info *ObjectInfo) error {
	panic("not yet impelemented")
}

// Delete deletes the specified object.
func (b *Bucket) Delete(name string) error {
	panic("not yet impelemented")
}

// Copy copies the source object to the destination with the new
// meta information properties provided.
// The destination object is insterted into the source bucket
// if the destination doesn't specify another bucket name.
func (b *Bucket) Copy(name string, dest *ObjectInfo) error {
	panic("not yet impelemented")
}

// NewReader creates a new io.ReadCloser to read the contents
// of the object.
func (b *Bucket) NewReader(name string) (io.ReadCloser, error) {
	panic("not yet impelemented")
}

// NewWriter creates a new io.WriteCloser to write to the GCS object
// identified by the specified bucket and name.
// If such object doesn't exist, it creates one. If info is not nil,
// write operation also modifies the meta information of the object.
func (b *Bucket) NewWriter(name string, info *ObjectInfo) (io.WriteCloser, error) {
	panic("not yet implemented")
}
<commit_msg>storage: Introducing RoundTripper as a connection object.<commit_after>// Package storage is a Google Cloud Storage client.
package storage

import (
	"errors"
	"io"
	"net/http"

	raw "code.google.com/p/google-api-go-client/storage/v1"
)

// ObjectInfo represents a Google Cloud Storage (GCS) object.
type ObjectInfo struct {
	// Bucket is the name of the bucket containing this GCS object.
	Bucket string `json:"bucket,omitempty"`

	// Name is the name of the object.
	Name string `json:"name,omitempty"`

	// ContentType is the MIME type of the object's content.
	ContentType string `json:"contentType,omitempty"`

	// Size is the length of the object's content.
	// Read-only.
	Size uint64 `json:"size,omitempty"`

	// ContentEncoding is the encoding of the object's content.
	// Read-only.
	ContentEncoding string `json:"contentEncoding,omitempty"`

	// MD5 is the MD5 hash of the data.
	// Read-only.
	MD5 []byte `json:"md5Hash,omitempty"`

	// CRC32C is the CRC32C checksum of the object's content.
	// Read-only.
	CRC32C []byte `json:"crc32c,omitempty"`

	// MediaLink is an URL to the object's content.
	// Read-only.
	MediaLink string `json:"mediaLink,omitempty"`

	// Metadata represents user-provided metadata, in key/value pairs.
	// It can be nil if no metadata is provided.
	Metadata map[string]string `json:"metadata,omitempty"`

	// Generation is the generation version of the object's content.
	// Read-only.
	Generation int64 `json:"generation,omitempty"`

	// MetaGeneration is the version of the metadata for this
	// object at this generation. This field is used for preconditions
	// and for detecting changes in metadata. A metageneration number
	// is only meaningful in the context of a particular generation
	// of a particular object. Readonly.
	MetaGeneration int64 `json:"metageneration,omitempty"`

	// TODO(jbd): Add ACL and owner.
	// TODO(jbd): Add timeDelete and updated.
}

func (o *ObjectInfo) toRawObject() *raw.Object {
	// TODO(jbd): add ACL and owner
	return &raw.Object{
		Bucket:      o.Bucket,
		Name:        o.Name,
		ContentType: o.ContentType,
	}
}

func newObjectInfo(o *raw.Object) *ObjectInfo {
	if o == nil {
		return nil
	}
	return &ObjectInfo{
		Bucket:          o.Bucket,
		Name:            o.Name,
		ContentType:     o.ContentType,
		ContentEncoding: o.ContentEncoding,
		Size:            o.Size,
		MD5:             []byte(o.Md5Hash),
		CRC32C:          []byte(o.Crc32c),
		MediaLink:       o.MediaLink,
		Generation:      o.Generation,
		MetaGeneration:  o.Metageneration,
	}
}

type BucketInfo struct {
	// Name is the name of the bucket.
	Name string `json:"name,omitempty"`
}

type Bucket struct {
	name string
	s    *raw.Service
}

type Client struct {
	s *raw.Service
}

func New(tr http.RoundTripper) (*Client, error) {
	return NewWithClient(&http.Client{Transport: tr})
}

func NewWithClient(c *http.Client) (*Client, error) {
	s, err := raw.New(c)
	if err != nil {
		return nil, err
	}
	return &Client{s: s}, nil
}

// TODO(jbd): Add storage.buckets.list.
// TODO(jbd): Add storage.buckets.insert.
// TODO(jbd): Add storage.buckets.update.
// TODO(jbd): Add storage.buckets.delete.

// TODO(jbd): Add storage.objects.list.

// GetBucketInfo returns the specified bucket.
func (c *Client) GetBucketInfo(name string) (*BucketInfo, error) {
	panic("not yet implemented")
}

func (c *Client) NewBucket(name string) *Bucket {
	return &Bucket{name: name, s: c.s}
}

// Stat returns the meta information of an object.
func (b *Bucket) Stat(name string) (*ObjectInfo, error) {
	o, err := b.s.Objects.Get(b.name, name).Do()
	if err != nil {
		// TODO(jbd): If 404, return ErrNotExists
		return nil, err
	}
	return newObjectInfo(o), nil
}

// Put inserts/updates an object with the provided meta information.
func (b *Bucket) Put(name string, info *ObjectInfo) (*ObjectInfo, error) {
	o, err := b.s.Objects.Insert(b.name, info.toRawObject()).Do()
	if err != nil {
		return nil, err
	}
	return newObjectInfo(o), nil
}

// Delete deletes the specified object.
func (b *Bucket) Delete(name string) error {
	return b.s.Objects.Delete(b.name, name).Do()
}

// Copy copies the source object to the destination with the new
// meta information properties provided.
// The destination object is inserted into the source bucket
// if the destination object doesn't specify another bucket name.
func (b *Bucket) Copy(name string, dest *ObjectInfo) (*ObjectInfo, error) {
	if dest.Name == "" {
		return nil, errors.New("storage: missing dest name")
	}
	destBucket := dest.Bucket
	if destBucket == "" {
		destBucket = b.name
	}
	o, err := b.s.Objects.Copy(
		b.name, name, destBucket, dest.Name, dest.toRawObject()).Do()
	if err != nil {
		// TODO(jbd): Return ErrNotExists if 404.
		return nil, err
	}
	return newObjectInfo(o), nil
}

// NewReader creates a new io.ReadCloser to read the contents
// of the object.
func (b *Bucket) NewReader(name string) (io.ReadCloser, error) {
	panic("not yet impelemented")
}

// NewWriter creates a new io.WriteCloser to write to the GCS object
// identified by the specified bucket and name.
// If such object doesn't exist, it creates one. If info is not nil,
// write operation also modifies the meta information of the object.
func (b *Bucket) NewWriter(name string, info *ObjectInfo) (io.WriteCloser, error) {
	panic("not yet implemented")
}
<|endoftext|>


<commit_before>//////////////////////////////////////////
// archivex.go
// Jhonathan Paulo Banczek - 2014
// jpbanczek@gmail.com - jhoonb.com
//////////////////////////////////////////

package archivex

import (
	"archive/tar"
	"archive/zip"
	"io"
	"io/ioutil"
	// "log"
	"os"
	"path"
	"strings"
)

// interface
type Archivex interface {
	Create(name string) error
	Add(name string, file []byte) error
	AddFile(name string) error
	AddAll(dir string, includeCurrentFolder bool) error
	Close() error
}

// ArchiveWriteFunc is the closure used by an archive's AddAll method to actually put a file into an archive
// Note that for directory entries, this func will be called with a nil 'file' param
type ArchiveWriteFunc func(info os.FileInfo, file io.Reader, entryName string) (err error)

// ZipFile implement *zip.Writer
type ZipFile struct {
	Writer *zip.Writer
	Name   string
}

// TarFile implement *tar.Writer
type TarFile struct {
	Writer *tar.Writer
	Name   string
}

// Create new file zip
func (z *ZipFile) Create(name string) error {
	// check extension .zip
	if strings.HasSuffix(name, ".zip") != true {
		if strings.HasSuffix(name, ".tar.gz") == true {
			name = strings.Replace(name, ".tar.gz", ".zip", -1)
		} else {
			name = name + ".zip"
		}
	}
	z.Name = name
	file, err := os.Create(z.Name)
	if err != nil {
		return err
	}
	z.Writer = zip.NewWriter(file)
	return nil
}

// Add add byte in archive zip
func (z *ZipFile) Add(name string, file []byte) error {

	iow, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = iow.Write(file)
	return err
}

// AddFile add file from dir in archive
func (z *ZipFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}
	filep, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = filep.Write(bytearq)
	if err != nil {
		return err
	}
	return nil
}

// AddAll adds all files from dir in archive, recursively.
// Directories receive a zero-size entry in the archive, with a trailing slash in the header name, and no compression
func (z *ZipFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Create a header based off of the fileinfo
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// If it's a file, set the compression method to deflate (leave directories uncompressed)
		if !info.IsDir() {
			header.Method = zip.Deflate
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Add a trailing slash if the entry is a directory
		if info.IsDir() {
			header.Name += string(os.PathSeparator)
		}

		// Get a writer in the archive based on our header
		writer, err := z.Writer.CreateHeader(header)
		if err != nil {
			return err
		}

		// If we have a file to write (i.e., not a directory) then pipe the file into the archive writer
		if file != nil {
			if _, err := io.Copy(writer, file); err != nil {
				return err
			}
		}

		return nil
	})
}

func (z *ZipFile) Close() error {
	err := z.Writer.Close()
	return err
}

// Create new Tar file
func (t *TarFile) Create(name string) error {
	// check extension .zip
	if strings.HasSuffix(name, ".tar.gz") != true {
		if strings.HasSuffix(name, ".zip") == true {
			name = strings.Replace(name, ".zip", ".tar.gz", -1)
		} else {
			name = name + ".tar.gz"
		}
	}
	t.Name = name
	file, err := os.Create(t.Name)
	if err != nil {
		return err
	}
	t.Writer = tar.NewWriter(file)
	return nil
}

// Add add byte in archive tar
func (t *TarFile) Add(name string, file []byte) error {

	hdr := &tar.Header{Name: name, Size: int64(len(file)), Mode: 0777}
	if err := t.Writer.WriteHeader(hdr); err != nil {
		return err
	}
	_, err := t.Writer.Write(file)
	return err
}

// AddFile add file from dir in archive tar
func (t *TarFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}

	info, err := os.Stat(name)
	if err != nil {
		return err
	}

	header, err := tar.FileInfoHeader(info, "")
	if err != nil {
		return err
	}

	err = t.Writer.WriteHeader(header)
	if err != nil {
		return err
	}
	_, err = t.Writer.Write(bytearq)
	if err != nil {
		return err
	}
	return nil

}

// AddAll adds all files from dir in archive
// Tar does not support directories
func (t *TarFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Skip directory entries
		if file == nil {
			return nil
		}

		// Create a header based off of the fileinfo
		header, err := tar.FileInfoHeader(info, "")
		if err != nil {
			return err
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Write the header into the tar file
		if err := t.Writer.WriteHeader(header); err != nil {
			return err
		}

		// Pipe the file into the tar
		if _, err := io.Copy(t.Writer, file); err != nil {
			return err
		}

		return nil
	})
}

// Close the file Tar
func (t *TarFile) Close() error {
	err := t.Writer.Close()
	return err
}

func getSubDir(dir string, rootDir string, includeCurrentFolder bool) (subDir string) {

	subDir = strings.Replace(dir, rootDir, "", 1)

	if includeCurrentFolder {
		parts := strings.Split(rootDir, string(os.PathSeparator))
		subDir = path.Join(parts[len(parts)-1], subDir)
	}

	return
}

// addAll is used to recursively go down through directories and add each file and directory to an archive, based on an ArchiveWriteFunc given to it
func addAll(dir string, rootDir string, includeCurrentFolder bool, writerFunc ArchiveWriteFunc) error {

	// Get a list of all entries in the directory, as []os.FileInfo
	fileInfos, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}

	// Loop through all entries
	for _, info := range fileInfos {

		full := path.Join(dir, info.Name())

		// If the entry is a file, get an io.Reader for it
		var file io.Reader
		if !info.IsDir() {
			file, err = os.Open(full)
			if err != nil {
				return err
			}
		}

		// Write the entry into the archive
		subDir := getSubDir(dir, rootDir, includeCurrentFolder)
		entryName := path.Join(subDir, info.Name())
		if err := writerFunc(info, file, entryName); err != nil {
			return err
		}

		// If the entry is a directory, recurse into it
		if info.IsDir() {
			addAll(full, rootDir, includeCurrentFolder, writerFunc)
		}
	}

	return nil
}
<commit_msg>adds compression<commit_after>//////////////////////////////////////////
// archivex.go
// Jhonathan Paulo Banczek - 2014
// jpbanczek@gmail.com - jhoonb.com
//////////////////////////////////////////

package archivex

import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"io"
	"io/ioutil"
	// "log"
	"os"
	"path"
	"strings"
)

// interface
type Archivex interface {
	Create(name string) error
	Add(name string, file []byte) error
	AddFile(name string) error
	AddAll(dir string, includeCurrentFolder bool) error
	Close() error
}

// ArchiveWriteFunc is the closure used by an archive's AddAll method to actually put a file into an archive
// Note that for directory entries, this func will be called with a nil 'file' param
type ArchiveWriteFunc func(info os.FileInfo, file io.Reader, entryName string) (err error)

// ZipFile implement *zip.Writer
type ZipFile struct {
	Writer *zip.Writer
	Name   string
}

// TarFile implement *tar.Writer
type TarFile struct {
	Writer     *tar.Writer
	Name       string
	GzWriter   *gzip.Writer
	Compressed bool
}

// Create new file zip
func (z *ZipFile) Create(name string) error {
	// check extension .zip
	if strings.HasSuffix(name, ".zip") != true {
		if strings.HasSuffix(name, ".tar.gz") == true {
			name = strings.Replace(name, ".tar.gz", ".zip", -1)
		} else {
			name = name + ".zip"
		}
	}
	z.Name = name
	file, err := os.Create(z.Name)
	if err != nil {
		return err
	}
	z.Writer = zip.NewWriter(file)
	return nil
}

// Add add byte in archive zip
func (z *ZipFile) Add(name string, file []byte) error {

	iow, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = iow.Write(file)
	return err
}

// AddFile add file from dir in archive
func (z *ZipFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}
	filep, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = filep.Write(bytearq)
	if err != nil {
		return err
	}
	return nil
}

// AddAll adds all files from dir in archive, recursively.
// Directories receive a zero-size entry in the archive, with a trailing slash in the header name, and no compression
func (z *ZipFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Create a header based off of the fileinfo
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// If it's a file, set the compression method to deflate (leave directories uncompressed)
		if !info.IsDir() {
			header.Method = zip.Deflate
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Add a trailing slash if the entry is a directory
		if info.IsDir() {
			header.Name += string(os.PathSeparator)
		}

		// Get a writer in the archive based on our header
		writer, err := z.Writer.CreateHeader(header)
		if err != nil {
			return err
		}

		// If we have a file to write (i.e., not a directory) then pipe the file into the archive writer
		if file != nil {
			if _, err := io.Copy(writer, file); err != nil {
				return err
			}
		}

		return nil
	})
}

func (z *ZipFile) Close() error {
	err := z.Writer.Close()
	return err
}

// Create new Tar file
func (t *TarFile) Create(name string) error {
	// check the filename extension

	// if it has a .gz, we'll compress it.
	if strings.HasSuffix(name, ".tar.gz") {
		t.Compressed = true
	} else {
		t.Compressed = false
	}

	// check to see if they have the wrong extension
	if strings.HasSuffix(name, ".tar.gz") != true && strings.HasSuffix(name, ".tar") != true {
		// is it .zip? replace it
		if strings.HasSuffix(name, ".zip") == true {
			name = strings.Replace(name, ".zip", ".tar.gz", -1)
		} else {
			// if it's not, add .tar
			// since we'll assume it's not compressed
			name = name + ".tar"
		}
	}

	t.Name = name
	file, err := os.Create(t.Name)
	if err != nil {
		return err
	}

	if t.Compressed {
		t.GzWriter = gzip.NewWriter(file)
		t.Writer = tar.NewWriter(t.GzWriter)
	} else {
		t.Writer = tar.NewWriter(file)
	}

	return nil
}

// Add add byte in archive tar
func (t *TarFile) Add(name string, file []byte) error {

	hdr := &tar.Header{Name: name, Size: int64(len(file)), Mode: 0777}
	if err := t.Writer.WriteHeader(hdr); err != nil {
		return err
	}
	_, err := t.Writer.Write(file)
	return err
}

// AddFile add file from dir in archive tar
func (t *TarFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}

	info, err := os.Stat(name)
	if err != nil {
		return err
	}

	header, err := tar.FileInfoHeader(info, "")
	if err != nil {
		return err
	}

	err = t.Writer.WriteHeader(header)
	if err != nil {
		return err
	}
	_, err = t.Writer.Write(bytearq)
	if err != nil {
		return err
	}
	return nil

}

// AddAll adds all files from dir in archive
// Tar does not support directories
func (t *TarFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Skip directory entries
		if file == nil {
			return nil
		}

		// Create a header based off of the fileinfo
		header, err := tar.FileInfoHeader(info, "")
		if err != nil {
			return err
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Write the header into the tar file
		if err := t.Writer.WriteHeader(header); err != nil {
			return err
		}

		// Pipe the file into the tar
		if _, err := io.Copy(t.Writer, file); err != nil {
			return err
		}

		return nil
	})
}

// Close the file Tar
func (t *TarFile) Close() error {
	if t.Compressed {
		err := t.GzWriter.Close()
		if err != nil {
			return err
		}
	}

	err := t.Writer.Close()
	if err != nil {
		return err
	}

	return err
}

func getSubDir(dir string, rootDir string, includeCurrentFolder bool) (subDir string) {

	subDir = strings.Replace(dir, rootDir, "", 1)

	if includeCurrentFolder {
		parts := strings.Split(rootDir, string(os.PathSeparator))
		subDir = path.Join(parts[len(parts)-1], subDir)
	}

	return
}

// addAll is used to recursively go down through directories and add each file and directory to an archive, based on an ArchiveWriteFunc given to it
func addAll(dir string, rootDir string, includeCurrentFolder bool, writerFunc ArchiveWriteFunc) error {

	// Get a list of all entries in the directory, as []os.FileInfo
	fileInfos, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}

	// Loop through all entries
	for _, info := range fileInfos {

		full := path.Join(dir, info.Name())

		// If the entry is a file, get an io.Reader for it
		var file io.Reader
		if !info.IsDir() {
			file, err = os.Open(full)
			if err != nil {
				return err
			}
		}

		// Write the entry into the archive
		subDir := getSubDir(dir, rootDir, includeCurrentFolder)
		entryName := path.Join(subDir, info.Name())
		if err := writerFunc(info, file, entryName); err != nil {
			return err
		}

		// If the entry is a directory, recurse into it
		if info.IsDir() {
			addAll(full, rootDir, includeCurrentFolder, writerFunc)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package surveys

import (
	"encoding/json"
	"github.com/AreaHQ/jsonhal"
	"github.com/ONSdigital/go-launch-a-survey/settings"
	"log"
	"net/http"
	"regexp"
)

// LauncherSchema is a representation of a schema in the Launcher
type LauncherSchema struct {
	Name     string
	EqID     string
	FormType string
	URL      string
}

// RegisterResponse is the response from the eq-survey-register request
type RegisterResponse struct {
	jsonhal.Hal
}

// Schemas is a list of Schema
type Schemas []Schema

// Schema is an available schema
type Schema struct {
	jsonhal.Hal
	Name string `json:"name"`
}

var eqIDFormTypeRegex = regexp.MustCompile(`^(?P<eq_id>[a-z0-9]+)_(?P<form_type>\w+)`)

func extractEqIDFormType(schema string) (EqID, formType string) {
	match := eqIDFormTypeRegex.FindStringSubmatch(schema)
	if match != nil {
		EqID = match[1]
		formType = match[2]
	}
	return
}

// LauncherSchemaFromFilename creates a LauncherSchema record from a schema filename
func LauncherSchemaFromFilename(filename string) LauncherSchema {
	EqID, formType := extractEqIDFormType(filename)
	return LauncherSchema{
		Name:     filename,
		EqID:     EqID,
		FormType: formType,
	}
}

// GetAvailableSchemas Gets the list of static schemas an joins them with any schemas from the eq-survey-register if defined
func GetAvailableSchemas() []LauncherSchema {
	schemaList := []LauncherSchema{
		LauncherSchemaFromFilename("0_star_wars.json"),
		LauncherSchemaFromFilename("1_0005.json"),
		LauncherSchemaFromFilename("1_0102.json"),
		LauncherSchemaFromFilename("1_0112.json"),
		LauncherSchemaFromFilename("1_0203.json"),
		LauncherSchemaFromFilename("1_0205.json"),
		LauncherSchemaFromFilename("1_0213.json"),
		LauncherSchemaFromFilename("1_0215.json"),
		LauncherSchemaFromFilename("2_0001.json"),
		LauncherSchemaFromFilename("census_communal.json"),
		LauncherSchemaFromFilename("census_household.json"),
		LauncherSchemaFromFilename("census_individual.json"),
		LauncherSchemaFromFilename("e_commerce.json"),
		LauncherSchemaFromFilename("mbs_0111.json"),
		LauncherSchemaFromFilename("multiple_answers.json"),
		LauncherSchemaFromFilename("test_big_list_naughty_strings.json"),
		LauncherSchemaFromFilename("test_checkbox.json"),
		LauncherSchemaFromFilename("test_conditional_dates.json"),
		LauncherSchemaFromFilename("test_conditional_routing.json"),
		LauncherSchemaFromFilename("test_currency.json"),
		LauncherSchemaFromFilename("test_dates.json"),
		LauncherSchemaFromFilename("test_dropdown_mandatory.json"),
		LauncherSchemaFromFilename("test_dropdown_mandatory_with_overridden_error.json"),
		LauncherSchemaFromFilename("test_dropdown_optional.json"),
		LauncherSchemaFromFilename("test_error_messages.json"),
		LauncherSchemaFromFilename("test_final_confirmation.json"),
		LauncherSchemaFromFilename("test_household_question.json"),
		LauncherSchemaFromFilename("test_interstitial_page.json"),
		LauncherSchemaFromFilename("test_introduction.json"),
		LauncherSchemaFromFilename("test_language.json"),
		LauncherSchemaFromFilename("test_language_cy.json"),
		LauncherSchemaFromFilename("test_markup.json"),
		LauncherSchemaFromFilename("test_metadata_routing.json"),
		LauncherSchemaFromFilename("test_multiple_piping.json"),
		LauncherSchemaFromFilename("test_navigation.json"),
		LauncherSchemaFromFilename("test_navigation_completeness.json"),
		LauncherSchemaFromFilename("test_navigation_confirmation.json"),
		LauncherSchemaFromFilename("test_navigation_routing.json"),
		LauncherSchemaFromFilename("test_numbers.json"),
		LauncherSchemaFromFilename("test_percentage.json"),
		LauncherSchemaFromFilename("test_question_guidance.json"),
		LauncherSchemaFromFilename("test_radio_checkbox_descriptions.json"),
		LauncherSchemaFromFilename("test_radio_mandatory.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_mandatory_other.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_mandatory_other_overridden_error.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_optional_other.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_overridden_error.json"),
		LauncherSchemaFromFilename("test_radio_optional.json"),
		LauncherSchemaFromFilename("test_radio_optional_with_mandatory_other.json"),
		LauncherSchemaFromFilename("test_radio_optional_with_mandatory_other_overridden_error.json"),
		LauncherSchemaFromFilename("test_radio_optional_with_optional_other.json"),
		LauncherSchemaFromFilename("test_relationship_household.json"),
		LauncherSchemaFromFilename("test_repeating_and_conditional_routing.json"),
		LauncherSchemaFromFilename("test_repeating_household.json"),
		LauncherSchemaFromFilename("test_routing_date_equals.json"),
		LauncherSchemaFromFilename("test_routing_date_greater_than.json"),
		LauncherSchemaFromFilename("test_routing_date_less_than.json"),
		LauncherSchemaFromFilename("test_routing_date_not_equals.json"),
		LauncherSchemaFromFilename("test_routing_group.json"),
		LauncherSchemaFromFilename("test_routing_number_equals.json"),
		LauncherSchemaFromFilename("test_routing_number_greater_than.json"),
		LauncherSchemaFromFilename("test_routing_number_greater_than_or_equal.json"),
		LauncherSchemaFromFilename("test_routing_number_less_than.json"),
		LauncherSchemaFromFilename("test_routing_number_less_than_or_equal.json"),
		LauncherSchemaFromFilename("test_routing_number_not_equals.json"),
		LauncherSchemaFromFilename("test_routing_on_multiple_select.json"),
		LauncherSchemaFromFilename("test_skip_condition.json"),
		LauncherSchemaFromFilename("test_skip_condition_block.json"),
		LauncherSchemaFromFilename("test_skip_condition_group.json"),
		LauncherSchemaFromFilename("test_summary.json"),
		LauncherSchemaFromFilename("test_section_summary.json"),
		LauncherSchemaFromFilename("test_sum_validation_against_total.json"),
		LauncherSchemaFromFilename("test_view_submitted_response.json"),
		LauncherSchemaFromFilename("test_textarea.json"),
		LauncherSchemaFromFilename("test_textfield.json"),
		LauncherSchemaFromFilename("test_timeout.json"),
		LauncherSchemaFromFilename("test_total_breakdown.json"),
		LauncherSchemaFromFilename("test_unit_patterns.json"),
	}

	return append(schemaList, getAvailableSchemasFromRegister()...)
}

func getAvailableSchemasFromRegister() []LauncherSchema {

	schemaList := []LauncherSchema{}

	if settings.Get("SURVEY_REGISTER_URL") != "" {
		req, err := http.NewRequest("GET", settings.Get("SURVEY_REGISTER_URL"), nil)
		if err != nil {
			log.Fatal("NewRequest: ", err)
			return []LauncherSchema{}
		}
		client := &http.Client{}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatal("Do: ", err)
			return []LauncherSchema{}
		}

		defer resp.Body.Close()

		var registerResponse RegisterResponse

		if err := json.NewDecoder(resp.Body).Decode(&registerResponse); err != nil {
			log.Println(err)
		}

		var schemas Schemas

		schemasJSON, _ := json.Marshal(registerResponse.Embedded["schemas"])

		if err := json.Unmarshal(schemasJSON, &schemas); err != nil {
			log.Println(err)
		}

		for _, schema := range schemas {
			url := schema.Links["self"]
			EqID, formType := extractEqIDFormType(schema.Name)
			schemaList = append(schemaList, LauncherSchema{
				Name:     schema.Name,
				URL:      url.Href,
				EqID:     EqID,
				FormType: formType,
			})
		}
	}

	return schemaList
}

// FindSurveyByName Finds the schema in the list of available schemas
func FindSurveyByName(name string) LauncherSchema {
	for _, survey := range GetAvailableSchemas() {
		if survey.Name == name {
			return survey
		}
	}
	panic("Survey not found")
}
<commit_msg>add multiple schemas to launcher for functional tests<commit_after>package surveys

import (
	"encoding/json"
	"github.com/AreaHQ/jsonhal"
	"github.com/ONSdigital/go-launch-a-survey/settings"
	"log"
	"net/http"
	"regexp"
)

// LauncherSchema is a representation of a schema in the Launcher
type LauncherSchema struct {
	Name     string
	EqID     string
	FormType string
	URL      string
}

// RegisterResponse is the response from the eq-survey-register request
type RegisterResponse struct {
	jsonhal.Hal
}

// Schemas is a list of Schema
type Schemas []Schema

// Schema is an available schema
type Schema struct {
	jsonhal.Hal
	Name string `json:"name"`
}

var eqIDFormTypeRegex = regexp.MustCompile(`^(?P<eq_id>[a-z0-9]+)_(?P<form_type>\w+)`)

func extractEqIDFormType(schema string) (EqID, formType string) {
	match := eqIDFormTypeRegex.FindStringSubmatch(schema)
	if match != nil {
		EqID = match[1]
		formType = match[2]
	}
	return
}

// LauncherSchemaFromFilename creates a LauncherSchema record from a schema filename
func LauncherSchemaFromFilename(filename string) LauncherSchema {
	EqID, formType := extractEqIDFormType(filename)
	return LauncherSchema{
		Name:     filename,
		EqID:     EqID,
		FormType: formType,
	}
}

// GetAvailableSchemas Gets the list of static schemas an joins them with any schemas from the eq-survey-register if defined
func GetAvailableSchemas() []LauncherSchema {
	schemaList := []LauncherSchema{
		LauncherSchemaFromFilename("0_star_wars.json"),
		LauncherSchemaFromFilename("1_0005.json"),
		LauncherSchemaFromFilename("1_0102.json"),
		LauncherSchemaFromFilename("1_0112.json"),
		LauncherSchemaFromFilename("1_0203.json"),
		LauncherSchemaFromFilename("1_0205.json"),
		LauncherSchemaFromFilename("1_0213.json"),
		LauncherSchemaFromFilename("1_0215.json"),
		LauncherSchemaFromFilename("2_0001.json"),
		LauncherSchemaFromFilename("census_communal.json"),
		LauncherSchemaFromFilename("census_household.json"),
		LauncherSchemaFromFilename("census_individual.json"),
		LauncherSchemaFromFilename("e_commerce.json"),
		LauncherSchemaFromFilename("mbs_0111.json"),
		LauncherSchemaFromFilename("multiple_answers.json"),
		LauncherSchemaFromFilename("test_average_equal_validation_against_total.json"),
		LauncherSchemaFromFilename("test_average_equal_or_less_validation_against_total.json"),
		LauncherSchemaFromFilename("test_average_less_validation_against_total.json"),
		LauncherSchemaFromFilename("test_big_list_naughty_strings.json"),
		LauncherSchemaFromFilename("test_checkbox.json"),
		LauncherSchemaFromFilename("test_conditional_dates.json"),
		LauncherSchemaFromFilename("test_conditional_routing.json"),
		LauncherSchemaFromFilename("test_currency.json"),
		LauncherSchemaFromFilename("test_dates.json"),
		LauncherSchemaFromFilename("test_dropdown_mandatory.json"),
		LauncherSchemaFromFilename("test_dropdown_mandatory_with_overridden_error.json"),
		LauncherSchemaFromFilename("test_dropdown_optional.json"),
		LauncherSchemaFromFilename("test_error_messages.json"),
		LauncherSchemaFromFilename("test_final_confirmation.json"),
		LauncherSchemaFromFilename("test_household_question.json"),
		LauncherSchemaFromFilename("test_interstitial_page.json"),
		LauncherSchemaFromFilename("test_introduction.json"),
		LauncherSchemaFromFilename("test_language.json"),
		LauncherSchemaFromFilename("test_language_cy.json"),
		LauncherSchemaFromFilename("test_markup.json"),
		LauncherSchemaFromFilename("test_metadata_routing.json"),
		LauncherSchemaFromFilename("test_multiple_piping.json"),
		LauncherSchemaFromFilename("test_navigation.json"),
		LauncherSchemaFromFilename("test_navigation_completeness.json"),
		LauncherSchemaFromFilename("test_navigation_confirmation.json"),
		LauncherSchemaFromFilename("test_navigation_routing.json"),
		LauncherSchemaFromFilename("test_numbers.json"),
		LauncherSchemaFromFilename("test_percentage.json"),
		LauncherSchemaFromFilename("test_question_guidance.json"),
		LauncherSchemaFromFilename("test_radio_checkbox_descriptions.json"),
		LauncherSchemaFromFilename("test_radio_mandatory.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_mandatory_other.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_mandatory_other_overridden_error.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_optional_other.json"),
		LauncherSchemaFromFilename("test_radio_mandatory_with_overridden_error.json"),
		LauncherSchemaFromFilename("test_radio_optional.json"),
		LauncherSchemaFromFilename("test_radio_optional_with_mandatory_other.json"),
		LauncherSchemaFromFilename("test_radio_optional_with_mandatory_other_overridden_error.json"),
		LauncherSchemaFromFilename("test_radio_optional_with_optional_other.json"),
		LauncherSchemaFromFilename("test_relationship_household.json"),
		LauncherSchemaFromFilename("test_repeating_and_conditional_routing.json"),
		LauncherSchemaFromFilename("test_repeating_household.json"),
		LauncherSchemaFromFilename("test_routing_date_equals.json"),
		LauncherSchemaFromFilename("test_routing_date_greater_than.json"),
		LauncherSchemaFromFilename("test_routing_date_less_than.json"),
		LauncherSchemaFromFilename("test_routing_date_not_equals.json"),
		LauncherSchemaFromFilename("test_routing_group.json"),
		LauncherSchemaFromFilename("test_routing_number_equals.json"),
		LauncherSchemaFromFilename("test_routing_number_greater_than.json"),
		LauncherSchemaFromFilename("test_routing_number_greater_than_or_equal.json"),
		LauncherSchemaFromFilename("test_routing_number_less_than.json"),
		LauncherSchemaFromFilename("test_routing_number_less_than_or_equal.json"),
		LauncherSchemaFromFilename("test_routing_number_not_equals.json"),
		LauncherSchemaFromFilename("test_routing_on_multiple_select.json"),
		LauncherSchemaFromFilename("test_skip_condition.json"),
		LauncherSchemaFromFilename("test_skip_condition_block.json"),
		LauncherSchemaFromFilename("test_skip_condition_group.json"),
		LauncherSchemaFromFilename("test_summary.json"),
		LauncherSchemaFromFilename("test_section_summary.json"),
		LauncherSchemaFromFilename("test_sum_equal_validation_against_total.json"),
		LauncherSchemaFromFilename("test_sum_equal_or_less_validation_against_total.json"),
		LauncherSchemaFromFilename("test_sum_less_validation_against_total.json"),
		LauncherSchemaFromFilename("test_view_submitted_response.json"),
		LauncherSchemaFromFilename("test_textarea.json"),
		LauncherSchemaFromFilename("test_textfield.json"),
		LauncherSchemaFromFilename("test_timeout.json"),
		LauncherSchemaFromFilename("test_total_breakdown.json"),
		LauncherSchemaFromFilename("test_unit_patterns.json"),
	}

	return append(schemaList, getAvailableSchemasFromRegister()...)
}

func getAvailableSchemasFromRegister() []LauncherSchema {

	schemaList := []LauncherSchema{}

	if settings.Get("SURVEY_REGISTER_URL") != "" {
		req, err := http.NewRequest("GET", settings.Get("SURVEY_REGISTER_URL"), nil)
		if err != nil {
			log.Fatal("NewRequest: ", err)
			return []LauncherSchema{}
		}
		client := &http.Client{}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatal("Do: ", err)
			return []LauncherSchema{}
		}

		defer resp.Body.Close()

		var registerResponse RegisterResponse

		if err := json.NewDecoder(resp.Body).Decode(&registerResponse); err != nil {
			log.Println(err)
		}

		var schemas Schemas

		schemasJSON, _ := json.Marshal(registerResponse.Embedded["schemas"])

		if err := json.Unmarshal(schemasJSON, &schemas); err != nil {
			log.Println(err)
		}

		for _, schema := range schemas {
			url := schema.Links["self"]
			EqID, formType := extractEqIDFormType(schema.Name)
			schemaList = append(schemaList, LauncherSchema{
				Name:     schema.Name,
				URL:      url.Href,
				EqID:     EqID,
				FormType: formType,
			})
		}
	}

	return schemaList
}

// FindSurveyByName Finds the schema in the list of available schemas
func FindSurveyByName(name string) LauncherSchema {
	for _, survey := range GetAvailableSchemas() {
		if survey.Name == name {
			return survey
		}
	}
	panic("Survey not found")
}
<|endoftext|>


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

package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"reflect"
	"strings"
	"time"

	"neugram.io/ng/eval"
	"neugram.io/ng/eval/environ"
	"neugram.io/ng/eval/shell"
	"neugram.io/ng/format"
	"neugram.io/ng/parser"
	"neugram.io/ng/tipe"

	"github.com/peterh/liner"
)

var (
	origMode liner.ModeApplier

	lineNg        *liner.State // ng-mode line reader
	historyNgFile = ""
	historyNg     = make(chan string, 1)
	historyShFile = ""
	historySh     = make(chan string, 1)
	sigint        = make(chan os.Signal)

	p   *parser.Parser
	prg *eval.Program
)

func exit(code int) {
	if lineNg != nil {
		lineNg.Close()
	}
	fmt.Fprintf(os.Stderr, "\n")
	os.Exit(code)
}

func exitf(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "ng: "+format+"\n", args...)
	exit(1)
}

func mode() liner.ModeApplier {
	m, err := liner.TerminalMode()
	if err != nil {
		exitf("terminal mode: %v", err)
	}
	return m
}

const usageLine = "ng [programfile | -e cmd] [arguments]"

func usage() {
	fmt.Fprintf(os.Stderr, `ng - neugram scripting language and shell

Usage:
	%s

Options:
`, usageLine)
	flag.PrintDefaults()
}

func main() {
	shell.Init()

	help := flag.Bool("h", false, "display help message and exit")
	e := flag.String("e", "", "program passed as a string")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s\n", usageLine)
		os.Exit(1)
	}
	flag.Parse()

	if *help {
		usage()
		os.Exit(0)
	}
	if *e != "" {
		initProgram(filepath.Join(cwd, "ng-arg"))
		res := p.ParseLine([]byte(*e))
		handleResult(res)
		return
	}
	if args := flag.Args(); len(args) > 0 {
		// TODO: plumb through the rest of the args
		path := args[0]
		initProgram(path)
		f, err := os.Open(path)
		if err != nil {
			exitf("%v", err)
		}
		state, err := runFile(f)
		if err != nil {
			exitf("%v", err)
		}
		if state == parser.StateCmd {
			exitf("%s: ends in an unclosed shell statement", args[0])
		}
		return
	}

	origMode = mode()
	lineNg = liner.NewLiner()
	defer lineNg.Close()

	loop()
}

func setWindowSize(env map[interface{}]interface{}) {
	// TODO windowsize
	// TODO
	// TODO
	// TODO
	/*
		rows, cols, err := job.WindowSize(os.Stderr.Fd())
		if err != nil {
			fmt.Printf("ng: could not get window size: %v\n", err)
		} else {
			// TODO: these are meant to be shell variables, not
			// environment variables. But then, how do programs
			// like `ls` read them?
			env["LINES"] = strconv.Itoa(rows)
			env["COLUMNS"] = strconv.Itoa(cols)
		}
	*/
}

func ps1(env *environ.Environ) string {
	v := env.Get("PS1")
	if v == "" {
		return "ng$ "
	}
	if strings.IndexByte(v, '\\') == -1 {
		return v
	}
	var buf []byte
	for {
		i := strings.IndexByte(v, '\\')
		if i == -1 || i == len(v)-1 {
			break
		}
		buf = append(buf, v[:i]...)
		b := v[i+1]
		v = v[i+2:]
		switch b {
		case 'h', 'H':
			out, err := exec.Command("hostname").CombinedOutput()
			if err != nil {
				fmt.Fprintf(os.Stderr, "ng: %v\n", err)
				continue
			}
			if b == 'h' {
				if i := bytes.IndexByte(out, '.'); i >= 0 {
					out = out[:i]
				}
			}
			if len(out) > 0 && out[len(out)-1] == '\n' {
				out = out[:len(out)-1]
			}
			buf = append(buf, out...)
		case 'n':
			buf = append(buf, '\n')
		case 'w', 'W':
			cwd := env.Get("PWD")
			if home := env.Get("HOME"); home != "" {
				cwd = strings.Replace(cwd, home, "~", 1)
			}
			if b == 'W' {
				cwd = filepath.Base(cwd)
			}
			buf = append(buf, cwd...)
		}
		// TODO: '!', '#', '$', 'nnn', 's', 'j', and more.
	}
	buf = append(buf, v...)
	return string(buf)
}

var cwd string

func init() {
	var err error
	cwd, err = os.Getwd()
	if err != nil {
		panic(err)
	}
}

func initProgram(path string) {
	p = parser.New()
	prg = eval.New(path)
	shell.Env = prg.Environ()
	shell.Alias = prg.Alias()

	// TODO this env setup could be done in neugram code
	env := prg.Environ()
	for _, s := range os.Environ() {
		i := strings.Index(s, "=")
		env.Set(s[:i], s[i+1:])
	}
	wd, err := os.Getwd()
	if err == nil {
		env.Set("PWD", wd)
	}
	//setWindowSize(env)

	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, os.Interrupt)
		for {
			s := <-sig
			select {
			case sigint <- s:
			case <-time.After(500 * time.Millisecond):
				// The evaluator has not handled the signal
				// promptly. There are several possible
				// reasons for this. The most likely right now
				// is the evaluator is in arbitrary Go code,
				// which does not have a way to be preempted.
				// It is also possible we have run into a
				// bug in the evaluator.
				//
				// Either way, instead of being one of those
				// obnoxious programs that refuses to respond
				// to Ctrl-C, be overly agressive and let the
				// entire ng process exit.
				//
				// This is bad if you use ng as your primary
				// shell, but good if you invoke ng to handle
				// scripts.
				fmt.Fprintf(os.Stderr, "ng: exiting on interrupt\n")
				exit(1)
			}
		}
	}()
}

func runFile(f *os.File) (parser.ParserState, error) {
	state := parser.StateStmt
	scanner := bufio.NewScanner(f)
	for i := 0; scanner.Scan(); i++ {
		b := scanner.Bytes()
		if i == 0 && len(b) > 2 && b[0] == '#' && b[1] == '!' { // shebang
			continue
		}
		res := p.ParseLine(b)
		handleResult(res)
		state = res.State
	}
	if err := scanner.Err(); err != nil {
		return state, fmt.Errorf("%s: %v", f.Name(), err)
	}
	switch state {
	case parser.StateStmtPartial, parser.StateCmdPartial:
		return state, fmt.Errorf("%s: ends in a partial statement", f.Name())
	default:
		return state, nil
	}
}

func loop() {
	path := filepath.Join(cwd, "ng-interactive")
	initProgram(path)

	state := parser.StateStmt
	if os.Args[0] == "ngsh" || os.Args[0] == "-ngsh" {
		initFile := filepath.Join(os.Getenv("HOME"), ".ngshinit")
		if f, err := os.Open(initFile); err == nil {
			var err error
			state, err = runFile(f)
			f.Close()
			if err != nil {
				exitf("%v", err)
			}
		}
		if state == parser.StateStmt {
			res := p.ParseLine([]byte("$$"))
			handleResult(res)
			state = res.State
		}
	}

	lineNg.SetTabCompletionStyle(liner.TabPrints)
	lineNg.SetWordCompleter(completer)
	lineNg.SetCtrlCAborts(true)

	if f, err := os.Open(historyShFile); err == nil {
		lineNg.SetMode("sh")
		lineNg.ReadHistory(f)
		f.Close()
	}
	go historyWriter(historyShFile, historySh)

	if f, err := os.Open(historyNgFile); err == nil {
		lineNg.SetMode("ng")
		lineNg.ReadHistory(f)
		f.Close()
	}
	go historyWriter(historyNgFile, historyNg)

	for {
		var (
			mode    string
			prompt  string
			history chan string
		)
		switch state {
		case parser.StateUnknown:
			mode, prompt, history = "ng", "??> ", historyNg
		case parser.StateStmt:
			mode, prompt, history = "ng", "ng> ", historyNg
		case parser.StateStmtPartial:
			mode, prompt, history = "ng", "..> ", historyNg
		case parser.StateCmd:
			mode, prompt, history = "sh", ps1(prg.Environ()), historySh
		case parser.StateCmdPartial:
			mode, prompt, history = "sh", "..$ ", historySh
		default:
			exitf("unkown parser state: %v", state)
		}
		lineNg.SetMode(mode)
		data, err := lineNg.Prompt(prompt)
		if err == liner.ErrPromptAborted {
			switch state {
			case parser.StateStmtPartial:
				fmt.Printf("TODO interrupt partial statement\n")
			case parser.StateCmdPartial:
				fmt.Printf("TODO interrupt partial command\n")
			}
		} else if err != nil {
			if err == io.EOF {
				exit(0)
			}
			exitf("error reading input: %v", err)
		}
		if data == "" {
			continue
		}
		lineNg.AppendHistory(mode, data)
		history <- data
		select { // drain sigint
		case <-sigint:
		default:
		}
		res := p.ParseLine([]byte(data))
		handleResult(res)
		state = res.State
	}
}

func handleResult(res parser.Result) {
	for _, s := range res.Stmts {
		v, err := prg.Eval(s, sigint)
		if err != nil {
			fmt.Printf("ng: %v\n", err)
			continue
		}
		if len(v) > 1 {
			fmt.Print("(")
		}
		for i, val := range v {
			if i > 0 {
				fmt.Print(", ")
			}
			if val == (reflect.Value{}) {
				fmt.Print("<nil>")
				continue
			}
			switch v := val.Interface().(type) {
			case eval.UntypedInt:
				fmt.Print(v.String())
			case eval.UntypedFloat:
				fmt.Print(v.String())
			case eval.UntypedComplex:
				fmt.Print(v.String())
			case eval.UntypedString:
				fmt.Print(v.String)
			case eval.UntypedRune:
				fmt.Printf("%v", v.Rune)
			case eval.UntypedBool:
				fmt.Print(v.Bool)
			default:
				fmt.Print(format.Debug(v))
			}
		}
		if len(v) > 1 {
			fmt.Println(")")
		} else if len(v) == 1 {
			fmt.Println("")
		}
	}
	for _, err := range res.Errs {
		fmt.Println(err.Error())
	}
	//editMode := mode()
	//origMode.ApplyMode()
	for _, cmd := range res.Cmds {
		j := &shell.Job{
			Cmd:    cmd,
			Params: prg,
			Stdin:  os.Stdin,
			Stdout: os.Stdout,
			Stderr: os.Stderr,
		}
		if err := j.Start(); err != nil {
			fmt.Println(err)
			continue
		}
		done, err := j.Wait()
		if err != nil {
			fmt.Println(err)
			continue
		}
		if !done {
			break // TODO not right, instead we should just have one cmd, not Cmds here.
		}
	}
	//editMode.ApplyMode()
}

func printValue(t tipe.Type, v interface{}) {
	// This is, effectively, a primitive type-aware printf implementation
	// that understands the neugram evaluator data layout. A far better
	// version of this would be an "ngfmt" package, that implemented the
	// printing command in neugram, using a "ngreflect" package. But it
	// will be a while until I build a reflect package, so this will have
	// to do.
	//
	// Still: avoid putting too much machinary in this. At some point soon
	// it's not worth the effort.
	/*switch t := tipe.Underlying(t).(type) {
	case *tipe.Struct:
	fmt.Print("{")
	for i, name := range t.FieldNames {
		fmt.Printf("%s: ", name)
		printValue(t.Fields[i], v.(*eval.StructVal).Fields[i].Value)
		if i < len(t.FieldNames)-1 {
			fmt.Print(", ")
		}
	}
	fmt.Print("}")
	default:
	}*/
	fmt.Print(v)
}

func init() {
	if home := os.Getenv("HOME"); home != "" {
		historyNgFile = filepath.Join(home, ".ng_history")
		historyShFile = filepath.Join(home, ".ngsh_history")
	}
}

func historyWriter(dst string, src <-chan string) {
	var batch []string
	ticker := time.Tick(250 * time.Millisecond)
	for {
		select {
		case line := <-src:
			batch = append(batch, line)
		case <-ticker:
			if len(batch) > 0 && dst != "" {
				// TODO: FcntlFlock
				f, err := os.OpenFile(dst, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0664)
				if err == nil {
					for _, line := range batch {
						fmt.Fprintf(f, "%s\n", line)
					}
					f.Close()
				}
			}
			batch = nil
		}
	}
}
<commit_msg>remove TerminalMode call that breaks reading stdin<commit_after>// Copyright 2016 The Neugram Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"reflect"
	"strings"
	"time"

	"neugram.io/ng/eval"
	"neugram.io/ng/eval/environ"
	"neugram.io/ng/eval/shell"
	"neugram.io/ng/format"
	"neugram.io/ng/parser"
	"neugram.io/ng/tipe"

	"github.com/peterh/liner"
)

var (
	origMode liner.ModeApplier

	lineNg        *liner.State // ng-mode line reader
	historyNgFile = ""
	historyNg     = make(chan string, 1)
	historyShFile = ""
	historySh     = make(chan string, 1)
	sigint        = make(chan os.Signal)

	p   *parser.Parser
	prg *eval.Program
)

func exit(code int) {
	if lineNg != nil {
		lineNg.Close()
	}
	fmt.Fprintf(os.Stderr, "\n")
	os.Exit(code)
}

func exitf(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "ng: "+format+"\n", args...)
	exit(1)
}

func mode() liner.ModeApplier {
	m, err := liner.TerminalMode()
	if err != nil {
		exitf("terminal mode: %v", err)
	}
	return m
}

const usageLine = "ng [programfile | -e cmd] [arguments]"

func usage() {
	fmt.Fprintf(os.Stderr, `ng - neugram scripting language and shell

Usage:
	%s

Options:
`, usageLine)
	flag.PrintDefaults()
}

func main() {
	shell.Init()

	help := flag.Bool("h", false, "display help message and exit")
	e := flag.String("e", "", "program passed as a string")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s\n", usageLine)
		os.Exit(1)
	}
	flag.Parse()

	if *help {
		usage()
		os.Exit(0)
	}
	if *e != "" {
		initProgram(filepath.Join(cwd, "ng-arg"))
		res := p.ParseLine([]byte(*e))
		handleResult(res)
		return
	}
	if args := flag.Args(); len(args) > 0 {
		// TODO: plumb through the rest of the args
		path := args[0]
		initProgram(path)
		f, err := os.Open(path)
		if err != nil {
			exitf("%v", err)
		}
		state, err := runFile(f)
		if err != nil {
			exitf("%v", err)
		}
		if state == parser.StateCmd {
			exitf("%s: ends in an unclosed shell statement", args[0])
		}
		return
	}

	lineNg = liner.NewLiner()
	defer lineNg.Close()

	loop()
}

func setWindowSize(env map[interface{}]interface{}) {
	// TODO windowsize
	// TODO
	// TODO
	// TODO
	/*
		rows, cols, err := job.WindowSize(os.Stderr.Fd())
		if err != nil {
			fmt.Printf("ng: could not get window size: %v\n", err)
		} else {
			// TODO: these are meant to be shell variables, not
			// environment variables. But then, how do programs
			// like `ls` read them?
			env["LINES"] = strconv.Itoa(rows)
			env["COLUMNS"] = strconv.Itoa(cols)
		}
	*/
}

func ps1(env *environ.Environ) string {
	v := env.Get("PS1")
	if v == "" {
		return "ng$ "
	}
	if strings.IndexByte(v, '\\') == -1 {
		return v
	}
	var buf []byte
	for {
		i := strings.IndexByte(v, '\\')
		if i == -1 || i == len(v)-1 {
			break
		}
		buf = append(buf, v[:i]...)
		b := v[i+1]
		v = v[i+2:]
		switch b {
		case 'h', 'H':
			out, err := exec.Command("hostname").CombinedOutput()
			if err != nil {
				fmt.Fprintf(os.Stderr, "ng: %v\n", err)
				continue
			}
			if b == 'h' {
				if i := bytes.IndexByte(out, '.'); i >= 0 {
					out = out[:i]
				}
			}
			if len(out) > 0 && out[len(out)-1] == '\n' {
				out = out[:len(out)-1]
			}
			buf = append(buf, out...)
		case 'n':
			buf = append(buf, '\n')
		case 'w', 'W':
			cwd := env.Get("PWD")
			if home := env.Get("HOME"); home != "" {
				cwd = strings.Replace(cwd, home, "~", 1)
			}
			if b == 'W' {
				cwd = filepath.Base(cwd)
			}
			buf = append(buf, cwd...)
		}
		// TODO: '!', '#', '$', 'nnn', 's', 'j', and more.
	}
	buf = append(buf, v...)
	return string(buf)
}

var cwd string

func init() {
	var err error
	cwd, err = os.Getwd()
	if err != nil {
		panic(err)
	}
}

func initProgram(path string) {
	p = parser.New()
	prg = eval.New(path)
	shell.Env = prg.Environ()
	shell.Alias = prg.Alias()

	// TODO this env setup could be done in neugram code
	env := prg.Environ()
	for _, s := range os.Environ() {
		i := strings.Index(s, "=")
		env.Set(s[:i], s[i+1:])
	}
	wd, err := os.Getwd()
	if err == nil {
		env.Set("PWD", wd)
	}
	//setWindowSize(env)

	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, os.Interrupt)
		for {
			s := <-sig
			select {
			case sigint <- s:
			case <-time.After(500 * time.Millisecond):
				// The evaluator has not handled the signal
				// promptly. There are several possible
				// reasons for this. The most likely right now
				// is the evaluator is in arbitrary Go code,
				// which does not have a way to be preempted.
				// It is also possible we have run into a
				// bug in the evaluator.
				//
				// Either way, instead of being one of those
				// obnoxious programs that refuses to respond
				// to Ctrl-C, be overly agressive and let the
				// entire ng process exit.
				//
				// This is bad if you use ng as your primary
				// shell, but good if you invoke ng to handle
				// scripts.
				fmt.Fprintf(os.Stderr, "ng: exiting on interrupt\n")
				exit(1)
			}
		}
	}()
}

func runFile(f *os.File) (parser.ParserState, error) {
	state := parser.StateStmt
	scanner := bufio.NewScanner(f)
	for i := 0; scanner.Scan(); i++ {
		b := scanner.Bytes()
		if i == 0 && len(b) > 2 && b[0] == '#' && b[1] == '!' { // shebang
			continue
		}
		res := p.ParseLine(b)
		handleResult(res)
		state = res.State
	}
	if err := scanner.Err(); err != nil {
		return state, fmt.Errorf("%s: %v", f.Name(), err)
	}
	switch state {
	case parser.StateStmtPartial, parser.StateCmdPartial:
		return state, fmt.Errorf("%s: ends in a partial statement", f.Name())
	default:
		return state, nil
	}
}

func loop() {
	path := filepath.Join(cwd, "ng-interactive")
	initProgram(path)

	state := parser.StateStmt
	if os.Args[0] == "ngsh" || os.Args[0] == "-ngsh" {
		initFile := filepath.Join(os.Getenv("HOME"), ".ngshinit")
		if f, err := os.Open(initFile); err == nil {
			var err error
			state, err = runFile(f)
			f.Close()
			if err != nil {
				exitf("%v", err)
			}
		}
		if state == parser.StateStmt {
			res := p.ParseLine([]byte("$$"))
			handleResult(res)
			state = res.State
		}
	}

	lineNg.SetTabCompletionStyle(liner.TabPrints)
	lineNg.SetWordCompleter(completer)
	lineNg.SetCtrlCAborts(true)

	if f, err := os.Open(historyShFile); err == nil {
		lineNg.SetMode("sh")
		lineNg.ReadHistory(f)
		f.Close()
	}
	go historyWriter(historyShFile, historySh)

	if f, err := os.Open(historyNgFile); err == nil {
		lineNg.SetMode("ng")
		lineNg.ReadHistory(f)
		f.Close()
	}
	go historyWriter(historyNgFile, historyNg)

	for {
		var (
			mode    string
			prompt  string
			history chan string
		)
		switch state {
		case parser.StateUnknown:
			mode, prompt, history = "ng", "??> ", historyNg
		case parser.StateStmt:
			mode, prompt, history = "ng", "ng> ", historyNg
		case parser.StateStmtPartial:
			mode, prompt, history = "ng", "..> ", historyNg
		case parser.StateCmd:
			mode, prompt, history = "sh", ps1(prg.Environ()), historySh
		case parser.StateCmdPartial:
			mode, prompt, history = "sh", "..$ ", historySh
		default:
			exitf("unkown parser state: %v", state)
		}
		lineNg.SetMode(mode)
		data, err := lineNg.Prompt(prompt)
		if err == liner.ErrPromptAborted {
			switch state {
			case parser.StateStmtPartial:
				fmt.Printf("TODO interrupt partial statement\n")
			case parser.StateCmdPartial:
				fmt.Printf("TODO interrupt partial command\n")
			}
		} else if err != nil {
			if err == io.EOF {
				exit(0)
			}
			exitf("error reading input: %v", err)
		}
		if data == "" {
			continue
		}
		lineNg.AppendHistory(mode, data)
		history <- data
		select { // drain sigint
		case <-sigint:
		default:
		}
		res := p.ParseLine([]byte(data))
		handleResult(res)
		state = res.State
	}
}

func handleResult(res parser.Result) {
	for _, s := range res.Stmts {
		v, err := prg.Eval(s, sigint)
		if err != nil {
			fmt.Printf("ng: %v\n", err)
			continue
		}
		if len(v) > 1 {
			fmt.Print("(")
		}
		for i, val := range v {
			if i > 0 {
				fmt.Print(", ")
			}
			if val == (reflect.Value{}) {
				fmt.Print("<nil>")
				continue
			}
			switch v := val.Interface().(type) {
			case eval.UntypedInt:
				fmt.Print(v.String())
			case eval.UntypedFloat:
				fmt.Print(v.String())
			case eval.UntypedComplex:
				fmt.Print(v.String())
			case eval.UntypedString:
				fmt.Print(v.String)
			case eval.UntypedRune:
				fmt.Printf("%v", v.Rune)
			case eval.UntypedBool:
				fmt.Print(v.Bool)
			default:
				fmt.Print(format.Debug(v))
			}
		}
		if len(v) > 1 {
			fmt.Println(")")
		} else if len(v) == 1 {
			fmt.Println("")
		}
	}
	for _, err := range res.Errs {
		fmt.Println(err.Error())
	}
	for _, cmd := range res.Cmds {
		j := &shell.Job{
			Cmd:    cmd,
			Params: prg,
			Stdin:  os.Stdin,
			Stdout: os.Stdout,
			Stderr: os.Stderr,
		}
		if err := j.Start(); err != nil {
			fmt.Println(err)
			continue
		}
		done, err := j.Wait()
		if err != nil {
			fmt.Println(err)
			continue
		}
		if !done {
			break // TODO not right, instead we should just have one cmd, not Cmds here.
		}
	}
	//editMode.ApplyMode()
}

func printValue(t tipe.Type, v interface{}) {
	// This is, effectively, a primitive type-aware printf implementation
	// that understands the neugram evaluator data layout. A far better
	// version of this would be an "ngfmt" package, that implemented the
	// printing command in neugram, using a "ngreflect" package. But it
	// will be a while until I build a reflect package, so this will have
	// to do.
	//
	// Still: avoid putting too much machinary in this. At some point soon
	// it's not worth the effort.
	/*switch t := tipe.Underlying(t).(type) {
	case *tipe.Struct:
	fmt.Print("{")
	for i, name := range t.FieldNames {
		fmt.Printf("%s: ", name)
		printValue(t.Fields[i], v.(*eval.StructVal).Fields[i].Value)
		if i < len(t.FieldNames)-1 {
			fmt.Print(", ")
		}
	}
	fmt.Print("}")
	default:
	}*/
	fmt.Print(v)
}

func init() {
	if home := os.Getenv("HOME"); home != "" {
		historyNgFile = filepath.Join(home, ".ng_history")
		historyShFile = filepath.Join(home, ".ngsh_history")
	}
}

func historyWriter(dst string, src <-chan string) {
	var batch []string
	ticker := time.Tick(250 * time.Millisecond)
	for {
		select {
		case line := <-src:
			batch = append(batch, line)
		case <-ticker:
			if len(batch) > 0 && dst != "" {
				// TODO: FcntlFlock
				f, err := os.OpenFile(dst, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0664)
				if err == nil {
					for _, line := range batch {
						fmt.Fprintf(f, "%s\n", line)
					}
					f.Close()
				}
			}
			batch = nil
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/docopt/docopt.go"
	"time"
)

func main() {
	usage := "Usage: oc add <time> <time>"
	args, _ := docopt.Parse(usage, nil, true, "", false)

	times := args["<time>"].([]string)

	time1, _ := time.Parse("15:04", times[0])
	time2, _ := time.Parse("15:04", times[1])

	fmt.Println(time2.Sub(time1))
}
<commit_msg>added error handling code<commit_after>package main

import (
	"fmt"
	"github.com/docopt/docopt.go"
	"time"
)

func main() {
	usage := "Usage: oc add <time> <time>"
	args, _ := docopt.Parse(usage, nil, true, "", false)

	times := args["<time>"].([]string)

	time1, err := time.Parse("15:04", times[0])
	time2, err2 := time.Parse("15:04", times[1])

	if err == nil && err2 == nil {
		fmt.Println(time1.Sub(time2))
	} else {
		if err != nil {
			fmt.Println(err)
		}

		if err2 != nil {
			fmt.Println(err)
		}
	}

}
<|endoftext|>


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

package main

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/cheggaaa/pb"
	"github.com/minio/mc/pkg/console"
)

type pbBarCmd int

const (
	pbBarCmdExtend pbBarCmd = iota
	pbBarCmdProgress
	pbBarCmdFinish
	pbBarCmdPutError
	pbBarCmdGetError
	pbBarCmdSetCaption
)

type proxyReader struct {
	io.Reader
	bar *barSend
}

func (r *proxyReader) Read(p []byte) (n int, err error) {
	n, err = r.Reader.Read(p)
	r.bar.progress(int64(n))
	return
}

type barMsg struct {
	Cmd pbBarCmd
	Arg interface{}
}

type barSend struct {
	cmdCh    chan<- barMsg
	finishCh <-chan bool
}

func (b barSend) Extend(total int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdExtend, Arg: total}
}

func (b barSend) progress(progress int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdProgress, Arg: progress}
}

func (b barSend) ErrorPut(size int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdPutError, Arg: size}
}

func (b barSend) ErrorGet(size int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdGetError, Arg: size}
}

func (b *barSend) NewProxyReader(r io.Reader) *proxyReader {
	return &proxyReader{r, b}
}

type caption struct {
	message   string
	separator rune
}

func (b *barSend) SetCaption(c caption) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdSetCaption, Arg: c}
}

func (b barSend) Finish() {
	defer close(b.cmdCh)
	b.cmdCh <- barMsg{Cmd: pbBarCmdFinish}
	<-b.finishCh
}

func trimBarCaption(c caption, width int) string {
	if len(c.message) > width {
		// Trim caption to fit within the screen
		trimSize := len(c.message) - width + 3 + 1
		if trimSize < len(c.message) {
			c.message = "..." + c.message[trimSize:]
			// Further trim partial names.
			partialTrimSize := strings.IndexByte(c.message, byte(c.separator))
			if partialTrimSize > 0 {
				c.message = c.message[partialTrimSize:]
			}
		}
	}
	return c.message
}

// newCpBar - instantiate a pbBar.
func newCpBar() barSend {
	cmdCh := make(chan barMsg)
	finishCh := make(chan bool)
	go func(cmdCh <-chan barMsg, finishCh chan<- bool) {
		var started bool
		var barCaption string
		var totalBytesRead int64 // total amounts of bytes read
		bar := pb.New64(0)
		bar.SetUnits(pb.U_BYTES)
		bar.SetRefreshRate(time.Millisecond * 10)
		bar.NotPrint = true
		bar.ShowSpeed = true
		cursorUp := fmt.Sprintf("%c[%dA", 27, 1)
		eraseCurrentLine := fmt.Sprintf("%c[2K\r", 27)
		cursorDown := fmt.Sprintf("%c[%dB", 27, 1)
		firstTime := true
		barLock := &sync.Mutex{}
		bar.Callback = func(s string) {
			barLock.Lock()
			{
				if !firstTime {
					console.Print(cursorUp)
					console.Print(eraseCurrentLine)
				}
				console.Bar(barCaption)
				console.Print(cursorDown)
				console.Print(eraseCurrentLine)
				console.Bar(s)

				firstTime = false
			}
			barLock.Unlock()
		}
		// Feels like wget
		bar.Format("[=> ]")
		for msg := range cmdCh {
			switch msg.Cmd {
			case pbBarCmdSetCaption:
				barCaption = trimBarCaption(msg.Arg.(caption), bar.GetWidth())
			case pbBarCmdExtend:
				atomic.AddInt64(&bar.Total, msg.Arg.(int64))
			case pbBarCmdProgress:
				if bar.Total > 0 && !started {
					started = true
					bar.Start()
				}
				if msg.Arg.(int64) > 0 {
					totalBytesRead += msg.Arg.(int64)
					bar.Add64(msg.Arg.(int64))
				}
			case pbBarCmdPutError:
				if totalBytesRead > msg.Arg.(int64) {
					bar.Set64(totalBytesRead - msg.Arg.(int64))
				}
			case pbBarCmdGetError:
				if msg.Arg.(int64) > 0 {
					bar.Add64(msg.Arg.(int64))
				}
			case pbBarCmdFinish:
				if started {
					bar.Finish()
				}
				finishCh <- true
				return
			}
		}
	}(cmdCh, finishCh)
	return barSend{cmdCh, finishCh}
}
<commit_msg>Now printing makes sure to show console after erasing current line<commit_after>/*
 * Minio Client (C) 2014, 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"io"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/cheggaaa/pb"
	"github.com/minio/mc/pkg/console"
)

type pbBarCmd int

const (
	pbBarCmdExtend pbBarCmd = iota
	pbBarCmdProgress
	pbBarCmdFinish
	pbBarCmdPutError
	pbBarCmdGetError
	pbBarCmdSetCaption
)

type proxyReader struct {
	io.Reader
	bar *barSend
}

func (r *proxyReader) Read(p []byte) (n int, err error) {
	n, err = r.Reader.Read(p)
	r.bar.progress(int64(n))
	return
}

type barMsg struct {
	Cmd pbBarCmd
	Arg interface{}
}

type barSend struct {
	cmdCh    chan<- barMsg
	finishCh <-chan bool
}

func (b barSend) Extend(total int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdExtend, Arg: total}
}

func (b barSend) progress(progress int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdProgress, Arg: progress}
}

func (b barSend) ErrorPut(size int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdPutError, Arg: size}
}

func (b barSend) ErrorGet(size int64) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdGetError, Arg: size}
}

func (b *barSend) NewProxyReader(r io.Reader) *proxyReader {
	return &proxyReader{r, b}
}

type caption struct {
	message   string
	separator rune
}

func (b *barSend) SetCaption(c caption) {
	b.cmdCh <- barMsg{Cmd: pbBarCmdSetCaption, Arg: c}
}

func (b barSend) Finish() {
	defer close(b.cmdCh)
	b.cmdCh <- barMsg{Cmd: pbBarCmdFinish}
	<-b.finishCh
}

func trimBarCaption(c caption, width int) string {
	if len(c.message) > width {
		// Trim caption to fit within the screen
		trimSize := len(c.message) - width + 3 + 1
		if trimSize < len(c.message) {
			c.message = "..." + c.message[trimSize:]
			// Further trim partial names.
			partialTrimSize := strings.IndexByte(c.message, byte(c.separator))
			if partialTrimSize > 0 {
				c.message = c.message[partialTrimSize:]
			}
		}
	}
	return c.message
}

const (
	cursorUP         = "\x1b[A"
	cursorDown       = "\x1b[B"
	eraseCurrentLine = "\x1b[2K\r"
)

// newCpBar - instantiate a pbBar.
func newCpBar() barSend {
	cmdCh := make(chan barMsg)
	finishCh := make(chan bool)
	go func(cmdCh <-chan barMsg, finishCh chan<- bool) {
		var started bool
		var barCaption string
		var totalBytesRead int64 // total amounts of bytes read
		bar := pb.New64(0)
		bar.SetUnits(pb.U_BYTES)
		bar.SetRefreshRate(time.Millisecond * 10)
		bar.NotPrint = true
		bar.ShowSpeed = true
		firstTime := true
		barLock := &sync.Mutex{}
		bar.Callback = func(s string) {
			barLock.Lock()
			{
				if !firstTime {
					console.Print(cursorUP)
					console.Print(eraseCurrentLine)
				}
				console.Bar(barCaption)
				console.Print(cursorDown)
				console.Print(eraseCurrentLine)
				console.Bar(s)

				firstTime = false
			}
			barLock.Unlock()
		}
		// Feels like wget
		bar.Format("[=> ]")
		for msg := range cmdCh {
			switch msg.Cmd {
			case pbBarCmdSetCaption:
				barCaption = trimBarCaption(msg.Arg.(caption), bar.GetWidth())
			case pbBarCmdExtend:
				atomic.AddInt64(&bar.Total, msg.Arg.(int64))
			case pbBarCmdProgress:
				if bar.Total > 0 && !started {
					started = true
					bar.Start()
				}
				if msg.Arg.(int64) > 0 {
					totalBytesRead += msg.Arg.(int64)
					bar.Add64(msg.Arg.(int64))
				}
			case pbBarCmdPutError:
				if totalBytesRead > msg.Arg.(int64) {
					bar.Set64(totalBytesRead - msg.Arg.(int64))
				}
			case pbBarCmdGetError:
				if msg.Arg.(int64) > 0 {
					bar.Add64(msg.Arg.(int64))
				}
			case pbBarCmdFinish:
				if started {
					bar.Finish()
				}
				finishCh <- true
				return
			}
		}
	}(cmdCh, finishCh)
	return barSend{cmdCh, finishCh}
}
<|endoftext|>


<commit_before>package qq

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"time"
)

type color string

const (
	bold     color = "\033[1m"
	yellow   color = "\033[33m"
	cyan     color = "\033[36m"
	endColor color = "\033[0m" // ANSI escape code for "reset everything"
)

var (
	// LogFile is the full path to the qq.log file.
	LogFile = filepath.Join(os.TempDir(), "qq.log")

	// set logger to output to stderr on init, but it will be replaced with
	// qq.log file when Log() is called. we have to open/close qq.log inside
	// every Log() call because it's the only way ensure qq.log is properly
	// closed.
	logger = log.New(os.Stderr, "", 0)

	// for grouping log messages by time of write
	timer = time.NewTimer(0)
)

// TODO: function comment here
func Log(a ...interface{}) {
	// get info about the func calling qq.Log()
	pc, file, line, ok := runtime.Caller(1)
	if ok {
		names, err := argNames(file, line)
		if err == nil {
			a = formatArgs(names, a)
		}

		logger.SetPrefix(prefix(pc, file, line))
	}

	// line break if more than 2s since last write (groups logs together)
	if wasRunning := timer.Reset(2 * time.Second); !wasRunning {
		logger.SetPrefix("\n" + logger.Prefix())
	}

	f := openLog()
	defer f.Close()
	logger.SetOutput(f)
	logger.Println(a...)
}

// argNames finds the qq.Log() call at the given filename/line number and
// returns its arguments as a slice of strings. If the argument is a literal,
// argNames will return an empty string at the index position of that argument.
// For example, qq.Log(ip, port, 5432) would return []string{"ip", "port", ""}.
// err will be non-nil if the source text cannot be parsed.
func argNames(filename string, line int) ([]string, error) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, file, nil, 0)
	if err != nil {
		return nil, err
	}

	var names []string
	ast.Inspect(f, func(n ast.Node) bool {
		call, is := n.(*ast.CallExpr)
		if !is {
			return true // visit next node
		}

		// is a function call, but on wrong line
		if fset.Position(call.End()).Line != line {
			return true
		}

		// is a function call on correct line, but not a qq function
		if !qqCall(call) {
			return true
		}

		for _, arg := range call.Args {
			names = append(names, argName(arg))
		}
		return true
	})

	return names, nil
}

// qqCall returns true if the given function call expression is for a qq
// function, e.g. qq.Log().
func qqCall(n *ast.CallExpr) bool {
	sel, is := n.Fun.(*ast.SelectorExpr) // example of SelectorExpr: a.B()
	if !is {
		return false
	}

	ident, is := sel.X.(*ast.Ident) // sel.X is
	if !is {
		return false
	}

	return ident.Name == "qq"
}

// argName returns the source text of the given argument if it's a variable or
// an expression. If the argument is something else, like a literal, argName
// returns an empty string.
func argName(arg ast.Expr) string {
	var name string
	switch a := arg.(type) {
	case *ast.Ident:
		if a.Obj.Kind == ast.Var {
			name = a.Obj.Name
		}
	case *ast.BinaryExpr,
		*ast.CallExpr,
		*ast.IndexExpr,
		*ast.KeyValueExpr,
		*ast.ParenExpr,
		*ast.SliceExpr,
		*ast.TypeAssertExpr,
		*ast.UnaryExpr:
		name = exprString(arg)
	}
	return name
}
<commit_msg>file -> filename<commit_after>package qq

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"time"
)

type color string

const (
	bold     color = "\033[1m"
	yellow   color = "\033[33m"
	cyan     color = "\033[36m"
	endColor color = "\033[0m" // ANSI escape code for "reset everything"
)

var (
	// LogFile is the full path to the qq.log file.
	LogFile = filepath.Join(os.TempDir(), "qq.log")

	// set logger to output to stderr on init, but it will be replaced with
	// qq.log file when Log() is called. we have to open/close qq.log inside
	// every Log() call because it's the only way ensure qq.log is properly
	// closed.
	logger = log.New(os.Stderr, "", 0)

	// for grouping log messages by time of write
	timer = time.NewTimer(0)
)

// TODO: function comment here
func Log(a ...interface{}) {
	// get info about the func calling qq.Log()
	pc, file, line, ok := runtime.Caller(1)
	if ok {
		names, err := argNames(file, line)
		if err == nil {
			a = formatArgs(names, a)
		}

		logger.SetPrefix(prefix(pc, file, line))
	}

	// line break if more than 2s since last write (groups logs together)
	if wasRunning := timer.Reset(2 * time.Second); !wasRunning {
		logger.SetPrefix("\n" + logger.Prefix())
	}

	f := openLog()
	defer f.Close()
	logger.SetOutput(f)
	logger.Println(a...)
}

// argNames finds the qq.Log() call at the given filename/line number and
// returns its arguments as a slice of strings. If the argument is a literal,
// argNames will return an empty string at the index position of that argument.
// For example, qq.Log(ip, port, 5432) would return []string{"ip", "port", ""}.
// err will be non-nil if the source text cannot be parsed.
func argNames(filename string, line int) ([]string, error) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, filename, nil, 0)
	if err != nil {
		return nil, err
	}

	var names []string
	ast.Inspect(f, func(n ast.Node) bool {
		call, is := n.(*ast.CallExpr)
		if !is {
			return true // visit next node
		}

		// is a function call, but on wrong line
		if fset.Position(call.End()).Line != line {
			return true
		}

		// is a function call on correct line, but not a qq function
		if !qqCall(call) {
			return true
		}

		for _, arg := range call.Args {
			names = append(names, argName(arg))
		}
		return true
	})

	return names, nil
}

// qqCall returns true if the given function call expression is for a qq
// function, e.g. qq.Log().
func qqCall(n *ast.CallExpr) bool {
	sel, is := n.Fun.(*ast.SelectorExpr) // example of SelectorExpr: a.B()
	if !is {
		return false
	}

	ident, is := sel.X.(*ast.Ident) // sel.X is
	if !is {
		return false
	}

	return ident.Name == "qq"
}

// argName returns the source text of the given argument if it's a variable or
// an expression. If the argument is something else, like a literal, argName
// returns an empty string.
func argName(arg ast.Expr) string {
	var name string
	switch a := arg.(type) {
	case *ast.Ident:
		if a.Obj.Kind == ast.Var {
			name = a.Obj.Name
		}
	case *ast.BinaryExpr,
		*ast.CallExpr,
		*ast.IndexExpr,
		*ast.KeyValueExpr,
		*ast.ParenExpr,
		*ast.SliceExpr,
		*ast.TypeAssertExpr,
		*ast.UnaryExpr:
		name = exprString(arg)
	}
	return name
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	bt "github.com/ikool-cn/gobeanstalk-connection-pool"
	"strconv"
	"strings"
	"time"
	// "regexp"
)

var (
	qqBot *QQBot
)

// QQFace ...
type QQFace int

// QQAt ...
type QQAt struct {
	qq string
}

// QQImage ...
type QQImage struct {
	file string
}

// QQBot ...
type QQBot struct {
	ID     string
	Config *QQConfig
	Client *bt.Pool
	RecvQ  string
	SendQ  string
}

// NewQQBot ...
func NewQQBot(cfg *Config) *QQBot {
	q := &QQBot{ID: cfg.QQ.BotID, Config: cfg.QQ}
	q.Client = &bt.Pool{
		Dial: func() (*bt.Conn, error) {
			return bt.Dial(cfg.BeanstalkAddr)
		},
		MaxIdle:     10,
		MaxActive:   100,
		IdleTimeout: 60 * time.Second,
		MaxLifetime: 180 * time.Second,
		Wait:        true,
	}

	q.RecvQ = q.ID + "(o)"
	q.SendQ = q.ID + "(i)"
	return q
}

// String generate code string for qq face
func (q QQFace) String() string {
	return fmt.Sprintf("[CQ:face,id=%d]", q)
}

// String generate code string for qq image
func (q QQImage) String() string {
	return fmt.Sprintf("[CQ:image,file=%s]", q.file)
}

// String generate code string for qq qt msg
func (q QQAt) String() string {
	return fmt.Sprintf("[CQ:at,qq=%s]", q.qq)
}

func (q *QQBot) send(msg []byte) {
	// wait longer with more errors
	var (
		conn *bt.Conn
		err  error
	)
	for i := 1; ; i++ {
		conn, err = q.Client.Get()
		if err == nil {
			break
		}
		time.Sleep(time.Duration(i) * time.Second)
		if i > q.Config.SendMaxRetry {
			logger.Error("Send failed:", string(msg))
			return
		}
	}
	conn.Use(q.SendQ)
	_, err = conn.Put(msg, 1, 0, time.Minute)
	if err != nil {
		logger.Error(err)
		return
	}

	q.Client.Release(conn, false)
	return
}

// SendGroupMsg ...
func (q *QQBot) SendGroupMsg(msg string) {
	logger.Infof(msg)
	fullMsg, err := formMsg("sendGroupMsg", q.Config.GroupID, msg)
	if err != nil {
		logger.Error(err)
		return
	}
	go q.send(fullMsg)
}

// SendPics ...
func (q *QQBot) SendPics(fn func(string), url string) {
	fileName, err := downloadFile(url, q.Config.ImgPath)
	if err != nil {
		return
	}
	fn(QQImage{fileName}.String())
}

// SendPrivateMsg ...
func (q *QQBot) SendPrivateMsg(qq string, msg string) {
	logger.Infof("TO:" + qq + ":" + msg)
	fullMsg, err := formMsg("sendPrivateMsg", qq, msg)
	if err != nil {
		logger.Error(err)
	} else {
		go q.send(fullMsg)
	}
}

//SendSelfMsg ...
func (q *QQBot) SendSelfMsg(msg string) {
	q.SendPrivateMsg(q.Config.SelfID, msg)
}

// CheckMention ...
func (q *QQBot) CheckMention(msg string) bool {
	for _, s := range q.Config.SelfNames {
		if strings.Contains(msg, s) {
			return true
		}
	}
	return false
}

// NoticeMention ...
func (q *QQBot) NoticeMention(msg string, group string) {
	if !q.CheckMention(msg) {
		return
	}
	key := q.Config.SelfID + "_mention"
	exists, err := redisClient.Expire(key, 10*time.Minute).Result()
	if err != nil {
		logger.Error(err)
		return
	}
	if exists {
		logger.Notice("Called in last 10min")
	} else {
		_, err := redisClient.Set(key, 0, 10*time.Minute).Result()
		if err != nil {
			logger.Error(err)
			return
		}
		q.SendGroupMsg("呀呀呀，召唤一号机" + QQAt{q.Config.SelfID}.String())
	}
}

// CheckRepeat ...
func (q *QQBot) CheckRepeat(msg string, group string) {
	key := group + "_last"
	defer redisClient.LPush(key, msg)
	lastMsgs, err := redisClient.LRange(key, 0, 3).Result()
	if err != nil {
		logger.Error(err)
		return
	}
	i := 0
	for _, s := range lastMsgs {
		if s == msg {
			i++
		}
	}
	if i > 1 {
		redisClient.Del(key)
		logger.Infof("Repeat: %s", msg)
		q.SendGroupMsg(msg)
	}
}

// Poll reserve msg from beanstalkd
func (q *QQBot) Poll(messages chan map[string]string) {
	for i := 1; ; i++ {
		conn, err := q.Client.Get()
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
			continue
		}
		conn.Watch(q.RecvQ)
		job, err := conn.Reserve()
		if err != nil {
			logger.Warning(err)
			time.Sleep(time.Duration(i) * time.Second)
			continue
		}
		i = 1
		body := strings.Split(string(job.Body), " ")
		ret := make(map[string]string)
		switch body[0] {
		case "eventPrivateMsg":
			ret["event"] = "PrivateMsg"
			ret["subtype"] = body[1]
			ret["time"] = body[2]
			ret["qq"] = body[3]
			ret["msg"], err = decodeMsg(body[4])
			if err != nil {
				logger.Error(err)
				time.Sleep(3 * time.Second)
				continue
			}
		case "eventGroupMsg":
			ret["event"] = "GroupMsg"
			ret["subtype"] = body[1]
			ret["time"] = body[2]
			ret["group"] = body[3]
			ret["qq"] = body[4]
			ret["anonymous"] = body[5]
			ret["msg"], err = decodeMsg(body[6])
			if err != nil {
				logger.Error(err)
				time.Sleep(3 * time.Second)
				continue
			}
		default:
			err = conn.Bury(job.ID, 0)
			if err != nil {
				logger.Error(err)
				time.Sleep(3 * time.Second)
			}
			continue
		}
		messages <- ret
		err = conn.Delete(job.ID)
		if err != nil {
			logger.Error(err)
			time.Sleep(3 * time.Second)
		}
		q.Client.Release(conn, false)
	}
}

func formMsg(t string, to string, msg string) ([]byte, error) {
	gb18030Msg, err := Utf8ToGb18030([]byte(msg))
	if err != nil {
		return nil, err
	}
	base64Msg := base64.StdEncoding.EncodeToString(gb18030Msg)
	return bytes.Join([][]byte{[]byte(t), []byte(to), []byte(base64Msg)}, []byte(" ")), nil
}

func decodeMsg(msg string) (string, error) {
	gb18030Msg, err := base64.StdEncoding.DecodeString(msg)
	if err != nil {
		return "", err
	}
	utf8Msg, err := Gb18030ToUtf8(gb18030Msg)
	if err != nil {
		return "", err
	}
	return string(utf8Msg), nil
}

func qqWatch(messages chan map[string]string) {
	groupIgnore := make(map[string]struct{})
	for _, q := range qqBot.Config.QQGroupIgnore {
		groupIgnore[q] = struct{}{}
	}
	privateIgnore := make(map[string]struct{})
	for _, q := range qqBot.Config.QQPrivateIgnore {
		privateIgnore[q] = struct{}{}
	}

	for msg := range messages {
		switch msg["event"] {
		case "PrivateMsg":
			if _, ok := privateIgnore[msg["qq"]]; ok {
				// logger.Debugf("Ignore [%s]:{%s}", msg["qq"], strconv.Qoute(msg["msg"]))
				continue
			}
			logger.Infof("[%s]:{%s}", msg["qq"], strconv.Qoute(msg["msg"]))
		case "GroupMsg":
			if _, ok := groupIgnore[msg["qq"]]; ok {
				logger.Debugf("Ignore (%s)[%s]:{%s}", msg["group"], msg["qq"], strconv.Qoute(msg["msg"]))
				continue
			}
			go qqBot.NoticeMention(msg["msg"], msg["group"])
			go qqBot.CheckRepeat(msg["msg"], msg["group"])
			logger.Infof("(%s)[%s]:{%s}", msg["group"], msg["qq"], strconv.Qoute(msg["msg"]))
		default:
			logger.Info(msg)
		}
	}
}
<commit_msg>fix typo<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	bt "github.com/ikool-cn/gobeanstalk-connection-pool"
	"strconv"
	"strings"
	"time"
	// "regexp"
)

var (
	qqBot *QQBot
)

// QQFace ...
type QQFace int

// QQAt ...
type QQAt struct {
	qq string
}

// QQImage ...
type QQImage struct {
	file string
}

// QQBot ...
type QQBot struct {
	ID     string
	Config *QQConfig
	Client *bt.Pool
	RecvQ  string
	SendQ  string
}

// NewQQBot ...
func NewQQBot(cfg *Config) *QQBot {
	q := &QQBot{ID: cfg.QQ.BotID, Config: cfg.QQ}
	q.Client = &bt.Pool{
		Dial: func() (*bt.Conn, error) {
			return bt.Dial(cfg.BeanstalkAddr)
		},
		MaxIdle:     10,
		MaxActive:   100,
		IdleTimeout: 60 * time.Second,
		MaxLifetime: 180 * time.Second,
		Wait:        true,
	}

	q.RecvQ = q.ID + "(o)"
	q.SendQ = q.ID + "(i)"
	return q
}

// String generate code string for qq face
func (q QQFace) String() string {
	return fmt.Sprintf("[CQ:face,id=%d]", q)
}

// String generate code string for qq image
func (q QQImage) String() string {
	return fmt.Sprintf("[CQ:image,file=%s]", q.file)
}

// String generate code string for qq qt msg
func (q QQAt) String() string {
	return fmt.Sprintf("[CQ:at,qq=%s]", q.qq)
}

func (q *QQBot) send(msg []byte) {
	// wait longer with more errors
	var (
		conn *bt.Conn
		err  error
	)
	for i := 1; ; i++ {
		conn, err = q.Client.Get()
		if err == nil {
			break
		}
		time.Sleep(time.Duration(i) * time.Second)
		if i > q.Config.SendMaxRetry {
			logger.Error("Send failed:", string(msg))
			return
		}
	}
	conn.Use(q.SendQ)
	_, err = conn.Put(msg, 1, 0, time.Minute)
	if err != nil {
		logger.Error(err)
		return
	}

	q.Client.Release(conn, false)
	return
}

// SendGroupMsg ...
func (q *QQBot) SendGroupMsg(msg string) {
	logger.Infof(msg)
	fullMsg, err := formMsg("sendGroupMsg", q.Config.GroupID, msg)
	if err != nil {
		logger.Error(err)
		return
	}
	go q.send(fullMsg)
}

// SendPics ...
func (q *QQBot) SendPics(fn func(string), url string) {
	fileName, err := downloadFile(url, q.Config.ImgPath)
	if err != nil {
		return
	}
	fn(QQImage{fileName}.String())
}

// SendPrivateMsg ...
func (q *QQBot) SendPrivateMsg(qq string, msg string) {
	logger.Infof("TO:" + qq + ":" + msg)
	fullMsg, err := formMsg("sendPrivateMsg", qq, msg)
	if err != nil {
		logger.Error(err)
	} else {
		go q.send(fullMsg)
	}
}

//SendSelfMsg ...
func (q *QQBot) SendSelfMsg(msg string) {
	q.SendPrivateMsg(q.Config.SelfID, msg)
}

// CheckMention ...
func (q *QQBot) CheckMention(msg string) bool {
	for _, s := range q.Config.SelfNames {
		if strings.Contains(msg, s) {
			return true
		}
	}
	return false
}

// NoticeMention ...
func (q *QQBot) NoticeMention(msg string, group string) {
	if !q.CheckMention(msg) {
		return
	}
	key := q.Config.SelfID + "_mention"
	exists, err := redisClient.Expire(key, 10*time.Minute).Result()
	if err != nil {
		logger.Error(err)
		return
	}
	if exists {
		logger.Notice("Called in last 10min")
	} else {
		_, err := redisClient.Set(key, 0, 10*time.Minute).Result()
		if err != nil {
			logger.Error(err)
			return
		}
		q.SendGroupMsg("呀呀呀，召唤一号机" + QQAt{q.Config.SelfID}.String())
	}
}

// CheckRepeat ...
func (q *QQBot) CheckRepeat(msg string, group string) {
	key := group + "_last"
	defer redisClient.LPush(key, msg)
	lastMsgs, err := redisClient.LRange(key, 0, 3).Result()
	if err != nil {
		logger.Error(err)
		return
	}
	i := 0
	for _, s := range lastMsgs {
		if s == msg {
			i++
		}
	}
	if i > 1 {
		redisClient.Del(key)
		logger.Infof("Repeat: %s", msg)
		q.SendGroupMsg(msg)
	}
}

// Poll reserve msg from beanstalkd
func (q *QQBot) Poll(messages chan map[string]string) {
	for i := 1; ; i++ {
		conn, err := q.Client.Get()
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
			continue
		}
		conn.Watch(q.RecvQ)
		job, err := conn.Reserve()
		if err != nil {
			logger.Warning(err)
			time.Sleep(time.Duration(i) * time.Second)
			continue
		}
		i = 1
		body := strings.Split(string(job.Body), " ")
		ret := make(map[string]string)
		switch body[0] {
		case "eventPrivateMsg":
			ret["event"] = "PrivateMsg"
			ret["subtype"] = body[1]
			ret["time"] = body[2]
			ret["qq"] = body[3]
			ret["msg"], err = decodeMsg(body[4])
			if err != nil {
				logger.Error(err)
				time.Sleep(3 * time.Second)
				continue
			}
		case "eventGroupMsg":
			ret["event"] = "GroupMsg"
			ret["subtype"] = body[1]
			ret["time"] = body[2]
			ret["group"] = body[3]
			ret["qq"] = body[4]
			ret["anonymous"] = body[5]
			ret["msg"], err = decodeMsg(body[6])
			if err != nil {
				logger.Error(err)
				time.Sleep(3 * time.Second)
				continue
			}
		default:
			err = conn.Bury(job.ID, 0)
			if err != nil {
				logger.Error(err)
				time.Sleep(3 * time.Second)
			}
			continue
		}
		messages <- ret
		err = conn.Delete(job.ID)
		if err != nil {
			logger.Error(err)
			time.Sleep(3 * time.Second)
		}
		q.Client.Release(conn, false)
	}
}

func formMsg(t string, to string, msg string) ([]byte, error) {
	gb18030Msg, err := Utf8ToGb18030([]byte(msg))
	if err != nil {
		return nil, err
	}
	base64Msg := base64.StdEncoding.EncodeToString(gb18030Msg)
	return bytes.Join([][]byte{[]byte(t), []byte(to), []byte(base64Msg)}, []byte(" ")), nil
}

func decodeMsg(msg string) (string, error) {
	gb18030Msg, err := base64.StdEncoding.DecodeString(msg)
	if err != nil {
		return "", err
	}
	utf8Msg, err := Gb18030ToUtf8(gb18030Msg)
	if err != nil {
		return "", err
	}
	return string(utf8Msg), nil
}

func qqWatch(messages chan map[string]string) {
	groupIgnore := make(map[string]struct{})
	for _, q := range qqBot.Config.QQGroupIgnore {
		groupIgnore[q] = struct{}{}
	}
	privateIgnore := make(map[string]struct{})
	for _, q := range qqBot.Config.QQPrivateIgnore {
		privateIgnore[q] = struct{}{}
	}

	for msg := range messages {
		switch msg["event"] {
		case "PrivateMsg":
			if _, ok := privateIgnore[msg["qq"]]; ok {
				// logger.Debugf("Ignore [%s]:{%s}", msg["qq"], strconv.Quote(msg["msg"]))
				continue
			}
			logger.Infof("[%s]:{%s}", msg["qq"], strconv.Quote(msg["msg"]))
		case "GroupMsg":
			if _, ok := groupIgnore[msg["qq"]]; ok {
				logger.Debugf("Ignore (%s)[%s]:{%s}", msg["group"], msg["qq"], strconv.Quote(msg["msg"]))
				continue
			}
			go qqBot.NoticeMention(msg["msg"], msg["group"])
			go qqBot.CheckRepeat(msg["msg"], msg["group"])
			logger.Infof("(%s)[%s]:{%s}", msg["group"], msg["qq"], strconv.Quote(msg["msg"]))
		default:
			logger.Info(msg)
		}
	}
}
<|endoftext|>