<commit_before>package s3gof3r

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"hash"
	"io"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

// defined by amazon
const (
	minPartSize = 5 * mb
	maxPartSize = 5 * gb
	maxObjSize  = 5 * tb
	maxNPart    = 10000
	md5Header   = "content-md5"
)

type part struct {
	r   io.ReadSeeker
	len int64
	b   *bytes.Buffer

	// read by xml encoder
	PartNumber int
	ETag       string

	// Used for checksum of checksums on completion
	contentMd5 string
}

type putter struct {
	url         url.URL
	client      *http.Client
	b           *Bucket
	concurrency int
	nTry        int
	c           *Config

	bufsz      int64
	buf        *bytes.Buffer
	ch         chan *part
	part       int
	closed     bool
	err        error
	wg         sync.WaitGroup
	md5OfParts hash.Hash
	md5        hash.Hash
	ETag       string

	bp *bp

	makes    int
	UploadId string
	xml      struct {
		XMLName string `xml:"CompleteMultipartUpload"`
		Part    []*part
	}
}

type completeXml struct {
	ETag string
}

// Sends an S3 multipart upload initiation request.
// See http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html.
// The initial request returns an UploadId that we use to identify
// subsequent PUT requests.
func newPutter(url url.URL, h http.Header, c *Config, b *Bucket) (p *putter, err error) {
	p = new(putter)
	p.url = url
	p.client = c.Client
	p.b = b
	p.concurrency = c.Concurrency
	p.nTry = c.NTry
	p.c = c
	p.bufsz = max64(minPartSize, c.PartSize)
	resp, err := p.retryRequest("POST", url.String()+"?uploads", nil, h)
	if err != nil {
		return nil, err
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return nil, newRespError(resp)
	}
	err = xml.NewDecoder(resp.Body).Decode(p)
	if err != nil {
		return nil, err
	}
	p.ch = make(chan *part)
	for i := 0; i < p.concurrency; i++ {
		go p.worker()
	}
	p.md5OfParts = md5.New()
	p.md5 = md5.New()

	p.bp = newBufferPool(p.bufsz)

	return p, nil
}

func (p *putter) Write(b []byte) (int, error) {
	if p.closed {
		return 0, syscall.EINVAL
	}
	if p.err != nil {
		return 0, p.err
	}
	if p.buf == nil {
		p.buf = <-p.bp.get
		p.buf.Reset()
	}
	n, err := p.buf.Write(b)
	if err != nil {
		return n, err
	}

	if int64(p.buf.Len()) >= p.bufsz {
		p.flush()
	}
	return n, nil
}

func (p *putter) flush() {
	p.wg.Add(1)
	p.part++
	b := *p.buf
	part := &part{bytes.NewReader(b.Bytes()), int64(b.Len()), p.buf, p.part, "", ""}
	var err error
	part.contentMd5, part.ETag, err = p.md5Content(part.r)
	if err != nil {
		p.err = err
	}

	p.xml.Part = append(p.xml.Part, part)
	p.ch <- part
	p.buf = nil
	// double buffer size every 1000 parts to
	// avoid exceeding the 10000-part AWS limit
	// while still reaching the 5 Terabyte max object size
	if p.part%1000 == 0 {
		p.bufsz = min64(p.bufsz*2, maxPartSize)
	}

}

func (p *putter) worker() {
	for part := range p.ch {
		p.retryPutPart(part)
	}
}

// Calls putPart up to nTry times to recover from transient errors.
func (p *putter) retryPutPart(part *part) {
	defer p.wg.Done()
	var err error
	for i := 0; i < p.nTry; i++ {
		time.Sleep(time.Duration(math.Exp2(float64(i))) * 100 * time.Millisecond) // exponential back-off
		err = p.putPart(part)
		if err == nil {
			p.bp.give <- part.b
			return
		}
		logger.debugPrintf("Error on attempt %d: Retrying part: %v, Error: %s", i, part, err)
	}
	p.err = err
}

// uploads a part, checking the etag against the calculated value
func (p *putter) putPart(part *part) error {
	v := url.Values{}
	v.Set("partNumber", strconv.Itoa(part.PartNumber))
	v.Set("uploadId", p.UploadId)
	if _, err := part.r.Seek(0, 0); err != nil { // move back to beginning, if retrying
		return err
	}
	req, err := http.NewRequest("PUT", p.url.String()+"?"+v.Encode(), part.r)
	if err != nil {
		return err
	}
	req.ContentLength = part.len
	req.Header.Set(md5Header, part.contentMd5)
	p.b.Sign(req)
	resp, err := p.client.Do(req)
	if err != nil {
		return err
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}
	s := resp.Header.Get("etag")
	s = s[1 : len(s)-1] // includes quote chars for some reason
	if part.ETag != s {
		return fmt.Errorf("Response etag does not match. Remote:%s Calculated:%s", s, p.ETag)
	}
	return nil
}

func (p *putter) Close() (err error) {
	if p.closed {
		return syscall.EINVAL
	}
	if p.buf != nil {
		buf := *p.buf
		if buf.Len() > 0 {
			p.flush()
		}
	}
	p.wg.Wait()
	close(p.ch)
	p.closed = true
	p.bp.quit <- true

	if p.part == 0 {
		return fmt.Errorf("0 bytes written")
	}
	if p.err != nil {
		err := p.abort()
		if err != nil {
			return err
		}
		return p.err
	}
	logger.debugPrintln("buffer allocations:", p.bp.makes)
	// Complete Multipart upload
	body, err := xml.Marshal(p.xml)
	if err != nil {
		return
	}
	b := bytes.NewReader(body)
	v := url.Values{}
	v.Set("uploadId", p.UploadId)
	resp, err := p.retryRequest("POST", p.url.String()+"?"+v.Encode(), b, nil)
	if err != nil {
		return
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}
	// Check md5 hash of concatenated part md5 hashes against ETag
	// more info: https://forums.aws.amazon.com/thread.jspa?messageID=456442&#456442
	calculatedMd5ofParts := fmt.Sprintf("%x", p.md5OfParts.Sum(nil))
	// Parse etag from body of response
	err = xml.NewDecoder(resp.Body).Decode(p)
	if err != nil {
		return
	}
	// strip part count from end and '"' from front.
	remoteMd5ofParts := strings.Split(p.ETag, "-")[0]
	remoteMd5ofParts = remoteMd5ofParts[1:len(remoteMd5ofParts)]
	if calculatedMd5ofParts != remoteMd5ofParts {
		// TODO: Delete file from S3?
		if err != nil {
			return err
		}
		return fmt.Errorf("MD5 hash of part hashes comparison failed. Hash from multipart complete header: %s."+
			" Calculated multipart hash: %s.", remoteMd5ofParts, calculatedMd5ofParts)
	}
	if p.c.Md5Check {
		for i := 0; i < p.nTry; i++ {
			if err = p.putMd5(); err == nil {
				break
			}
		}
		return
	}
	return
}

