<commit_before>package local

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/filters"
	"github.com/containerd/containerd/log"
	digest "github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
)

var (
	bufPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, 1<<20)
		},
	}
)

// Store is digest-keyed store for content. All data written into the store is
// stored under a verifiable digest.
//
// Store can generally support multi-reader, single-writer ingest of data,
// including resumable ingest.
type store struct {
	root string
}

func NewStore(root string) (content.Store, error) {
	if err := os.MkdirAll(filepath.Join(root, "ingest"), 0777); err != nil && !os.IsExist(err) {
		return nil, err
	}

	return &store{
		root: root,
	}, nil
}

func (s *store) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
	p := s.blobPath(dgst)
	fi, err := os.Stat(p)
	if err != nil {
		if os.IsNotExist(err) {
			err = errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
		}

		return content.Info{}, err
	}

	return s.info(dgst, fi), nil
}

func (s *store) info(dgst digest.Digest, fi os.FileInfo) content.Info {
	return content.Info{
		Digest:    dgst,
		Size:      fi.Size(),
		CreatedAt: fi.ModTime(),
		UpdatedAt: fi.ModTime(),
	}
}

// Reader returns an io.ReadCloser for the blob.
func (s *store) Reader(ctx context.Context, dgst digest.Digest) (io.ReadCloser, error) {
	fp, err := os.Open(s.blobPath(dgst))
	if err != nil {
		if os.IsNotExist(err) {
			err = errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
		}
		return nil, err
	}

	return fp, nil
}

// ReaderAt returns an io.ReaderAt for the blob.
func (s *store) ReaderAt(ctx context.Context, dgst digest.Digest) (io.ReaderAt, error) {
	return readerAt{f: s.blobPath(dgst)}, nil
}

// Delete removes a blob by its digest.
//
// While this is safe to do concurrently, safe exist-removal logic must hold
// some global lock on the store.
func (cs *store) Delete(ctx context.Context, dgst digest.Digest) error {
	if err := os.RemoveAll(cs.blobPath(dgst)); err != nil {
		if !os.IsNotExist(err) {
			return err
		}

		return errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
	}

	return nil
}

func (cs *store) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
	// TODO: Support persisting and updating mutable content data
	return content.Info{}, errors.Wrapf(errdefs.ErrFailedPrecondition, "update not supported on immutable content store")
}

func (cs *store) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
	// TODO: Support filters
	root := filepath.Join(cs.root, "blobs")
	var alg digest.Algorithm
	return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !fi.IsDir() && !alg.Available() {
			return nil
		}

		// TODO(stevvooe): There are few more cases with subdirs that should be
		// handled in case the layout gets corrupted. This isn't strict enough
		// an may spew bad data.

		if path == root {
			return nil
		}
		if filepath.Dir(path) == root {
			alg = digest.Algorithm(filepath.Base(path))

			if !alg.Available() {
				alg = ""
				return filepath.SkipDir
			}

			// descending into a hash directory
			return nil
		}

		dgst := digest.NewDigestFromHex(alg.String(), filepath.Base(path))
		if err := dgst.Validate(); err != nil {
			// log error but don't report
			log.L.WithError(err).WithField("path", path).Error("invalid digest for blob path")
			// if we see this, it could mean some sort of corruption of the
			// store or extra paths not expected previously.
		}

		return fn(cs.info(dgst, fi))
	})
}

func (s *store) Status(ctx context.Context, ref string) (content.Status, error) {
	return s.status(s.ingestRoot(ref))
}

func (s *store) ListStatuses(ctx context.Context, fs ...string) ([]content.Status, error) {
	fp, err := os.Open(filepath.Join(s.root, "ingest"))
	if err != nil {
		return nil, err
	}

	defer fp.Close()

	fis, err := fp.Readdir(-1)
	if err != nil {
		return nil, err
	}

	filter, err := filters.ParseAll(fs...)
	if err != nil {
		return nil, err
	}

	var active []content.Status
	for _, fi := range fis {
		p := filepath.Join(s.root, "ingest", fi.Name())
		stat, err := s.status(p)
		if err != nil {
			if !os.IsNotExist(err) {
				return nil, err
			}

			// TODO(stevvooe): This is a common error if uploads are being
			// completed while making this listing. Need to consider taking a
			// lock on the whole store to coordinate this aspect.
			//
			// Another option is to cleanup downloads asynchronously and
			// coordinate this method with the cleanup process.
			//
			// For now, we just skip them, as they really don't exist.
			continue
		}

		if filter.Match(adaptStatus(stat)) {
			active = append(active, stat)
		}
	}

	return active, nil
}

// status works like stat above except uses the path to the ingest.
func (s *store) status(ingestPath string) (content.Status, error) {
	dp := filepath.Join(ingestPath, "data")
	fi, err := os.Stat(dp)
	if err != nil {
		return content.Status{}, err
	}

	ref, err := readFileString(filepath.Join(ingestPath, "ref"))
	if err != nil {
		return content.Status{}, err
	}

	return content.Status{
		Ref:       ref,
		Offset:    fi.Size(),
		Total:     s.total(ingestPath),
		UpdatedAt: fi.ModTime(),
		StartedAt: getStartTime(fi),
	}, nil
}

func adaptStatus(status content.Status) filters.Adaptor {
	return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
		if len(fieldpath) == 0 {
			return "", false
		}
		switch fieldpath[0] {
		case "ref":
			return status.Ref, true
		}

		return "", false
	})
}

// total attempts to resolve the total expected size for the write.
func (s *store) total(ingestPath string) int64 {
	totalS, err := readFileString(filepath.Join(ingestPath, "total"))
	if err != nil {
		return 0
	}

	total, err := strconv.ParseInt(totalS, 10, 64)
	if err != nil {
		// represents a corrupted file, should probably remove.
		return 0
	}

	return total
}

// Writer begins or resumes the active writer identified by ref. If the writer
// is already in use, an error is returned. Only one writer may be in use per
// ref at a time.
//
// The argument `ref` is used to uniquely identify a long-lived writer transaction.
func (s *store) Writer(ctx context.Context, ref string, total int64, expected digest.Digest) (content.Writer, error) {
	// TODO(stevvooe): Need to actually store expected here. We have
	// code in the service that shouldn't be dealing with this.
	if expected != "" {
		p := s.blobPath(expected)
		if _, err := os.Stat(p); err == nil {
			return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected)
		}
	}

	path, refp, data := s.ingestPaths(ref)

	if err := tryLock(ref); err != nil {
		return nil, errors.Wrapf(err, "locking ref %v failed", ref)
	}

	var (
		digester  = digest.Canonical.Digester()
		offset    int64
		startedAt time.Time
		updatedAt time.Time
	)

	// ensure that the ingest path has been created.
	if err := os.Mkdir(path, 0755); err != nil {
		if !os.IsExist(err) {
			return nil, err
		}

		status, err := s.status(path)
		if err != nil {
			return nil, errors.Wrap(err, "failed reading status of resume write")
		}

		if ref != status.Ref {
			// NOTE(stevvooe): This is fairly catastrophic. Either we have some
			// layout corruption or a hash collision for the ref key.
			return nil, errors.Wrapf(err, "ref key does not match: %v != %v", ref, status.Ref)
		}

		if total > 0 && status.Total > 0 && total != status.Total {
			return nil, errors.Errorf("provided total differs from status: %v != %v", total, status.Total)
		}

		// slow slow slow!!, send to goroutine or use resumable hashes
		fp, err := os.Open(data)
		if err != nil {
			return nil, err
		}
		defer fp.Close()

		p := bufPool.Get().([]byte)
		defer bufPool.Put(p)

		offset, err = io.CopyBuffer(digester.Hash(), fp, p)
		if err != nil {
			return nil, err
		}

		updatedAt = status.UpdatedAt
		startedAt = status.StartedAt
		total = status.Total
	} else {
		// the ingest is new, we need to setup the target location.
		// write the ref to a file for later use
		if err := ioutil.WriteFile(refp, []byte(ref), 0666); err != nil {
			return nil, err
		}

		if total > 0 {
			if err := ioutil.WriteFile(filepath.Join(path, "total"), []byte(fmt.Sprint(total)), 0666); err != nil {
				return nil, err
			}
		}

		startedAt = time.Now()
		updatedAt = startedAt
	}

	fp, err := os.OpenFile(data, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		return nil, errors.Wrap(err, "failed to open data file")
	}

	return &writer{
		s:         s,
		fp:        fp,
		ref:       ref,
		path:      path,
		offset:    offset,
		total:     total,
		digester:  digester,
		startedAt: startedAt,
		updatedAt: updatedAt,
	}, nil
}

// Abort an active transaction keyed by ref. If the ingest is active, it will
// be cancelled. Any resources associated with the ingest will be cleaned.
func (s *store) Abort(ctx context.Context, ref string) error {
	root := s.ingestRoot(ref)
	if err := os.RemoveAll(root); err != nil {
		if os.IsNotExist(err) {
			return errors.Wrapf(errdefs.ErrNotFound, "ingest ref %q", ref)
		}

		return err
	}

	return nil
}

func (cs *store) blobPath(dgst digest.Digest) string {
	return filepath.Join(cs.root, "blobs", dgst.Algorithm().String(), dgst.Hex())
}

func (s *store) ingestRoot(ref string) string {
	dgst := digest.FromString(ref)
	return filepath.Join(s.root, "ingest", dgst.Hex())
}

// ingestPaths are returned. The paths are the following:
//
// - root: entire ingest directory
// - ref: name of the starting ref, must be unique
// - data: file where data is written
//
func (s *store) ingestPaths(ref string) (string, string, string) {
	var (
		fp = s.ingestRoot(ref)
		rp = filepath.Join(fp, "ref")
		dp = filepath.Join(fp, "data")
	)

	return fp, rp, dp
}

func readFileString(path string) (string, error) {
	p, err := ioutil.ReadFile(path)
	return string(p), err
}
<commit_msg>Convert file not exists to status not found<commit_after>package local

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/filters"
	"github.com/containerd/containerd/log"
	digest "github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
)

var (
	bufPool = sync.Pool{
		New: func() interface{} {
			return make([]byte, 1<<20)
		},
	}
)

// Store is digest-keyed store for content. All data written into the store is
// stored under a verifiable digest.
//
// Store can generally support multi-reader, single-writer ingest of data,
// including resumable ingest.
type store struct {
	root string
}

func NewStore(root string) (content.Store, error) {
	if err := os.MkdirAll(filepath.Join(root, "ingest"), 0777); err != nil && !os.IsExist(err) {
		return nil, err
	}

	return &store{
		root: root,
	}, nil
}

func (s *store) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
	p := s.blobPath(dgst)
	fi, err := os.Stat(p)
	if err != nil {
		if os.IsNotExist(err) {
			err = errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
		}

		return content.Info{}, err
	}

	return s.info(dgst, fi), nil
}

func (s *store) info(dgst digest.Digest, fi os.FileInfo) content.Info {
	return content.Info{
		Digest:    dgst,
		Size:      fi.Size(),
		CreatedAt: fi.ModTime(),
		UpdatedAt: fi.ModTime(),
	}
}

// Reader returns an io.ReadCloser for the blob.
func (s *store) Reader(ctx context.Context, dgst digest.Digest) (io.ReadCloser, error) {
	fp, err := os.Open(s.blobPath(dgst))
	if err != nil {
		if os.IsNotExist(err) {
			err = errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
		}
		return nil, err
	}

	return fp, nil
}

// ReaderAt returns an io.ReaderAt for the blob.
func (s *store) ReaderAt(ctx context.Context, dgst digest.Digest) (io.ReaderAt, error) {
	return readerAt{f: s.blobPath(dgst)}, nil
}