func (p *putter) abort() (err error) {
	v := url.Values{}
	v.Set("uploadId", p.UploadId)
	s := p.url.String() + "?" + v.Encode()
	resp, err := p.retryRequest("DELETE", s, nil, nil)
	if err != nil {
		return
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 204 {
		return newRespError(resp)
	}
	return
}

// Md5 functions
func (p *putter) md5Content(r io.ReadSeeker) (string, string, error) {
	h := md5.New()
	mw := io.MultiWriter(h, p.md5)
	if _, err := io.Copy(mw, r); err != nil {
		return "", "", err
	}
	sum := h.Sum(nil)
	hexSum := fmt.Sprintf("%x", sum)
	// add to checksum of all parts for verification on upload completion
	if _, err := p.md5OfParts.Write(sum); err != nil {
		return "", "", err
	}
	return base64.StdEncoding.EncodeToString(sum), hexSum, nil
}

// Put md5 file in .md5 subdirectory of bucket  where the file is stored
// e.g. the md5 for https://mybucket.s3.amazonaws.com/gof3r will be stored in
// https://mybucket.s3.amazonaws.com/.md5/gof3r.md5
func (p *putter) putMd5() (err error) {
	calcMd5 := fmt.Sprintf("%x", p.md5.Sum(nil))
	md5Reader := strings.NewReader(calcMd5)
	md5Path := fmt.Sprint(".md5", p.url.Path, ".md5")
	md5Url := p.b.Url(md5Path, p.c)
	logger.debugPrintln("md5: ", calcMd5)
	logger.debugPrintln("md5Path: ", md5Path)
	r, err := http.NewRequest("PUT", md5Url.String(), md5Reader)
	if err != nil {
		return
	}
	p.b.Sign(r)
	resp, err := p.client.Do(r)
	if err != nil {
		return
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}
	return
}

func (p *putter) retryRequest(method, urlStr string, body io.ReadSeeker, h http.Header) (resp *http.Response, err error) {
	for i := 0; i < p.nTry; i++ {
		var req *http.Request
		req, err = http.NewRequest(method, urlStr, body)
		if err != nil {
			return
		}
		for k := range h {
			for _, v := range h[k] {
				req.Header.Add(k, v)
			}
		}

		p.b.Sign(req)
		resp, err = p.client.Do(req)
		if err == nil {
			return
		}
		logger.debugPrintln(err)
		if body != nil {
			if _, err = body.Seek(0, 0); err != nil {
				return
			}
		}
	}
	return
}
<commit_msg>Abort multipart upload on fatal upload errors. Closes #1<commit_after>package s3gof3r

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"hash"
	"io"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

// defined by amazon
const (
	minPartSize = 5 * mb
	maxPartSize = 5 * gb
	maxObjSize  = 5 * tb
	maxNPart    = 10000
	md5Header   = "content-md5"
)

type part struct {
	r   io.ReadSeeker
	len int64
	b   *bytes.Buffer

	// read by xml encoder
	PartNumber int
	ETag       string

	// Used for checksum of checksums on completion
	contentMd5 string
}

type putter struct {
	url         url.URL
	client      *http.Client
	b           *Bucket
	concurrency int
	nTry        int
	c           *Config

	bufsz      int64
	buf        *bytes.Buffer
	ch         chan *part
	part       int
	closed     bool
	err        error
	wg         sync.WaitGroup
	md5OfParts hash.Hash
	md5        hash.Hash
	ETag       string

	bp *bp

	makes    int
	UploadId string
	xml      struct {
		XMLName string `xml:"CompleteMultipartUpload"`
		Part    []*part
	}
}

type completeXml struct {
	ETag string
}

// Sends an S3 multipart upload initiation request.
// See http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html.
// The initial request returns an UploadId that we use to identify
// subsequent PUT requests.
func newPutter(url url.URL, h http.Header, c *Config, b *Bucket) (p *putter, err error) {
	p = new(putter)
	p.url = url
	p.client = c.Client
	p.b = b
	p.concurrency = c.Concurrency
	p.nTry = c.NTry
	p.c = c
	p.bufsz = max64(minPartSize, c.PartSize)
	resp, err := p.retryRequest("POST", url.String()+"?uploads", nil, h)
	if err != nil {
		return nil, err
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return nil, newRespError(resp)
	}
	err = xml.NewDecoder(resp.Body).Decode(p)
	if err != nil {
		return nil, err
	}
	p.ch = make(chan *part)
	for i := 0; i < p.concurrency; i++ {
		go p.worker()
	}
	p.md5OfParts = md5.New()
	p.md5 = md5.New()

	p.bp = newBufferPool(p.bufsz)

	return p, nil
}

func (p *putter) Write(b []byte) (int, error) {
	if p.closed {
		p.abort()
		return 0, syscall.EINVAL
	}
	if p.err != nil {
		p.abort()
		return 0, p.err
	}
	if p.buf == nil {
		p.buf = <-p.bp.get
		p.buf.Reset()
	}
	n, err := p.buf.Write(b)
	if err != nil {
		p.abort()
		return n, err
	}

	if int64(p.buf.Len()) >= p.bufsz {
		p.flush()
	}
	return n, nil
}

func (p *putter) flush() {
	p.wg.Add(1)
	p.part++
	b := *p.buf
	part := &part{bytes.NewReader(b.Bytes()), int64(b.Len()), p.buf, p.part, "", ""}
	var err error
	part.contentMd5, part.ETag, err = p.md5Content(part.r)
	if err != nil {
		p.err = err
	}

	p.xml.Part = append(p.xml.Part, part)
	p.ch <- part
	p.buf = nil
	// double buffer size every 1000 parts to
	// avoid exceeding the 10000-part AWS limit
	// while still reaching the 5 Terabyte max object size
	if p.part%1000 == 0 {
		p.bufsz = min64(p.bufsz*2, maxPartSize)
	}

}

func (p *putter) worker() {
	for part := range p.ch {
		p.retryPutPart(part)
	}
}

// Calls putPart up to nTry times to recover from transient errors.
func (p *putter) retryPutPart(part *part) {
	defer p.wg.Done()
	var err error
	for i := 0; i < p.nTry; i++ {
		time.Sleep(time.Duration(math.Exp2(float64(i))) * 100 * time.Millisecond) // exponential back-off
		err = p.putPart(part)
		if err == nil {
			p.bp.give <- part.b
			return
		}
		logger.debugPrintf("Error on attempt %d: Retrying part: %v, Error: %s", i, part, err)
	}
	p.err = err
}

// uploads a part, checking the etag against the calculated value
func (p *putter) putPart(part *part) error {
	v := url.Values{}
	v.Set("partNumber", strconv.Itoa(part.PartNumber))
	v.Set("uploadId", p.UploadId)
	if _, err := part.r.Seek(0, 0); err != nil { // move back to beginning, if retrying
		return err
	}
	req, err := http.NewRequest("PUT", p.url.String()+"?"+v.Encode(), part.r)
	if err != nil {
		return err
	}
	req.ContentLength = part.len
	req.Header.Set(md5Header, part.contentMd5)
	p.b.Sign(req)
	resp, err := p.client.Do(req)
	if err != nil {
		return err
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}
	s := resp.Header.Get("etag")
	s = s[1 : len(s)-1] // includes quote chars for some reason
	if part.ETag != s {
		return fmt.Errorf("Response etag does not match. Remote:%s Calculated:%s", s, p.ETag)
	}
	return nil
}

func (p *putter) Close() (err error) {
	if p.closed {
		return syscall.EINVAL
	}
	if p.buf != nil {
		buf := *p.buf
		if buf.Len() > 0 {
			p.flush()
		}
	}
	p.wg.Wait()
	close(p.ch)
	p.closed = true
	p.bp.quit <- true

	if p.part == 0 {
		return fmt.Errorf("0 bytes written")
	}
	if p.err != nil {
		p.abort()
		return p.err
	}
	logger.debugPrintln("buffer allocations:", p.bp.makes)
	// Complete Multipart upload
	body, err := xml.Marshal(p.xml)
	if err != nil {
		return
	}
	b := bytes.NewReader(body)
	v := url.Values{}
	v.Set("uploadId", p.UploadId)
	resp, err := p.retryRequest("POST", p.url.String()+"?"+v.Encode(), b, nil)
	if err != nil {
		return
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}
	// Check md5 hash of concatenated part md5 hashes against ETag
	// more info: https://forums.aws.amazon.com/thread.jspa?messageID=456442&#456442
	calculatedMd5ofParts := fmt.Sprintf("%x", p.md5OfParts.Sum(nil))
	// Parse etag from body of response
	err = xml.NewDecoder(resp.Body).Decode(p)
	if err != nil {
		return
	}
	// strip part count from end and '"' from front.
	remoteMd5ofParts := strings.Split(p.ETag, "-")[0]
	remoteMd5ofParts = remoteMd5ofParts[1:len(remoteMd5ofParts)]
	if calculatedMd5ofParts != remoteMd5ofParts {
		// TODO: Delete file from S3?
		if err != nil {
			return err
		}
		return fmt.Errorf("MD5 hash of part hashes comparison failed. Hash from multipart complete header: %s."+
			" Calculated multipart hash: %s.", remoteMd5ofParts, calculatedMd5ofParts)
	}
	if p.c.Md5Check {
		for i := 0; i < p.nTry; i++ {
			if err = p.putMd5(); err == nil {
				break
			}
		}
		return
	}
	return
}

func (p *putter) abort() {
	v := url.Values{}
	v.Set("uploadId", p.UploadId)
	s := p.url.String() + "?" + v.Encode()
	resp, err := p.retryRequest("DELETE", s, nil, nil)
	if err != nil {
		logger.Printf("Error aborting multipart upload: %v", err)
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 204 {
		logger.Printf("Error aborting multipart upload: %v", newRespError(resp))
	}
	return
}

// Md5 functions
func (p *putter) md5Content(r io.ReadSeeker) (string, string, error) {
	h := md5.New()
	mw := io.MultiWriter(h, p.md5)
	if _, err := io.Copy(mw, r); err != nil {
		return "", "", err
	}
	sum := h.Sum(nil)
	hexSum := fmt.Sprintf("%x", sum)
	// add to checksum of all parts for verification on upload completion
	if _, err := p.md5OfParts.Write(sum); err != nil {
		return "", "", err
	}
	return base64.StdEncoding.EncodeToString(sum), hexSum, nil
}

// Put md5 file in .md5 subdirectory of bucket  where the file is stored
// e.g. the md5 for https://mybucket.s3.amazonaws.com/gof3r will be stored in
// https://mybucket.s3.amazonaws.com/.md5/gof3r.md5
func (p *putter) putMd5() (err error) {
	calcMd5 := fmt.Sprintf("%x", p.md5.Sum(nil))
	md5Reader := strings.NewReader(calcMd5)
	md5Path := fmt.Sprint(".md5", p.url.Path, ".md5")
	md5Url := p.b.Url(md5Path, p.c)
	logger.debugPrintln("md5: ", calcMd5)
	logger.debugPrintln("md5Path: ", md5Path)
	r, err := http.NewRequest("PUT", md5Url.String(), md5Reader)
	if err != nil {
		return
	}
	p.b.Sign(r)
	resp, err := p.client.Do(r)
	if err != nil {
		return
	}
	defer checkClose(resp.Body, &err)
	if resp.StatusCode != 200 {
		return newRespError(resp)
	}
	return
}

func (p *putter) retryRequest(method, urlStr string, body io.ReadSeeker, h http.Header) (resp *http.Response, err error) {
	for i := 0; i < p.nTry; i++ {
		var req *http.Request
		req, err = http.NewRequest(method, urlStr, body)
		if err != nil {
			return
		}
		for k := range h {
			for _, v := range h[k] {
				req.Header.Add(k, v)
			}
		}

		p.b.Sign(req)
		resp, err = p.client.Do(req)
		if err == nil {
			return
		}
		logger.debugPrintln(err)
		if body != nil {
			if _, err = body.Seek(0, 0); err != nil {
				return
			}
		}
	}
	return
}
<|endoftext|>


<commit_before>package provision

import (
	"fmt"
	"net/url"
	"strings"

	"github.com/docker/machine/libmachine/auth"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcndockerclient"
	"github.com/docker/machine/libmachine/swarm"
	"github.com/samalba/dockerclient"
)

func configureSwarm(p Provisioner, swarmOptions swarm.Options, authOptions auth.Options) error {
	if !swarmOptions.IsSwarm {
		return nil
	}

	log.Info("Configuring swarm...")

	ip, err := p.GetDriver().GetIP()
	if err != nil {
		return err
	}

	u, err := url.Parse(swarmOptions.Host)
	if err != nil {
		return err
	}

	parts := strings.Split(u.Host, ":")
	port := parts[1]

	dockerPort := "2376"
	dockerDir := p.GetDockerOptionsDir()
	dockerHost := &mcndockerclient.RemoteDocker{
		HostURL:    fmt.Sprintf("tcp://%s:%s", ip, dockerPort),
		AuthOption: &authOptions,
	}
	advertiseInfo := fmt.Sprintf("%s:%s", ip, dockerPort)

	if swarmOptions.Master {
		advertiseMasterInfo := fmt.Sprintf("%s:%s", ip, "3376")
		cmd := fmt.Sprintf("manage --tlsverify --tlscacert=%s --tlscert=%s --tlskey=%s -H %s --strategy %s --advertise %s",
			authOptions.CaCertRemotePath,
			authOptions.ServerCertRemotePath,
			authOptions.ServerKeyRemotePath,
			swarmOptions.Host,
			swarmOptions.Strategy,
			advertiseMasterInfo,
		)

		cmdMaster := strings.Fields(cmd)
		for _, option := range swarmOptions.ArbitraryFlags {
			cmdMaster = append(cmdMaster, "--"+option)
		}

		//Discovery must be at end of command
		cmdMaster = append(cmdMaster, swarmOptions.Discovery)

		hostBind := fmt.Sprintf("%s:%s", dockerDir, dockerDir)
		masterHostConfig := dockerclient.HostConfig{
			RestartPolicy: dockerclient.RestartPolicy{
				Name:              "always",
				MaximumRetryCount: 0,
			},
			Binds:        []string{hostBind},
			PortBindings: map[string][]dockerclient.PortBinding{"3376/tcp": {{"", port}}},
			NetworkMode:  "host",
		}

		swarmMasterConfig := &dockerclient.ContainerConfig{
			Image: swarmOptions.Image,
			Env:   swarmOptions.Env,
			ExposedPorts: map[string]struct{}{
				"2375/tcp": {},
				"3376/tcp": {},
			},
			Cmd:        cmdMaster,
			HostConfig: masterHostConfig,
		}

		err = mcndockerclient.CreateContainer(dockerHost, swarmMasterConfig, "swarm-agent-master")
		if err != nil {
			return err
		}
	}

	workerHostConfig := dockerclient.HostConfig{
		RestartPolicy: dockerclient.RestartPolicy{
			Name:              "always",
			MaximumRetryCount: 0,
		},
		NetworkMode: "host",
	}

	swarmWorkerConfig := &dockerclient.ContainerConfig{
		Image: swarmOptions.Image,
		Env:   swarmOptions.Env,
		Cmd: []string{
			"join",
			"--advertise",
			advertiseInfo,
			swarmOptions.Discovery,
		},
		HostConfig: workerHostConfig,
	}

	return mcndockerclient.CreateContainer(dockerHost, swarmWorkerConfig, "swarm-agent")
}
<commit_msg>Configures swarm-master with failover support<commit_after>package provision

import (
	"fmt"
	"net/url"
	"strings"

	"github.com/docker/machine/libmachine/auth"
	"github.com/docker/machine/libmachine/log"
	"github.com/docker/machine/libmachine/mcndockerclient"
	"github.com/docker/machine/libmachine/swarm"
	"github.com/samalba/dockerclient"
)

func configureSwarm(p Provisioner, swarmOptions swarm.Options, authOptions auth.Options) error {
	if !swarmOptions.IsSwarm {
		return nil
	}

	log.Info("Configuring swarm...")

	ip, err := p.GetDriver().GetIP()
	if err != nil {
		return err
	}

	u, err := url.Parse(swarmOptions.Host)
	if err != nil {
		return err
	}

	parts := strings.Split(u.Host, ":")
	port := parts[1]

	dockerPort := "2376"
	dockerDir := p.GetDockerOptionsDir()
	dockerHost := &mcndockerclient.RemoteDocker{
		HostURL:    fmt.Sprintf("tcp://%s:%s", ip, dockerPort),
		AuthOption: &authOptions,
	}
	advertiseInfo := fmt.Sprintf("%s:%s", ip, dockerPort)

	if swarmOptions.Master {
		advertiseMasterInfo := fmt.Sprintf("%s:%s", ip, "3376")
		cmd := fmt.Sprintf("manage --tlsverify --tlscacert=%s --tlscert=%s --tlskey=%s -H %s --strategy %s --replication  --advertise %s",
			authOptions.CaCertRemotePath,
			authOptions.ServerCertRemotePath,
			authOptions.ServerKeyRemotePath,
			swarmOptions.Host,
			swarmOptions.Strategy,
			advertiseMasterInfo,
		)

		cmdMaster := strings.Fields(cmd)
		for _, option := range swarmOptions.ArbitraryFlags {
			cmdMaster = append(cmdMaster, "--"+option)
		}

		//Discovery must be at end of command
		cmdMaster = append(cmdMaster, swarmOptions.Discovery)

		hostBind := fmt.Sprintf("%s:%s", dockerDir, dockerDir)
		masterHostConfig := dockerclient.HostConfig{
			RestartPolicy: dockerclient.RestartPolicy{
				Name:              "always",
				MaximumRetryCount: 0,
			},
			Binds:        []string{hostBind},
			PortBindings: map[string][]dockerclient.PortBinding{"3376/tcp": {{"", port}}},
			NetworkMode:  "host",
		}

		swarmMasterConfig := &dockerclient.ContainerConfig{
			Image: swarmOptions.Image,
			Env:   swarmOptions.Env,
			ExposedPorts: map[string]struct{}{
				"2375/tcp": {},
				"3376/tcp": {},
			},
			Cmd:        cmdMaster,
			HostConfig: masterHostConfig,
		}

		err = mcndockerclient.CreateContainer(dockerHost, swarmMasterConfig, "swarm-agent-master")
		if err != nil {
			return err
		}
	}

	workerHostConfig := dockerclient.HostConfig{
		RestartPolicy: dockerclient.RestartPolicy{
			Name:              "always",
			MaximumRetryCount: 0,
		},
		NetworkMode: "host",
	}

	swarmWorkerConfig := &dockerclient.ContainerConfig{
		Image: swarmOptions.Image,
		Env:   swarmOptions.Env,
		Cmd: []string{
			"join",
			"--advertise",
			advertiseInfo,
			swarmOptions.Discovery,
		},
		HostConfig: workerHostConfig,
	}

	return mcndockerclient.CreateContainer(dockerHost, swarmWorkerConfig, "swarm-agent")
}
<|endoftext|>


<commit_before>package structor

import (
	. "github.com/101loops/bdd"
	"reflect"
)

var _ = Describe("Codec", func() {

	data := newTestData()

	Context("struct codec", func() {
		// TODO
	})

	Context("create field codec", func() {

		It("from exported field", func() {
			codec := newFieldCodec(reflect.ValueOf(data).Type(), 0, "test")

			Check(codec, NotNil)
			Check(codec.Index, Equals, 0)
			Check(codec.Name, Equals, "Dummy")
			Check(codec.Label, Equals, "dummytag")
			Check(*codec.Tag, Equals, TagCodec{Name: "dummytag"})
			Check(codec.Type, Equals, reflect.ValueOf("string").Type())
		})

		It("from hidden field", func() {
			codec := newFieldCodec(reflect.ValueOf(data).Type(), 2, "test")
			Check(codec, IsNil)
		})

		It("from unexported field", func() {
			codec := newFieldCodec(reflect.ValueOf(data).Type(), 3, "test")
			Check(codec, IsNil)
		})
	})

	Context("create tag codec", func() {

		It("from empty tag", func() {
			codec := newTagCodec("")
			Check(*codec, Equals, TagCodec{})
		})

		It("from tag with name", func() {
			codec := newTagCodec("name")
			Check(*codec, Equals, TagCodec{Name: "name"})
		})

		It("from tag with modifiers only", func() {
			codec := newTagCodec(",omitempty")
			Check(*codec, Equals, TagCodec{Name: "", Mods: []string{"omitempty"}})
		})

		It("from tag with name and modifiers", func() {
			codec := newTagCodec("name,omitempty")
			Check(*codec, Equals, TagCodec{Name: "name", Mods: []string{"omitempty"}})
		})
	})
})
<commit_msg>add even more test cases<commit_after>package structor

import (
	. "github.com/101loops/bdd"
	"reflect"
)

var _ = Describe("Codec", func() {

	data := newTestData()

	Context("struct codec", func() {

		set := NewSet("test")

		It("from struct", func() {
			rTyp := reflect.ValueOf(data).Type()
			codec := newCodec(rTyp, set)

			Check(codec, NotNil)
			Check(codec.Type(), Equals, rTyp)
			Check(codec.FieldNames(), Equals, []string{"Dummy", "Yummy"})

			fields := codec.Fields()
			Check(fields, HasLen, 2)
			Check(fields[0].Name, Equals, "Dummy")
			Check(fields[1].Name, Equals, "Yummy")
		})
	})

	Context("create field codec", func() {

		It("from exported field", func() {
			codec := newFieldCodec(reflect.ValueOf(data).Type(), 0, "test")

			Check(codec, NotNil)
			Check(codec.Index, Equals, 0)
			Check(codec.Name, Equals, "Dummy")
			Check(codec.Label, Equals, "dummytag")
			Check(*codec.Tag, Equals, TagCodec{Name: "dummytag"})
			Check(codec.Type, Equals, reflect.ValueOf("string").Type())
		})

		It("from hidden field", func() {
			codec := newFieldCodec(reflect.ValueOf(data).Type(), 2, "test")
			Check(codec, IsNil)
		})

		It("from unexported field", func() {
			codec := newFieldCodec(reflect.ValueOf(data).Type(), 3, "test")
			Check(codec, IsNil)
		})
	})

	Context("create tag codec", func() {

		It("from empty tag", func() {
			codec := newTagCodec("")
			Check(*codec, Equals, TagCodec{})
		})

		It("from tag with name", func() {
			codec := newTagCodec("name")
			Check(*codec, Equals, TagCodec{Name: "name"})
		})

		It("from tag with modifiers only", func() {
			codec := newTagCodec(",omitempty")
			Check(*codec, Equals, TagCodec{Name: "", Mods: []string{"omitempty"}})
		})

		It("from tag with name and modifiers", func() {
			codec := newTagCodec("name,omitempty")
			Check(*codec, Equals, TagCodec{Name: "name", Mods: []string{"omitempty"}})
		})
	})
})
<|endoftext|>


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

// Package text offers functions to draw texts on an Ebiten's image.
//
// For the example using a TTF font, see font package in the examples.
package text

import (
	"image"
	"image/color"
	"math"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

var (
	monotonicClock int64
)

func now() int64 {
	monotonicClock++
	return monotonicClock
}

func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
	return float64(x) / (1 << 6)
}

const (
	cacheLimit = 512 // This is an arbitrary number.
)

type colorMCacheKey uint32

type colorMCacheEntry struct {
	m     ebiten.ColorM
	atime int64
}

var (
	colorMCache = map[colorMCacheKey]*colorMCacheEntry{}
)

func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *glyphImage, x, y fixed.Int26_6, clr color.Color) {
	if img == nil {
		return
	}

	// RGBA() is in [0 - 0xffff]. Adjust them in [0 - 0xff].
	cr, cg, cb, ca := clr.RGBA()
	cr >>= 8
	cg >>= 8
	cb >>= 8
	ca >>= 8
	if ca == 0 {
		return
	}

	b := getGlyphBounds(face, r)
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(fixed26_6ToFloat64(x+b.Min.X), fixed26_6ToFloat64(y+b.Min.Y))

	key := colorMCacheKey(uint32(cr) | (uint32(cg) << 8) | (uint32(cb) << 16) | (uint32(ca) << 24))
	e, ok := colorMCache[key]
	if ok {
		e.atime = now()
	} else {
		if len(colorMCache) > cacheLimit {
			oldest := int64(math.MaxInt64)
			oldestKey := colorMCacheKey(0)
			for key, c := range colorMCache {
				if c.atime < oldest {
					oldestKey = key
					oldest = c.atime
				}
			}
			delete(colorMCache, oldestKey)
		}

		cm := ebiten.ColorM{}
		rf := float64(cr) / float64(ca)
		gf := float64(cg) / float64(ca)
		bf := float64(cb) / float64(ca)
		af := float64(ca) / 0xff
		cm.Scale(rf, gf, bf, af)
		e = &colorMCacheEntry{
			m:     cm,
			atime: now(),
		}
		colorMCache[key] = e
	}
	op.ColorM = e.m
	re := image.Rect(img.x, img.y, img.x+img.width, img.y+img.height)
	op.SourceRect = &re

	_ = dst.DrawImage(img.image, op)
}

var (
	// Use pointers to avoid copying on browsers.
	glyphBoundsCache = map[font.Face]map[rune]*fixed.Rectangle26_6{}
)

func getGlyphBounds(face font.Face, r rune) *fixed.Rectangle26_6 {
	if _, ok := glyphBoundsCache[face]; !ok {
		glyphBoundsCache[face] = map[rune]*fixed.Rectangle26_6{}
	}
	if b, ok := glyphBoundsCache[face][r]; ok {
		return b
	}
	b, _, _ := face.GlyphBounds(r)
	glyphBoundsCache[face][r] = &b
	return &b
}

type glyphImage struct {
	image  *ebiten.Image
	x      int
	y      int
	width  int
	height int
}

type glyphImageCacheEntry struct {
	image *glyphImage
	atime int64
}

var (
	glyphImageCache = map[font.Face]map[rune]*glyphImageCacheEntry{}
	emptyGlyphs     = map[font.Face]map[rune]struct{}{}
)

func getGlyphImages(face font.Face, runes []rune) []*glyphImage {
	if _, ok := emptyGlyphs[face]; !ok {
		emptyGlyphs[face] = map[rune]struct{}{}
	}
	if _, ok := glyphImageCache[face]; !ok {
		glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
	}

	imgs := make([]*glyphImage, len(runes))
	neededGlyphs := map[int]*fixed.Rectangle26_6{}
	for i, r := range runes {
		if _, ok := emptyGlyphs[face][r]; ok {
			continue
		}

		if e, ok := glyphImageCache[face][r]; ok {
			e.atime = now()
			imgs[i] = e.image
			continue
		}

		b := getGlyphBounds(face, r)
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
		if w == 0 || h == 0 {
			emptyGlyphs[face][r] = struct{}{}
			continue
		}

		// TODO: What if len(runes) > cacheLimit?
		if len(glyphImageCache[face]) > cacheLimit {
			oldest := int64(math.MaxInt64)
			oldestKey := rune(-1)
			for r, e := range glyphImageCache[face] {
				if e.atime < oldest {
					oldestKey = r
					oldest = e.atime
				}
			}
			delete(glyphImageCache[face], oldestKey)
		}

		neededGlyphs[i] = b
	}

	w2 := 0
	h2 := 0
	for _, b := range neededGlyphs {
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
		w2 += w
		if h2 < h {
			h2 = h
		}
	}
	rgba := image.NewRGBA(image.Rect(0, 0, w2, h2))

	x := 0
	for i, b := range neededGlyphs {
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()

		r := runes[i]
		d := font.Drawer{
			Dst:  rgba,
			Src:  image.White,
			Face: face,
		}
		d.Dot = fixed.Point26_6{fixed.I(x) - b.Min.X, -b.Min.Y}
		d.DrawString(string(r))

		img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault)
		g := &glyphImage{
			image:  img,
			x:      x,
			y:      0,
			width:  w,
			height: h,
		}
		glyphImageCache[face][r] = &glyphImageCacheEntry{
			image: g,
			atime: now(),
		}
		imgs[i] = g

		x += w
	}
	return imgs
}

var textM sync.Mutex

// Draw draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// (x, y) represents a 'dot' (period) position.
// Be careful that this doesn't represent left-upper corner position.
// clr is the color for text rendering.
//
// Glyphs used for rendering are cached in least-recently-used way.
// It is OK to call this function with a same text and a same face at every frame in terms of performance.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// This function is concurrent-safe.
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
	textM.Lock()

	fx := fixed.I(x)
	prevR := rune(-1)

	runes := []rune(text)
	glyphImgs := getGlyphImages(face, runes)
	for i, r := range runes {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		drawGlyph(dst, face, r, glyphImgs[i], fx, fixed.I(y), clr)
		fx += glyphAdvance(face, r)

		prevR = r
	}

	textM.Unlock()
}
<commit_msg>text: Optimization: reduce ColorM calc<commit_after>// Copyright 2017 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package text offers functions to draw texts on an Ebiten's image.
//
// For the example using a TTF font, see font package in the examples.
package text

import (
	"image"
	"image/color"
	"math"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

var (
	monotonicClock int64
)

func now() int64 {
	monotonicClock++
	return monotonicClock
}

func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
	return float64(x) / (1 << 6)
}

const (
	cacheLimit = 512 // This is an arbitrary number.
)

type colorMCacheKey uint32

type colorMCacheEntry struct {
	m     ebiten.ColorM
	atime int64
}

func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *glyphImage, x, y fixed.Int26_6, clr ebiten.ColorM) {
	if img == nil {
		return
	}

	b := getGlyphBounds(face, r)
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(fixed26_6ToFloat64(x+b.Min.X), fixed26_6ToFloat64(y+b.Min.Y))

	op.ColorM = clr
	re := image.Rect(img.x, img.y, img.x+img.width, img.y+img.height)
	op.SourceRect = &re

	_ = dst.DrawImage(img.image, op)
}

var (
	// Use pointers to avoid copying on browsers.
	glyphBoundsCache = map[font.Face]map[rune]*fixed.Rectangle26_6{}
)

func getGlyphBounds(face font.Face, r rune) *fixed.Rectangle26_6 {
	if _, ok := glyphBoundsCache[face]; !ok {
		glyphBoundsCache[face] = map[rune]*fixed.Rectangle26_6{}
	}
	if b, ok := glyphBoundsCache[face][r]; ok {
		return b
	}
	b, _, _ := face.GlyphBounds(r)
	glyphBoundsCache[face][r] = &b
	return &b
}

type glyphImage struct {
	image  *ebiten.Image
	x      int
	y      int
	width  int
	height int
}

type glyphImageCacheEntry struct {
	image *glyphImage
	atime int64
}

var (
	glyphImageCache = map[font.Face]map[rune]*glyphImageCacheEntry{}
	emptyGlyphs     = map[font.Face]map[rune]struct{}{}
)

func getGlyphImages(face font.Face, runes []rune) []*glyphImage {
	if _, ok := emptyGlyphs[face]; !ok {
		emptyGlyphs[face] = map[rune]struct{}{}
	}
	if _, ok := glyphImageCache[face]; !ok {
		glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
	}

	imgs := make([]*glyphImage, len(runes))
	neededGlyphs := map[int]*fixed.Rectangle26_6{}
	for i, r := range runes {
		if _, ok := emptyGlyphs[face][r]; ok {
			continue
		}

		if e, ok := glyphImageCache[face][r]; ok {
			e.atime = now()
			imgs[i] = e.image
			continue
		}

		b := getGlyphBounds(face, r)
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
		if w == 0 || h == 0 {
			emptyGlyphs[face][r] = struct{}{}
			continue
		}

		// TODO: What if len(runes) > cacheLimit?
		if len(glyphImageCache[face]) > cacheLimit {
			oldest := int64(math.MaxInt64)
			oldestKey := rune(-1)
			for r, e := range glyphImageCache[face] {
				if e.atime < oldest {
					oldestKey = r
					oldest = e.atime
				}
			}
			delete(glyphImageCache[face], oldestKey)
		}

		neededGlyphs[i] = b
	}

	// TODO: What if w2 is too big (e.g. > 4096)?
	w2 := 0
	h2 := 0
	for _, b := range neededGlyphs {
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
		w2 += w
		if h2 < h {
			h2 = h
		}
	}
	rgba := image.NewRGBA(image.Rect(0, 0, w2, h2))

	x := 0
	for i, b := range neededGlyphs {
		w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()

		r := runes[i]
		d := font.Drawer{
			Dst:  rgba,
			Src:  image.White,
			Face: face,
		}
		d.Dot = fixed.Point26_6{fixed.I(x) - b.Min.X, -b.Min.Y}
		d.DrawString(string(r))

		img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault)
		g := &glyphImage{
			image:  img,
			x:      x,
			y:      0,
			width:  w,
			height: h,
		}
		glyphImageCache[face][r] = &glyphImageCacheEntry{
			image: g,
			atime: now(),
		}
		imgs[i] = g

		x += w
	}
	return imgs
}

var textM sync.Mutex

var (
	colorMCache = map[colorMCacheKey]*colorMCacheEntry{}
	emptyColorM ebiten.ColorM
)

func init() {
	emptyColorM.Scale(0, 0, 0, 0)
}

func colorToColorM(clr color.Color) ebiten.ColorM {
	// RGBA() is in [0 - 0xffff]. Adjust them in [0 - 0xff].
	cr, cg, cb, ca := clr.RGBA()
	cr >>= 8
	cg >>= 8
	cb >>= 8
	ca >>= 8
	if ca == 0 {
		return emptyColorM
	}
	key := colorMCacheKey(uint32(cr) | (uint32(cg) << 8) | (uint32(cb) << 16) | (uint32(ca) << 24))
	e, ok := colorMCache[key]
	if ok {
		e.atime = now()
		return e.m
	}
	if len(colorMCache) > cacheLimit {
		oldest := int64(math.MaxInt64)
		oldestKey := colorMCacheKey(0)
		for key, c := range colorMCache {
			if c.atime < oldest {
				oldestKey = key
				oldest = c.atime
			}
		}
		delete(colorMCache, oldestKey)
	}

	cm := ebiten.ColorM{}
	rf := float64(cr) / float64(ca)
	gf := float64(cg) / float64(ca)
	bf := float64(cb) / float64(ca)
	af := float64(ca) / 0xff
	cm.Scale(rf, gf, bf, af)
	e = &colorMCacheEntry{
		m:     cm,
		atime: now(),
	}
	colorMCache[key] = e

	return e.m
}

// Draw draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// (x, y) represents a 'dot' (period) position.
// Be careful that this doesn't represent left-upper corner position.
// clr is the color for text rendering.
//
// Glyphs used for rendering are cached in least-recently-used way.
// It is OK to call this function with a same text and a same face at every frame in terms of performance.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// This function is concurrent-safe.
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
	textM.Lock()

	fx := fixed.I(x)
	prevR := rune(-1)

	runes := []rune(text)
	glyphImgs := getGlyphImages(face, runes)
	colorm := colorToColorM(clr)

	for i, r := range runes {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		drawGlyph(dst, face, r, glyphImgs[i], fx, fixed.I(y), colorm)
		fx += glyphAdvance(face, r)

		prevR = r
	}

	textM.Unlock()
}
<|endoftext|>


<commit_before>package text

import (
	"image/color"
	"math"
	"unicode"
	"unicode/utf8"

	"github.com/faiface/pixel"
	"golang.org/x/image/font"
)

var ASCII []rune

func init() {
	ASCII = make([]rune, unicode.MaxASCII-32)
	for i := range ASCII {
		ASCII[i] = rune(32 + i)
	}
}

func RangeTable(table *unicode.RangeTable) []rune {
	var runes []rune
	for _, rng := range table.R16 {
		for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
			runes = append(runes, rune(r))
		}
	}
	for _, rng := range table.R32 {
		for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
			runes = append(runes, rune(r))
		}
	}
	return runes
}

type Text struct {
	Orig pixel.Vec
	Dot  pixel.Vec

	atlas *Atlas

	lineHeight float64
	tabWidth   float64

	prevR  rune
	bounds pixel.Rect
	glyph  pixel.TrianglesData
	tris   pixel.TrianglesData

	mat    pixel.Matrix
	col    pixel.RGBA
	trans  pixel.TrianglesData
	transD pixel.Drawer
	dirty  bool
}