// Delete removes a blob by its digest.
//
// While this is safe to do concurrently, safe exist-removal logic must hold
// some global lock on the store.
func (cs *store) Delete(ctx context.Context, dgst digest.Digest) error {
	if err := os.RemoveAll(cs.blobPath(dgst)); err != nil {
		if !os.IsNotExist(err) {
			return err
		}

		return errors.Wrapf(errdefs.ErrNotFound, "content %v", dgst)
	}

	return nil
}

func (cs *store) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
	// TODO: Support persisting and updating mutable content data
	return content.Info{}, errors.Wrapf(errdefs.ErrFailedPrecondition, "update not supported on immutable content store")
}

func (cs *store) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
	// TODO: Support filters
	root := filepath.Join(cs.root, "blobs")
	var alg digest.Algorithm
	return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !fi.IsDir() && !alg.Available() {
			return nil
		}

		// TODO(stevvooe): There are few more cases with subdirs that should be
		// handled in case the layout gets corrupted. This isn't strict enough
		// an may spew bad data.

		if path == root {
			return nil
		}
		if filepath.Dir(path) == root {
			alg = digest.Algorithm(filepath.Base(path))

			if !alg.Available() {
				alg = ""
				return filepath.SkipDir
			}

			// descending into a hash directory
			return nil
		}

		dgst := digest.NewDigestFromHex(alg.String(), filepath.Base(path))
		if err := dgst.Validate(); err != nil {
			// log error but don't report
			log.L.WithError(err).WithField("path", path).Error("invalid digest for blob path")
			// if we see this, it could mean some sort of corruption of the
			// store or extra paths not expected previously.
		}

		return fn(cs.info(dgst, fi))
	})
}

func (s *store) Status(ctx context.Context, ref string) (content.Status, error) {
	return s.status(s.ingestRoot(ref))
}

func (s *store) ListStatuses(ctx context.Context, fs ...string) ([]content.Status, error) {
	fp, err := os.Open(filepath.Join(s.root, "ingest"))
	if err != nil {
		return nil, err
	}

	defer fp.Close()

	fis, err := fp.Readdir(-1)
	if err != nil {
		return nil, err
	}

	filter, err := filters.ParseAll(fs...)
	if err != nil {
		return nil, err
	}

	var active []content.Status
	for _, fi := range fis {
		p := filepath.Join(s.root, "ingest", fi.Name())
		stat, err := s.status(p)
		if err != nil {
			if !os.IsNotExist(err) {
				return nil, err
			}

			// TODO(stevvooe): This is a common error if uploads are being
			// completed while making this listing. Need to consider taking a
			// lock on the whole store to coordinate this aspect.
			//
			// Another option is to cleanup downloads asynchronously and
			// coordinate this method with the cleanup process.
			//
			// For now, we just skip them, as they really don't exist.
			continue
		}

		if filter.Match(adaptStatus(stat)) {
			active = append(active, stat)
		}
	}

	return active, nil
}

// status works like stat above except uses the path to the ingest.
func (s *store) status(ingestPath string) (content.Status, error) {
	dp := filepath.Join(ingestPath, "data")
	fi, err := os.Stat(dp)
	if err != nil {
		if os.IsNotExist(err) {
			err = errors.Wrap(errdefs.ErrNotFound, err.Error())
		}
		return content.Status{}, err
	}

	ref, err := readFileString(filepath.Join(ingestPath, "ref"))
	if err != nil {
		if os.IsNotExist(err) {
			err = errors.Wrap(errdefs.ErrNotFound, err.Error())
		}
		return content.Status{}, err
	}

	return content.Status{
		Ref:       ref,
		Offset:    fi.Size(),
		Total:     s.total(ingestPath),
		UpdatedAt: fi.ModTime(),
		StartedAt: getStartTime(fi),
	}, nil
}

func adaptStatus(status content.Status) filters.Adaptor {
	return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
		if len(fieldpath) == 0 {
			return "", false
		}
		switch fieldpath[0] {
		case "ref":
			return status.Ref, true
		}

		return "", false
	})
}

// total attempts to resolve the total expected size for the write.
func (s *store) total(ingestPath string) int64 {
	totalS, err := readFileString(filepath.Join(ingestPath, "total"))
	if err != nil {
		return 0
	}

	total, err := strconv.ParseInt(totalS, 10, 64)
	if err != nil {
		// represents a corrupted file, should probably remove.
		return 0
	}

	return total
}

// Writer begins or resumes the active writer identified by ref. If the writer
// is already in use, an error is returned. Only one writer may be in use per
// ref at a time.
//
// The argument `ref` is used to uniquely identify a long-lived writer transaction.
func (s *store) Writer(ctx context.Context, ref string, total int64, expected digest.Digest) (content.Writer, error) {
	// TODO(stevvooe): Need to actually store expected here. We have
	// code in the service that shouldn't be dealing with this.
	if expected != "" {
		p := s.blobPath(expected)
		if _, err := os.Stat(p); err == nil {
			return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected)
		}
	}

	path, refp, data := s.ingestPaths(ref)

	if err := tryLock(ref); err != nil {
		return nil, errors.Wrapf(err, "locking ref %v failed", ref)
	}

	var (
		digester  = digest.Canonical.Digester()
		offset    int64
		startedAt time.Time
		updatedAt time.Time
	)

	// ensure that the ingest path has been created.
	if err := os.Mkdir(path, 0755); err != nil {
		if !os.IsExist(err) {
			return nil, err
		}

		status, err := s.status(path)
		if err != nil {
			return nil, errors.Wrap(err, "failed reading status of resume write")
		}

		if ref != status.Ref {
			// NOTE(stevvooe): This is fairly catastrophic. Either we have some
			// layout corruption or a hash collision for the ref key.
			return nil, errors.Wrapf(err, "ref key does not match: %v != %v", ref, status.Ref)
		}

		if total > 0 && status.Total > 0 && total != status.Total {
			return nil, errors.Errorf("provided total differs from status: %v != %v", total, status.Total)
		}

		// slow slow slow!!, send to goroutine or use resumable hashes
		fp, err := os.Open(data)
		if err != nil {
			return nil, err
		}
		defer fp.Close()

		p := bufPool.Get().([]byte)
		defer bufPool.Put(p)

		offset, err = io.CopyBuffer(digester.Hash(), fp, p)
		if err != nil {
			return nil, err
		}

		updatedAt = status.UpdatedAt
		startedAt = status.StartedAt
		total = status.Total
	} else {
		// the ingest is new, we need to setup the target location.
		// write the ref to a file for later use
		if err := ioutil.WriteFile(refp, []byte(ref), 0666); err != nil {
			return nil, err
		}

		if total > 0 {
			if err := ioutil.WriteFile(filepath.Join(path, "total"), []byte(fmt.Sprint(total)), 0666); err != nil {
				return nil, err
			}
		}

		startedAt = time.Now()
		updatedAt = startedAt
	}

	fp, err := os.OpenFile(data, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		return nil, errors.Wrap(err, "failed to open data file")
	}

	return &writer{
		s:         s,
		fp:        fp,
		ref:       ref,
		path:      path,
		offset:    offset,
		total:     total,
		digester:  digester,
		startedAt: startedAt,
		updatedAt: updatedAt,
	}, nil
}

// Abort an active transaction keyed by ref. If the ingest is active, it will
// be cancelled. Any resources associated with the ingest will be cleaned.
func (s *store) Abort(ctx context.Context, ref string) error {
	root := s.ingestRoot(ref)
	if err := os.RemoveAll(root); err != nil {
		if os.IsNotExist(err) {
			return errors.Wrapf(errdefs.ErrNotFound, "ingest ref %q", ref)
		}

		return err
	}

	return nil
}

func (cs *store) blobPath(dgst digest.Digest) string {
	return filepath.Join(cs.root, "blobs", dgst.Algorithm().String(), dgst.Hex())
}

func (s *store) ingestRoot(ref string) string {
	dgst := digest.FromString(ref)
	return filepath.Join(s.root, "ingest", dgst.Hex())
}

// ingestPaths are returned. The paths are the following:
//
// - root: entire ingest directory
// - ref: name of the starting ref, must be unique
// - data: file where data is written
//
func (s *store) ingestPaths(ref string) (string, string, string) {
	var (
		fp = s.ingestRoot(ref)
		rp = filepath.Join(fp, "ref")
		dp = filepath.Join(fp, "data")
	)

	return fp, rp, dp
}

func readFileString(path string) (string, error) {
	p, err := ioutil.ReadFile(path)
	return string(p), err
}
<|endoftext|>


<commit_before>// test program to parse dwml xml format from NOAA API

package main

import (
	"fmt"
	"os"
	"time"
	"io/ioutil"
	"net/http"
	"reflect"
	"encoding/xml"
	"flag"
	"runtime/pprof"
)

var inputFile = flag.String("infile", "1.txt", "Input file path")

func dump_httpresp(resp *http.Response) {
	fmt.Println("resp: ", resp)

	st := reflect.TypeOf(resp)
	fmt.Println(st)

	val := reflect.ValueOf(resp).Elem()

	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)
		typeName := valueField.Type().Name()

		fmt.Printf("Field Name: %s(%s),\t\t\t Field Value: %v\n", typeField.Name, typeName, valueField.Interface())
	}

	fmt.Println("=== headers ===")
	for k, v := range resp.Header {
		fmt.Printf("%20s = %20s\n", k, v)
	}
}

func dump_value(val reflect.Value) {
	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)
		typeName := valueField.Type().Name()
		fmt.Printf("%s(%s): %v\n", typeField.Name, typeName, valueField.Interface())
	}
}

// ===========================================================================
// NOAA API dwml structures (top to bottom order)

// root element

type Dwml struct {
	XMLName       xml.Name `xml:"dwml"`
	Header        Header   `xml:"head"`
	Data          Data     `xml:"data"`
}

// 3 second level elements

type Header struct {
	XMLName         xml.Name `xml:"head"`
	Product         Product  `xml:"product"`
}

type Data struct {
	XMLName         xml.Name     `xml:"data"`
	TimeLayouts     []TimeLayout `xml:"time-layout"`
	Parameters      Parameters   `xml:"parameters"`
}

// head element children

type Product struct {
	XMLName  xml.Name `xml:"product"`
	Src      string   `xml:"srsName,attr"`
	Name     string   `xml:"concise-name,attr"`
	Mode     string   `xml:"operational-mode,attr"`
}

// data element children

type TimeLayout struct {
	XMLName       xml.Name `xml:"time-layout"`
	Coordinate    string   `xml:"time-coordinate,attr"`
	Summarization string   `xml:"summarization,attr"`
	Key           string   `xml:"layout-key"`
	StartTime     []string `xml:"start-valid-time"`
	EndTime       []string `xml:"end-valid-time"`
}

type Parameters struct {
	XMLName       xml.Name    `xml:"parameters"`
	Temperatures  []Valueset  `xml:"temperature"`
	Precipitation Valueset    `xml:"precipitation"`
}

type Valueset struct {
	Type          string   `xml:"type,attr"`
	Units         string   `xml:"units,attr"`
	TimeLayout    string   `xml:"time-layout,attr"`
	Name          string   `xml:"name"`
	Values        []string `xml:"value"`
}

// ===========================================================================