func New(face font.Face, runeSets ...[]rune) *Text {
	runes := []rune{unicode.ReplacementChar}
	for _, set := range runeSets {
		runes = append(runes, set...)
	}

	atlas := NewAtlas(face, runes)

	txt := &Text{
		atlas:      atlas,
		lineHeight: atlas.LineHeight(),
		tabWidth:   atlas.Glyph(' ').Advance * 4,
		mat:        pixel.IM,
		col:        pixel.Alpha(1),
	}

	txt.glyph.SetLen(6)
	for i := range txt.glyph {
		txt.glyph[i].Color = pixel.Alpha(1)
		txt.glyph[i].Intensity = 1
	}

	txt.transD.Picture = txt.atlas.pic
	txt.transD.Triangles = &txt.trans

	txt.Clear()

	return txt
}

func (txt *Text) Atlas() *Atlas {
	return txt.atlas
}

func (txt *Text) SetMatrix(m pixel.Matrix) {
	if txt.mat != m {
		txt.mat = m
		txt.dirty = true
	}
}

func (txt *Text) SetColorMask(c color.Color) {
	rgba := pixel.ToRGBA(c)
	if txt.col != rgba {
		txt.col = rgba
		txt.dirty = true
	}
}

func (txt *Text) Bounds() pixel.Rect {
	return txt.bounds
}

func (txt *Text) Color(c color.Color) {
	rgba := pixel.ToRGBA(c)
	for i := range txt.glyph {
		txt.glyph[i].Color = rgba
	}
}

func (txt *Text) LineHeight(scale float64) {
	txt.lineHeight = scale
}

func (txt *Text) TabWidth(width float64) {
	txt.tabWidth = width
}

func (txt *Text) Clear() {
	txt.prevR = -1
	txt.bounds = pixel.Rect{}
	txt.tris.SetLen(0)
	txt.dirty = true
}

func (txt *Text) Write(p []byte) (n int, err error) {
	n, err = len(p), nil // always returns this

	if len(p) == 0 {
		return
	}

	for len(p) > 0 {
		r, size := utf8.DecodeRune(p)
		p = p[size:]
		txt.WriteRune(r)
	}

	return
}

func (txt *Text) WriteString(s string) (n int, err error) {
	if len(s) == 0 {
		return
	}

	for _, r := range s {
		txt.WriteRune(r)
	}

	return len(s), nil
}

func (txt *Text) WriteByte(c byte) error {
	//FIXME: this is not correct, what if I want to write a 4-byte rune byte by byte?
	_, err := txt.WriteRune(rune(c))
	return err
}

func (txt *Text) WriteRune(r rune) (n int, err error) {
	n, err = utf8.RuneLen(r), nil // always returns this

	switch r {
	case '\n':
		txt.Dot -= pixel.Y(txt.lineHeight)
		txt.Dot = txt.Dot.WithX(txt.Orig.X())
		return
	case '\r':
		txt.Dot = txt.Dot.WithX(txt.Orig.X())
		return
	case '\t':
		rem := math.Mod(txt.Dot.X()-txt.Orig.X(), txt.tabWidth)
		rem = math.Mod(rem, rem+txt.tabWidth)
		if rem == 0 {
			rem = txt.tabWidth
		}
		txt.Dot += pixel.X(rem)
		return
	}

	if !txt.atlas.Contains(r) {
		r = unicode.ReplacementChar
	}
	if !txt.atlas.Contains(unicode.ReplacementChar) {
		return
	}

	glyph := txt.atlas.Glyph(r)

	if txt.prevR >= 0 {
		txt.Dot += pixel.X(txt.atlas.Kern(txt.prevR, r))
	}

	glyphBounds := glyph.Frame.Moved(txt.Dot - glyph.Orig)
	if glyphBounds.W()*glyphBounds.H() != 0 {
		glyphBounds = pixel.R(
			glyphBounds.Min.X(),
			txt.Dot.Y()-txt.Atlas().Descent(),
			glyphBounds.Max.X(),
			txt.Dot.Y()+txt.Atlas().Ascent(),
		)
		if txt.bounds.W()*txt.bounds.H() == 0 {
			txt.bounds = glyphBounds
		} else {
			txt.bounds = txt.bounds.Union(glyphBounds)
		}
	}

	a := pixel.V(glyph.Frame.Min.X(), glyph.Frame.Min.Y())
	b := pixel.V(glyph.Frame.Max.X(), glyph.Frame.Min.Y())
	c := pixel.V(glyph.Frame.Max.X(), glyph.Frame.Max.Y())
	d := pixel.V(glyph.Frame.Min.X(), glyph.Frame.Max.Y())

	for i, v := range []pixel.Vec{a, b, c, a, c, d} {
		txt.glyph[i].Position = v - glyph.Orig + txt.Dot
		txt.glyph[i].Picture = v
	}

	txt.tris = append(txt.tris, txt.glyph...)

	txt.Dot += pixel.X(glyph.Advance)
	txt.prevR = r

	txt.dirty = true

	return
}

func (txt *Text) Draw(t pixel.Target) {
	if txt.dirty {
		txt.trans.SetLen(txt.tris.Len())
		txt.trans.Update(&txt.tris)
		for i := range txt.trans {
			txt.trans[i].Position = txt.mat.Project(txt.trans[i].Position)
			txt.trans[i].Color = txt.trans[i].Color.Mul(txt.col)
		}
		txt.transD.Dirty()
		txt.dirty = false
	}
	txt.transD.Draw(t)
}
<commit_msg>restructure Text writing for more flexibility and consistency<commit_after>package text

import (
	"image/color"
	"math"
	"unicode"
	"unicode/utf8"

	"github.com/faiface/pixel"
	"golang.org/x/image/font"
)

var ASCII []rune

func init() {
	ASCII = make([]rune, unicode.MaxASCII-32)
	for i := range ASCII {
		ASCII[i] = rune(32 + i)
	}
}

func RangeTable(table *unicode.RangeTable) []rune {
	var runes []rune
	for _, rng := range table.R16 {
		for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
			runes = append(runes, rune(r))
		}
	}
	for _, rng := range table.R32 {
		for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
			runes = append(runes, rune(r))
		}
	}
	return runes
}

type Text struct {
	Orig pixel.Vec
	Dot  pixel.Vec

	atlas *Atlas

	lineHeight float64
	tabWidth   float64

	buf    []byte
	prevR  rune
	bounds pixel.Rect
	glyph  pixel.TrianglesData
	tris   pixel.TrianglesData

	mat    pixel.Matrix
	col    pixel.RGBA
	trans  pixel.TrianglesData
	transD pixel.Drawer
	dirty  bool
}

func New(face font.Face, runeSets ...[]rune) *Text {
	runes := []rune{unicode.ReplacementChar}
	for _, set := range runeSets {
		runes = append(runes, set...)
	}

	atlas := NewAtlas(face, runes)

	txt := &Text{
		atlas:      atlas,
		lineHeight: atlas.LineHeight(),
		tabWidth:   atlas.Glyph(' ').Advance * 4,
		mat:        pixel.IM,
		col:        pixel.Alpha(1),
	}

	txt.glyph.SetLen(6)
	for i := range txt.glyph {
		txt.glyph[i].Color = pixel.Alpha(1)
		txt.glyph[i].Intensity = 1
	}

	txt.transD.Picture = txt.atlas.pic
	txt.transD.Triangles = &txt.trans

	txt.Clear()

	return txt
}

func (txt *Text) Atlas() *Atlas {
	return txt.atlas
}

func (txt *Text) SetMatrix(m pixel.Matrix) {
	if txt.mat != m {
		txt.mat = m
		txt.dirty = true
	}
}

func (txt *Text) SetColorMask(c color.Color) {
	rgba := pixel.ToRGBA(c)
	if txt.col != rgba {
		txt.col = rgba
		txt.dirty = true
	}
}

func (txt *Text) Bounds() pixel.Rect {
	return txt.bounds
}

func (txt *Text) Color(c color.Color) {
	rgba := pixel.ToRGBA(c)
	for i := range txt.glyph {
		txt.glyph[i].Color = rgba
	}
}

func (txt *Text) LineHeight(scale float64) {
	txt.lineHeight = scale
}

func (txt *Text) TabWidth(width float64) {
	txt.tabWidth = width
}

func (txt *Text) Clear() {
	txt.prevR = -1
	txt.bounds = pixel.Rect{}
	txt.tris.SetLen(0)
	txt.dirty = true
}

func (txt *Text) Write(p []byte) (n int, err error) {
	txt.buf = append(txt.buf, p...)
	txt.drawBuf()
	return len(p), nil
}

func (txt *Text) WriteString(s string) (n int, err error) {
	txt.buf = append(txt.buf, s...)
	txt.drawBuf()
	return len(s), nil
}

func (txt *Text) WriteByte(c byte) error {
	txt.buf = append(txt.buf, c)
	txt.drawBuf()
	return nil
}

func (txt *Text) WriteRune(r rune) (n int, err error) {
	var b [4]byte
	n = utf8.EncodeRune(b[:], r)
	txt.buf = append(txt.buf, b[:n]...)
	txt.drawBuf()
	return n, nil
}

func (txt *Text) drawBuf() {
	for utf8.FullRune(txt.buf) {
		r, size := utf8.DecodeRune(txt.buf)
		txt.buf = txt.buf[size:]

		switch r {
		case '\n':
			txt.Dot -= pixel.Y(txt.lineHeight)
			txt.Dot = txt.Dot.WithX(txt.Orig.X())
			continue
		case '\r':
			txt.Dot = txt.Dot.WithX(txt.Orig.X())
			continue
		case '\t':
			rem := math.Mod(txt.Dot.X()-txt.Orig.X(), txt.tabWidth)
			rem = math.Mod(rem, rem+txt.tabWidth)
			if rem == 0 {
				rem = txt.tabWidth
			}
			txt.Dot += pixel.X(rem)
			continue
		}

		var rect, frame, bounds pixel.Rect
		rect, frame, bounds, txt.Dot = txt.Atlas().DrawRune(txt.prevR, r, txt.Dot)

		txt.prevR = r

		rv := [...]pixel.Vec{pixel.V(rect.Min.X(), rect.Min.Y()),
			pixel.V(rect.Max.X(), rect.Min.Y()),
			pixel.V(rect.Max.X(), rect.Max.Y()),
			pixel.V(rect.Min.X(), rect.Max.Y()),
		}

		fv := [...]pixel.Vec{pixel.V(frame.Min.X(), frame.Min.Y()),
			pixel.V(frame.Max.X(), frame.Min.Y()),
			pixel.V(frame.Max.X(), frame.Max.Y()),
			pixel.V(frame.Min.X(), frame.Max.Y()),
		}

		for i, j := range [...]int{0, 1, 2, 0, 2, 3} {
			txt.glyph[i].Position = rv[j]
			txt.glyph[i].Picture = fv[j]
		}

		txt.tris = append(txt.tris, txt.glyph...)
		txt.dirty = true

		if txt.bounds.W()*txt.bounds.H() == 0 {
			txt.bounds = bounds
		} else {
			txt.bounds = txt.bounds.Union(bounds)
		}
	}
}

func (txt *Text) Draw(t pixel.Target) {
	if txt.dirty {
		txt.trans.SetLen(txt.tris.Len())
		txt.trans.Update(&txt.tris)
		for i := range txt.trans {
			txt.trans[i].Position = txt.mat.Project(txt.trans[i].Position)
			txt.trans[i].Color = txt.trans[i].Color.Mul(txt.col)
		}
		txt.transD.Dirty()
		txt.dirty = false
	}
	txt.transD.Draw(t)
}
<|endoftext|>


<commit_before>package http

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"regexp"
	"testing"
	"time"

	"github.com/influxdata/flux/csv"
	"github.com/influxdata/flux/lang"
	platform "github.com/influxdata/influxdb"
	"github.com/influxdata/influxdb/query"
)

func TestFluxService_Query(t *testing.T) {
	tests := []struct {
		name    string
		token   string
		ctx     context.Context
		r       *query.ProxyRequest
		status  int
		want    int64
		wantW   string
		wantErr bool
	}{
		{
			name:  "query",
			ctx:   context.Background(),
			token: "mytoken",
			r: &query.ProxyRequest{
				Request: query.Request{
					Compiler: lang.FluxCompiler{
						Query: "from()",
					},
				},
				Dialect: csv.DefaultDialect(),
			},
			status: http.StatusOK,
			want:   6,
			wantW:  "howdy\n",
		},
		{
			name:  "error status",
			token: "mytoken",
			ctx:   context.Background(),
			r: &query.ProxyRequest{
				Request: query.Request{
					Compiler: lang.FluxCompiler{
						Query: "from()",
					},
				},
				Dialect: csv.DefaultDialect(),
			},
			status:  http.StatusUnauthorized,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(tt.status)
				fmt.Fprintln(w, "howdy")
			}))
			defer ts.Close()
			s := &FluxService{
				Addr:  ts.URL,
				Token: tt.token,
			}

			w := &bytes.Buffer{}
			got, err := s.Query(tt.ctx, w, tt.r)
			if (err != nil) != tt.wantErr {
				t.Errorf("FluxService.Query() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("FluxService.Query() = %v, want %v", got, tt.want)
			}
			if gotW := w.String(); gotW != tt.wantW {
				t.Errorf("FluxService.Query() = %v, want %v", gotW, tt.wantW)
			}
		})
	}
}

func TestFluxQueryService_Query(t *testing.T) {
	var orgID platform.ID
	orgID.DecodeFromString("aaaaaaaaaaaaaaaa")
	tests := []struct {
		name    string
		token   string
		ctx     context.Context
		r       *query.Request
		csv     string
		status  int
		want    string
		wantErr bool
	}{
		{
			name:  "error status",
			token: "mytoken",
			ctx:   context.Background(),
			r: &query.Request{
				OrganizationID: orgID,
				Compiler: lang.FluxCompiler{
					Query: "from()",
				},
			},
			status:  http.StatusUnauthorized,
			wantErr: true,
		},
		{
			name:  "returns csv",
			token: "mytoken",
			ctx:   context.Background(),
			r: &query.Request{
				OrganizationID: orgID,
				Compiler: lang.FluxCompiler{
					Query: "from()",
				},
			},
			status: http.StatusOK,
			csv: `#datatype,string,long,dateTime:RFC3339,double,long,string,boolean,string,string,string
#group,false,false,false,false,false,false,false,true,true,true
#default,_result,,,,,,,,,
,result,table,_time,usage_user,test,mystr,this,cpu,host,_measurement
,,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui
`,
			want: toCRLF(`,0,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui

`),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var orgIDStr string
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				orgIDStr = r.URL.Query().Get(OrgID)
				w.WriteHeader(tt.status)
				fmt.Fprintln(w, tt.csv)
			}))
			s := &FluxQueryService{
				Addr:  ts.URL,
				Token: tt.token,
			}
			res, err := s.Query(tt.ctx, tt.r)
			if (err != nil) != tt.wantErr {
				t.Errorf("FluxQueryService.Query() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if res != nil && res.Err() != nil {
				t.Errorf("FluxQueryService.Query() result error = %v", res.Err())
				return
			}
			if tt.wantErr {
				return
			}
			defer res.Release()

			enc := csv.NewMultiResultEncoder(csv.ResultEncoderConfig{
				NoHeader:  true,
				Delimiter: ',',
			})
			b := bytes.Buffer{}
			n, err := enc.Encode(&b, res)
			if err != nil {
				t.Errorf("FluxQueryService.Query() encode error = %v", err)
				return
			}
			if n != int64(len(tt.want)) {
				t.Errorf("FluxQueryService.Query() encode result = %d, want %d", n, len(tt.want))
			}
			if orgIDStr == "" {
				t.Error("FluxQueryService.Query() encoded orgID is empty")
			}
			if got, want := orgIDStr, tt.r.OrganizationID.String(); got != want {
				t.Errorf("FluxQueryService.Query() encoded orgID = %s, want %s", got, want)
			}

			got := b.String()
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("FluxQueryService.Query() =\n%s\n%s", got, tt.want)
			}
		})
	}
}

func TestFluxHandler_postFluxAST(t *testing.T) {
	tests := []struct {
		name   string
		w      *httptest.ResponseRecorder
		r      *http.Request
		want   string
		status int
	}{
		{
			name: "get ast from()",
			w:    httptest.NewRecorder(),
			r:    httptest.NewRequest("POST", "/api/v2/query/ast", bytes.NewBufferString(`{"query": "from()"}`)),
			want: `{"ast":{"type":"Package","package":"main","files":[{"type":"File","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"package":null,"imports":null,"body":[{"type":"ExpressionStatement","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"expression":{"type":"CallExpression","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"callee":{"type":"Identifier","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":5},"source":"from"},"name":"from"}}}]}]}}
`,
			status: http.StatusOK,
		},
		{
			name:   "error from bad json",
			w:      httptest.NewRecorder(),
			r:      httptest.NewRequest("POST", "/api/v2/query/ast", bytes.NewBufferString(`error!`)),
			want:   `{"code":"invalid","message":"invalid json","error":"invalid character 'e' looking for beginning of value"}`,
			status: http.StatusBadRequest,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := &FluxHandler{}
			h.postFluxAST(tt.w, tt.r)
			if got := tt.w.Body.String(); got != tt.want {
				t.Errorf("http.postFluxAST = got\n%vwant\n%v", got, tt.want)
			}
			if got := tt.w.Code; got != tt.status {
				t.Errorf("http.postFluxAST = got %d\nwant %d", got, tt.status)
			}
		})
	}
}

func TestFluxHandler_postFluxSpec(t *testing.T) {
	tests := []struct {
		name   string
		w      *httptest.ResponseRecorder
		r      *http.Request
		now    func() time.Time
		want   string
		status int
	}{
		{
			name: "get spec from()",
			w:    httptest.NewRecorder(),
			r:    httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`{"query": "from(bucket: \"telegraf\")"}`)),
			now:  func() time.Time { return time.Unix(0, 0).UTC() },
			want: `{"spec":{"operations":[{"kind":"influxDBFrom","id":"influxDBFrom0","spec":{"bucket":"telegraf"}}],"edges":null,"resources":{"priority":"high","concurrency_quota":0,"memory_bytes_quota":0},"now":"1970-01-01T00:00:00Z"}}
`,
			status: http.StatusOK,
		},
		{
			name:   "error from bad json",
			w:      httptest.NewRecorder(),
			r:      httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`error!`)),
			want:   `{"code":"invalid","message":"invalid json","error":"invalid character 'e' looking for beginning of value"}`,
			status: http.StatusBadRequest,
		},
		{
			name:   "error from incomplete spec",
			w:      httptest.NewRecorder(),
			r:      httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`{"query": "from()"}`)),
			now:    func() time.Time { return time.Unix(0, 0).UTC() },
			want:   `{"code":"unprocessable entity","message":"invalid spec","error":"error calling function \"from\": must specify one of bucket or bucketID"}`,
			status: http.StatusUnprocessableEntity,
		},
		{
			name: "get spec with range and last",
			w:    httptest.NewRecorder(),
			r:    httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`{"query": "from(bucket:\"demo-bucket-in-1\") |> range(start:-2s) |> last()"}`)),
			now:  func() time.Time { return time.Unix(0, 0).UTC() },
			want: `{"spec":{"operations":[{"kind":"influxDBFrom","id":"influxDBFrom0","spec":{"bucket":"demo-bucket-in-1"}},{"kind":"range","id":"range1","spec":{"start":"-2s","stop":"now","timeColumn":"_time","startColumn":"_start","stopColumn":"_stop"}},{"kind":"last","id":"last2","spec":{"column":""}}],"edges":[{"parent":"influxDBFrom0","child":"range1"},{"parent":"range1","child":"last2"}],"resources":{"priority":"high","concurrency_quota":0,"memory_bytes_quota":0},"now":"1970-01-01T00:00:00Z"}}
`,
			status: http.StatusOK,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := &FluxHandler{
				Now: tt.now,
			}
			h.postFluxSpec(tt.w, tt.r)
			if got := tt.w.Body.String(); got != tt.want {
				t.Errorf("http.postFluxSpec = got %s\nwant %s", got, tt.want)
			}

			if got := tt.w.Code; got != tt.status {
				t.Errorf("http.postFluxSpec = got %d\nwant %d", got, tt.status)
				t.Log(tt.w.Header())
			}
		})
	}
}

var crlfPattern = regexp.MustCompile(`\r?\n`)

func toCRLF(data string) string {
	return crlfPattern.ReplaceAllString(data, "\r\n")
}
<commit_msg>Fix test case<commit_after>package http

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"regexp"
	"testing"
	"time"

	"github.com/influxdata/flux/csv"
	"github.com/influxdata/flux/lang"
	platform "github.com/influxdata/influxdb"
	"github.com/influxdata/influxdb/query"
)

func TestFluxService_Query(t *testing.T) {
	tests := []struct {
		name    string
		token   string
		ctx     context.Context
		r       *query.ProxyRequest
		status  int
		want    int64
		wantW   string
		wantErr bool
	}{
		{
			name:  "query",
			ctx:   context.Background(),
			token: "mytoken",
			r: &query.ProxyRequest{
				Request: query.Request{
					Compiler: lang.FluxCompiler{
						Query: "from()",
					},
				},
				Dialect: csv.DefaultDialect(),
			},
			status: http.StatusOK,
			want:   6,
			wantW:  "howdy\n",
		},
		{
			name:  "error status",
			token: "mytoken",
			ctx:   context.Background(),
			r: &query.ProxyRequest{
				Request: query.Request{
					Compiler: lang.FluxCompiler{
						Query: "from()",
					},
				},
				Dialect: csv.DefaultDialect(),
			},
			status:  http.StatusUnauthorized,
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(tt.status)
				fmt.Fprintln(w, "howdy")
			}))
			defer ts.Close()
			s := &FluxService{
				Addr:  ts.URL,
				Token: tt.token,
			}

			w := &bytes.Buffer{}
			got, err := s.Query(tt.ctx, w, tt.r)
			if (err != nil) != tt.wantErr {
				t.Errorf("FluxService.Query() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("FluxService.Query() = %v, want %v", got, tt.want)
			}
			if gotW := w.String(); gotW != tt.wantW {
				t.Errorf("FluxService.Query() = %v, want %v", gotW, tt.wantW)
			}
		})
	}
}

func TestFluxQueryService_Query(t *testing.T) {
	var orgID platform.ID
	orgID.DecodeFromString("aaaaaaaaaaaaaaaa")
	tests := []struct {
		name    string
		token   string
		ctx     context.Context
		r       *query.Request
		csv     string
		status  int
		want    string
		wantErr bool
	}{
		{
			name:  "error status",
			token: "mytoken",
			ctx:   context.Background(),
			r: &query.Request{
				OrganizationID: orgID,
				Compiler: lang.FluxCompiler{
					Query: "from()",
				},
			},
			status:  http.StatusUnauthorized,
			wantErr: true,
		},
		{
			name:  "returns csv",
			token: "mytoken",
			ctx:   context.Background(),
			r: &query.Request{
				OrganizationID: orgID,
				Compiler: lang.FluxCompiler{
					Query: "from()",
				},
			},
			status: http.StatusOK,
			csv: `#datatype,string,long,dateTime:RFC3339,double,long,string,boolean,string,string,string
#group,false,false,false,false,false,false,false,true,true,true
#default,_result,,,,,,,,,
,result,table,_time,usage_user,test,mystr,this,cpu,host,_measurement
,,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui
`,
			want: toCRLF(`,_result,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui

`),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var orgIDStr string
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				orgIDStr = r.URL.Query().Get(OrgID)
				w.WriteHeader(tt.status)
				fmt.Fprintln(w, tt.csv)
			}))
			s := &FluxQueryService{
				Addr:  ts.URL,
				Token: tt.token,
			}
			res, err := s.Query(tt.ctx, tt.r)
			if (err != nil) != tt.wantErr {
				t.Errorf("FluxQueryService.Query() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if res != nil && res.Err() != nil {
				t.Errorf("FluxQueryService.Query() result error = %v", res.Err())
				return
			}
			if tt.wantErr {
				return
			}
			defer res.Release()

			enc := csv.NewMultiResultEncoder(csv.ResultEncoderConfig{
				NoHeader:  true,
				Delimiter: ',',
			})
			b := bytes.Buffer{}
			n, err := enc.Encode(&b, res)
			if err != nil {
				t.Errorf("FluxQueryService.Query() encode error = %v", err)
				return
			}
			if n != int64(len(tt.want)) {
				t.Errorf("FluxQueryService.Query() encode result = %d, want %d", n, len(tt.want))
			}
			if orgIDStr == "" {
				t.Error("FluxQueryService.Query() encoded orgID is empty")
			}
			if got, want := orgIDStr, tt.r.OrganizationID.String(); got != want {
				t.Errorf("FluxQueryService.Query() encoded orgID = %s, want %s", got, want)
			}

			got := b.String()
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("FluxQueryService.Query() =\n%s\n%s", got, tt.want)
			}
		})
	}
}

func TestFluxHandler_postFluxAST(t *testing.T) {
	tests := []struct {
		name   string
		w      *httptest.ResponseRecorder
		r      *http.Request
		want   string
		status int
	}{
		{
			name: "get ast from()",
			w:    httptest.NewRecorder(),
			r:    httptest.NewRequest("POST", "/api/v2/query/ast", bytes.NewBufferString(`{"query": "from()"}`)),
			want: `{"ast":{"type":"Package","package":"main","files":[{"type":"File","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"package":null,"imports":null,"body":[{"type":"ExpressionStatement","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"expression":{"type":"CallExpression","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"callee":{"type":"Identifier","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":5},"source":"from"},"name":"from"}}}]}]}}
`,
			status: http.StatusOK,
		},
		{
			name:   "error from bad json",
			w:      httptest.NewRecorder(),
			r:      httptest.NewRequest("POST", "/api/v2/query/ast", bytes.NewBufferString(`error!`)),
			want:   `{"code":"invalid","message":"invalid json","error":"invalid character 'e' looking for beginning of value"}`,
			status: http.StatusBadRequest,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := &FluxHandler{}
			h.postFluxAST(tt.w, tt.r)
			if got := tt.w.Body.String(); got != tt.want {
				t.Errorf("http.postFluxAST = got\n%vwant\n%v", got, tt.want)
			}
			if got := tt.w.Code; got != tt.status {
				t.Errorf("http.postFluxAST = got %d\nwant %d", got, tt.status)
			}
		})
	}
}

func TestFluxHandler_postFluxSpec(t *testing.T) {
	tests := []struct {
		name   string
		w      *httptest.ResponseRecorder
		r      *http.Request
		now    func() time.Time
		want   string
		status int
	}{
		{
			name: "get spec from()",
			w:    httptest.NewRecorder(),
			r:    httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`{"query": "from(bucket: \"telegraf\")"}`)),
			now:  func() time.Time { return time.Unix(0, 0).UTC() },
			want: `{"spec":{"operations":[{"kind":"influxDBFrom","id":"influxDBFrom0","spec":{"bucket":"telegraf"}}],"edges":null,"resources":{"priority":"high","concurrency_quota":0,"memory_bytes_quota":0},"now":"1970-01-01T00:00:00Z"}}
`,
			status: http.StatusOK,
		},
		{
			name:   "error from bad json",
			w:      httptest.NewRecorder(),
			r:      httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`error!`)),
			want:   `{"code":"invalid","message":"invalid json","error":"invalid character 'e' looking for beginning of value"}`,
			status: http.StatusBadRequest,
		},
		{
			name:   "error from incomplete spec",
			w:      httptest.NewRecorder(),
			r:      httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`{"query": "from()"}`)),
			now:    func() time.Time { return time.Unix(0, 0).UTC() },
			want:   `{"code":"unprocessable entity","message":"invalid spec","error":"error calling function \"from\": must specify one of bucket or bucketID"}`,
			status: http.StatusUnprocessableEntity,
		},
		{
			name: "get spec with range and last",
			w:    httptest.NewRecorder(),
			r:    httptest.NewRequest("POST", "/api/v2/query/spec", bytes.NewBufferString(`{"query": "from(bucket:\"demo-bucket-in-1\") |> range(start:-2s) |> last()"}`)),
			now:  func() time.Time { return time.Unix(0, 0).UTC() },
			want: `{"spec":{"operations":[{"kind":"influxDBFrom","id":"influxDBFrom0","spec":{"bucket":"demo-bucket-in-1"}},{"kind":"range","id":"range1","spec":{"start":"-2s","stop":"now","timeColumn":"_time","startColumn":"_start","stopColumn":"_stop"}},{"kind":"last","id":"last2","spec":{"column":""}}],"edges":[{"parent":"influxDBFrom0","child":"range1"},{"parent":"range1","child":"last2"}],"resources":{"priority":"high","concurrency_quota":0,"memory_bytes_quota":0},"now":"1970-01-01T00:00:00Z"}}
`,
			status: http.StatusOK,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := &FluxHandler{
				Now: tt.now,
			}
			h.postFluxSpec(tt.w, tt.r)
			if got := tt.w.Body.String(); got != tt.want {
				t.Errorf("http.postFluxSpec = got %s\nwant %s", got, tt.want)
			}

			if got := tt.w.Code; got != tt.status {
				t.Errorf("http.postFluxSpec = got %d\nwant %d", got, tt.status)
				t.Log(tt.w.Header())
			}
		})
	}
}