func decode_dwml(xmlFile *os.File) {
	decoder := xml.NewDecoder(xmlFile)

	p := &Dwml{}
	if err := decoder.Decode(p); err != nil {
		fmt.Println("ERROR: %v", err)
		return
	}

	fmt.Println("Time layouts:");

	for idx, v := range p.Data.TimeLayouts {
		fmt.Println("    ", idx, v.Key, len(v.StartTime))
	}

	fmt.Println("Temperatures:");

	for idx, v := range p.Data.Parameters.Temperatures {
		fmt.Println("    ", idx, v.Name, v.TimeLayout, len(v.Values))
	}

	pr := p.Data.Parameters.Precipitation
	fmt.Println("Precipitation:", pr.TimeLayout, len(pr.Values));
}

func file_cached(fname *string) bool {

	fi, err := os.Stat(*fname)

	if err != nil {
		fmt.Println("ERROR: %v", err)
		return false
	}

	if fi == nil {
		return false
	}

	if fi.ModTime().Unix() - time.Now().UTC().Unix() < 60*72 {
		return true
	}

	newname := *fname + ".old"

	err = os.Rename(*fname, newname)
	if err != nil {
		fmt.Println("ERROR: %v", err)
	}

	return false
}

func main() {

	flag.Parse()

	dump_resp := false

	if !file_cached(inputFile) {

		fmt.Println("loading from NOAA")

		url := "http://www.weather.gov/forecasts/xml/SOAP_server/ndfdXMLclient.php?whichClient=NDFDgen&zipCodeList=10001&product=time-series&maxt=maxt&mint=mint&temp=temp&wspd=wspd&wdir=wdir&wx=wx&rh=rh&snow=snow&wwa=wwa&sky=sky&appt=appt&Submit=Submit"

		resp, err := http.Get(url)

		fmt.Println("err: ", err)

		if dump_resp {
			dump_httpresp(resp)
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		//fmt.Println(body)

		err = ioutil.WriteFile("1.txt", body, 0666)
		fmt.Println("write: ", err)
	}

	xmlFile, err := os.Open(*inputFile)

	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}

	defer xmlFile.Close()

	f, err := os.Create("1.prof")
	if err != nil {
		fmt.Println(err)
	}

	pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()

	decode_dwml(xmlFile)
}
<commit_msg>change out file names<commit_after>// test program to parse dwml xml format from NOAA API

package main

import (
	"fmt"
	"os"
	"time"
	"io/ioutil"
	"net/http"
	"reflect"
	"encoding/xml"
	"flag"
	"runtime/pprof"
)

var inputFile = flag.String("infile", "1~.txt", "Input file path")

func dump_httpresp(resp *http.Response) {
	fmt.Println("resp: ", resp)

	st := reflect.TypeOf(resp)
	fmt.Println(st)

	val := reflect.ValueOf(resp).Elem()

	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)
		typeName := valueField.Type().Name()

		fmt.Printf("Field Name: %s(%s),\t\t\t Field Value: %v\n", typeField.Name, typeName, valueField.Interface())
	}

	fmt.Println("=== headers ===")
	for k, v := range resp.Header {
		fmt.Printf("%20s = %20s\n", k, v)
	}
}

func dump_value(val reflect.Value) {
	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)
		typeName := valueField.Type().Name()
		fmt.Printf("%s(%s): %v\n", typeField.Name, typeName, valueField.Interface())
	}
}

// ===========================================================================
// NOAA API dwml structures (top to bottom order)

// root element

type Dwml struct {
	XMLName       xml.Name `xml:"dwml"`
	Header        Header   `xml:"head"`
	Data          Data     `xml:"data"`
}

// 3 second level elements

type Header struct {
	XMLName         xml.Name `xml:"head"`
	Product         Product  `xml:"product"`
}

type Data struct {
	XMLName         xml.Name     `xml:"data"`
	TimeLayouts     []TimeLayout `xml:"time-layout"`
	Parameters      Parameters   `xml:"parameters"`
}

// head element children

type Product struct {
	XMLName  xml.Name `xml:"product"`
	Src      string   `xml:"srsName,attr"`
	Name     string   `xml:"concise-name,attr"`
	Mode     string   `xml:"operational-mode,attr"`
}

// data element children

type TimeLayout struct {
	XMLName       xml.Name `xml:"time-layout"`
	Coordinate    string   `xml:"time-coordinate,attr"`
	Summarization string   `xml:"summarization,attr"`
	Key           string   `xml:"layout-key"`
	StartTime     []string `xml:"start-valid-time"`
	EndTime       []string `xml:"end-valid-time"`
}

type Parameters struct {
	XMLName       xml.Name    `xml:"parameters"`
	Temperatures  []Valueset  `xml:"temperature"`
	Precipitation Valueset    `xml:"precipitation"`
}

type Valueset struct {
	Type          string   `xml:"type,attr"`
	Units         string   `xml:"units,attr"`
	TimeLayout    string   `xml:"time-layout,attr"`
	Name          string   `xml:"name"`
	Values        []string `xml:"value"`
}

// ===========================================================================

func decode_dwml(xmlFile *os.File) {
	decoder := xml.NewDecoder(xmlFile)

	p := &Dwml{}
	if err := decoder.Decode(p); err != nil {
		fmt.Println("ERROR: %v", err)
		return
	}

	fmt.Println("Time layouts:");

	for idx, v := range p.Data.TimeLayouts {
		fmt.Println("    ", idx, v.Key, len(v.StartTime))
	}

	fmt.Println("Temperatures:");

	for idx, v := range p.Data.Parameters.Temperatures {
		fmt.Println("    ", idx, v.Name, v.TimeLayout, len(v.Values))
	}

	pr := p.Data.Parameters.Precipitation
	fmt.Println("Precipitation:", pr.TimeLayout, len(pr.Values));
}

func file_cached(fname *string) bool {

	fi, err := os.Stat(*fname)

	if err != nil || fi == nil {
		return false
	}

	if fi.ModTime().Unix() - time.Now().UTC().Unix() < 60*72 {
		return true
	}

	newname := *fname + ".old"

	err = os.Rename(*fname, newname)
	if err != nil {
		fmt.Println("ERROR: %v", err)
	}

	return false
}

func main() {

	flag.Parse()

	dump_resp := false

	if !file_cached(inputFile) {

		fmt.Println("loading from NOAA")

		url := "http://www.weather.gov/forecasts/xml/SOAP_server/ndfdXMLclient.php?whichClient=NDFDgen&zipCodeList=10001&product=time-series&maxt=maxt&mint=mint&temp=temp&wspd=wspd&wdir=wdir&wx=wx&rh=rh&snow=snow&wwa=wwa&sky=sky&appt=appt&Submit=Submit"

		resp, err := http.Get(url)

		fmt.Println("err: ", err)

		if dump_resp {
			dump_httpresp(resp)
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		//fmt.Println(body)

		err = ioutil.WriteFile(*inputFile, body, 0666)
		fmt.Println("write: ", err)
	}

	xmlFile, err := os.Open(*inputFile)

	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}

	defer xmlFile.Close()

	f, err := os.Create("1~.prof")
	if err != nil {
		fmt.Println(err)
	}

	pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()

	decode_dwml(xmlFile)
}
<|endoftext|>


<commit_before>package goptions

import (
	"os"
	"testing"
)

func TestParse_StringValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"--name, -n"`
	}
	expected := "SomeName"

	args = []string{"--name", "SomeName"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Flag parsing failed: %s", err)
	}
	if options.Name != expected {
		t.Fatalf("Expected %s for options.Name, got %s", expected, options.Name)
	}

	options.Name = ""

	args = []string{"-n", "SomeName"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Flag parsing failed: %s", err)
	}
	if options.Name != expected {
		t.Fatalf("Expected %s for options.Name, got %s", expected, options.Name)
	}
}

func TestParse_ObligatoryStringValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"-n, obligatory"`
	}
	args = []string{}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed.")
	}

	args = []string{"-n", "SomeName"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	expected := "SomeName"
	if options.Name != expected {
		t.Fatalf("Expected %s for options.Name, got %s", expected, options.Name)
	}
}

func TestParse_UnknownFlag(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"--name, -n"`
	}
	args = []string{"-k", "4"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed.")
	}
}

func TestParse_FlagCluster(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Fast    bool `goptions:"-f"`
		Silent  bool `goptions:"-q"`
		Serious bool `goptions:"-s"`
		Crazy   bool `goptions:"-c"`
		Verbose bool `goptions:"-v"`
	}
	args = []string{"-fqcv"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(options.Fast &&
		options.Silent &&
		!options.Serious &&
		options.Crazy &&
		options.Verbose) {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_MutexGroup(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Create bool `goptions:"--create, mutexgroup='action'"`
		Delete bool `goptions:"--delete, mutexgroup='action'"`
	}
	args = []string{"--create", "--delete"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed.")
	}
}

func TestParse_HelpFlag(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"--name, -n"`
		Help `goptions:"--help, -h"`
	}
	args = []string{"-n", "SomeNone", "-h"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != ErrHelpRequest {
		t.Fatalf("Expected ErrHelpRequest, got: %s", err)
	}

	args = []string{"-n", "SomeNone"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Unexpected error returned: %s", err)
	}
}