var crlfPattern = regexp.MustCompile(`\r?\n`)

func toCRLF(data string) string {
	return crlfPattern.ReplaceAllString(data, "\r\n")
}
<|endoftext|>


<commit_before>package effio

// the JSON format is documented in README.md

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"os"
	"path"
	"syscall"
)

type Device struct {
	Name       string `json:"name"`
	Notes      string `json:"notes"`
	Ignore     bool   `json:"ignore"`
	Device     string `json:"device"`
	Mountpoint string `json:"mountpoint"`
	Filesystem string `json:"filesystem"`
	Brand      string `json:"brand"`
	Series     string `json:"series"`
	Datasheet  string `json:"datasheet"`
	Capacity   int64  `json:"capacity"`
	Rotational bool   `json:"rotational"`
	Transport  string `json:"transport"`
	HBA        string `json:"hba"`
	Media      string `json:"media"`
	Blocksize  int    `json:"blocksize"`
	RPM        int    `json:"rpm"`
}

type Devices []Device

func (d *Device) IsMounted() (bool, error) {
	dfi, err := os.Stat(d.Mountpoint)
	if err != nil {
		return false, err
	}

	pfi, err := os.Stat(path.Join(d.Mountpoint, ".."))
	if err != nil {
		return false, err
	}

	// get the underlying OS stat structure (breaking !unix portability)
	dst := dfi.Sys().(*syscall.Stat_t)
	pst := pfi.Sys().(*syscall.Stat_t)

	if dst.Dev == pst.Dev {
		return false, errors.New("mountpoint has same device number as parent directory")
	} else {
		return true, nil
	}
}
func (d *Device) Mount() error {

func (d *Device) Mount() error {
	if d.Device == "" {
		return errors.New("'device' must be defined in device json for mounting support")
	}
	if d.Mountpoint == "" {
		return errors.New("'mountpoint' must be defined in device json for mounting support")
	}
	if d.Filesystem == "" {
		return errors.New("'filesystem' must be defined in device json for mounting support")
	}

	if err := os.MkdirAll(d.Mountpoint, 0755); err != nil {
		return err
	}

	var flags uintptr
	flags = syscall.MS_NOATIME | syscall.MS_NODIRATIME
	data := ""

	return syscall.Mount(d.Device, d.Mountpoint, d.Filesystem, uintptr(flags), data)
}

func (d *Device) Umount() error {
	return syscall.Unmount(d.Mountpoint, 0)
}

func (d *Device) ToJson() string {
	js, err := json.MarshalIndent(d, "  ", "  ")
	if err != nil {
		log.Fatalf("Failed to encode device as JSON: %s\n", err)
	}
	return string(js)
}

// implement the sort interface
func (devs Devices) Len() int {
	return len(devs)
}

func (devs Devices) Swap(i, j int) {
	devs[i], devs[j] = devs[j], devs[i]
}

// sort by mountpoint, which usually ends with the driveletter
// in my setups (for now) ...
func (devs Devices) Less(i, j int) bool {
	return devs[i].Mountpoint < devs[j].Mountpoint
}

func LoadDevicesFile(fname string) (devs Devices) {
	mdbuf, err := ioutil.ReadFile(fname)
	if err != nil {
		log.Fatalf("Could not read '%s': %s\n", fname, err)
	}
	err = json.Unmarshal(mdbuf, &devs)
	if err != nil {
		log.Fatalf("Could not parse JSON: %s\n", err)
	}

	return devs
}
<commit_msg>Backing out device.go changes<commit_after>package effio

// the JSON format is documented in README.md

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"os"
	"path"
	"syscall"
)

type Device struct {
	Name       string `json:"name"`
	Notes      string `json:"notes"`
	Ignore     bool   `json:"ignore"`
	Device     string `json:"device"`
	Mountpoint string `json:"mountpoint"`
	Filesystem string `json:"filesystem"`
	Brand      string `json:"brand"`
	Series     string `json:"series"`
	Datasheet  string `json:"datasheet"`
	Capacity   int64  `json:"capacity"`
	Rotational bool   `json:"rotational"`
	Transport  string `json:"transport"`
	HBA        string `json:"hba"`
	Media      string `json:"media"`
	Blocksize  int    `json:"blocksize"`
	RPM        int    `json:"rpm"`
}

type Devices []Device

func (d *Device) IsMounted() (bool, error) {
	dfi, err := os.Stat(d.Mountpoint)
	if err != nil {
		return false, err
	}

	pfi, err := os.Stat(path.Join(d.Mountpoint, ".."))
	if err != nil {
		return false, err
	}

	// get the underlying OS stat structure (breaking !unix portability)
	dst := dfi.Sys().(*syscall.Stat_t)
	pst := pfi.Sys().(*syscall.Stat_t)

	if dst.Dev == pst.Dev {
		return false, errors.New("mountpoint has same device number as parent directory")
	} else {
		return true, nil
	}
}

func (d *Device) Mount() error {
	if d.Device == "" {
		return errors.New("'device' must be defined in device json for mounting support")
	}
	if d.Mountpoint == "" {
		return errors.New("'mountpoint' must be defined in device json for mounting support")
	}
	if d.Filesystem == "" {
		return errors.New("'filesystem' must be defined in device json for mounting support")
	}

	if err := os.MkdirAll(d.Mountpoint, 0755); err != nil {
		return err
	}

	var flags uintptr
	flags = syscall.MS_NOATIME | syscall.MS_NODIRATIME
	data := ""

	return syscall.Mount(d.Device, d.Mountpoint, d.Filesystem, uintptr(flags), data)
}

func (d *Device) Umount() error {
	return syscall.Unmount(d.Mountpoint, 0)
}

func (d *Device) ToJson() string {
	js, err := json.MarshalIndent(d, "  ", "  ")
	if err != nil {
		log.Fatalf("Failed to encode device as JSON: %s\n", err)
	}
	return string(js)
}

// implement the sort interface
func (devs Devices) Len() int {
	return len(devs)
}

func (devs Devices) Swap(i, j int) {
	devs[i], devs[j] = devs[j], devs[i]
}

// sort by mountpoint, which usually ends with the driveletter
// in my setups (for now) ...
func (devs Devices) Less(i, j int) bool {
	return devs[i].Mountpoint < devs[j].Mountpoint
}

func LoadDevicesFile(fname string) (devs Devices) {
	mdbuf, err := ioutil.ReadFile(fname)
	if err != nil {
		log.Fatalf("Could not read '%s': %s\n", fname, err)
	}
	err = json.Unmarshal(mdbuf, &devs)
	if err != nil {
		log.Fatalf("Could not parse JSON: %s\n", err)
	}

	return devs
}
<|endoftext|>


<commit_before>// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package logs

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/issue9/logs/config"
	"github.com/issue9/logs/internal/xml"
)

// 定义了一些日志的类型
const (
	LevelInfo = iota
	LevelTrace
	LevelDebug
	LevelWarn
	LevelError
	LevelCritical
	levelSize
)

var levels = map[string]int{
	"info":     LevelInfo,
	"trace":    LevelTrace,
	"debug":    LevelDebug,
	"warn":     LevelWarn,
	"error":    LevelError,
	"critical": LevelCritical,
}

var defaultLogs = New()

// Logs 日志输出
type Logs struct {
	loggers []*logger
}

// New 声明 Logs 变量
func New() *Logs {
	logs := &Logs{
		loggers: make([]*logger, levelSize, levelSize),
	}

	for index := range logs.loggers {
		logs.loggers[index] = newLogger("", 0)
	}

	return logs
}

// InitFromXMLFile 从一个 XML 文件中初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func (logs *Logs) InitFromXMLFile(path string) error {
	cfg, err := xml.ParseXMLFile(path)
	if err != nil {
		return err
	}
	return logs.initFromConfig(cfg)
}

// InitFromXMLString 从一个 XML 字符串初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func (logs *Logs) InitFromXMLString(str string) error {
	cfg, err := xml.ParseXMLString(str)
	if err != nil {
		return err
	}
	return logs.initFromConfig(cfg)
}

// SetOutput 设置某一个类型的输出通道
//
// 若将 w 设置为 nil 等同于 iotuil.Discard，即关闭此类型的输出。
func (logs *Logs) SetOutput(level int, w io.Writer, prefix string, flag int) error {
	if level < 0 || level > levelSize {
		return errors.New("无效的 level 值")
	}

	logs.loggers[level].setOutput(w, prefix, flag)
	return nil
}

// 从 config.Config 中初始化整个 logs 系统
func (logs *Logs) initFromConfig(cfg *config.Config) error {
	for name, c := range cfg.Items {
		index, found := levels[name]
		if !found {
			return fmt.Errorf("未知道的二级元素名称:[%s]", name)
		}

		l, err := toWriter(c)
		if err != nil {
			return err
		}

		ll, ok := l.(*logger)
		if !ok {
			return errors.New("无效的日志类型")
		}

		logs.loggers[index] = ll
	}

	return nil
}

// Flush 输出所有的缓存内容。
// 若是通过 os.Exit() 退出程序的，在执行之前，
// 一定记得调用 Flush() 输出可能缓存的日志内容。
func (logs *Logs) Flush() {
	for _, l := range logs.loggers {
		l.container.Flush()
	}
}

// INFO 获取 INFO 级别的 log.Logger 实例，在未指定 info 级别的日志时，该实例返回一个 nil。
func (logs *Logs) INFO() *log.Logger {
	return logs.loggers[LevelInfo].log
}

// Info 相当于 INFO().Println(v...) 的简写方式
// Info 函数默认是带换行符的，若需要不带换行符的，请使用 DEBUG().Print() 函数代替。
// 其它相似函数也有类型功能。
func (logs *Logs) Info(v ...interface{}) {
	logs.INFO().Output(2, fmt.Sprintln(v...))
}

// Infof 相当于 INFO().Printf(format, v...) 的简写方式
func (logs *Logs) Infof(format string, v ...interface{}) {
	logs.INFO().Output(2, fmt.Sprintf(format, v...))
}

// DEBUG 获取 DEBUG 级别的 log.Logger 实例，在未指定 debug 级别的日志时，该实例返回一个 nil。
func (logs *Logs) DEBUG() *log.Logger {
	return logs.loggers[LevelDebug].log
}

// Debug 相当于 DEBUG().Println(v...) 的简写方式
func (logs *Logs) Debug(v ...interface{}) {
	logs.DEBUG().Output(2, fmt.Sprintln(v...))
}

// Debugf 相当于 DEBUG().Printf(format, v...) 的简写方式
func (logs *Logs) Debugf(format string, v ...interface{}) {
	logs.DEBUG().Output(2, fmt.Sprintf(format, v...))
}

// TRACE 获取 TRACE 级别的 log.Logger 实例，在未指定 trace 级别的日志时，该实例返回一个 nil。
func (logs *Logs) TRACE() *log.Logger {
	return logs.loggers[LevelTrace].log
}

// Trace 相当于 TRACE().Println(v...) 的简写方式
func (logs *Logs) Trace(v ...interface{}) {
	logs.TRACE().Output(2, fmt.Sprintln(v...))
}

// Tracef 相当于 TRACE().Printf(format, v...) 的简写方式
func (logs *Logs) Tracef(format string, v ...interface{}) {
	logs.TRACE().Output(2, fmt.Sprintf(format, v...))
}

// WARN 获取 WARN 级别的 log.Logger 实例，在未指定 warn 级别的日志时，该实例返回一个 nil。
func (logs *Logs) WARN() *log.Logger {
	return logs.loggers[LevelWarn].log
}

// Warn 相当于 WARN().Println(v...) 的简写方式
func (logs *Logs) Warn(v ...interface{}) {
	logs.WARN().Output(2, fmt.Sprintln(v...))
}

// Warnf 相当于 WARN().Printf(format, v...) 的简写方式
func (logs *Logs) Warnf(format string, v ...interface{}) {
	logs.WARN().Output(2, fmt.Sprintf(format, v...))
}

// ERROR 获取 ERROR 级别的 log.Logger 实例，在未指定 error 级别的日志时，该实例返回一个 nil。
func (logs *Logs) ERROR() *log.Logger {
	return logs.loggers[LevelError].log
}

// Error 相当于 ERROR().Println(v...) 的简写方式
func (logs *Logs) Error(v ...interface{}) {
	logs.ERROR().Output(2, fmt.Sprintln(v...))
}

// Errorf 相当于 ERROR().Printf(format, v...) 的简写方式
func (logs *Logs) Errorf(format string, v ...interface{}) {
	logs.ERROR().Output(2, fmt.Sprintf(format, v...))
}

// CRITICAL 获取 CRITICAL 级别的 log.Logger 实例，在未指定 critical 级别的日志时，该实例返回一个 nil。
func (logs *Logs) CRITICAL() *log.Logger {
	return logs.loggers[LevelCritical].log
}

// Critical 相当于 CRITICAL().Println(v...)的简写方式
func (logs *Logs) Critical(v ...interface{}) {
	logs.CRITICAL().Output(2, fmt.Sprintln(v...))
}

// Criticalf 相当于 CRITICAL().Printf(format, v...) 的简写方式
func (logs *Logs) Criticalf(format string, v ...interface{}) {
	logs.CRITICAL().Output(2, fmt.Sprintf(format, v...))
}

// All 向所有的日志输出内容。
func (logs *Logs) All(v ...interface{}) {
	logs.all(v...)
}

// Allf 向所有的日志输出内容。
func (logs *Logs) Allf(format string, v ...interface{}) {
	logs.allf(format, v...)
}

// Fatal 输出错误信息，然后退出程序。
func (logs *Logs) Fatal(v ...interface{}) {
	logs.all(v...)
	logs.Flush()
	os.Exit(2)
}

// Fatalf 输出错误信息，然后退出程序。
func (logs *Logs) Fatalf(format string, v ...interface{}) {
	logs.allf(format, v...)
	logs.Flush()
	os.Exit(2)
}

// Panic 输出错误信息，然后触发 panic。
func (logs *Logs) Panic(v ...interface{}) {
	s := fmt.Sprint(v...)
	logs.all(s)
	logs.Flush()
	panic(s)
}

// Panicf 输出错误信息，然后触发 panic。
func (logs *Logs) Panicf(format string, v ...interface{}) {
	logs.allf(format, v...)
	logs.Flush()
	panic(fmt.Sprintf(format, v...))
}

func (logs *Logs) all(v ...interface{}) {
	for _, l := range logs.loggers {
		l.log.Output(3, fmt.Sprintln(v...))
	}
}

func (logs *Logs) allf(format string, v ...interface{}) {
	for _, l := range logs.loggers {
		l.log.Output(3, fmt.Sprintf(format, v...))
	}
}

// InitFromXMLFile 从一个 XML 文件中初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func InitFromXMLFile(path string) error {
	return defaultLogs.InitFromXMLFile(path)
}

// InitFromXMLString 从一个 XML 字符串初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func InitFromXMLString(str string) error {
	return defaultLogs.InitFromXMLString(str)
}

// SetOutput 设置某一个类型的输出通道
//
// 若将 w 设置为 nil 等同于 iotuil.Discard，即关闭此类型的输出。
func SetOutput(level int, w io.Writer, prefix string, flag int) error {
	return defaultLogs.SetOutput(level, w, prefix, flag)
}

// Flush 输出所有的缓存内容。
// 若是通过 os.Exit() 退出程序的，在执行之前，
// 一定记得调用 Flush() 输出可能缓存的日志内容。
func Flush() {
	defaultLogs.Flush()
}

// INFO 获取 INFO 级别的 log.Logger 实例，在未指定 info 级别的日志时，该实例返回一个 nil。
func INFO() *log.Logger {
	return defaultLogs.INFO()
}

// Info 相当于 INFO().Println(v...) 的简写方式
// Info 函数默认是带换行符的，若需要不带换行符的，请使用 DEBUG().Print() 函数代替。
// 其它相似函数也有类型功能。
func Info(v ...interface{}) {
	defaultLogs.INFO().Output(2, fmt.Sprintln(v...))
}

// Infof 相当于 INFO().Printf(format, v...) 的简写方式
func Infof(format string, v ...interface{}) {
	defaultLogs.INFO().Output(2, fmt.Sprintf(format, v...))
}

// DEBUG 获取 DEBUG 级别的 log.Logger 实例，在未指定 debug 级别的日志时，该实例返回一个 nil。
func DEBUG() *log.Logger {
	return defaultLogs.loggers[LevelDebug].log
}

// Debug 相当于 DEBUG().Println(v...) 的简写方式
func Debug(v ...interface{}) {
	defaultLogs.DEBUG().Output(2, fmt.Sprintln(v...))
}

// Debugf 相当于 DEBUG().Printf(format, v...) 的简写方式
func Debugf(format string, v ...interface{}) {
	defaultLogs.DEBUG().Output(2, fmt.Sprintf(format, v...))
}

// TRACE 获取 TRACE 级别的 log.Logger 实例，在未指定 trace 级别的日志时，该实例返回一个 nil。
func TRACE() *log.Logger {
	return defaultLogs.loggers[LevelTrace].log
}

// Trace 相当于 TRACE().Println(v...) 的简写方式
func Trace(v ...interface{}) {
	defaultLogs.TRACE().Output(2, fmt.Sprintln(v...))
}

// Tracef 相当于 TRACE().Printf(format, v...) 的简写方式
func Tracef(format string, v ...interface{}) {
	defaultLogs.TRACE().Output(2, fmt.Sprintf(format, v...))
}

// WARN 获取 WARN 级别的 log.Logger 实例，在未指定 warn 级别的日志时，该实例返回一个 nil。
func WARN() *log.Logger {
	return defaultLogs.loggers[LevelWarn].log
}

// Warn 相当于 WARN().Println(v...) 的简写方式
func Warn(v ...interface{}) {
	defaultLogs.WARN().Output(2, fmt.Sprintln(v...))
}

// Warnf 相当于 WARN().Printf(format, v...) 的简写方式
func Warnf(format string, v ...interface{}) {
	defaultLogs.WARN().Output(2, fmt.Sprintf(format, v...))
}

// ERROR 获取 ERROR 级别的 log.Logger 实例，在未指定 error 级别的日志时，该实例返回一个 nil。
func ERROR() *log.Logger {
	return defaultLogs.loggers[LevelError].log
}

// Error 相当于 ERROR().Println(v...) 的简写方式
func Error(v ...interface{}) {
	defaultLogs.ERROR().Output(2, fmt.Sprintln(v...))
}

// Errorf 相当于 ERROR().Printf(format, v...) 的简写方式
func Errorf(format string, v ...interface{}) {
	defaultLogs.ERROR().Output(2, fmt.Sprintf(format, v...))
}

// CRITICAL 获取 CRITICAL 级别的 log.Logger 实例，在未指定 critical 级别的日志时，该实例返回一个 nil。
func CRITICAL() *log.Logger {
	return defaultLogs.loggers[LevelCritical].log
}

// Critical 相当于 CRITICAL().Println(v...)的简写方式
func Critical(v ...interface{}) {
	defaultLogs.CRITICAL().Output(2, fmt.Sprintln(v...))
}

// Criticalf 相当于 CRITICAL().Printf(format, v...) 的简写方式
func Criticalf(format string, v ...interface{}) {
	defaultLogs.CRITICAL().Output(2, fmt.Sprintf(format, v...))
}

// All 向所有的日志输出内容。
func All(v ...interface{}) {
	defaultLogs.All(v...)
}

// Allf 向所有的日志输出内容。
func Allf(format string, v ...interface{}) {
	defaultLogs.Allf(format, v...)
}

// Fatal 输出错误信息，然后退出程序。
func Fatal(v ...interface{}) {
	defaultLogs.Fatal(v...)
}

// Fatalf 输出错误信息，然后退出程序。
func Fatalf(format string, v ...interface{}) {
	defaultLogs.Fatalf(format, v...)
}

// Panic 输出错误信息，然后触发 panic。
func Panic(v ...interface{}) {
	defaultLogs.Panic(v...)
}

// Panicf 输出错误信息，然后触发 panic。
func Panicf(format string, v ...interface{}) {
	defaultLogs.Panicf(format, v...)
}
<commit_msg>添加 Init 方法<commit_after>// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package logs

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/issue9/logs/config"
	"github.com/issue9/logs/internal/xml"
)

// 定义了一些日志的类型
const (
	LevelInfo = iota
	LevelTrace
	LevelDebug
	LevelWarn
	LevelError
	LevelCritical
	levelSize
)

var levels = map[string]int{
	"info":     LevelInfo,
	"trace":    LevelTrace,
	"debug":    LevelDebug,
	"warn":     LevelWarn,
	"error":    LevelError,
	"critical": LevelCritical,
}

var defaultLogs = New()

// Logs 日志输出
type Logs struct {
	loggers []*logger
}

// New 声明 Logs 变量
//
// 需要调用 InitFromXMLFile 或是 InitFromXMLString 进行具体的初始化。
func New() *Logs {
	logs := &Logs{
		loggers: make([]*logger, levelSize, levelSize),
	}

	for index := range logs.loggers {
		logs.loggers[index] = newLogger("", 0)
	}

	return logs
}

// Init 从 config.Config 中初始化整个 logs 系统
func (logs *Logs) Init(cfg *config.Config) error {
	for name, c := range cfg.Items {
		index, found := levels[name]
		if !found {
			panic("未知的二级元素名称:" + name)
		}

		l, err := toWriter(c)
		if err != nil {
			return err
		}

		logs.loggers[index] = l.(*logger)
	}

	return nil
}

// InitFromXMLFile 从一个 XML 文件中初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func (logs *Logs) InitFromXMLFile(path string) error {
	cfg, err := xml.ParseXMLFile(path)
	if err != nil {
		return err
	}
	return logs.Init(cfg)
}

// InitFromXMLString 从一个 XML 字符串初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func (logs *Logs) InitFromXMLString(str string) error {
	cfg, err := xml.ParseXMLString(str)
	if err != nil {
		return err
	}
	return logs.Init(cfg)
}

// SetOutput 设置某一个类型的输出通道
//
// 若将 w 设置为 nil 等同于 iotuil.Discard，即关闭此类型的输出。
func (logs *Logs) SetOutput(level int, w io.Writer, prefix string, flag int) error {
	if level < 0 || level > levelSize {
		return errors.New("无效的 level 值")
	}

	logs.loggers[level].setOutput(w, prefix, flag)
	return nil
}

// Flush 输出所有的缓存内容。
// 若是通过 os.Exit() 退出程序的，在执行之前，
// 一定记得调用 Flush() 输出可能缓存的日志内容。
func (logs *Logs) Flush() {
	for _, l := range logs.loggers {
		l.container.Flush()
	}
}

// INFO 获取 INFO 级别的 log.Logger 实例，在未指定 info 级别的日志时，该实例返回一个 nil。
func (logs *Logs) INFO() *log.Logger {
	return logs.loggers[LevelInfo].log
}

// Info 相当于 INFO().Println(v...) 的简写方式
// Info 函数默认是带换行符的，若需要不带换行符的，请使用 DEBUG().Print() 函数代替。
// 其它相似函数也有类型功能。
func (logs *Logs) Info(v ...interface{}) {
	logs.INFO().Output(2, fmt.Sprintln(v...))
}

// Infof 相当于 INFO().Printf(format, v...) 的简写方式
func (logs *Logs) Infof(format string, v ...interface{}) {
	logs.INFO().Output(2, fmt.Sprintf(format, v...))
}

// DEBUG 获取 DEBUG 级别的 log.Logger 实例，在未指定 debug 级别的日志时，该实例返回一个 nil。
func (logs *Logs) DEBUG() *log.Logger {
	return logs.loggers[LevelDebug].log
}

// Debug 相当于 DEBUG().Println(v...) 的简写方式
func (logs *Logs) Debug(v ...interface{}) {
	logs.DEBUG().Output(2, fmt.Sprintln(v...))
}

// Debugf 相当于 DEBUG().Printf(format, v...) 的简写方式
func (logs *Logs) Debugf(format string, v ...interface{}) {
	logs.DEBUG().Output(2, fmt.Sprintf(format, v...))
}

// TRACE 获取 TRACE 级别的 log.Logger 实例，在未指定 trace 级别的日志时，该实例返回一个 nil。
func (logs *Logs) TRACE() *log.Logger {
	return logs.loggers[LevelTrace].log
}

// Trace 相当于 TRACE().Println(v...) 的简写方式
func (logs *Logs) Trace(v ...interface{}) {
	logs.TRACE().Output(2, fmt.Sprintln(v...))
}

// Tracef 相当于 TRACE().Printf(format, v...) 的简写方式
func (logs *Logs) Tracef(format string, v ...interface{}) {
	logs.TRACE().Output(2, fmt.Sprintf(format, v...))
}

// WARN 获取 WARN 级别的 log.Logger 实例，在未指定 warn 级别的日志时，该实例返回一个 nil。
func (logs *Logs) WARN() *log.Logger {
	return logs.loggers[LevelWarn].log
}

// Warn 相当于 WARN().Println(v...) 的简写方式
func (logs *Logs) Warn(v ...interface{}) {
	logs.WARN().Output(2, fmt.Sprintln(v...))
}

// Warnf 相当于 WARN().Printf(format, v...) 的简写方式
func (logs *Logs) Warnf(format string, v ...interface{}) {
	logs.WARN().Output(2, fmt.Sprintf(format, v...))
}

// ERROR 获取 ERROR 级别的 log.Logger 实例，在未指定 error 级别的日志时，该实例返回一个 nil。
func (logs *Logs) ERROR() *log.Logger {
	return logs.loggers[LevelError].log
}

// Error 相当于 ERROR().Println(v...) 的简写方式
func (logs *Logs) Error(v ...interface{}) {
	logs.ERROR().Output(2, fmt.Sprintln(v...))
}

// Errorf 相当于 ERROR().Printf(format, v...) 的简写方式
func (logs *Logs) Errorf(format string, v ...interface{}) {
	logs.ERROR().Output(2, fmt.Sprintf(format, v...))
}

// CRITICAL 获取 CRITICAL 级别的 log.Logger 实例，在未指定 critical 级别的日志时，该实例返回一个 nil。
func (logs *Logs) CRITICAL() *log.Logger {
	return logs.loggers[LevelCritical].log
}

// Critical 相当于 CRITICAL().Println(v...)的简写方式
func (logs *Logs) Critical(v ...interface{}) {
	logs.CRITICAL().Output(2, fmt.Sprintln(v...))
}

// Criticalf 相当于 CRITICAL().Printf(format, v...) 的简写方式
func (logs *Logs) Criticalf(format string, v ...interface{}) {
	logs.CRITICAL().Output(2, fmt.Sprintf(format, v...))
}

// All 向所有的日志输出内容。
func (logs *Logs) All(v ...interface{}) {
	logs.all(v...)
}

// Allf 向所有的日志输出内容。
func (logs *Logs) Allf(format string, v ...interface{}) {
	logs.allf(format, v...)
}

// Fatal 输出错误信息，然后退出程序。
func (logs *Logs) Fatal(v ...interface{}) {
	logs.all(v...)
	logs.Flush()
	os.Exit(2)
}

// Fatalf 输出错误信息，然后退出程序。
func (logs *Logs) Fatalf(format string, v ...interface{}) {
	logs.allf(format, v...)
	logs.Flush()
	os.Exit(2)
}

// Panic 输出错误信息，然后触发 panic。
func (logs *Logs) Panic(v ...interface{}) {
	s := fmt.Sprint(v...)
	logs.all(s)
	logs.Flush()
	panic(s)
}

// Panicf 输出错误信息，然后触发 panic。
func (logs *Logs) Panicf(format string, v ...interface{}) {
	logs.allf(format, v...)
	logs.Flush()
	panic(fmt.Sprintf(format, v...))
}

func (logs *Logs) all(v ...interface{}) {
	for _, l := range logs.loggers {
		l.log.Output(3, fmt.Sprintln(v...))
	}
}

func (logs *Logs) allf(format string, v ...interface{}) {
	for _, l := range logs.loggers {
		l.log.Output(3, fmt.Sprintf(format, v...))
	}
}

// Init 从 config.Config 中初始化整个 logs 系统
func Init(cfg *config.Config) error {
	return defaultLogs.Init(cfg)
}

// InitFromXMLFile 从一个 XML 文件中初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func InitFromXMLFile(path string) error {
	return defaultLogs.InitFromXMLFile(path)
}