func TestParse_Verbs(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Server string `goptions:"--server, -s"`

		Verbs
		Create struct {
			Name string `goptions:"--name, -n"`
		} `goptions:"create"`
	}

	args = []string{"-s", "127.0.0.1", "create", "-n", "SomeDocument"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(options.Server == "127.0.0.1" &&
		options.Create.Name == "SomeDocument") {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_IntValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Limit int `goptions:"-l"`
	}

	args = []string{"-l", "123"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(options.Limit == 123) {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_Remainder(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Limit int `goptions:"-l"`
		Remainder
	}

	args = []string{"-l", "123", "Something", "SomethingElse"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(len(options.Remainder) == 2 &&
		options.Remainder[0] == "Something" &&
		options.Remainder[1] == "SomethingElse") {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_VerbRemainder(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Limit int `goptions:"-l"`
		Remainder

		Verbs
		Create struct {
			Fast bool `goptions:"-f"`
			Remainder
		} `goptions:"create"`
	}

	args = []string{"create", "-f", "Something", "SomethingElse"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(len(options.Remainder) == 2 &&
		options.Remainder[0] == "Something" &&
		options.Remainder[1] == "SomethingElse") {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_NoRemainder(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Fast bool `goptions:"-f"`
	}

	args = []string{"-f", "Something", "SomethingElse"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}
}

func TestParse_MissingValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"-n, --name"`
	}

	args = []string{"-n"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}

	args = []string{"--name"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}
}

func TestParse_ObligatoryMutexGroup(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Create bool `goptions:"-c, mutexgroup='action', obligatory"`
		Delete bool `goptions:"-d, mutexgroup='action'"`
	}

	args = []string{}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}

	args = []string{"-c", "-d"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}

	args = []string{"-d"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}
}

func TestParse_Array(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Servers []string `goptions:"-s"`
	}

	args = []string{"-s", "server1", "-s", "server2", "-s", "server3"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}
	if !(len(options.Servers) == 3 &&
		options.Servers[0] == "server1" &&
		options.Servers[1] == "server2" &&
		options.Servers[2] == "server3") {
		t.Fatalf("Unexpected value: %#v", options)
	}
}

func TestParse_File(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Output *os.File `goptions:"-o, create, trunc, wronly"`
	}

	args = []string{"-o", "testfile"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}
	if !(options.Output != nil) {
		t.Fatalf("Unexpected value: %#v", options)
	}
	options.Output.Close()
}
<commit_msg>Remove files created during test<commit_after>package goptions

import (
	"os"
	"testing"
)

func TestParse_StringValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"--name, -n"`
	}
	expected := "SomeName"

	args = []string{"--name", "SomeName"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Flag parsing failed: %s", err)
	}
	if options.Name != expected {
		t.Fatalf("Expected %s for options.Name, got %s", expected, options.Name)
	}

	options.Name = ""

	args = []string{"-n", "SomeName"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Flag parsing failed: %s", err)
	}
	if options.Name != expected {
		t.Fatalf("Expected %s for options.Name, got %s", expected, options.Name)
	}
}

func TestParse_ObligatoryStringValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"-n, obligatory"`
	}
	args = []string{}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed.")
	}

	args = []string{"-n", "SomeName"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	expected := "SomeName"
	if options.Name != expected {
		t.Fatalf("Expected %s for options.Name, got %s", expected, options.Name)
	}
}

func TestParse_UnknownFlag(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"--name, -n"`
	}
	args = []string{"-k", "4"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed.")
	}
}

func TestParse_FlagCluster(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Fast    bool `goptions:"-f"`
		Silent  bool `goptions:"-q"`
		Serious bool `goptions:"-s"`
		Crazy   bool `goptions:"-c"`
		Verbose bool `goptions:"-v"`
	}
	args = []string{"-fqcv"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(options.Fast &&
		options.Silent &&
		!options.Serious &&
		options.Crazy &&
		options.Verbose) {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_MutexGroup(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Create bool `goptions:"--create, mutexgroup='action'"`
		Delete bool `goptions:"--delete, mutexgroup='action'"`
	}
	args = []string{"--create", "--delete"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed.")
	}
}

func TestParse_HelpFlag(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"--name, -n"`
		Help `goptions:"--help, -h"`
	}
	args = []string{"-n", "SomeNone", "-h"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != ErrHelpRequest {
		t.Fatalf("Expected ErrHelpRequest, got: %s", err)
	}

	args = []string{"-n", "SomeNone"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Unexpected error returned: %s", err)
	}
}

func TestParse_Verbs(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Server string `goptions:"--server, -s"`

		Verbs
		Create struct {
			Name string `goptions:"--name, -n"`
		} `goptions:"create"`
	}

	args = []string{"-s", "127.0.0.1", "create", "-n", "SomeDocument"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(options.Server == "127.0.0.1" &&
		options.Create.Name == "SomeDocument") {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_IntValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Limit int `goptions:"-l"`
	}

	args = []string{"-l", "123"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(options.Limit == 123) {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_Remainder(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Limit int `goptions:"-l"`
		Remainder
	}

	args = []string{"-l", "123", "Something", "SomethingElse"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(len(options.Remainder) == 2 &&
		options.Remainder[0] == "Something" &&
		options.Remainder[1] == "SomethingElse") {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_VerbRemainder(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Limit int `goptions:"-l"`
		Remainder

		Verbs
		Create struct {
			Fast bool `goptions:"-f"`
			Remainder
		} `goptions:"create"`
	}

	args = []string{"create", "-f", "Something", "SomethingElse"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}

	if !(len(options.Remainder) == 2 &&
		options.Remainder[0] == "Something" &&
		options.Remainder[1] == "SomethingElse") {
		t.Fatalf("Unexpected value: %v", options)
	}
}

func TestParse_NoRemainder(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Fast bool `goptions:"-f"`
	}

	args = []string{"-f", "Something", "SomethingElse"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}
}

func TestParse_MissingValue(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Name string `goptions:"-n, --name"`
	}

	args = []string{"-n"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}

	args = []string{"--name"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}
}

func TestParse_ObligatoryMutexGroup(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Create bool `goptions:"-c, mutexgroup='action', obligatory"`
		Delete bool `goptions:"-d, mutexgroup='action'"`
	}

	args = []string{}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}

	args = []string{"-c", "-d"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err == nil {
		t.Fatalf("Parsing should have failed")
	}

	args = []string{"-d"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}
}

func TestParse_Array(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Servers []string `goptions:"-s"`
	}

	args = []string{"-s", "server1", "-s", "server2", "-s", "server3"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}
	if !(len(options.Servers) == 3 &&
		options.Servers[0] == "server1" &&
		options.Servers[1] == "server2" &&
		options.Servers[2] == "server3") {
		t.Fatalf("Unexpected value: %#v", options)
	}
}

func TestParse_File(t *testing.T) {
	var args []string
	var err error
	var fs *FlagSet
	var options struct {
		Output *os.File `goptions:"-o, create, trunc, wronly"`
	}

	args = []string{"-o", "testfile"}
	fs = NewFlagSet("goptions", &options)
	err = fs.Parse(args)
	if err != nil {
		t.Fatalf("Parsing failed: %s", err)
	}
	if !(options.Output != nil) {
		t.Fatalf("Unexpected value: %#v", options)
	}
	options.Output.Close()
	os.Remove("testfile")
}
<|endoftext|>


<commit_before>package control

import (
	"errors"
	"io"
	"testing"
	"time"

	"github.com/intelsdilabs/gomit"
	"github.com/intelsdilabs/pulse/control/plugin"
	. "github.com/smartystreets/goconvey/convey"
)

type MockController struct {
}

func (p *MockController) GenerateArgs(daemon bool) plugin.Arg {
	a := plugin.Arg{
		PluginLogPath: "/tmp/pulse-test-plugin.log",
		RunAsDaemon:   daemon,
	}
	return a
}

type MockExecutablePlugin struct {
	Timeout     bool
	NilResponse bool
}

func (m *MockExecutablePlugin) ResponseReader() io.Reader {
	return nil
}

func (m *MockExecutablePlugin) Start() error {
	return nil
}

func (m *MockExecutablePlugin) Kill() error {
	return nil
}

func (m *MockExecutablePlugin) Wait() error {
	return nil
}

func (m *MockExecutablePlugin) WaitForResponse(t time.Duration) (*plugin.Response, error) {
	if m.Timeout {
		return nil, errors.New("timeout")
	}

	if m.NilResponse {
		return nil, nil
	}

	resp := new(plugin.Response)
	resp.Type = plugin.CollectorPluginType
	return resp, nil
}

type MockHandlerDelegate struct {
	ErrorMode       bool
	WasRegistered   bool
	WasUnregistered bool
	StopError       error
}

func (m *MockHandlerDelegate) RegisterHandler(s string, h gomit.Handler) error {
	if m.ErrorMode {
		return errors.New("fake delegate error")
	}
	m.WasRegistered = true
	return nil
}

func (m *MockHandlerDelegate) UnregisterHandler(s string) error {
	if m.StopError != nil {
		return m.StopError
	}
	m.WasUnregistered = true
	return nil
}

func (m *MockHandlerDelegate) HandlerCount() int {
	return 0
}

func (m *MockHandlerDelegate) IsHandlerRegistered(s string) bool {
	return false
}

func TestRunnerState(t *testing.T) {
	Convey("pulse/control", t, func() {

		Convey("Runner", func() {

			Convey(".AddDelegates", func() {

				Convey("adds a handler delegate", func() {
					r := new(Runner)

					r.AddDelegates(new(MockHandlerDelegate))
					So(len(r.delegates), ShouldEqual, 1)
				})

				Convey("adds multiple delegates", func() {
					r := new(Runner)

					r.AddDelegates(new(MockHandlerDelegate))
					r.AddDelegates(new(MockHandlerDelegate))
					So(len(r.delegates), ShouldEqual, 2)
				})

				Convey("adds multiple delegates (batch)", func() {
					r := new(Runner)

					r.AddDelegates(new(MockHandlerDelegate), new(MockHandlerDelegate))
					So(len(r.delegates), ShouldEqual, 2)
				})

			})

			Convey(".Start", func() {

				Convey("returns error without adding delegates", func() {
					r := new(Runner)
					e := r.Start()

					So(e, ShouldNotBeNil)
					So(e.Error(), ShouldEqual, "No delegates added before called Start()")
				})

				Convey("starts after adding one delegates", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					e := r.Start()

					So(e, ShouldBeNil)
					So(m1.WasRegistered, ShouldBeTrue)
				})

				Convey("starts after  after adding multiple delegates", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					m2 := new(MockHandlerDelegate)
					m3 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					r.AddDelegates(m2, m3)
					e := r.Start()

					So(e, ShouldBeNil)
					So(m1.WasRegistered, ShouldBeTrue)
					So(m2.WasRegistered, ShouldBeTrue)
					So(m3.WasRegistered, ShouldBeTrue)
				})

				Convey("error if delegate cannot RegisterHandler", func() {
					r := new(Runner)
					me := new(MockHandlerDelegate)
					me.ErrorMode = true
					r.AddDelegates(me)
					e := r.Start()

					So(e, ShouldNotBeNil)
					So(e.Error(), ShouldEqual, "fake delegate error")
				})

			})

			Convey(".Stop", func() {

				Convey("removes handlers from delegates", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					m2 := new(MockHandlerDelegate)
					m3 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					r.AddDelegates(m2, m3)
					r.Start()
					e := r.Stop()

					So(m1.WasUnregistered, ShouldBeTrue)
					So(m2.WasUnregistered, ShouldBeTrue)
					So(m3.WasUnregistered, ShouldBeTrue)
					// No errors
					So(len(e), ShouldEqual, 0)
				})

				Convey("returns errors for handlers errors on stop", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					m1.StopError = errors.New("0")
					m2 := new(MockHandlerDelegate)
					m2.StopError = errors.New("1")
					m3 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					r.AddDelegates(m2, m3)
					r.Start()
					e := r.Stop()

					So(m1.WasUnregistered, ShouldBeFalse)
					So(m2.WasUnregistered, ShouldBeFalse)
					So(m3.WasUnregistered, ShouldBeTrue)
					// No errors
					So(len(e), ShouldEqual, 2)
					So(e[0].Error(), ShouldEqual, "0")
					So(e[1].Error(), ShouldEqual, "1")
				})

			})

		})
	})
}

func TestRunnerPluginRunning(t *testing.T) {
	Convey("pulse/control", t, func() {

		Convey("Runner", func() {
			Convey("startPlugin", func() {

				// These tests only work if Pulse Path is known to discover dummy plugin used for testing
				if PulsePath != "" {
					Convey("should return an AvailablePlugin in a Running state", func() {
						a := plugin.Arg{
							PluginLogPath: "/tmp/pulse-test-plugin.log",
						}
						exPlugin, err := plugin.NewExecutablePlugin(a, PluginPath, true)
						if err != nil {
							panic(err)
						}

						So(err, ShouldBeNil)

						// exPlugin := new(MockExecutablePlugin)
						ap, e := startPlugin(exPlugin)

						So(e, ShouldBeNil)
						So(ap, ShouldNotBeNil)
						println(ap.State)
						So(ap.State, ShouldEqual, PluginRunning)
					})

					// Convey("should return error for WaitForResponse error", func() {
					// 	exPlugin := new(MockExecutablePlugin)
					// 	exPlugin.Timeout = true // set to not response
					// 	ap, e := startPlugin(exPlugin)

					// 	So(ap, ShouldBeNil)
					// 	So(e, ShouldResemble, errors.New("timeout"))
					// })

					// Convey("should return error for nil availablePlugin", func() {
					// 	exPlugin := new(MockExecutablePlugin)
					// 	exPlugin.NilResponse = true // set to not response
					// 	ap, e := startPlugin(exPlugin)

					// 	So(ap, ShouldBeNil)
					// 	So(e, ShouldResemble, errors.New("no reponse object returned from plugin"))
					// })
				}

			})

			Convey("stopPlugin", func() {
				// TODO
			})
		})
	})
}
<commit_msg>Disabled Runner test for now<commit_after>package control

import (
	"errors"
	"io"
	"testing"
	"time"

	"github.com/intelsdilabs/gomit"
	"github.com/intelsdilabs/pulse/control/plugin"
	. "github.com/smartystreets/goconvey/convey"
)

type MockController struct {
}

func (p *MockController) GenerateArgs(daemon bool) plugin.Arg {
	a := plugin.Arg{
		PluginLogPath: "/tmp/pulse-test-plugin.log",
		RunAsDaemon:   daemon,
	}
	return a
}

type MockExecutablePlugin struct {
	Timeout     bool
	NilResponse bool
}

func (m *MockExecutablePlugin) ResponseReader() io.Reader {
	return nil
}

func (m *MockExecutablePlugin) Start() error {
	return nil
}

func (m *MockExecutablePlugin) Kill() error {
	return nil
}

func (m *MockExecutablePlugin) Wait() error {
	return nil
}

func (m *MockExecutablePlugin) WaitForResponse(t time.Duration) (*plugin.Response, error) {
	if m.Timeout {
		return nil, errors.New("timeout")
	}

	if m.NilResponse {
		return nil, nil
	}

	resp := new(plugin.Response)
	resp.Type = plugin.CollectorPluginType
	return resp, nil
}

type MockHandlerDelegate struct {
	ErrorMode       bool
	WasRegistered   bool
	WasUnregistered bool
	StopError       error
}

func (m *MockHandlerDelegate) RegisterHandler(s string, h gomit.Handler) error {
	if m.ErrorMode {
		return errors.New("fake delegate error")
	}
	m.WasRegistered = true
	return nil
}

func (m *MockHandlerDelegate) UnregisterHandler(s string) error {
	if m.StopError != nil {
		return m.StopError
	}
	m.WasUnregistered = true
	return nil
}

func (m *MockHandlerDelegate) HandlerCount() int {
	return 0
}

func (m *MockHandlerDelegate) IsHandlerRegistered(s string) bool {
	return false
}

func TestRunnerState(t *testing.T) {
	Convey("pulse/control", t, func() {

		Convey("Runner", func() {

			Convey(".AddDelegates", func() {

				Convey("adds a handler delegate", func() {
					r := new(Runner)

					r.AddDelegates(new(MockHandlerDelegate))
					So(len(r.delegates), ShouldEqual, 1)
				})

				Convey("adds multiple delegates", func() {
					r := new(Runner)

					r.AddDelegates(new(MockHandlerDelegate))
					r.AddDelegates(new(MockHandlerDelegate))
					So(len(r.delegates), ShouldEqual, 2)
				})

				Convey("adds multiple delegates (batch)", func() {
					r := new(Runner)

					r.AddDelegates(new(MockHandlerDelegate), new(MockHandlerDelegate))
					So(len(r.delegates), ShouldEqual, 2)
				})

			})

			Convey(".Start", func() {

				Convey("returns error without adding delegates", func() {
					r := new(Runner)
					e := r.Start()

					So(e, ShouldNotBeNil)
					So(e.Error(), ShouldEqual, "No delegates added before called Start()")
				})

				Convey("starts after adding one delegates", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					e := r.Start()

					So(e, ShouldBeNil)
					So(m1.WasRegistered, ShouldBeTrue)
				})

				Convey("starts after  after adding multiple delegates", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					m2 := new(MockHandlerDelegate)
					m3 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					r.AddDelegates(m2, m3)
					e := r.Start()

					So(e, ShouldBeNil)
					So(m1.WasRegistered, ShouldBeTrue)
					So(m2.WasRegistered, ShouldBeTrue)
					So(m3.WasRegistered, ShouldBeTrue)
				})

				Convey("error if delegate cannot RegisterHandler", func() {
					r := new(Runner)
					me := new(MockHandlerDelegate)
					me.ErrorMode = true
					r.AddDelegates(me)
					e := r.Start()

					So(e, ShouldNotBeNil)
					So(e.Error(), ShouldEqual, "fake delegate error")
				})

			})

			Convey(".Stop", func() {

				Convey("removes handlers from delegates", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					m2 := new(MockHandlerDelegate)
					m3 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					r.AddDelegates(m2, m3)
					r.Start()
					e := r.Stop()

					So(m1.WasUnregistered, ShouldBeTrue)
					So(m2.WasUnregistered, ShouldBeTrue)
					So(m3.WasUnregistered, ShouldBeTrue)
					// No errors
					So(len(e), ShouldEqual, 0)
				})

				Convey("returns errors for handlers errors on stop", func() {
					r := new(Runner)
					m1 := new(MockHandlerDelegate)
					m1.StopError = errors.New("0")
					m2 := new(MockHandlerDelegate)
					m2.StopError = errors.New("1")
					m3 := new(MockHandlerDelegate)
					r.AddDelegates(m1)
					r.AddDelegates(m2, m3)
					r.Start()
					e := r.Stop()

					So(m1.WasUnregistered, ShouldBeFalse)
					So(m2.WasUnregistered, ShouldBeFalse)
					So(m3.WasUnregistered, ShouldBeTrue)
					// No errors
					So(len(e), ShouldEqual, 2)
					So(e[0].Error(), ShouldEqual, "0")
					So(e[1].Error(), ShouldEqual, "1")
				})

			})

		})
	})
}

func TestRunnerPluginRunning(t *testing.T) {
	Convey("pulse/control", t, func() {

		Convey("Runner", func() {
			Convey("startPlugin", func() {

				// These tests only work if Pulse Path is known to discover dummy plugin used for testing
				if PulsePath != "" {
					// Convey("should return an AvailablePlugin in a Running state", func() {
					// 	a := plugin.Arg{
					// 		PluginLogPath: "/tmp/pulse-test-plugin.log",
					// 	}
					// 	exPlugin, err := plugin.NewExecutablePlugin(a, PluginPath, true)
					// 	if err != nil {
					// 		panic(err)
					// 	}

					// 	So(err, ShouldBeNil)

					// 	// exPlugin := new(MockExecutablePlugin)
					// 	ap, e := startPlugin(exPlugin)

					// 	So(e, ShouldBeNil)
					// 	So(ap, ShouldNotBeNil)
					// 	println(ap.State)
					// 	So(ap.State, ShouldEqual, PluginRunning)
					// })

					// Convey("should return error for WaitForResponse error", func() {
					// 	exPlugin := new(MockExecutablePlugin)
					// 	exPlugin.Timeout = true // set to not response
					// 	ap, e := startPlugin(exPlugin)

					// 	So(ap, ShouldBeNil)
					// 	So(e, ShouldResemble, errors.New("timeout"))
					// })

					// Convey("should return error for nil availablePlugin", func() {
					// 	exPlugin := new(MockExecutablePlugin)
					// 	exPlugin.NilResponse = true // set to not response
					// 	ap, e := startPlugin(exPlugin)

					// 	So(ap, ShouldBeNil)
					// 	So(e, ShouldResemble, errors.New("no reponse object returned from plugin"))
					// })
				}

			})

			Convey("stopPlugin", func() {
				// TODO
			})
		})
	})
}
<|endoftext|>


<commit_before>package tusd_test

import (
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"

	. "github.com/tus/tusd"
)

func TestPatch(t *testing.T) {
	SubTest(t, "UploadChunk", func(t *testing.T, store *MockFullDataStore) {
		gomock.InOrder(
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				ID:     "yes",
				Offset: 5,
				Size:   10,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(5), NewReaderMatcher("hello")).Return(int64(5), nil),
			store.EXPECT().FinishUpload("yes"),
		)

		handler, _ := NewHandler(Config{
			DataStore:             store,
			NotifyCompleteUploads: true,
		})

		c := make(chan FileInfo, 1)
		handler.CompleteUploads = c

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			ReqBody: strings.NewReader("hello"),
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "10",
			},
		}).Run(handler, t)

		a := assert.New(t)
		info := <-c
		a.Equal("yes", info.ID)
		a.EqualValues(int64(10), info.Size)
		a.Equal(int64(10), info.Offset)
	})

	SubTest(t, "MethodOverriding", func(t *testing.T, store *MockFullDataStore) {
		gomock.InOrder(
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				ID:     "yes",
				Offset: 5,
				Size:   20,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(5), NewReaderMatcher("hello")).Return(int64(5), nil),
		)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "POST",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable":          "1.0.0",
				"Upload-Offset":          "5",
				"Content-Type":           "application/offset+octet-stream",
				"X-HTTP-Method-Override": "PATCH",
			},
			ReqBody: strings.NewReader("hello"),
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "10",
			},
		}).Run(handler, t)
	})

	SubTest(t, "UploadChunkToFinished", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("yes").Return(FileInfo{
			Offset: 20,
			Size:   20,
		}, nil)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "20",
			},
			ReqBody: strings.NewReader(""),
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "20",
			},
		}).Run(handler, t)
	})

	SubTest(t, "UploadNotFoundFail", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("no").Return(FileInfo{}, os.ErrNotExist)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "no",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			Code: http.StatusNotFound,
		}).Run(handler, t)
	})

	SubTest(t, "MissmatchingOffsetFail", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("yes").Return(FileInfo{
			Offset: 5,
		}, nil)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "4",
			},
			Code: http.StatusConflict,
		}).Run(handler, t)
	})

	SubTest(t, "ExceedingMaxSizeFail", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("yes").Return(FileInfo{
			Offset: 5,
			Size:   10,
		}, nil)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			ReqBody: strings.NewReader("hellothisismorethan15bytes"),
			Code:    http.StatusRequestEntityTooLarge,
		}).Run(handler, t)
	})

	SubTest(t, "InvalidContentTypeFail", func(t *testing.T, store *MockFullDataStore) {
		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/fail",
				"Upload-Offset": "5",
			},
			ReqBody: strings.NewReader("hellothisismorethan15bytes"),
			Code:    http.StatusBadRequest,
		}).Run(handler, t)
	})

	SubTest(t, "InvalidOffsetFail", func(t *testing.T, store *MockFullDataStore) {
		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "-5",
			},
			ReqBody: strings.NewReader("hellothisismorethan15bytes"),
			Code:    http.StatusBadRequest,
		}).Run(handler, t)
	})

	SubTest(t, "OverflowWithoutLength", func(t *testing.T, store *MockFullDataStore) {
		// In this test we attempt to upload more than 15 bytes to an upload
		// which has only space for 15 bytes (offset of 5 and size of 20).
		// The request does not contain the Content-Length header and the handler
		// therefore does not know the chunk's size before. The wanted behavior
		// is that even if the uploader supplies more than 15 bytes, we only
		// pass 15 bytes to the data store and ignore the rest.

		gomock.InOrder(
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				Offset: 5,
				Size:   20,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(5), NewReaderMatcher("hellothisismore")).Return(int64(15), nil),
			store.EXPECT().FinishUpload("yes"),
		)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		// Wrap the string.Reader in a NopCloser to hide its type. else
		// http.NewRequest() will detect the we supply a strings.Reader as body
		// and use this information to set the Content-Length header which we
		// explicitly do not want (see comment above for reason).
		body := ioutil.NopCloser(strings.NewReader("hellothisismorethan15bytes"))

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			ReqBody: body,
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "20",
			},
		}).Run(handler, t)
	})

	SubTest(t, "Locker", func(t *testing.T, store *MockFullDataStore) {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()
		locker := NewMockLocker(ctrl)

		gomock.InOrder(
			locker.EXPECT().LockUpload("yes").Return(nil),
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				Offset: 0,
				Size:   20,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(0), NewReaderMatcher("hello")).Return(int64(5), nil),
			locker.EXPECT().UnlockUpload("yes").Return(nil),
		)

		composer := NewStoreComposer()
		composer.UseCore(store)
		composer.UseLocker(locker)

		handler, _ := NewHandler(Config{
			StoreComposer: composer,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "0",
			},
			ReqBody: strings.NewReader("hello"),
			Code:    http.StatusNoContent,
		}).Run(handler, t)
	})
}
<commit_msg>Add test for NotifiyUploadProgress<commit_after>package tusd_test

import (
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"

	. "github.com/tus/tusd"
)

func TestPatch(t *testing.T) {
	SubTest(t, "UploadChunk", func(t *testing.T, store *MockFullDataStore) {
		gomock.InOrder(
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				ID:     "yes",
				Offset: 5,
				Size:   10,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(5), NewReaderMatcher("hello")).Return(int64(5), nil),
			store.EXPECT().FinishUpload("yes"),
		)

		handler, _ := NewHandler(Config{
			DataStore:             store,
			NotifyCompleteUploads: true,
		})

		c := make(chan FileInfo, 1)
		handler.CompleteUploads = c

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			ReqBody: strings.NewReader("hello"),
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "10",
			},
		}).Run(handler, t)

		a := assert.New(t)
		info := <-c
		a.Equal("yes", info.ID)
		a.EqualValues(int64(10), info.Size)
		a.Equal(int64(10), info.Offset)
	})

	SubTest(t, "MethodOverriding", func(t *testing.T, store *MockFullDataStore) {
		gomock.InOrder(
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				ID:     "yes",
				Offset: 5,
				Size:   20,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(5), NewReaderMatcher("hello")).Return(int64(5), nil),
		)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "POST",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable":          "1.0.0",
				"Upload-Offset":          "5",
				"Content-Type":           "application/offset+octet-stream",
				"X-HTTP-Method-Override": "PATCH",
			},
			ReqBody: strings.NewReader("hello"),
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "10",
			},
		}).Run(handler, t)
	})

	SubTest(t, "UploadChunkToFinished", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("yes").Return(FileInfo{
			Offset: 20,
			Size:   20,
		}, nil)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "20",
			},
			ReqBody: strings.NewReader(""),
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "20",
			},
		}).Run(handler, t)
	})

	SubTest(t, "UploadNotFoundFail", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("no").Return(FileInfo{}, os.ErrNotExist)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "no",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			Code: http.StatusNotFound,
		}).Run(handler, t)
	})

	SubTest(t, "MissmatchingOffsetFail", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("yes").Return(FileInfo{
			Offset: 5,
		}, nil)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "4",
			},
			Code: http.StatusConflict,
		}).Run(handler, t)
	})

	SubTest(t, "ExceedingMaxSizeFail", func(t *testing.T, store *MockFullDataStore) {
		store.EXPECT().GetInfo("yes").Return(FileInfo{
			Offset: 5,
			Size:   10,
		}, nil)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			ReqBody: strings.NewReader("hellothisismorethan15bytes"),
			Code:    http.StatusRequestEntityTooLarge,
		}).Run(handler, t)
	})

	SubTest(t, "InvalidContentTypeFail", func(t *testing.T, store *MockFullDataStore) {
		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/fail",
				"Upload-Offset": "5",
			},
			ReqBody: strings.NewReader("hellothisismorethan15bytes"),
			Code:    http.StatusBadRequest,
		}).Run(handler, t)
	})

	SubTest(t, "InvalidOffsetFail", func(t *testing.T, store *MockFullDataStore) {
		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "-5",
			},
			ReqBody: strings.NewReader("hellothisismorethan15bytes"),
			Code:    http.StatusBadRequest,
		}).Run(handler, t)
	})

	SubTest(t, "OverflowWithoutLength", func(t *testing.T, store *MockFullDataStore) {
		// In this test we attempt to upload more than 15 bytes to an upload
		// which has only space for 15 bytes (offset of 5 and size of 20).
		// The request does not contain the Content-Length header and the handler
		// therefore does not know the chunk's size before. The wanted behavior
		// is that even if the uploader supplies more than 15 bytes, we only
		// pass 15 bytes to the data store and ignore the rest.

		gomock.InOrder(
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				Offset: 5,
				Size:   20,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(5), NewReaderMatcher("hellothisismore")).Return(int64(15), nil),
			store.EXPECT().FinishUpload("yes"),
		)

		handler, _ := NewHandler(Config{
			DataStore: store,
		})

		// Wrap the string.Reader in a NopCloser to hide its type. else
		// http.NewRequest() will detect the we supply a strings.Reader as body
		// and use this information to set the Content-Length header which we
		// explicitly do not want (see comment above for reason).
		body := ioutil.NopCloser(strings.NewReader("hellothisismorethan15bytes"))

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "5",
			},
			ReqBody: body,
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "20",
			},
		}).Run(handler, t)
	})

	SubTest(t, "Locker", func(t *testing.T, store *MockFullDataStore) {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()
		locker := NewMockLocker(ctrl)

		gomock.InOrder(
			locker.EXPECT().LockUpload("yes").Return(nil),
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				Offset: 0,
				Size:   20,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(0), NewReaderMatcher("hello")).Return(int64(5), nil),
			locker.EXPECT().UnlockUpload("yes").Return(nil),
		)

		composer := NewStoreComposer()
		composer.UseCore(store)
		composer.UseLocker(locker)

		handler, _ := NewHandler(Config{
			StoreComposer: composer,
		})

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "0",
			},
			ReqBody: strings.NewReader("hello"),
			Code:    http.StatusNoContent,
		}).Run(handler, t)
	})

	SubTest(t, "NotifyUploadProgress", func(t *testing.T, store *MockFullDataStore) {
		gomock.InOrder(
			store.EXPECT().GetInfo("yes").Return(FileInfo{
				ID:     "yes",
				Offset: 0,
				Size:   100,
			}, nil),
			store.EXPECT().WriteChunk("yes", int64(0), NewReaderMatcher("first second third")).Return(int64(18), nil),
		)

		handler, _ := NewHandler(Config{
			DataStore:            store,
			NotifyUploadProgress: true,
		})

		c := make(chan FileInfo, 10)
		handler.UploadProgress = c

		reader, writer := io.Pipe()

		go func() {
			writer.Write([]byte("first "))
			<-time.After(time.Second)
			writer.Write([]byte("second "))
			writer.Write([]byte("third"))
			writer.Close()
		}()

		(&httpTest{
			Method: "PATCH",
			URL:    "yes",
			ReqHeader: map[string]string{
				"Tus-Resumable": "1.0.0",
				"Content-Type":  "application/offset+octet-stream",
				"Upload-Offset": "0",
			},
			ReqBody: reader,
			Code:    http.StatusNoContent,
			ResHeader: map[string]string{
				"Upload-Offset": "18",
			},
		}).Run(handler, t)

		// Wait a short time after the request has been handled before closing the
		// channel because another goroutine may still write to the channel.
		<-time.After(10 * time.Millisecond)
		close(handler.UploadProgress)

		a := assert.New(t)

		info := <-c
		a.Equal("yes", info.ID)
		a.Equal(int64(100), info.Size)
		a.Equal(int64(6), info.Offset)

		info = <-c
		a.Equal("yes", info.ID)
		a.Equal(int64(100), info.Size)
		a.Equal(int64(18), info.Offset)

		_, more := <-c
		a.False(more)
	})
}
<|endoftext|>


<commit_before>package conf

import (
	"fmt"
	"os"
	"strconv"

	"github.com/jmoiron/sqlx"
	// import MySQL driver
	_ "github.com/go-sql-driver/mysql"
	// import PostgreSQL driver
	_ "github.com/lib/pq"
	// import SQL Server driver
	_ "github.com/denisenkom/go-mssqldb"
)

/*
DbConnInfo - Database Connection information
*/
type DbConnInfo struct {
	DBType string
	User   string
	Pw     string
	DBName string
	Host   string
	Port   string
}

/*
Database - Database connection and configuration
*/
type Database interface {
	DSN() string
	GetDB() (*sqlx.DB, error)
}

/*
DB - Represents SQLX database connection
*/
var DB *sqlx.DB

/*
DBConnData - Value of database connection information
*/
var DBConnData *DbConnInfo

/*
GetDB - Gets Database connection
*/
func GetDB() (*sqlx.DB, error) {
	var err error
	if DB == nil {
		if DBConnData == nil {
			DBConnData = LoadDbConfig()
		}
		dsn := DSN()
		DB, err = sqlx.Open(DBConnData.DBType, dsn)
		if err != nil {
			return nil, err
		}
		err = DB.Ping()
		if err != nil {
			return nil, err
		}
		idle := Cfg.Section("").Key("idle_conn").Value()
		numIdle, err := strconv.Atoi(idle)
		if err != nil {
			return nil, err
		}
		DB.SetMaxIdleConns(numIdle)
		max := Cfg.Section("").Key("max_conn").Value()
		numMax, err := strconv.Atoi(max)
		if err != nil {
			return nil, err
		}
		DB.SetMaxOpenConns(numMax)
	}
	return DB, err
}

/*
DSN - Gets database connection string
*/
func DSN() string {
	if DBConnData.DBType == "mysql" {
		return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", DBConnData.User, DBConnData.Pw, DBConnData.Host, DBConnData.Port, DBConnData.DBName)
	} else if DBConnData.DBType == "postgres" {
		return fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=disable", DBConnData.User, DBConnData.Pw, DBConnData.Host, DBConnData.Port, DBConnData.DBName)
	} else if DBConnData.DBType == "mssql" {
		return fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=%s", DBConnData.Host, DBConnData.User, DBConnData.Pw, DBConnData.Port, DBConnData.DBName)
	}
	return ""
}

/*
LoadDbConfig - Loads specific database connection information
*/
func LoadDbConfig() (dbConnectionInfo *DbConnInfo) {
	if len(os.Getenv("db_type")) < 1 {
		dbConnectionInfo = &DbConnInfo{
			DBType: Cfg.Section("").Key("db_type").Value(),
			User:   Cfg.Section("").Key("db_user").Value(),
			Pw:     Cfg.Section("").Key("db_pw").Value(),
			DBName: Cfg.Section("").Key("db_name").Value(),
			Host:   Cfg.Section("").Key("db_host").Value(),
			Port:   Cfg.Section("").Key("db_port").Value(),
		}
	} else {
		dbConnectionInfo = &DbConnInfo{
			DBType: os.Getenv("db_type"),
			User:   os.Getenv("db_user"),
			Pw:     os.Getenv("db_pw"),
			DBName: os.Getenv("db_name"),
			Host:   os.Getenv("db_host"),
			Port:   os.Getenv("db_port"),
		}
	}
	return
}
<commit_msg>Gets max and idle number of connections from env variables<commit_after>package conf

import (
	"fmt"
	"os"
	"strconv"

	"github.com/jmoiron/sqlx"
	// import MySQL driver
	_ "github.com/go-sql-driver/mysql"
	// import PostgreSQL driver
	_ "github.com/lib/pq"
	// import SQL Server driver
	_ "github.com/denisenkom/go-mssqldb"
)

/*
DbConnInfo - Database Connection information
*/
type DbConnInfo struct {
	DBType string
	User   string
	Pw     string
	DBName string
	Host   string
	Port   string
}

/*
Database - Database connection and configuration
*/
type Database interface {
	DSN() string
	GetDB() (*sqlx.DB, error)
}

/*
DB - Represents SQLX database connection
*/
var DB *sqlx.DB

/*
DBConnData - Value of database connection information
*/
var DBConnData *DbConnInfo

/*
GetDB - Gets Database connection
*/
func GetDB() (*sqlx.DB, error) {
	var err error
	if DB == nil {
		if DBConnData == nil {
			DBConnData = LoadDbConfig()
		}
		dsn := DSN()
		DB, err = sqlx.Open(DBConnData.DBType, dsn)
		if err != nil {
			return nil, err
		}
		err = DB.Ping()
		if err != nil {
			return nil, err
		}
		var numMax, numIdle int
		if len(os.Getenv("db_type")) < 1 {
			numIdle, err = strconv.Atoi(Cfg.Section("").Key("idle_conn").Value())
			if err != nil {
				return nil, err
			}
			numMax, err = strconv.Atoi(Cfg.Section("").Key("max_conn").Value())
			if err != nil {
				return nil, err
			}
		} else {
			numIdle, err = strconv.Atoi(os.Getenv("idle_conn"))
			if err != nil {
				return nil, err
			}
			numMax, err = strconv.Atoi(os.Getenv("max_conn"))
			if err != nil {
				return nil, err
			}
		}
		DB.SetMaxIdleConns(numIdle)
		DB.SetMaxOpenConns(numMax)
	}
	return DB, err
}

/*
DSN - Gets database connection string
*/
func DSN() string {
	if DBConnData.DBType == "mysql" {
		return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", DBConnData.User, DBConnData.Pw, DBConnData.Host, DBConnData.Port, DBConnData.DBName)
	} else if DBConnData.DBType == "postgres" {
		return fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=disable", DBConnData.User, DBConnData.Pw, DBConnData.Host, DBConnData.Port, DBConnData.DBName)
	} else if DBConnData.DBType == "mssql" {
		return fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=%s", DBConnData.Host, DBConnData.User, DBConnData.Pw, DBConnData.Port, DBConnData.DBName)
	}
	return ""
}

/*
LoadDbConfig - Loads specific database connection information
*/
func LoadDbConfig() (dbConnectionInfo *DbConnInfo) {
	if len(os.Getenv("db_type")) < 1 {
		dbConnectionInfo = &DbConnInfo{
			DBType: Cfg.Section("").Key("db_type").Value(),
			User:   Cfg.Section("").Key("db_user").Value(),
			Pw:     Cfg.Section("").Key("db_pw").Value(),
			DBName: Cfg.Section("").Key("db_name").Value(),
			Host:   Cfg.Section("").Key("db_host").Value(),
			Port:   Cfg.Section("").Key("db_port").Value(),
		}
	} else {
		dbConnectionInfo = &DbConnInfo{
			DBType: os.Getenv("db_type"),
			User:   os.Getenv("db_user"),
			Pw:     os.Getenv("db_pw"),
			DBName: os.Getenv("db_name"),
			Host:   os.Getenv("db_host"),
			Port:   os.Getenv("db_port"),
		}
	}
	return
}
<|endoftext|>


<commit_before>// Copyright 2018 The Peggy Authors
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.

package peg

import (
	"bytes"
	"io"
	"strconv"
)

type nodeOrFail interface {
	name() string
	numKids() int
	kid(int) nodeOrFail
	text() string
}

func (f *Node) name() string         { return f.Name }
func (f *Node) numKids() int         { return len(f.Kids) }
func (f *Node) kid(i int) nodeOrFail { return f.Kids[i] }
func (f *Node) text() string         { return f.Text }
func (f *Fail) name() string         { return f.Name }
func (f *Fail) numKids() int         { return len(f.Kids) }
func (f *Fail) kid(i int) nodeOrFail { return f.Kids[i] }
func (f *Fail) text() string         { return f.Want }

// Pretty returns a human-readable string of a Node or Fail
// and the subtree beneath it.
// The output looks like:
// 	<n.Name>{
// 		<Pretty(n.Kids)[0])>,
// 		<Pretty(n.Kids[1])>,
// 		…
// 		<Pretty(n.Kids[n-1])>,
// 	}
func Pretty(n nodeOrFail) string {
	b := bytes.NewBuffer(nil)
	PrettyWrite(b, n)
	return b.String()
}

// PrettyWrite is like Pretty but outputs to an io.Writer.
func PrettyWrite(w io.Writer, n nodeOrFail) error {
	return prettyWrite(w, "", n)
}

func prettyWrite(w io.Writer, tab string, n nodeOrFail) error {
	if _, err := io.WriteString(w, tab); err != nil {
		return err
	}
	_, fail := n.(*Fail)
	if n.numKids() == 0 && (n.name() == "" || fail && n.text() != "") {
		if _, err := io.WriteString(w, `"`+n.text()+`"`); err != nil {
			return err
		}
		return nil
	}
	if _, err := io.WriteString(w, n.name()); err != nil {
		return err
	}
	if f, ok := n.(*Fail); ok {
		pos := "[" + strconv.Itoa(f.Pos) + "]"
		if _, err := io.WriteString(w, pos); err != nil {
			return err
		}
	}
	if n.numKids() == 0 {
		if n.name() == "" {
			if _, err := io.WriteString(w, "{}"); err != nil {
				return err
			}
		}
		return nil
	}
	if _, err := io.WriteString(w, "{"); err != nil {
		return err
	}
	if n.numKids() == 1 && n.kid(0).numKids() == 0 {
		if err := prettyWrite(w, "", n.kid(0)); err != nil {
			return err
		}
		if _, err := io.WriteString(w, "}"); err != nil {
			return err
		}
		return nil
	}
	for i := 0; i < n.numKids(); i++ {
		if _, err := io.WriteString(w, "\n"); err != nil {
			return err
		}
		if err := prettyWrite(w, tab+"\t", n.kid(i)); err != nil {
			return err
		}
		if _, err := io.WriteString(w, ","); err != nil {
			return err
		}
	}
	if _, err := io.WriteString(w, "\n"+tab+"}"); err != nil {
		return err
	}
	return nil
}
<commit_msg>Pretty-print text for named leaf nodes.<commit_after>// Copyright 2018 The Peggy Authors
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.

package peg

import (
	"bytes"
	"io"
	"strconv"
)

type nodeOrFail interface {
	name() string
	numKids() int
	kid(int) nodeOrFail
	text() string
}

func (f *Node) name() string         { return f.Name }
func (f *Node) numKids() int         { return len(f.Kids) }
func (f *Node) kid(i int) nodeOrFail { return f.Kids[i] }
func (f *Node) text() string         { return f.Text }
func (f *Fail) name() string         { return f.Name }
func (f *Fail) numKids() int         { return len(f.Kids) }
func (f *Fail) kid(i int) nodeOrFail { return f.Kids[i] }
func (f *Fail) text() string         { return f.Want }

// Pretty returns a human-readable string of a Node or Fail
// and the subtree beneath it.
// The output looks like:
// 	<n.Name>{
// 		<Pretty(n.Kids)[0])>,
// 		<Pretty(n.Kids[1])>,
// 		…
// 		<Pretty(n.Kids[n-1])>,
// 	}
func Pretty(n nodeOrFail) string {
	b := bytes.NewBuffer(nil)
	PrettyWrite(b, n)
	return b.String()
}

// PrettyWrite is like Pretty but outputs to an io.Writer.
func PrettyWrite(w io.Writer, n nodeOrFail) error {
	return prettyWrite(w, "", n)
}

func prettyWrite(w io.Writer, tab string, n nodeOrFail) error {
	if _, err := io.WriteString(w, tab); err != nil {
		return err
	}
	if n.numKids() == 0 {
		if n.name() != "" {
			if _, err := io.WriteString(w, n.name()+"("); err != nil {
				return err
			}
		}
		if _, err := io.WriteString(w, `"`+n.text()+`"`); err != nil {
			return err
		}
		if n.name() != "" {
			if _, err := io.WriteString(w, ")"); err != nil {
				return err
			}
		}
		return nil
	}
	if _, err := io.WriteString(w, n.name()); err != nil {
		return err
	}
	if f, ok := n.(*Fail); ok {
		pos := "[" + strconv.Itoa(f.Pos) + "]"
		if _, err := io.WriteString(w, pos); err != nil {
			return err
		}
	}
	if n.numKids() == 0 {
		if n.name() == "" {
			if _, err := io.WriteString(w, "{}"); err != nil {
				return err
			}
		}
		return nil
	}
	if _, err := io.WriteString(w, "{"); err != nil {
		return err
	}
	if n.numKids() == 1 && n.kid(0).numKids() == 0 {
		if err := prettyWrite(w, "", n.kid(0)); err != nil {
			return err
		}
		if _, err := io.WriteString(w, "}"); err != nil {
			return err
		}
		return nil
	}
	for i := 0; i < n.numKids(); i++ {
		if _, err := io.WriteString(w, "\n"); err != nil {
			return err
		}
		if err := prettyWrite(w, tab+"\t", n.kid(i)); err != nil {
			return err
		}
		if _, err := io.WriteString(w, ","); err != nil {
			return err
		}
	}
	if _, err := io.WriteString(w, "\n"+tab+"}"); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


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

package period

import (
	"database/sql/driver"
	"fmt"
)

// Scan parses some value, which can be either string ot []byte.
// It implements sql.Scanner, https://golang.org/pkg/database/sql/#Scanner
func (p *Period) Scan(value interface{}) (err error) {
	if value == nil {
		return nil
	}

	err = nil
	switch v := value.(type) {
	case []byte:
		*p, err = Parse(string(v))
	case string:
		*p, err = Parse(v)
	default:
		err = fmt.Errorf("%T %+v is not a meaningful period", value, value)
	}

	return err
}

// Value converts the value to a string. It implements driver.Valuer,
// https://golang.org/pkg/database/sql/driver/#Valuer
func (p Period) Value() (driver.Value, error) {
	return p.String(), nil
}
<commit_msg>comments etc<commit_after>// Copyright 2015 Rick Beton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package period

import (
	"database/sql/driver"
	"fmt"
)

// Scan parses some value, which can be either string or []byte.
// It implements sql.Scanner, https://golang.org/pkg/database/sql/#Scanner
func (period *Period) Scan(value interface{}) (err error) {
	if value == nil {
		return nil
	}

	err = nil
	switch v := value.(type) {
	case []byte:
		*period, err = Parse(string(v))
	case string:
		*period, err = Parse(v)
	default:
		err = fmt.Errorf("%T %+v is not a meaningful period", value, value)
	}

	return err
}

// Value converts the period to a string. It implements driver.Valuer,
// https://golang.org/pkg/database/sql/driver/#Valuer
func (period Period) Value() (driver.Value, error) {
	return period.String(), nil
}
<|endoftext|>


<commit_before>package personplan

import (
	"fmt"
	"time"

	"github.com/xyproto/moskus"
)

const (
	FIRSTHOUR = 8
	LASTHOUR  = 22
)

// Info about one thing that can happen during an hour
type HourEvent struct {
	who   string
	when  time.Time
	where string
}

// Info about everything that happens during an hour, per person
type HourEvents []HourEvent

// A plan is a collecion of plans for just a few months at a time
type Plans struct {
	all []*SemesterPlan
}

type WorkDayAndLocation struct {
	dayoftheweek time.Weekday
	fromHour     int
	uptoHour     int
	location     string
}

type PersonPlan struct {
	who      string
	workdays []*WorkDayAndLocation
	locale   string
}

type SemesterPlan struct {
	year        int
	fromMonth   int
	uptoMonth   int
	personPlans []*PersonPlan
}

func NewPersonPlan(who string) *PersonPlan {
	var pp PersonPlan
	pp.who = who
	pp.locale = "nb_NO"
	return &pp
}

func (pp *PersonPlan) AddWorkday(dayoftheweek time.Weekday, fromHour, uptoHour int, location string) {
	newday := &WorkDayAndLocation{dayoftheweek, fromHour, uptoHour, location}
	pp.workdays = append(pp.workdays, newday)
}

func (pp *PersonPlan) String() string {
	cal, err := moskus.NewCalendar(pp.locale, true)
	if err != nil {
		panic("No calendar available for " + pp.locale)
	}
	s := "User: " + pp.who + "\n"
	s += "-----------------------------------------------\n"
	for _, day := range pp.workdays {
		s += "\n"
		s += "\t" + day.dayoftheweek.String() + " (" + cal.DayName(day.dayoftheweek) + ")\n"
		s += fmt.Sprintf("\tFrom this hour: \t%d\n", day.fromHour)
		s += fmt.Sprintf("\tUp to this hour:\t%d\n", day.uptoHour)
		s += fmt.Sprintf("\tAt this location:\t%s\n", day.location)
	}
	return s
}

func NewSemesterPlan(year, fromMonth, uptoMonth int) *SemesterPlan {
	var pps []*PersonPlan
	return &SemesterPlan{year, fromMonth, uptoMonth, pps}
}

func (sp *SemesterPlan) AddPersonPlan(persplan *PersonPlan) {
	sp.personPlans = append(sp.personPlans, persplan)
}

func (sp *SemesterPlan) ForAllWeekdays(fn func(string, time.Weekday, int, string) string) string {
	s := ""
	for day := 0; day < 7; day++ {
		for hour := FIRSTHOUR; hour <= LASTHOUR; hour++ {
			for _, persplan := range sp.personPlans {
				for _, personday := range persplan.workdays {
					if personday.dayoftheweek == time.Weekday(day) {
						if (hour >= personday.fromHour) && (hour < personday.uptoHour) {
							s += fn(persplan.who, time.Weekday(day), hour, personday.location)
						}
					}
				}
			}
		}
	}
	return s
}

func infoline(who string, weekday time.Weekday, hour int, location string) string {
	return fmt.Sprintf("%s on %s hour that starts at %d at %s\n", who, weekday, hour, location)
}

func (sp *SemesterPlan) String() string {
	s := fmt.Sprintf("From %d, month %d\n", sp.year, sp.fromMonth)
	s += fmt.Sprintf("Up to %d, month %d\n", sp.year, sp.uptoMonth)
	s += sp.ForAllWeekdays(infoline)
	return s
}

// Given an hour, gets information from all the person plans in the period plan
func (sp *SemesterPlan) GetHourEventStructs(t time.Time) HourEvents {

	hourevents := make(HourEvents, 0)

	// if not the right year
	if t.Year() != sp.year {
		return hourevents
	}

	// if not within the month range
	if !((t.Month() >= time.Month(sp.fromMonth)) && (t.Month() < time.Month(sp.uptoMonth))) {
		return hourevents
	}

	var hev HourEvent
	for _, persplan := range sp.personPlans {
		for _, wd := range persplan.workdays {

			// If not the right day of the week
			if wd.dayoftheweek != t.Weekday() {
				//fmt.Printf("Wrong day of the week! (%v and %v)\n", wd.dayoftheweek, t.Weekday())
				continue
			}

			// If not within the hour range
			if !((t.Hour() >= wd.fromHour) && (t.Hour() < wd.uptoHour)) {
				//fmt.Printf("Wrong hour range! (%v is not between %v and %v)\n", t.Hour(), wd.fromHour, wd.uptoHour)
				continue
			}

			// Found!
			hev.who = persplan.who
			hev.when = t
			hev.where = wd.location
			hourevents = append(hourevents, hev)
		}
	}

	// HourEvent structs
	return hourevents
}

func (sp *SemesterPlan) ViewHour(t time.Time) string {
	s := ""
	hourinfo := sp.GetHourEventStructs(t)
	for _, hev := range hourinfo {
		s += fmt.Sprintf("%s %s at %s, %v at hour %v\n", hev.when.String()[:10], hev.who, hev.where, hev.when.Weekday(), hev.when.Hour())
	}
	return s
}

func (sp *SemesterPlan) ViewDay(date time.Time) string {
	var t time.Time
	var hourString string
	s := ""
	for hour := FIRSTHOUR; hour <= LASTHOUR; hour++ {
		//fmt.Printf("hour: %d\n", hour)
		t = time.Date(date.Year(), date.Month(), date.Day(), hour, 0, 0, 0, time.UTC)
		hourString = sp.ViewHour(t)
		if hourString != "" {
			s += hourString + "\n"
		}
	}
	return s
}

// Make new plans, which is a collection of SemesterPlans
func NewPlans() *Plans {
	var plans Plans
	plans.all = make([]*SemesterPlan, 0)
	return &plans
}

// Add a SemesterPlan to the collection of plans
func (plans *Plans) AddSemesterPlan(sp *SemesterPlan) {
	plans.all = append(plans.all, sp)
}

func (plans *Plans) HTMLHourEvents(date time.Time) string {
	s := ""
	for _, pp := range plans.all {
		s2 := pp.ViewHour(date)
		if s2 == "" {
			s += "Nothing"
		} else {
			s += s2
		}
	}
	return s
}

// TODO: Create a function just like this that returns a list of HourEvent structs
func (plans *Plans) PrintHourEvents(date time.Time) {
	fmt.Printf("What's up at %s?\n", date.String())
	s := ""
	for _, pp := range plans.all {
		s += pp.ViewHour(date)
	}
	if s == "" {
		fmt.Println("Nothing!")
	} else {
		fmt.Println(s)
	}
}

<commit_msg>Formatting<commit_after>package personplan

import (
	"fmt"
	"time"

	"github.com/xyproto/moskus"
)

const (
	FIRSTHOUR = 8
	LASTHOUR  = 22
)

// Info about one thing that can happen during an hour
type HourEvent struct {
	who   string
	when  time.Time
	where string
}

// Info about everything that happens during an hour, per person
type HourEvents []HourEvent

// A plan is a collecion of plans for just a few months at a time
type Plans struct {
	all []*SemesterPlan
}

type WorkDayAndLocation struct {
	dayoftheweek time.Weekday
	fromHour     int
	uptoHour     int
	location     string
}

type PersonPlan struct {
	who      string
	workdays []*WorkDayAndLocation
	locale   string
}

type SemesterPlan struct {
	year        int
	fromMonth   int
	uptoMonth   int
	personPlans []*PersonPlan
}

func NewPersonPlan(who string) *PersonPlan {
	var pp PersonPlan
	pp.who = who
	pp.locale = "nb_NO"
	return &pp
}

func (pp *PersonPlan) AddWorkday(dayoftheweek time.Weekday, fromHour, uptoHour int, location string) {
	newday := &WorkDayAndLocation{dayoftheweek, fromHour, uptoHour, location}
	pp.workdays = append(pp.workdays, newday)
}

func (pp *PersonPlan) String() string {
	cal, err := moskus.NewCalendar(pp.locale, true)
	if err != nil {
		panic("No calendar available for " + pp.locale)
	}
	s := "User: " + pp.who + "\n"
	s += "-----------------------------------------------\n"
	for _, day := range pp.workdays {
		s += "\n"
		s += "\t" + day.dayoftheweek.String() + " (" + cal.DayName(day.dayoftheweek) + ")\n"
		s += fmt.Sprintf("\tFrom this hour: \t%d\n", day.fromHour)
		s += fmt.Sprintf("\tUp to this hour:\t%d\n", day.uptoHour)
		s += fmt.Sprintf("\tAt this location:\t%s\n", day.location)
	}
	return s
}

func NewSemesterPlan(year, fromMonth, uptoMonth int) *SemesterPlan {
	var pps []*PersonPlan
	return &SemesterPlan{year, fromMonth, uptoMonth, pps}
}

func (sp *SemesterPlan) AddPersonPlan(persplan *PersonPlan) {
	sp.personPlans = append(sp.personPlans, persplan)
}

func (sp *SemesterPlan) ForAllWeekdays(fn func(string, time.Weekday, int, string) string) string {
	s := ""
	for day := 0; day < 7; day++ {
		for hour := FIRSTHOUR; hour <= LASTHOUR; hour++ {
			for _, persplan := range sp.personPlans {
				for _, personday := range persplan.workdays {
					if personday.dayoftheweek == time.Weekday(day) {
						if (hour >= personday.fromHour) && (hour < personday.uptoHour) {
							s += fn(persplan.who, time.Weekday(day), hour, personday.location)
						}
					}
				}
			}
		}
	}
	return s
}

func infoline(who string, weekday time.Weekday, hour int, location string) string {
	return fmt.Sprintf("%s on %s hour that starts at %d at %s\n", who, weekday, hour, location)
}

func (sp *SemesterPlan) String() string {
	s := fmt.Sprintf("From %d, month %d\n", sp.year, sp.fromMonth)
	s += fmt.Sprintf("Up to %d, month %d\n", sp.year, sp.uptoMonth)
	s += sp.ForAllWeekdays(infoline)
	return s
}

// Given an hour, gets information from all the person plans in the period plan
func (sp *SemesterPlan) GetHourEventStructs(t time.Time) HourEvents {

	hourevents := make(HourEvents, 0)

	// if not the right year
	if t.Year() != sp.year {
		return hourevents
	}

	// if not within the month range
	if !((t.Month() >= time.Month(sp.fromMonth)) && (t.Month() < time.Month(sp.uptoMonth))) {
		return hourevents
	}

	var hev HourEvent
	for _, persplan := range sp.personPlans {
		for _, wd := range persplan.workdays {

			// If not the right day of the week
			if wd.dayoftheweek != t.Weekday() {
				//fmt.Printf("Wrong day of the week! (%v and %v)\n", wd.dayoftheweek, t.Weekday())
				continue
			}

			// If not within the hour range
			if !((t.Hour() >= wd.fromHour) && (t.Hour() < wd.uptoHour)) {
				//fmt.Printf("Wrong hour range! (%v is not between %v and %v)\n", t.Hour(), wd.fromHour, wd.uptoHour)
				continue
			}

			// Found!
			hev.who = persplan.who
			hev.when = t
			hev.where = wd.location
			hourevents = append(hourevents, hev)
		}
	}

	// HourEvent structs
	return hourevents
}

func (sp *SemesterPlan) ViewHour(t time.Time) string {
	s := ""
	hourinfo := sp.GetHourEventStructs(t)
	for _, hev := range hourinfo {
		s += fmt.Sprintf("%s %s at %s, %v at hour %v\n", hev.when.String()[:10], hev.who, hev.where, hev.when.Weekday(), hev.when.Hour())
	}
	return s
}

func (sp *SemesterPlan) ViewDay(date time.Time) string {
	var t time.Time
	var hourString string
	s := ""
	for hour := FIRSTHOUR; hour <= LASTHOUR; hour++ {
		//fmt.Printf("hour: %d\n", hour)
		t = time.Date(date.Year(), date.Month(), date.Day(), hour, 0, 0, 0, time.UTC)
		hourString = sp.ViewHour(t)
		if hourString != "" {
			s += hourString + "\n"
		}
	}
	return s
}

// Make new plans, which is a collection of SemesterPlans
func NewPlans() *Plans {
	var plans Plans
	plans.all = make([]*SemesterPlan, 0)
	return &plans
}

// Add a SemesterPlan to the collection of plans
func (plans *Plans) AddSemesterPlan(sp *SemesterPlan) {
	plans.all = append(plans.all, sp)
}

func (plans *Plans) HTMLHourEvents(date time.Time) string {
	s := ""
	for _, pp := range plans.all {
		s2 := pp.ViewHour(date)
		if s2 == "" {
			s += "Nothing"
		} else {
			s += s2
		}
	}
	return s
}

// TODO: Create a function just like this that returns a list of HourEvent structs
func (plans *Plans) PrintHourEvents(date time.Time) {
	fmt.Printf("What's up at %s?\n", date.String())
	s := ""
	for _, pp := range plans.all {
		s += pp.ViewHour(date)
	}
	if s == "" {
		fmt.Println("Nothing!")
	} else {
		fmt.Println(s)
	}
}
<|endoftext|>