// InitFromXMLString 从一个 XML 字符串初始化日志系统。
//
// 再次调用该函数，将会根据新的配置文件重新初始化日志系统。
func InitFromXMLString(str string) error {
	return defaultLogs.InitFromXMLString(str)
}

// SetOutput 设置某一个类型的输出通道
//
// 若将 w 设置为 nil 等同于 iotuil.Discard，即关闭此类型的输出。
func SetOutput(level int, w io.Writer, prefix string, flag int) error {
	return defaultLogs.SetOutput(level, w, prefix, flag)
}

// Flush 输出所有的缓存内容。
// 若是通过 os.Exit() 退出程序的，在执行之前，
// 一定记得调用 Flush() 输出可能缓存的日志内容。
func Flush() {
	defaultLogs.Flush()
}

// INFO 获取 INFO 级别的 log.Logger 实例，在未指定 info 级别的日志时，该实例返回一个 nil。
func INFO() *log.Logger {
	return defaultLogs.INFO()
}

// Info 相当于 INFO().Println(v...) 的简写方式
// Info 函数默认是带换行符的，若需要不带换行符的，请使用 DEBUG().Print() 函数代替。
// 其它相似函数也有类型功能。
func Info(v ...interface{}) {
	defaultLogs.INFO().Output(2, fmt.Sprintln(v...))
}

// Infof 相当于 INFO().Printf(format, v...) 的简写方式
func Infof(format string, v ...interface{}) {
	defaultLogs.INFO().Output(2, fmt.Sprintf(format, v...))
}

// DEBUG 获取 DEBUG 级别的 log.Logger 实例，在未指定 debug 级别的日志时，该实例返回一个 nil。
func DEBUG() *log.Logger {
	return defaultLogs.loggers[LevelDebug].log
}

// Debug 相当于 DEBUG().Println(v...) 的简写方式
func Debug(v ...interface{}) {
	defaultLogs.DEBUG().Output(2, fmt.Sprintln(v...))
}

// Debugf 相当于 DEBUG().Printf(format, v...) 的简写方式
func Debugf(format string, v ...interface{}) {
	defaultLogs.DEBUG().Output(2, fmt.Sprintf(format, v...))
}

// TRACE 获取 TRACE 级别的 log.Logger 实例，在未指定 trace 级别的日志时，该实例返回一个 nil。
func TRACE() *log.Logger {
	return defaultLogs.loggers[LevelTrace].log
}

// Trace 相当于 TRACE().Println(v...) 的简写方式
func Trace(v ...interface{}) {
	defaultLogs.TRACE().Output(2, fmt.Sprintln(v...))
}

// Tracef 相当于 TRACE().Printf(format, v...) 的简写方式
func Tracef(format string, v ...interface{}) {
	defaultLogs.TRACE().Output(2, fmt.Sprintf(format, v...))
}

// WARN 获取 WARN 级别的 log.Logger 实例，在未指定 warn 级别的日志时，该实例返回一个 nil。
func WARN() *log.Logger {
	return defaultLogs.loggers[LevelWarn].log
}

// Warn 相当于 WARN().Println(v...) 的简写方式
func Warn(v ...interface{}) {
	defaultLogs.WARN().Output(2, fmt.Sprintln(v...))
}

// Warnf 相当于 WARN().Printf(format, v...) 的简写方式
func Warnf(format string, v ...interface{}) {
	defaultLogs.WARN().Output(2, fmt.Sprintf(format, v...))
}

// ERROR 获取 ERROR 级别的 log.Logger 实例，在未指定 error 级别的日志时，该实例返回一个 nil。
func ERROR() *log.Logger {
	return defaultLogs.loggers[LevelError].log
}

// Error 相当于 ERROR().Println(v...) 的简写方式
func Error(v ...interface{}) {
	defaultLogs.ERROR().Output(2, fmt.Sprintln(v...))
}

// Errorf 相当于 ERROR().Printf(format, v...) 的简写方式
func Errorf(format string, v ...interface{}) {
	defaultLogs.ERROR().Output(2, fmt.Sprintf(format, v...))
}

// CRITICAL 获取 CRITICAL 级别的 log.Logger 实例，在未指定 critical 级别的日志时，该实例返回一个 nil。
func CRITICAL() *log.Logger {
	return defaultLogs.loggers[LevelCritical].log
}

// Critical 相当于 CRITICAL().Println(v...)的简写方式
func Critical(v ...interface{}) {
	defaultLogs.CRITICAL().Output(2, fmt.Sprintln(v...))
}

// Criticalf 相当于 CRITICAL().Printf(format, v...) 的简写方式
func Criticalf(format string, v ...interface{}) {
	defaultLogs.CRITICAL().Output(2, fmt.Sprintf(format, v...))
}

// All 向所有的日志输出内容。
func All(v ...interface{}) {
	defaultLogs.All(v...)
}

// Allf 向所有的日志输出内容。
func Allf(format string, v ...interface{}) {
	defaultLogs.Allf(format, v...)
}

// Fatal 输出错误信息，然后退出程序。
func Fatal(v ...interface{}) {
	defaultLogs.Fatal(v...)
}

// Fatalf 输出错误信息，然后退出程序。
func Fatalf(format string, v ...interface{}) {
	defaultLogs.Fatalf(format, v...)
}

// Panic 输出错误信息，然后触发 panic。
func Panic(v ...interface{}) {
	defaultLogs.Panic(v...)
}

// Panicf 输出错误信息，然后触发 panic。
func Panicf(format string, v ...interface{}) {
	defaultLogs.Panicf(format, v...)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"

	"github.com/bjwschaap/docker-events-splunk-forwarder/dess"
	"github.com/urfave/cli"
)

func main() {
	log.SetOutput(os.Stdout)

	app := cli.NewApp()
	app.Name = "dess"
	app.Usage = "Daemon that forwards Docker events to Splunk"
	app.Version = "0.1.0"
	app.Copyright = "(C)2016 Bastiaan Schaap"
	app.Author = "Bastiaan Schaap"
	app.Email = "bastiaan.schaap@gmail.com"
	app.UsageText = `./dess -s 123.123.1.2:4873 -u user -p pass
	 This daemon uses DOCKER_HOST, DOCKER_CERT_PATH and DOCKER_TLS settings to connect to the Docker daemon.
   Optionally you can override Docker settings with --host, --certs and --tls.`

	// Define the configuration flags the program can/should use
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "host,H",
			Value:  "unix:///var/run/docker.sock",
			Usage:  "Address or location of the Docker API endpoint",
			EnvVar: "DOCKER_HOST",
		},
		cli.StringFlag{
			Name:   "certs,c",
			Value:  "",
			Usage:  "Location of TLS certificates for connecting with Docker",
			EnvVar: "DOCKER_CERT_PATH",
		},
		cli.BoolFlag{
			Name:   "tls",
			Usage:  "Should TLS be used for connecting to Docker",
			EnvVar: "DOCKER_TLS_VERIFY",
		},
		cli.StringFlag{
			Name:   "splunk,s",
			Value:  "",
			Usage:  "Address of Splunk",
			EnvVar: "SPLUNK_ADDRESS",
		},
		cli.StringFlag{
			Name:   "user,u",
			Value:  "",
			Usage:  "Username of the Splunk credentials",
			EnvVar: "SPLUNK_USER",
		},
		cli.StringFlag{
			Name:   "password,p",
			Value:  "",
			Usage:  "Password of the Splunk credentials",
			EnvVar: "SPLUNK_PASSWORD",
		},
	}

	// Set the main program logic
	app.Action = func(c *cli.Context) error {
		return dess.Start(c)
	}

	// Now start doing stuff
	app.Run(os.Args)
}
<commit_msg>Rename package to match renamed repo<commit_after>package main

import (
	"log"
	"os"

	"github.com/bjwschaap/docker-events-syslog/dess"
	"github.com/urfave/cli"
)

func main() {
	log.SetOutput(os.Stdout)

	app := cli.NewApp()
	app.Name = "dess"
	app.Usage = "Daemon that forwards Docker events to Splunk"
	app.Version = "0.1.0"
	app.Copyright = "(C)2016 Bastiaan Schaap"
	app.Author = "Bastiaan Schaap"
	app.Email = "bastiaan.schaap@gmail.com"
	app.UsageText = `./dess -s 123.123.1.2:4873 -u user -p pass
	 This daemon uses DOCKER_HOST, DOCKER_CERT_PATH and DOCKER_TLS settings to connect to the Docker daemon.
   Optionally you can override Docker settings with --host, --certs and --tls.`

	// Define the configuration flags the program can/should use
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "host,H",
			Value:  "unix:///var/run/docker.sock",
			Usage:  "Address or location of the Docker API endpoint",
			EnvVar: "DOCKER_HOST",
		},
		cli.StringFlag{
			Name:   "certs,c",
			Value:  "",
			Usage:  "Location of TLS certificates for connecting with Docker",
			EnvVar: "DOCKER_CERT_PATH",
		},
		cli.BoolFlag{
			Name:   "tls",
			Usage:  "Should TLS be used for connecting to Docker",
			EnvVar: "DOCKER_TLS_VERIFY",
		},
		cli.StringFlag{
			Name:   "splunk,s",
			Value:  "",
			Usage:  "Address of Splunk",
			EnvVar: "SPLUNK_ADDRESS",
		},
		cli.StringFlag{
			Name:   "user,u",
			Value:  "",
			Usage:  "Username of the Splunk credentials",
			EnvVar: "SPLUNK_USER",
		},
		cli.StringFlag{
			Name:   "password,p",
			Value:  "",
			Usage:  "Password of the Splunk credentials",
			EnvVar: "SPLUNK_PASSWORD",
		},
	}

	// Set the main program logic
	app.Action = func(c *cli.Context) error {
		return dess.Start(c)
	}

	// Now start doing stuff
	app.Run(os.Args)
}
<|endoftext|>


<commit_before><commit_msg>reformatted<commit_after><|endoftext|>


<commit_before>/*

Package diva converts diva.cmore.se URLs into img-cdn-cmore.b17g.services URLs

Installation

Just go get the package:

    go get -u github.com/TV4/diva

Usage

A small usage example

    package main

    import (
    	"fmt"

    	"github.com/TV4/diva"
    )

    func main() {
      fmt.Println(diva.RawURL("http://diva.cmore.se/image.aspx?formatid=221&id=a21630f5-ef51-4632-bf6f-cc94073d3cb1"))
    }

*/
package diva

import (
	"fmt"
	"net/url"
	"strings"
)

var (
	// ErrNotDivaURL is returned when trying to parse a
	// rawurl that isn’t from diva.cmore.se
	ErrNotDivaURL = fmt.Errorf("not a diva URL")

	// ErrMissingRequiredArgument is returned if the id
	// or formatid query parameters are missing
	ErrMissingRequiredArgument = fmt.Errorf("missing required argument")
)

// RawURL converts diva rawurl string into CDN rawurl string
type RawURL string

func (u RawURL) String() string {
	rawurl := string(u)

	if url, err := Parse(rawurl); err == nil {
		return url.String()
	}

	return rawurl
}

// Parse diva rawurl into CDN *url.URL
func Parse(rawurl string) (*url.URL, error) {
	if !strings.Contains(rawurl, "diva.cmore.se/image.aspx") {
		return nil, &url.Error{Op: "parse", URL: rawurl, Err: ErrNotDivaURL}
	}

	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}

	if u.Query().Get("id") == "" || u.Query().Get("formatid") == "" {
		return nil, &url.Error{Op: "parse", URL: rawurl, Err: ErrMissingRequiredArgument}
	}

	return NewURL(u.Query().Get("id"), u.Query().Get("id2"), u.Query().Get("formatid")), nil
}

// NewURL creates a new image URL with the given ids and format
func NewURL(id, id2, formatID string) *url.URL {
	if id == "" || formatID == "" {
		return &url.URL{}
	}

	return &url.URL{
		Scheme: "https",
		Host:   "img-cdn-cmore.b17g.services",
		Path:   path(id, id2, formatID),
	}
}

func path(id, id2, formatID string) string {
	if id2 != "" {
		return fmt.Sprintf("%s/%s/%s.img", id, id2, formatID)
	}

	return fmt.Sprintf("%s/%s.img", id, formatID)
}
<commit_msg>Fixed documentation indentation<commit_after>/*

Package diva converts diva.cmore.se URLs into img-cdn-cmore.b17g.services URLs

Installation

Just go get the package:

    go get -u github.com/TV4/diva

Usage

A small usage example

    package main

    import (
        "fmt"

        "github.com/TV4/diva"
    )

    func main() {
        fmt.Println(diva.RawURL("http://diva.cmore.se/image.aspx?formatid=221&id=a21630f5-ef51-4632-bf6f-cc94073d3cb1"))
    }

*/
package diva

import (
	"fmt"
	"net/url"
	"strings"
)

var (
	// ErrNotDivaURL is returned when trying to parse a
	// rawurl that isn’t from diva.cmore.se
	ErrNotDivaURL = fmt.Errorf("not a diva URL")

	// ErrMissingRequiredArgument is returned if the id
	// or formatid query parameters are missing
	ErrMissingRequiredArgument = fmt.Errorf("missing required argument")
)

// RawURL converts diva rawurl string into CDN rawurl string
type RawURL string

func (u RawURL) String() string {
	rawurl := string(u)

	if url, err := Parse(rawurl); err == nil {
		return url.String()
	}

	return rawurl
}

// Parse diva rawurl into CDN *url.URL
func Parse(rawurl string) (*url.URL, error) {
	if !strings.Contains(rawurl, "diva.cmore.se/image.aspx") {
		return nil, &url.Error{Op: "parse", URL: rawurl, Err: ErrNotDivaURL}
	}

	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}

	if u.Query().Get("id") == "" || u.Query().Get("formatid") == "" {
		return nil, &url.Error{Op: "parse", URL: rawurl, Err: ErrMissingRequiredArgument}
	}

	return NewURL(u.Query().Get("id"), u.Query().Get("id2"), u.Query().Get("formatid")), nil
}

// NewURL creates a new image URL with the given ids and format
func NewURL(id, id2, formatID string) *url.URL {
	if id == "" || formatID == "" {
		return &url.URL{}
	}

	return &url.URL{
		Scheme: "https",
		Host:   "img-cdn-cmore.b17g.services",
		Path:   path(id, id2, formatID),
	}
}

func path(id, id2, formatID string) string {
	if id2 != "" {
		return fmt.Sprintf("%s/%s/%s.img", id, id2, formatID)
	}

	return fmt.Sprintf("%s/%s.img", id, formatID)
}
<|endoftext|>