<commit_before>// md2pdf converts Markdown content containing frontmatter into PDFs.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/gohugoio/hugo/parser"
	"github.com/russross/blackfriday/v2"
	"gopkg.in/yaml.v2"
)

func pandoc(content []byte, pdfFile string, toc bool) error {
	args := []string{
		"--standalone", "-o", pdfFile,
		//"--variable", "classoption=twocolumn",
		"--variable", "papersize=a4paper",
		"--variable", "links-as-notes",
		"--filter", "pandoc-citeproc",
	}
	if toc {
		args = append(args, "--toc")
	}
	cmd := exec.Command("pandoc", args...)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}
	go func() {
		defer stdin.Close()
		stdin.Write(content)
	}()
	out, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Fprintln(os.Stderr, out)
		return err
	}
	return nil
}

func md2pdf(mdFile, pdfFile string, toc, hugo bool) error {
	// parse the file with hugo/parser to extract front matter
	fp, err := os.Open(mdFile)
	if err != nil {
		return err
	}
	defer fp.Close()
	page, err := parser.ReadFrom(fp)
	if err != nil {
		return err
	}

	// parse YAML frontmatter
	yml, err := page.Metadata()
	if err != nil {
		return err
	}

	// parse title (h1) from markdown
	opt := blackfriday.WithExtensions(blackfriday.CommonExtensions)
	mdParser := blackfriday.New(opt)
	ast := mdParser.Parse(page.Content())
	var title string
	ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
		if node.Type == blackfriday.Heading {
			if node.HeadingData.Level == 1 {
				title = string(node.FirstChild.Literal)
				return blackfriday.Terminate
			}
		}
		return blackfriday.GoToNext
	})

	// add title to frontmatter and remove it from markdown
	md, err := yaml.Marshal(yml)
	if err != nil {
		return err
	}
	title = fmt.Sprintf("---\ntitle: %s\n", title)
	md = append([]byte(title), md...)
	md = append(md, []byte("---\n\n")...)
	if hugo {
		base := filepath.Base(mdFile)
		category := filepath.Base(filepath.Dir(mdFile))
		date, ok := yml["date"].(string)
		if ok && date != "" {
			md = append(md, []byte(date+" ")...)
		}
		pdfFile := strings.TrimSuffix(base, ".txt") + ".pdf"
		txtFile := strings.TrimSuffix(base, ".txt") + ".txt"
		download := fmt.Sprintf("[read as [txt](/%s/%s) or [PDF](/%s/%s)]\n\n",
			category, txtFile, category, pdfFile)
		md = append(md, []byte(download)...)
		md = append(md, []byte("<!--more-->\n")...)
	}
	re, err := regexp.Compile("^.+\n=+\n")
	if err != nil {
		return err
	}
	content := re.ReplaceAll(page.Content(), nil)
	md = append(md, content...)

	//fmt.Println(string(md))

	if hugo {
		if err := ioutil.WriteFile(pdfFile, md, 0600); err != nil {
			return err
		}
	} else {
		if err := pandoc(md, pdfFile, toc); err != nil {
			return err
		}
	}
	return nil
}

func fatal(err error) {
	fmt.Fprintf(os.Stderr, "%s: error: %s\n", os.Args[0], err)
	os.Exit(1)
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s [options] markdown_file pdf_file\n",
		os.Args[0])
	flag.PrintDefaults()
	os.Exit(2)
}

func main() {
	hugo := flag.Bool("hugo", false, "generate output for Hugo instead of PDF")
	toc := flag.Bool("toc", false, "generate table of contents (TOC)")
	flag.Parse()
	if flag.NArg() != 2 {
		usage()
	}
	if err := md2pdf(flag.Arg(0), flag.Arg(1), *toc, *hugo); err != nil {
		fatal(err)
	}
}
<commit_msg>md2pdf: fix error message<commit_after>// md2pdf converts Markdown content containing frontmatter into PDFs.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/gohugoio/hugo/parser"
	"github.com/russross/blackfriday/v2"
	"gopkg.in/yaml.v2"
)

func pandoc(content []byte, pdfFile string, toc bool) error {
	args := []string{
		"--standalone", "-o", pdfFile,
		//"--variable", "classoption=twocolumn",
		"--variable", "papersize=a4paper",
		"--variable", "links-as-notes",
		"--filter", "pandoc-citeproc",
	}
	if toc {
		args = append(args, "--toc")
	}
	cmd := exec.Command("pandoc", args...)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}
	go func() {
		defer stdin.Close()
		stdin.Write(content)
	}()
	out, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Fprintln(os.Stderr, string(out))
		return err
	}
	return nil
}

func md2pdf(mdFile, pdfFile string, toc, hugo bool) error {
	// parse the file with hugo/parser to extract front matter
	fp, err := os.Open(mdFile)
	if err != nil {
		return err
	}
	defer fp.Close()
	page, err := parser.ReadFrom(fp)
	if err != nil {
		return err
	}

	// parse YAML frontmatter
	yml, err := page.Metadata()
	if err != nil {
		return err
	}

	// parse title (h1) from markdown
	opt := blackfriday.WithExtensions(blackfriday.CommonExtensions)
	mdParser := blackfriday.New(opt)
	ast := mdParser.Parse(page.Content())
	var title string
	ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
		if node.Type == blackfriday.Heading {
			if node.HeadingData.Level == 1 {
				title = string(node.FirstChild.Literal)
				return blackfriday.Terminate
			}
		}
		return blackfriday.GoToNext
	})

	// add title to frontmatter and remove it from markdown
	md, err := yaml.Marshal(yml)
	if err != nil {
		return err
	}
	title = fmt.Sprintf("---\ntitle: %s\n", title)
	md = append([]byte(title), md...)
	md = append(md, []byte("---\n\n")...)
	if hugo {
		base := filepath.Base(mdFile)
		category := filepath.Base(filepath.Dir(mdFile))
		date, ok := yml["date"].(string)
		if ok && date != "" {
			md = append(md, []byte(date+" ")...)
		}
		pdfFile := strings.TrimSuffix(base, ".txt") + ".pdf"
		txtFile := strings.TrimSuffix(base, ".txt") + ".txt"
		download := fmt.Sprintf("[read as [txt](/%s/%s) or [PDF](/%s/%s)]\n\n",
			category, txtFile, category, pdfFile)
		md = append(md, []byte(download)...)
		md = append(md, []byte("<!--more-->\n")...)
	}
	re, err := regexp.Compile("^.+\n=+\n")
	if err != nil {
		return err
	}
	content := re.ReplaceAll(page.Content(), nil)
	md = append(md, content...)

	//fmt.Println(string(md))

	if hugo {
		if err := ioutil.WriteFile(pdfFile, md, 0600); err != nil {
			return err
		}
	} else {
		if err := pandoc(md, pdfFile, toc); err != nil {
			return err
		}
	}
	return nil
}

func fatal(err error) {
	fmt.Fprintf(os.Stderr, "%s: error: %s\n", os.Args[0], err)
	os.Exit(1)
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s [options] markdown_file pdf_file\n",
		os.Args[0])
	flag.PrintDefaults()
	os.Exit(2)
}

func main() {
	hugo := flag.Bool("hugo", false, "generate output for Hugo instead of PDF")
	toc := flag.Bool("toc", false, "generate table of contents (TOC)")
	flag.Parse()
	if flag.NArg() != 2 {
		usage()
	}
	if err := md2pdf(flag.Arg(0), flag.Arg(1), *toc, *hugo); err != nil {
		fatal(err)
	}
}
<|endoftext|>


<commit_before>package lint

import (
	"bytes"
	"os/exec"
	"strings"
	"unicode/utf8"

	"github.com/ValeLint/vale/core"
	"github.com/russross/blackfriday"
	"golang.org/x/net/html"
	"matloob.io/regexp"
)

// reStructuredText configuration.
//
// reCodeBlock is used to convert Sphinx-style code directives to the regular
// `::` for rst2html.
var reCodeBlock = regexp.MustCompile(`.. (?:raw|code(?:-block)?):: (\w+)`)
var rstArgs = []string{
	"--quiet",
	"--halt=5",
	"--report=5",
	"--link-stylesheet",
	"--no-file-insertion",
	"--no-toc-backlinks",
	"--no-footnote-backlinks",
	"--no-section-numbering",
}

// AsciiDoc configuration.
var adocArgs = []string{
	"-s",
	"--quiet",
	"--safe-mode",
	"secure",
	"-",
}

// Blackfriday configuration.
var commonHTMLFlags = 0 | blackfriday.HTML_USE_XHTML
var commonExtensions = 0 |
	blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
	blackfriday.EXTENSION_TABLES |
	blackfriday.EXTENSION_FENCED_CODE
var renderer = blackfriday.HtmlRenderer(commonHTMLFlags, "", "")
var options = blackfriday.Options{Extensions: commonExtensions}

// HTML configuration.
var heading = regexp.MustCompile(`^h\d$`)

// skipTags are tags that we don't want to lint.
var skipTags = []string{"script", "style", "pre", "figure"}
var inlineTags = []string{
	"b", "big", "i", "small", "abbr", "acronym", "cite", "dfn", "em", "kbd",
	"strong", "a", "br", "img", "span", "sub", "sup", "code", "tt"}
var tagToScope = map[string]string{
	"th": "text.table.header",
	"td": "text.table.cell",
	"li": "text.list",
}

func (l Linter) lintHTMLTokens(f *core.File, ctx string, fsrc []byte, offset int) {
	var txt string
	var tokt html.TokenType
	var tok html.Token
	var inBlock, inline, skip bool

	punct := []string{".", "?", "!", ",", ":", ";"}
	lines := len(f.Lines) + offset
	buf := bytes.NewBufferString("")
	queue := []string{}
	tagHistory := []string{}

	tokens := html.NewTokenizer(bytes.NewReader(fsrc))
	for {
		tokt = tokens.Next()
		tok = tokens.Token()
		txt = html.UnescapeString(strings.TrimSpace(tok.Data))

		if tokt == html.ErrorToken {
			break
		} else if tokt == html.StartTagToken && core.StringInSlice(txt, skipTags) {
			inBlock = true
		} else if inBlock && core.StringInSlice(txt, skipTags) {
			inBlock = false
		} else if tokt == html.StartTagToken {
			inline = core.StringInSlice(txt, inlineTags)
			skip = (txt == "tt" || txt == "code")
			tagHistory = append(tagHistory, txt)
		} else if tokt == html.CommentToken {
			f.UpdateComments(txt)
		} else if tokt == html.TextToken {
			queue = append(queue, txt)
			if !inBlock {
				first, _ := utf8.DecodeRuneInString(txt)
				if skip {
					txt, _ = core.Substitute(txt, txt, '*')
					skip = false
				}
				if inline && !core.StringInSlice(string(first), punct) {
					txt = " " + txt
				}
				buf.WriteString(txt)
			}
		}

		if tokt == html.EndTagToken && !core.StringInSlice(txt, inlineTags) {
			content := buf.String()
			if content != "" {
				l.lintScope(f, ctx, content, tagHistory, lines)
			}
			for _, s := range queue {
				ctx = updateCtx(ctx, s, html.TextToken)
			}
			queue = []string{}
			tagHistory = []string{}
			buf.Reset()
		}

		ctx = clearElements(ctx, tok)
	}
}

func (l Linter) lintScope(f *core.File, ctx, txt string, tags []string, lines int) {
	for _, tag := range tags {
		scope, match := tagToScope[tag]
		if match || heading.MatchString(tag) {
			if match {
				scope = scope + f.RealExt
			} else {
				scope = "text.heading." + tag + f.RealExt
			}
			l.lintText(f, NewBlock(ctx, txt, scope), lines, 0)
			return
		}
	}
	l.lintProse(f, ctx, txt, lines, 0)
}

func clearElements(ctx string, tok html.Token) string {
	if tok.Data == "img" || tok.Data == "a" {
		for _, a := range tok.Attr {
			if a.Key == "alt" || a.Key == "href" {
				ctx = updateCtx(ctx, a.Val, html.TextToken)
			}
		}
	}
	return ctx
}

func updateCtx(ctx string, txt string, tokt html.TokenType) string {
	var found bool
	if (tokt == html.TextToken || tokt == html.CommentToken) && txt != "" {
		for _, s := range strings.Split(txt, "\n") {
			ctx, found = core.Substitute(ctx, s, '@')
			if !found {
				for _, w := range strings.Fields(s) {
					ctx, _ = core.Substitute(ctx, w, '@')
				}
			}
		}
	}
	return ctx
}

func (l Linter) lintHTML(f *core.File) {
	l.lintHTMLTokens(f, f.Content, []byte(f.Content), 0)
}

func (l Linter) lintMarkdown(f *core.File) {
	html := blackfriday.MarkdownOptions([]byte(f.Content), renderer, options)
	l.lintHTMLTokens(f, f.Content, html, 0)
}

func (l Linter) lintRST(f *core.File, python string, rst2html string) {
	var out bytes.Buffer
	cmd := exec.Command(python, append([]string{rst2html}, rstArgs...)...)
	cmd.Stdin = strings.NewReader(reCodeBlock.ReplaceAllString(f.Content, "::"))
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		html := bytes.Replace(out.Bytes(), []byte("\r"), []byte(""), -1)
		bodyStart := bytes.Index(html, []byte("\n"))
		if bodyStart < 0 {
			bodyStart = -7
		}
		bodyEnd := bytes.Index(html, []byte("\n"))
		if bodyEnd < 0 || bodyEnd >= len(html) {
			bodyEnd = len(html) - 1
			if bodyEnd < 0 {
				bodyEnd = 0
			}
		}
		l.lintHTMLTokens(f, f.Content, html[bodyStart+7:bodyEnd], 0)
	}
}

func (l Linter) lintADoc(f *core.File, asciidoctor string) {
	var out bytes.Buffer
	cmd := exec.Command(asciidoctor, adocArgs...)
	cmd.Stdin = strings.NewReader(f.Content)
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		l.lintHTMLTokens(f, f.Content, out.Bytes(), 0)
	}
}
<commit_msg>refactor: skip padding for non-block scopes<commit_after>package lint

import (
	"bytes"
	"os/exec"
	"strings"
	"unicode/utf8"

	"github.com/ValeLint/vale/core"
	"github.com/russross/blackfriday"
	"golang.org/x/net/html"
	"matloob.io/regexp"
)

// reStructuredText configuration.
//
// reCodeBlock is used to convert Sphinx-style code directives to the regular
// `::` for rst2html.
var reCodeBlock = regexp.MustCompile(`.. (?:raw|code(?:-block)?):: (\w+)`)
var rstArgs = []string{
	"--quiet",
	"--halt=5",
	"--report=5",
	"--link-stylesheet",
	"--no-file-insertion",
	"--no-toc-backlinks",
	"--no-footnote-backlinks",
	"--no-section-numbering",
}

// AsciiDoc configuration.
var adocArgs = []string{
	"-s",
	"--quiet",
	"--safe-mode",
	"secure",
	"-",
}

// Blackfriday configuration.
var commonHTMLFlags = 0 | blackfriday.HTML_USE_XHTML
var commonExtensions = 0 |
	blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
	blackfriday.EXTENSION_TABLES |
	blackfriday.EXTENSION_FENCED_CODE
var renderer = blackfriday.HtmlRenderer(commonHTMLFlags, "", "")
var options = blackfriday.Options{Extensions: commonExtensions}

// HTML configuration.
var heading = regexp.MustCompile(`^h\d$`)

// skipTags are tags that we don't want to lint.
var skipTags = []string{"script", "style", "pre", "figure"}
var inlineTags = []string{
	"b", "big", "i", "small", "abbr", "acronym", "cite", "dfn", "em", "kbd",
	"strong", "a", "br", "img", "span", "sub", "sup", "code", "tt"}
var tagToScope = map[string]string{
	"th": "text.table.header",
	"td": "text.table.cell",
	"li": "text.list",
}

func (l Linter) lintHTMLTokens(f *core.File, ctx string, fsrc []byte, offset int) {
	var txt string
	var tokt html.TokenType
	var tok html.Token
	var inBlock, inline, skip bool

	punct := []string{".", "?", "!", ",", ":", ";"}
	lines := len(f.Lines) + offset
	buf := bytes.NewBufferString("")
	queue := []string{}
	tagHistory := []string{}

	tokens := html.NewTokenizer(bytes.NewReader(fsrc))
	for {
		tokt = tokens.Next()
		tok = tokens.Token()
		txt = html.UnescapeString(strings.TrimSpace(tok.Data))

		if tokt == html.ErrorToken {
			break
		} else if tokt == html.StartTagToken && core.StringInSlice(txt, skipTags) {
			inBlock = true
		} else if inBlock && core.StringInSlice(txt, skipTags) {
			inBlock = false
		} else if tokt == html.StartTagToken {
			inline = core.StringInSlice(txt, inlineTags)
			skip = (txt == "tt" || txt == "code")
			tagHistory = append(tagHistory, txt)
		} else if tokt == html.CommentToken {
			f.UpdateComments(txt)
		} else if tokt == html.TextToken {
			queue = append(queue, txt)
			if !inBlock {
				first, _ := utf8.DecodeRuneInString(txt)
				if skip {
					txt, _ = core.Substitute(txt, txt, '*')
					txt = "`" + txt + "`"
					skip = false
				}
				if inline && !core.StringInSlice(string(first), punct) {
					txt = " " + txt
				}
				buf.WriteString(txt)
			}
		}

		if tokt == html.EndTagToken && !core.StringInSlice(txt, inlineTags) {
			content := buf.String()
			if content != "" {
				l.lintScope(f, ctx, content, tagHistory, lines)
			}
			for _, s := range queue {
				ctx = updateCtx(ctx, s, html.TextToken)
			}
			queue = []string{}
			tagHistory = []string{}
			buf.Reset()
		}

		ctx = clearElements(ctx, tok)
	}
}

func (l Linter) lintScope(f *core.File, ctx, txt string, tags []string, lines int) {
	for _, tag := range tags {
		scope, match := tagToScope[tag]
		if match || heading.MatchString(tag) {
			if match {
				scope = scope + f.RealExt
			} else {
				scope = "text.heading." + tag + f.RealExt
			}
			txt = strings.TrimLeft(txt, " ")
			l.lintText(f, NewBlock(ctx, txt, scope), lines, 0)
			return
		}
	}
	l.lintProse(f, ctx, txt, lines, 0)
}

func clearElements(ctx string, tok html.Token) string {
	if tok.Data == "img" || tok.Data == "a" {
		for _, a := range tok.Attr {
			if a.Key == "alt" || a.Key == "href" {
				ctx = updateCtx(ctx, a.Val, html.TextToken)
			}
		}
	}
	return ctx
}

func updateCtx(ctx string, txt string, tokt html.TokenType) string {
	var found bool
	if (tokt == html.TextToken || tokt == html.CommentToken) && txt != "" {
		for _, s := range strings.Split(txt, "\n") {
			ctx, found = core.Substitute(ctx, s, '@')
			if !found {
				for _, w := range strings.Fields(s) {
					ctx, _ = core.Substitute(ctx, w, '@')
				}
			}
		}
	}
	return ctx
}

func (l Linter) lintHTML(f *core.File) {
	l.lintHTMLTokens(f, f.Content, []byte(f.Content), 0)
}

func (l Linter) lintMarkdown(f *core.File) {
	html := blackfriday.MarkdownOptions([]byte(f.Content), renderer, options)
	l.lintHTMLTokens(f, f.Content, html, 0)
}

func (l Linter) lintRST(f *core.File, python string, rst2html string) {
	var out bytes.Buffer
	cmd := exec.Command(python, append([]string{rst2html}, rstArgs...)...)
	cmd.Stdin = strings.NewReader(reCodeBlock.ReplaceAllString(f.Content, "::"))
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		html := bytes.Replace(out.Bytes(), []byte("\r"), []byte(""), -1)
		bodyStart := bytes.Index(html, []byte("\n"))
		if bodyStart < 0 {
			bodyStart = -7
		}
		bodyEnd := bytes.Index(html, []byte("\n"))
		if bodyEnd < 0 || bodyEnd >= len(html) {
			bodyEnd = len(html) - 1
			if bodyEnd < 0 {
				bodyEnd = 0
			}
		}
		l.lintHTMLTokens(f, f.Content, html[bodyStart+7:bodyEnd], 0)
	}
}

func (l Linter) lintADoc(f *core.File, asciidoctor string) {
	var out bytes.Buffer
	cmd := exec.Command(asciidoctor, adocArgs...)
	cmd.Stdin = strings.NewReader(f.Content)
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		l.lintHTMLTokens(f, f.Content, out.Bytes(), 0)
	}
}
<|endoftext|>


<commit_before>package gandalf

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

type bufferCloser struct {
	*bytes.Buffer
}

func (b bufferCloser) Close() error { return nil }

func request(url string, b io.Reader, t *testing.T) (*httptest.ResponseRecorder, *http.Request) {
	request, err := http.NewRequest("POST", url, b)
	if err != nil {
		t.Errorf("Error when creating new request: %s", err)
		t.FailNow()
	}
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()
	return recorder, request
}

func createUser(name string, t *testing.T) (u user, err error) {
    u = user{Name: "pippin"}
    c := session.DB("gandalf").C("user")
    err = c.Insert(&u)
    return
}

func readBody(b io.Reader, t *testing.T) string {
	body, err := ioutil.ReadAll(b)
	if err != nil {
		t.Errorf("Error when reading body: %s", err)
		t.FailNow()
	}
	return string(body)
}

func TestCreateUser(t *testing.T) {
	b := strings.NewReader(`{"name": "brain", "key": ["some id_rsa.pub key.. use your imagination!"]}`)
	recorder, request := request("/user", b, t)
	CreateUser(recorder, request)
	c := session.DB("gandalf").C("user")
	defer c.Remove(bson.M{"_id": "brain"})
	if recorder.Code != 200 {
		t.Errorf(`Failed to create user, expected "%d" status code, got: "%d"`, 200, recorder.Code)
	}
}

func TestCreateUserShouldSaveInDB(t *testing.T) {
	b := strings.NewReader(`{"name": "brain", "key": ["some id_rsa.pub key.. use your imagination!"]}`)
	recorder, request := request("/user", b, t)
	CreateUser(recorder, request)
	c := session.DB("gandalf").C("user")
	var u user
	err := c.Find(bson.M{"_id": "brain"}).One(&u)
	defer c.Remove(bson.M{"_id": "brain"})
	if err != nil {
		t.Errorf(`Error when searching for user: "%s"`, err.Error())
	}
}

func TestCreateUserShouldRepassParseBodyErrors(t *testing.T) {
	b := strings.NewReader("{]9afe}")
	recorder, request := request("/user", b, t)
	CreateUser(recorder, request)
	body := readBody(recorder.Body, t)
	expected := "Could not parse json: invalid character ']' looking for beginning of object key string"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected error to matches: "%s", got: "%s"`, expected, got)
	}
}

func TestCreateUserShouldRequireUserName(t *testing.T) {
	b := strings.NewReader(`{"name": ""}`)
	recorder, request := request("/user", b, t)
	CreateUser(recorder, request)
	body := readBody(recorder.Body, t)
	expected := "User needs a name"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, got)
	}
}

func TestCreateUserWihoutKey(t *testing.T) {
	b := strings.NewReader(`{"name": "brain"}`)
	recorder, request := request("/user", b, t)
	CreateUser(recorder, request)
	c := session.DB("gandalf").C("user")
	defer c.Remove(bson.M{"_id": "brain"})
	if recorder.Code != 200 {
		t.Errorf(`Failed to create user, expected "%d" status code, got: "%d"`, 200, recorder.Code)
	}
}

func TestCreateRepository(t *testing.T) {
	c := session.DB("gandalf").C("repository")
	defer c.Remove(bson.M{"_id": "some_repository"})
	b := strings.NewReader(`{"name": "some_repository", "users": ["r2d2"]}`)
	recorder, request := request("/repository", b, t)
	CreateRepository(recorder, request)
	got := readBody(recorder.Body, t)
	expected := "Repository some_repository successfuly created"
	if got != expected {
		t.Errorf(`Expected body to be "%s", got: "%s"`, expected, got)
	}
}

func TestCreateRepositoryShouldSaveInDB(t *testing.T) {
	b := strings.NewReader(`{"name": "myRepository", "users": ["r2d2"]}`)
	recorder, request := request("/repository", b, t)
	CreateRepository(recorder, request)
	c := session.DB("gandalf").C("repository")
	defer c.Remove(bson.M{"_id": "myRepository"})
	var p repository
	err := c.Find(bson.M{"_id": "myRepository"}).One(&p)
	if err != nil {
		t.Errorf(`There was an error while retrieving repository: "%s"`, err.Error())
	}
}

func TestCreateRepositoryShouldSaveUserIdInRepository(t *testing.T) {
	b := strings.NewReader(`{"name": "myRepository", "users": ["r2d2", "brain"]}`)
	recorder, request := request("/repository", b, t)
	CreateRepository(recorder, request)
	c := session.DB("gandalf").C("repository")
	defer c.Remove(bson.M{"_id": "myRepository"})
	var p repository
	err := c.Find(bson.M{"_id": "myRepository"}).One(&p)
	if err != nil {
		t.Errorf(`There was an error while retrieving repository: "%s"`, err.Error())
	}
	if len(p.Users) == 0 {
		t.Errorf(`Expected user to be %s and %s, got empty.`, "r2d2", "brain")
	}
}

func TestCreateRepositoryShouldReturnErrorWhenNoUserIsPassed(t *testing.T) {
	b := strings.NewReader(`{"name": "myRepository"}`)
	recorder, request := request("/repository", b, t)
	CreateRepository(recorder, request)
	if recorder.Code != 400 {
		t.Errorf(`Expected code to be "400", got "%d"`, recorder.Code)
	}
	body := readBody(recorder.Body, t)
	expected := "Repository needs a user"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected body to matches: "%s", got: "%s"`, expected, got)
	}
}

func TestCreateRepositoryShouldReturnErrorWhenNoParametersArePassed(t *testing.T) {
	b := strings.NewReader("{}")
	recorder, request := request("/repository", b, t)
	CreateRepository(recorder, request)
	if recorder.Code != 400 {
		t.Errorf(`Expected code to be "400", got "%d"`, recorder.Code)
	}
	body := readBody(recorder.Body, t)
	expected := "Repository needs a name"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected body to matches: "%s", got: "%s"`, expected, got)
	}
}

func TestParseBodyShouldMapBodyJsonToGivenStruct(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString(`{"name": "Dummy Repository"}`)}
	err := parseBody(b, &p)
	if err != nil {
		t.Errorf(`Expecting err to be nil, got: "%s"`, err.Error())
	}
	expected := "Dummy Repository"
	if p.Name != expected {
		t.Errorf(`Expecting err to be "%s", got: "%s"`, expected, p.Name)
	}
}

func TestParseBodyShouldReturnErrorWhenJsonIsInvalid(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString("{]ja9aW}")}
	err := parseBody(b, &p)
	expected := "Could not parse json: invalid character ']' looking for beginning of object key string"
	if err.Error() != expected {
		t.Errorf(`Expected error to matches: "%s", got: "%s"`, expected, err.Error())
	}
}

func TestParseBodyShouldReturnErrorWhenBodyIsEmpty(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString("")}
	err := parseBody(b, &p)
	expected := "Could not parse json: unexpected end of JSON input"
	if err.Error() != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, err.Error())
	}
}

func TestParseBodyShouldReturnErrorWhenResultParamIsNotAPointer(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString(`{"name": "something"}`)}
	err := parseBody(b, p)
	expected := "parseBody function cannot deal with struct. Use pointer"
	if err.Error() != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, err.Error())
	}
}

func TestCreateRepositoryShouldReturnErrorWhenBodyIsEmpty(t *testing.T) {
	b := strings.NewReader("")
	recorder, request := request("/repository", b, t)
	CreateRepository(recorder, request)
	if recorder.Code != 400 {
		t.Errorf(`Expected code to be "400", got "%d"`, recorder.Code)
	}
}

func TestAddKey(t *testing.T) {
	user := user{Name: "Frodo"}
	c := session.DB("gandalf").C("user")
	c.Insert(&user)
	b := strings.NewReader(`{"key": "a public key"}`)
	recorder, request := request(fmt.Sprintf("/user/%s/key?:name=%s", user.Name, user.Name), b, t)
	defer c.Remove(bson.M{"_id": "Frodo"})
	AddKey(recorder, request)
	got := readBody(recorder.Body, t)
	expected := "Key \"a public key\" successfuly created"
	if got != expected {
		t.Errorf(`Expected body to be "%s", got: "%s"`, expected, got)
	}
}

func TestGrantAccess(t *testing.T) {
    u, err := createUser("pippin", t)
    c := session.DB("gandalf").C("user")
	defer c.Remove(bson.M{"_id": "pippin"})
    r := repository{Name: "repo"}
    c = Session.Repository()
    err = c.Insert(&r)
    if err != nil {
        t.Errorf(`Expected error to be nil, got %s`, err.Error())
    }
	defer c.Remove(bson.M{"_id": "repo"})
    b := strings.NewReader(`{"users": ["pippin"]}`)
    url := fmt.Sprintf("/repository/%s/grant?:name=%s", r.Name, r.Name)
    rec, req := request(url, b, t)
    GrantAccess(rec, req)
    c.Find(bson.M{"_id": "repo"}).One(&r)
    if len(r.Users) == 0 {
        t.Errorf(`Expected repository to have one user, got 0`)
        t.FailNow()
    }
    if r.Users[0] != u.Name {
        t.Errorf(`Expected repository's user to be %s, got %s`, u.Name, r.Users[0])
    }
}

func TestAddKeyShouldReturnErorWhenUserDoesNotExists(t *testing.T) {
	b := strings.NewReader(`{"key": "a public key"}`)
	recorder, request := request("/user/Frodo/key?:name=Frodo", b, t)
	AddKey(recorder, request)
	if recorder.Code != 404 {
		t.Errorf(`Expected code to be "404", got "%d"`, recorder.Code)
	}
}

func TestAddKeyShouldRequireKey(t *testing.T) {
	user := user{Name: "Frodo"}
	c := session.DB("gandalf").C("user")
	c.Insert(&user)
	defer c.Remove(bson.M{"_id": "Frodo"})
	b := strings.NewReader(`{"key": ""}`)
	recorder, request := request("/user/Frodo/key?:name=Frodo", b, t)
	AddKey(recorder, request)
	body := readBody(recorder.Body, t)
	expected := "A key is needed"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, got)
	}
}
<commit_msg>handler: reorganizing test code and changing test helper function s/request/post<commit_after>package gandalf

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

type bufferCloser struct {
	*bytes.Buffer
}

func (b bufferCloser) Close() error { return nil }

func post(url string, b io.Reader, t *testing.T) (*httptest.ResponseRecorder, *http.Request) {
	request, err := http.NewRequest("POST", url, b)
	if err != nil {
		t.Errorf("Error when creating new request: %s", err)
		t.FailNow()
	}
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()
	return recorder, request
}

func createUser(name string, t *testing.T) (u user, err error) {
    u = user{Name: "pippin"}
    c := session.DB("gandalf").C("user")
    err = c.Insert(&u)
    return
}

func readBody(b io.Reader, t *testing.T) string {
	body, err := ioutil.ReadAll(b)
	if err != nil {
		t.Errorf("Error when reading body: %s", err)
		t.FailNow()
	}
	return string(body)
}

func TestCreateUser(t *testing.T) {
	b := strings.NewReader(`{"name": "brain", "key": ["some id_rsa.pub key.. use your imagination!"]}`)
	recorder, request := post("/user", b, t)
	CreateUser(recorder, request)
	c := session.DB("gandalf").C("user")
	defer c.Remove(bson.M{"_id": "brain"})
	if recorder.Code != 200 {
		t.Errorf(`Failed to create user, expected "%d" status code, got: "%d"`, 200, recorder.Code)
	}
}

func TestCreateUserShouldSaveInDB(t *testing.T) {
	b := strings.NewReader(`{"name": "brain", "key": ["some id_rsa.pub key.. use your imagination!"]}`)
	recorder, request := post("/user", b, t)
	CreateUser(recorder, request)
	c := session.DB("gandalf").C("user")
	var u user
	err := c.Find(bson.M{"_id": "brain"}).One(&u)
	defer c.Remove(bson.M{"_id": "brain"})
	if err != nil {
		t.Errorf(`Error when searching for user: "%s"`, err.Error())
	}
}

func TestCreateUserShouldRepassParseBodyErrors(t *testing.T) {
	b := strings.NewReader("{]9afe}")
	recorder, request := post("/user", b, t)
	CreateUser(recorder, request)
	body := readBody(recorder.Body, t)
	expected := "Could not parse json: invalid character ']' looking for beginning of object key string"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected error to matches: "%s", got: "%s"`, expected, got)
	}
}

func TestCreateUserShouldRequireUserName(t *testing.T) {
	b := strings.NewReader(`{"name": ""}`)
	recorder, request := post("/user", b, t)
	CreateUser(recorder, request)
	body := readBody(recorder.Body, t)
	expected := "User needs a name"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, got)
	}
}

func TestCreateUserWihoutKey(t *testing.T) {
	b := strings.NewReader(`{"name": "brain"}`)
	recorder, request := post("/user", b, t)
	CreateUser(recorder, request)
	c := session.DB("gandalf").C("user")
	defer c.Remove(bson.M{"_id": "brain"})
	if recorder.Code != 200 {
		t.Errorf(`Failed to create user, expected "%d" status code, got: "%d"`, 200, recorder.Code)
	}
}

func TestCreateRepository(t *testing.T) {
	c := session.DB("gandalf").C("repository")
	defer c.Remove(bson.M{"_id": "some_repository"})
	b := strings.NewReader(`{"name": "some_repository", "users": ["r2d2"]}`)
	recorder, request := post("/repository", b, t)
	CreateRepository(recorder, request)
	got := readBody(recorder.Body, t)
	expected := "Repository some_repository successfuly created"
	if got != expected {
		t.Errorf(`Expected body to be "%s", got: "%s"`, expected, got)
	}
}

func TestCreateRepositoryShouldSaveInDB(t *testing.T) {
	b := strings.NewReader(`{"name": "myRepository", "users": ["r2d2"]}`)
	recorder, request := post("/repository", b, t)
	CreateRepository(recorder, request)
	c := session.DB("gandalf").C("repository")
	defer c.Remove(bson.M{"_id": "myRepository"})
	var p repository
	err := c.Find(bson.M{"_id": "myRepository"}).One(&p)
	if err != nil {
		t.Errorf(`There was an error while retrieving repository: "%s"`, err.Error())
	}
}

func TestCreateRepositoryShouldSaveUserIdInRepository(t *testing.T) {
	b := strings.NewReader(`{"name": "myRepository", "users": ["r2d2", "brain"]}`)
	recorder, request := post("/repository", b, t)
	CreateRepository(recorder, request)
	c := session.DB("gandalf").C("repository")
	defer c.Remove(bson.M{"_id": "myRepository"})
	var p repository
	err := c.Find(bson.M{"_id": "myRepository"}).One(&p)
	if err != nil {
		t.Errorf(`There was an error while retrieving repository: "%s"`, err.Error())
	}
	if len(p.Users) == 0 {
		t.Errorf(`Expected user to be %s and %s, got empty.`, "r2d2", "brain")
	}
}

func TestCreateRepositoryShouldReturnErrorWhenNoUserIsPassed(t *testing.T) {
	b := strings.NewReader(`{"name": "myRepository"}`)
	recorder, request := post("/repository", b, t)
	CreateRepository(recorder, request)
	if recorder.Code != 400 {
		t.Errorf(`Expected code to be "400", got "%d"`, recorder.Code)
	}
	body := readBody(recorder.Body, t)
	expected := "Repository needs a user"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected body to matches: "%s", got: "%s"`, expected, got)
	}
}

func TestCreateRepositoryShouldReturnErrorWhenNoParametersArePassed(t *testing.T) {
	b := strings.NewReader("{}")
	recorder, request := post("/repository", b, t)
	CreateRepository(recorder, request)
	if recorder.Code != 400 {
		t.Errorf(`Expected code to be "400", got "%d"`, recorder.Code)
	}
	body := readBody(recorder.Body, t)
	expected := "Repository needs a name"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected body to matches: "%s", got: "%s"`, expected, got)
	}
}

func TestParseBodyShouldMapBodyJsonToGivenStruct(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString(`{"name": "Dummy Repository"}`)}
	err := parseBody(b, &p)
	if err != nil {
		t.Errorf(`Expecting err to be nil, got: "%s"`, err.Error())
	}
	expected := "Dummy Repository"
	if p.Name != expected {
		t.Errorf(`Expecting err to be "%s", got: "%s"`, expected, p.Name)
	}
}

func TestParseBodyShouldReturnErrorWhenJsonIsInvalid(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString("{]ja9aW}")}
	err := parseBody(b, &p)
	expected := "Could not parse json: invalid character ']' looking for beginning of object key string"
	if err.Error() != expected {
		t.Errorf(`Expected error to matches: "%s", got: "%s"`, expected, err.Error())
	}
}

func TestParseBodyShouldReturnErrorWhenBodyIsEmpty(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString("")}
	err := parseBody(b, &p)
	expected := "Could not parse json: unexpected end of JSON input"
	if err.Error() != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, err.Error())
	}
}

func TestParseBodyShouldReturnErrorWhenResultParamIsNotAPointer(t *testing.T) {
	var p repository
	b := bufferCloser{bytes.NewBufferString(`{"name": "something"}`)}
	err := parseBody(b, p)
	expected := "parseBody function cannot deal with struct. Use pointer"
	if err.Error() != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, err.Error())
	}
}

func TestCreateRepositoryShouldReturnErrorWhenBodyIsEmpty(t *testing.T) {
	b := strings.NewReader("")
	recorder, request := post("/repository", b, t)
	CreateRepository(recorder, request)
	if recorder.Code != 400 {
		t.Errorf(`Expected code to be "400", got "%d"`, recorder.Code)
	}
}

func TestGrantAccess(t *testing.T) {
    u, err := createUser("pippin", t)
    c := session.DB("gandalf").C("user")
	defer c.Remove(bson.M{"_id": "pippin"})
    r := repository{Name: "repo"}
    c = Session.Repository()
    err = c.Insert(&r)
    if err != nil {
        t.Errorf(`Expected error to be nil, got %s`, err.Error())
    }
	defer c.Remove(bson.M{"_id": "repo"})
    b := strings.NewReader(`{"users": ["pippin"]}`)
    url := fmt.Sprintf("/repository/%s/grant?:name=%s", r.Name, r.Name)
    rec, req := post(url, b, t)
    GrantAccess(rec, req)
    c.Find(bson.M{"_id": "repo"}).One(&r)
    if len(r.Users) == 0 {
        t.Errorf(`Expected repository to have one user, got 0`)
        t.FailNow()
    }
    if r.Users[0] != u.Name {
        t.Errorf(`Expected repository's user to be %s, got %s`, u.Name, r.Users[0])
    }
}

func TestAddKey(t *testing.T) {
	user := user{Name: "Frodo"}
	c := session.DB("gandalf").C("user")
	c.Insert(&user)
	b := strings.NewReader(`{"key": "a public key"}`)
	recorder, request := post(fmt.Sprintf("/user/%s/key?:name=%s", user.Name, user.Name), b, t)
	defer c.Remove(bson.M{"_id": "Frodo"})
	AddKey(recorder, request)
	got := readBody(recorder.Body, t)
	expected := "Key \"a public key\" successfuly created"
	if got != expected {
		t.Errorf(`Expected body to be "%s", got: "%s"`, expected, got)
	}
}

func TestAddKeyShouldReturnErorWhenUserDoesNotExists(t *testing.T) {
	b := strings.NewReader(`{"key": "a public key"}`)
	recorder, request := post("/user/Frodo/key?:name=Frodo", b, t)
	AddKey(recorder, request)
	if recorder.Code != 404 {
		t.Errorf(`Expected code to be "404", got "%d"`, recorder.Code)
	}
}

func TestAddKeyShouldRequireKey(t *testing.T) {
	user := user{Name: "Frodo"}
	c := session.DB("gandalf").C("user")
	c.Insert(&user)
	defer c.Remove(bson.M{"_id": "Frodo"})
	b := strings.NewReader(`{"key": ""}`)
	recorder, request := post("/user/Frodo/key?:name=Frodo", b, t)
	AddKey(recorder, request)
	body := readBody(recorder.Body, t)
	expected := "A key is needed"
	got := strings.Replace(body, "\n", "", -1)
	if got != expected {
		t.Errorf(`Expected error to matches "%s", got: "%s"`, expected, got)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/gophergala/matching-snuggies/slicerjob"
)

func main() {
	server := flag.String("server", "localhost:8888", "snuggied server address")
	slicerBackend := flag.String("backend", "slic3r", "backend slicer")
	slicerPreset := flag.String("preset", "hiQ", "specify a configuration preset for the backend")
	gcodeDest := flag.String("o", "", "specify an output gcode filename")
	flag.Parse()

	if flag.NArg() < 1 {
		log.Fatalf("missing argument: mesh file")
	}
	meshpath := flag.Arg(0)

	client := &Client{
		ServerAddr: *server,
	}

	// start intercepting signals from the operating system
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	// send files to the slicer to be printed and poll the slicer until the job
	// has completed.
	log.Printf("sending file(s) to snuggied server at %v", *server)
	job, err := client.SliceFile(*slicerBackend, *slicerPreset, meshpath)
	if err != nil {
		log.Fatalf("sending files: %v", err)
	}
	maxTick := time.Second * 5
	currentTick := time.Millisecond * 1
	tick := time.After(currentTick)
	for job.Status != slicerjob.Complete {
		// with exponential backoff on network failure
		select {
		case s := <-sig:
			// stop intercepting signals. if the job cancellation is taking too
			// long let the future signals terminate the process naturally.
			signal.Stop(sig)
			log.Printf("signal: %v", s)
			err := client.Cancel(job)
			if err != nil {
				log.Printf("failed to cancel job: %v", err)
			}
			log.Printf("slicing job canceled")
			return
		case <-tick:
			job, err = client.SlicerStatus(job)
			if err != nil {
				log.Fatalf("waiting: %v", err)
			}

			if currentTick < maxTick {
				currentTick *= 2
			} else if currentTick > maxTick {
				currentTick = maxTick
			}
			log.Println("tick", currentTick)
			tick = time.After(currentTick)
		}
	}
	// stop intercepting signals because it because much more difficult to stop
	// gracefully while reading gcode from the server.
	signal.Stop(sig)

	// download gcode from the slicer and write to the specified file.
	log.Printf("retreiving gcode file")
	r, err := client.GCode(job)
	if err != nil {
		log.Fatalf("gcode: %v", err)
	}
	defer r.Close()
	var f *os.File
	if *gcodeDest == "" {
		f = os.Stdout
	} else {
		f, err = os.Create(*gcodeDest)
		if err != nil {
			log.Panic(err)
		}
	}
	defer func() {
		err := f.Close()
		if err != nil {
			log.Panic(err)
		}
	}()
	_, err = io.Copy(f, r)
	if err != nil {
		log.Panic(err)
	}
}

type Client struct {
	Client     *http.Client
	ServerAddr string
	HTTPS      bool
}

// SliceFiles tells the server to slice the specified paths.
func (c *Client) SliceFile(backend, preset string, path string) (*slicerjob.Job, error) {
	// check that a mesh file is given as the first argument and open it
	// so it may to encode in the form.
	if !IsMeshFile(path) {
		log.Fatalf("path is not a mesh file: %v", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	// write the multipart form out to a temporary file.  the temporary
	// file is closed and unlinked when the function terminates.
	tmp, err := ioutil.TempFile("", "matching-snuggies-post-")
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()
	bodyw := multipart.NewWriter(tmp)
	err = c.writeJobForm(bodyw, backend, preset, path, f)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}

	// seek back to the beginning of the form and POST it to the slicer
	// server.  decode a slicerjob.Job from successful responses.
	var job *slicerjob.Job
	_, err = tmp.Seek(0, 0)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	url := c.url("/slicer/jobs")
	log.Printf("POST %v", url)
	resp, err := c.client().Post(url, bodyw.FormDataContentType(), tmp)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, httpStatusError(resp)
	}
	err = json.NewDecoder(resp.Body).Decode(&job)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return job, nil
}

func (c *Client) writeJobForm(w *multipart.Writer, backend, preset, filename string, r io.Reader) error {
	err := w.WriteField("slicer", backend)
	if err != nil {
		return err
	}
	err = w.WriteField("preset", preset)
	if err != nil {
		return err
	}
	file, err := w.CreateFormFile("meshfile", filepath.Base(filename))
	if err != nil {
		return err
	}
	_, err = io.Copy(file, r)
	if err != nil {
		return err
	}
	return w.Close()
}

func (c *Client) Cancel(job *slicerjob.Job) error {
	if job.ID == "" {
		return fmt.Errorf("job missing id")
	}
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("DELETE %v", url)
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return fmt.Errorf("request: %v", err)
	}
	resp, err := c.client().Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return httpStatusError(resp)
	}
	return nil
}

// SlicerStatus returns a current copy of the provided job.
func (c *Client) SlicerStatus(job *slicerjob.Job) (*slicerjob.Job, error) {
	if job.ID == "" {
		return nil, fmt.Errorf("job missing id")
	}
	var jobcurr *slicerjob.Job
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("GET %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("GET /slicer/jobs/: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, httpStatusError(resp)
	}
	err = json.NewDecoder(resp.Body).Decode(&jobcurr)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return jobcurr, nil
}

// GCode requests the gcode for job.
func (c *Client) GCode(job *slicerjob.Job) (io.ReadCloser, error) {
	url := c.url("/slicer/gcodes/" + job.ID)
	log.Printf("GET %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("GET /slicer/codes/: %v", err)
	}
	if resp.StatusCode != 200 {
		return nil, httpStatusError(resp)
	}
	return resp.Body, nil
}

func (c *Client) client() *http.Client {
	if c.Client == nil {
		return http.DefaultClient
	}
	return c.Client
}

func (c *Client) url(pathquery string) string {
	pathquery = strings.TrimPrefix(pathquery, "/")
	scheme := "http"
	if c.HTTPS {
		scheme = "https"
	}
	return scheme + "://" + c.ServerAddr + "/" + pathquery
}

var meshExts = map[string]bool{
	".stl": true,
	".amf": true,
}

func IsMeshFile(path string) bool {
	return meshExts[filepath.Ext(path)]
}

func httpStatusError(resp *http.Response) error {
	p, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 85))
	msg := trimMessage(string(p), 80)
	return fmt.Errorf("http %d %s: %q", resp.StatusCode, http.StatusText(resp.StatusCode), msg)
}

func trimMessage(s string, n int) string {
	s = strings.TrimSpace(s)
	if len(s) < n {
		return s
	}
	var rs []rune
	var m int
	for _, c := range s {
		if m >= n {
			break
		}
		rs = append(rs, c)
		m++
	}
	return string(rs) + "..."
}
<commit_msg>increase default snuggier poll delay to 100ms<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/gophergala/matching-snuggies/slicerjob"
)

func main() {
	server := flag.String("server", "localhost:8888", "snuggied server address")
	slicerBackend := flag.String("backend", "slic3r", "backend slicer")
	slicerPreset := flag.String("preset", "hiQ", "specify a configuration preset for the backend")
	gcodeDest := flag.String("o", "", "specify an output gcode filename")
	flag.Parse()

	if flag.NArg() < 1 {
		log.Fatalf("missing argument: mesh file")
	}
	meshpath := flag.Arg(0)

	client := &Client{
		ServerAddr: *server,
	}

	// start intercepting signals from the operating system
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	// send files to the slicer to be printed and poll the slicer until the job
	// has completed.
	log.Printf("sending file(s) to snuggied server at %v", *server)
	job, err := client.SliceFile(*slicerBackend, *slicerPreset, meshpath)
	if err != nil {
		log.Fatalf("sending files: %v", err)
	}

	// poll the server until the job has completed.  use exponential backoff to
	// reduce spam for slice slicing jobs.
	maxTick := time.Second * 5
	currentTick := 100 * time.Millisecond
	tick := time.After(currentTick)
	for job.Status != slicerjob.Complete {
		select {
		case s := <-sig:
			// stop intercepting signals. if the job cancellation is taking too
			// long let the future signals terminate the process naturally.
			signal.Stop(sig)
			log.Printf("signal: %v", s)
			err := client.Cancel(job)
			if err != nil {
				log.Printf("failed to cancel job: %v", err)
			}
			log.Printf("slicing job canceled")
			return
		case <-tick:
			job, err = client.SlicerStatus(job)
			if err != nil {
				// TODO:
				// detect potentially intermittent network failures and
				// continue polling up to some reasonable time limit.
				log.Fatalf("waiting: %v", err)
			}

			if currentTick < maxTick {
				currentTick *= 2
			} else if currentTick > maxTick {
				currentTick = maxTick
			}
			log.Println("tick", currentTick)
			tick = time.After(currentTick)
		}
	}
	// stop intercepting signals because it because much more difficult to stop
	// gracefully while reading gcode from the server.
	signal.Stop(sig)

	// download gcode from the slicer and write to the specified file.
	log.Printf("retreiving gcode file")
	r, err := client.GCode(job)
	if err != nil {
		log.Fatalf("gcode: %v", err)
	}
	defer r.Close()
	var f *os.File
	if *gcodeDest == "" {
		f = os.Stdout
	} else {
		f, err = os.Create(*gcodeDest)
		if err != nil {
			log.Panic(err)
		}
	}
	defer func() {
		err := f.Close()
		if err != nil {
			log.Panic(err)
		}
	}()
	_, err = io.Copy(f, r)
	if err != nil {
		log.Panic(err)
	}
}

type Client struct {
	Client     *http.Client
	ServerAddr string
	HTTPS      bool
}

// SliceFiles tells the server to slice the specified paths.
func (c *Client) SliceFile(backend, preset string, path string) (*slicerjob.Job, error) {
	// check that a mesh file is given as the first argument and open it
	// so it may to encode in the form.
	if !IsMeshFile(path) {
		log.Fatalf("path is not a mesh file: %v", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	// write the multipart form out to a temporary file.  the temporary
	// file is closed and unlinked when the function terminates.
	tmp, err := ioutil.TempFile("", "matching-snuggies-post-")
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()
	bodyw := multipart.NewWriter(tmp)
	err = c.writeJobForm(bodyw, backend, preset, path, f)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}

	// seek back to the beginning of the form and POST it to the slicer
	// server.  decode a slicerjob.Job from successful responses.
	var job *slicerjob.Job
	_, err = tmp.Seek(0, 0)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	url := c.url("/slicer/jobs")
	log.Printf("POST %v", url)
	resp, err := c.client().Post(url, bodyw.FormDataContentType(), tmp)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, httpStatusError(resp)
	}
	err = json.NewDecoder(resp.Body).Decode(&job)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return job, nil
}

func (c *Client) writeJobForm(w *multipart.Writer, backend, preset, filename string, r io.Reader) error {
	err := w.WriteField("slicer", backend)
	if err != nil {
		return err
	}
	err = w.WriteField("preset", preset)
	if err != nil {
		return err
	}
	file, err := w.CreateFormFile("meshfile", filepath.Base(filename))
	if err != nil {
		return err
	}
	_, err = io.Copy(file, r)
	if err != nil {
		return err
	}
	return w.Close()
}

func (c *Client) Cancel(job *slicerjob.Job) error {
	if job.ID == "" {
		return fmt.Errorf("job missing id")
	}
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("DELETE %v", url)
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return fmt.Errorf("request: %v", err)
	}
	resp, err := c.client().Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return httpStatusError(resp)
	}
	return nil
}

// SlicerStatus returns a current copy of the provided job.
func (c *Client) SlicerStatus(job *slicerjob.Job) (*slicerjob.Job, error) {
	if job.ID == "" {
		return nil, fmt.Errorf("job missing id")
	}
	var jobcurr *slicerjob.Job
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("GET %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("GET /slicer/jobs/: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, httpStatusError(resp)
	}
	err = json.NewDecoder(resp.Body).Decode(&jobcurr)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return jobcurr, nil
}

// GCode requests the gcode for job.
func (c *Client) GCode(job *slicerjob.Job) (io.ReadCloser, error) {
	url := c.url("/slicer/gcodes/" + job.ID)
	log.Printf("GET %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("GET /slicer/codes/: %v", err)
	}
	if resp.StatusCode != 200 {
		return nil, httpStatusError(resp)
	}
	return resp.Body, nil
}

func (c *Client) client() *http.Client {
	if c.Client == nil {
		return http.DefaultClient
	}
	return c.Client
}

func (c *Client) url(pathquery string) string {
	pathquery = strings.TrimPrefix(pathquery, "/")
	scheme := "http"
	if c.HTTPS {
		scheme = "https"
	}
	return scheme + "://" + c.ServerAddr + "/" + pathquery
}

var meshExts = map[string]bool{
	".stl": true,
	".amf": true,
}

func IsMeshFile(path string) bool {
	return meshExts[filepath.Ext(path)]
}

func httpStatusError(resp *http.Response) error {
	p, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 85))
	msg := trimMessage(string(p), 80)
	return fmt.Errorf("http %d %s: %q", resp.StatusCode, http.StatusText(resp.StatusCode), msg)
}

func trimMessage(s string, n int) string {
	s = strings.TrimSpace(s)
	if len(s) < n {
		return s
	}
	var rs []rune
	var m int
	for _, c := range s {
		if m >= n {
			break
		}
		rs = append(rs, c)
		m++
	}
	return string(rs) + "..."
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"flag"
	"io/ioutil"
	"net/http"
	"strconv"
	"syscall"

	"github.com/Sirupsen/logrus"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/binding"
	"github.com/martini-contrib/render"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type LMVFile struct {
	Size      int64      `bson:"size"         json:"size"         binding:"required"`
	Name      string     `bson:"name"         json:"name"         binding:"required"`
	Algorithm string     `bson:"algorithm"    json:"algorithm"    binding:"required"`
	Chunks    []LMVChunk `bson:"chunks"       json:"chunks"       binding:"required"`
	Tar       bool       `bson:"tar"          json:"tar"`
	Token     string     `bson:"token"        json:"token"`
}

type LMVChunk struct {
	Hash  string `bson:"hash"         json:"hash"         binding:"required"`
	Size  int64  `bson:"size"         json:"size"         binding:"required"`
	Index int    `bson:"index"        json:"index"        binding:"required"`
}

const (
	token_length = 10
	mgo_host     = "localhost"
	mgo_db       = "Ludicrous-MV"
	mgo_col      = "Files"
)

var log = logrus.New()

func randstr(length int) string {

	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	var bytes = make([]byte, length)

	rand.Read(bytes)

	for i, b := range bytes {
		bytes[i] = alphanum[b%byte(len(alphanum))]
	}

	return string(bytes)

}

func init() {
	log.Formatter = new(logrus.TextFormatter)
}

func main() {

	host := flag.String("host", "127.0.0.1", "")
	port := flag.String("port", "5688", "")
	pid := flag.Bool("pid", false, "Save the PID to lmv-server.pid")

	flag.Parse()

	if *pid {
		ioutil.WriteFile("lmv-tracker.pid", []byte(strconv.Itoa(syscall.Getpid())), 0644)
	}

	session, err := mgo.Dial(mgo_host)

	if err != nil {
		panic(err)
	}

	defer session.Close()

	session.SetMode(mgo.Monotonic, true)

	c := session.DB(mgo_db).C(mgo_col)

	m := martini.Classic()
	m.Use(render.Renderer())

	m.Get("/files/", func(r render.Render) {

		var lmv_files []LMVFile

		err := c.Find(bson.M{}).All(&lmv_files)

		if err != nil {
			log.Fatal(err)
		}

		r.JSON(200, lmv_files)

	})

	m.Get("/files/:token/", func(params martini.Params, r render.Render) {

		n, err := c.Find(bson.M{"token": params["token"]}).Count()

		if err != nil {
			log.Fatal(err)
		}

		if n != 1 {
			log.WithFields(logrus.Fields{
				"token": params["token"],
			}).Error("Token not found.")

			r.JSON(404, "")
		} else {
			var lmv_file LMVFile

			err = c.Find(bson.M{"token": params["token"]}).One(&lmv_file)

			if err != nil {
				log.Fatal(err)
			}

			log.WithFields(logrus.Fields{
				"token": params["token"],
			}).Info("Retrieved existing token.")

			r.JSON(200, lmv_file)
		}

	})

	m.Post("/files/", binding.Bind(LMVFile{}), func(r render.Render, lmv_file LMVFile) {

		token := randstr(token_length)

		for {
			n, err := c.Find(bson.M{"token": token}).Count()

			if err != nil {
				log.Fatal(err)
			}

			if n > 0 {
				token = randstr(token_length)
			} else {
				break
			}
		}

		lmv_file.Token = token

		err := c.Insert(lmv_file)

		if err != nil {
			log.Fatal(err)
		}

		r.JSON(200, map[string]interface{}{"token": token})

		log.WithFields(logrus.Fields{
			"token": token,
		}).Info("Inserted new file.")

	})

	log.Fatal(http.ListenAndServe(*host+":"+*port, m))
}
<commit_msg>Transitioned from martini to gin<commit_after>package main

import (
	"crypto/rand"
	"flag"
	"io/ioutil"
	"strconv"
	"syscall"

    "github.com/gin-gonic/gin"
	"github.com/Sirupsen/logrus"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type LMVFile struct {
	Size      int64      `bson:"size"         json:"size"         binding:"required"`
	Name      string     `bson:"name"         json:"name"         binding:"required"`
	Algorithm string     `bson:"algorithm"    json:"algorithm"    binding:"required"`
	Chunks    []LMVChunk `bson:"chunks"       json:"chunks"       binding:"required"`
	Tar       bool       `bson:"tar"          json:"tar"`
	Token     string     `bson:"token"        json:"token"`
}

type LMVChunk struct {
	Hash  string `bson:"hash"         json:"hash"         binding:"required"`
	Size  int64  `bson:"size"         json:"size"         binding:"required"`
	Index int    `bson:"index"        json:"index"        binding:"required"`
}

const (
	token_length = 10
	mgo_host     = "localhost"
	mgo_db       = "Ludicrous-MV"
	mgo_col      = "Files"
)

var log = logrus.New()

func randstr(length int) string {

	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	var bytes = make([]byte, length)

	rand.Read(bytes)

	for i, b := range bytes {
		bytes[i] = alphanum[b%byte(len(alphanum))]
	}

	return string(bytes)

}

func init() {
	log.Formatter = new(logrus.TextFormatter)
}

func main() {

	pid := flag.Bool("pid", false, "Save the PID to lmv-server.pid")

	flag.Parse()

	if *pid {
		ioutil.WriteFile("lmv-tracker.pid", []byte(strconv.Itoa(syscall.Getpid())), 0644)
	}

	session, err := mgo.Dial(mgo_host)

	if err != nil {
		panic(err)
	}

	defer session.Close()

	session.SetMode(mgo.Monotonic, true)

	c := session.DB(mgo_db).C(mgo_col)

    r := gin.Default()

    r.GET("/files/", func(gc *gin.Context) {
        var lmv_files []LMVFile

        err := c.Find(bson.M{}).All(&lmv_files)

        if err != nil {
            log.Fatal(err)
        }

        gc.JSON(200, lmv_files)
    })

    r.GET("/files/:token/", func(gc *gin.Context) {

        token := gc.Params.ByName("token")

		n, err := c.Find(bson.M{"token": token}).Count()

		if err != nil {
			log.Fatal(err)
		}

		if n != 1 {
			log.WithFields(logrus.Fields{
				"token": token,
			}).Error("Token not found.")

			gc.JSON(404, "")
		} else {
			var lmv_file LMVFile

			err = c.Find(bson.M{"token": token}).One(&lmv_file)

			if err != nil {
				log.Fatal(err)
			}

			log.WithFields(logrus.Fields{
				"token": token,
			}).Info("Retrieved existing token.")

			gc.JSON(200, lmv_file)
		}

	})

    r.POST("/files/", func (gc *gin.Context) {

		token := randstr(token_length)
        var lmv_file LMVFile

        gc.Bind(&lmv_file)

		for {
			n, err := c.Find(bson.M{"token": token}).Count()

			if err != nil {
				log.Fatal(err)
			}

			if n > 0 {
				token = randstr(token_length)
			} else {
				break
			}
		}

		lmv_file.Token = token

		err := c.Insert(lmv_file)

		if err != nil {
			log.Fatal(err)
		}

		gc.JSON(200, map[string]interface{}{"token": token})

		log.WithFields(logrus.Fields{
			"token": token,
		}).Info("Inserted new file.")

	})

    r.GET("/ping/", func(c *gin.Context) {
        c.String(200, "pong")
    })

    r.Run(":8080")

}
<|endoftext|>


<commit_before>package azurerm

import (
	"bytes"
	"fmt"
	"log"
	"net/http"
	"regexp"

	"github.com/Azure/azure-sdk-for-go/arm/documentdb"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceArmCosmosDB() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmCosmosDBCreateUpdate,
		Read:   resourceArmCosmosDBRead,
		Update: resourceArmCosmosDBCreateUpdate,
		Delete: resourceArmCosmosDBDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateAzureRmCosmosDBName,
			},

			"location": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: azureRMNormalizeLocation,
			},

			"resource_group_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"offer_type": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(documentdb.Standard),
				}, true),
			},

			"ip_range_filter": {
				Type:     schema.TypeString,
				Optional: true,
			},

			"consistency_policy": {
				Type:     schema.TypeSet,
				Required: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"consistency_level": {
							Type:             schema.TypeString,
							Required:         true,
							DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							ValidateFunc: validation.StringInSlice([]string{
								string(documentdb.BoundedStaleness),
								string(documentdb.Eventual),
								string(documentdb.Session),
								string(documentdb.Strong),
							}, true),
						},

						"max_interval_in_seconds": {
							// TODO: apparently these can be optional/computed
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
							//ValidateFunc: validation.IntBetween(1, 100),
						},

						"max_staleness_prefix": {
							// TODO: apparently these can be optional/computed
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
							//ValidateFunc: validation.IntBetween(1, 2147483647),
						},
					},
				},
				Set: resourceAzureRMCosmosDBConsistencyPolicyHash,
			},

			"failover_policy": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"id": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},

						"location": {
							Type:      schema.TypeString,
							Required:  true,
							StateFunc: azureRMNormalizeLocation,
						},

						"priority": {
							Type:     schema.TypeInt,
							Required: true,
						},
					},
				},
				Set: resourceAzureRMCosmosDBFailoverPolicyHash,
			},

			"primary_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"secondary_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"primary_readonly_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"secondary_readonly_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmCosmosDBCreateUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).documentDBClient
	log.Printf("[INFO] preparing arguments for AzureRM Cosmos DB creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	offerType := d.Get("offer_type").(string)
	ipRangeFilter := d.Get("ip_range_filter").(string)

	consistencyPolicy := expandAzureRmCosmosDBConsistencyPolicy(d)
	failoverPolicies, err := expandAzureRmCosmosDBFailoverPolicies(name, d)
	if err != nil {
		return err
	}
	tags := d.Get("tags").(map[string]interface{})

	parameters := documentdb.DatabaseAccountCreateUpdateParameters{
		Location: &location,
		DatabaseAccountCreateUpdateProperties: &documentdb.DatabaseAccountCreateUpdateProperties{
			ConsistencyPolicy:        &consistencyPolicy,
			DatabaseAccountOfferType: &offerType,
			Locations:                &failoverPolicies,
			IPRangeFilter:            &ipRangeFilter,
		},
		Tags: expandTags(tags),
	}

	_, error := client.CreateOrUpdate(resGroup, name, parameters, make(chan struct{}))
	err = <-error
	if err != nil {
		return err
	}

	read, err := client.Get(resGroup, name)
	if err != nil {
		return err
	}

	if read.ID == nil {
		return fmt.Errorf("Cannot read Document DB instance %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	return resourceArmCosmosDBRead(d, meta)
}

func resourceArmCosmosDBRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).documentDBClient
	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["databaseAccounts"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		return fmt.Errorf("Error making Read request on AzureRM CosmosDB %s: %s", name, err)
	}
	if resp.StatusCode == http.StatusNotFound {
		d.SetId("")
		return nil
	}

	d.Set("name", resp.Name)
	d.Set("location", azureRMNormalizeLocation(*resp.Location))
	d.Set("resource_group_name", resGroup)
	d.Set("offer_type", string(resp.DatabaseAccountOfferType))
	d.Set("ip_range_filter", resp.IPRangeFilter)
	flattenAndSetAzureRmCosmosDBConsistencyPolicy(d, resp.ConsistencyPolicy)
	flattenAndSetAzureRmCosmosDBFailoverPolicy(d, resp.FailoverPolicies)

	keys, err := client.ListKeys(resGroup, name)
	if err != nil {
		log.Printf("[ERROR] Unable to List Write keys for CosmosDB %s: %s", name, err)
	} else {
		d.Set("primary_master_key", keys.PrimaryMasterKey)
		d.Set("secondary_master_key", keys.SecondaryMasterKey)
	}

	readonlyKeys, err := client.ListReadOnlyKeys(resGroup, name)
	if err != nil {
		log.Printf("[ERROR] Unable to List read-only keys for CosmosDB %s: %s", name, err)
	} else {
		d.Set("primary_readonly_master_key", readonlyKeys.PrimaryReadonlyMasterKey)
		d.Set("secondary_readonly_master_key", readonlyKeys.SecondaryReadonlyMasterKey)
	}

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmCosmosDBDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).documentDBClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["databaseAccounts"]

	deleteResp, error := client.Delete(resGroup, name, make(chan struct{}))
	resp := <-deleteResp
	err = <-error

	if err != nil {
		if resp.StatusCode == http.StatusNotFound {
			return nil
		}

		return fmt.Errorf("Error issuing AzureRM delete request for CosmosDB instance '%s': %+v", name, err)
	}

	return nil
}

func expandAzureRmCosmosDBConsistencyPolicy(d *schema.ResourceData) documentdb.ConsistencyPolicy {
	inputs := d.Get("consistency_policy").(*schema.Set).List()
	input := inputs[0].(map[string]interface{})

	consistencyLevel := input["consistency_level"].(string)

	policy := documentdb.ConsistencyPolicy{
		DefaultConsistencyLevel: documentdb.DefaultConsistencyLevel(consistencyLevel),
	}

	// TODO: file a bug about these two being required
	// documentdb.DatabaseAccountsClient#CreateOrUpdate:
	// Invalid input: autorest/validation: validation failed:
	// parameter=createUpdateParameters.DatabaseAccountCreateUpdateProperties.ConsistencyPolicy.MaxStalenessPrefix
	// constraint=InclusiveMinimum value=0 details: value must be greater than or equal to 1
	if stalenessPrefix, ok := input["max_staleness_prefix"].(int); ok {
		maxStalenessPrefix := int64(stalenessPrefix)
		policy.MaxStalenessPrefix = &maxStalenessPrefix
	}

	if maxInterval, ok := input["max_interval_in_seconds"].(int); ok {
		maxIntervalInSeconds := int32(maxInterval)
		policy.MaxIntervalInSeconds = &maxIntervalInSeconds
	}

	return policy
}

func expandAzureRmCosmosDBFailoverPolicies(databaseName string, d *schema.ResourceData) ([]documentdb.Location, error) {
	input := d.Get("failover_policy").(*schema.Set).List()
	locations := make([]documentdb.Location, 0, len(input))

	for _, configRaw := range input {
		data := configRaw.(map[string]interface{})

		locationName := azureRMNormalizeLocation(data["location"].(string))
		id := fmt.Sprintf("%s-%s", databaseName, locationName)
		failoverPriority := int32(data["priority"].(int))

		location := documentdb.Location{
			ID:               &id,
			LocationName:     &locationName,
			FailoverPriority: &failoverPriority,
		}

		locations = append(locations, location)
	}

	containsWriteLocation := false
	writeFailoverPriority := int32(0)
	for _, location := range locations {
		if *location.FailoverPriority == writeFailoverPriority {
			containsWriteLocation = true
			break
		}
	}

	// all priorities must be unique
	locationIds := make(map[int]struct{}, len(locations))
	for _, location := range locations {
		priority := int(*location.FailoverPriority)
		if _, ok := locationIds[priority]; ok {
			err := fmt.Errorf("Each CosmosDB Failover Policy needs to be unique")
			return nil, err
		}

		locationIds[priority] = struct{}{}
	}

	if !containsWriteLocation {
		err := fmt.Errorf("CosmosDB Failover Policy should contain a Write Location (Location '0')")
		return nil, err
	}

	return locations, nil
}

func flattenAndSetAzureRmCosmosDBConsistencyPolicy(d *schema.ResourceData, policy *documentdb.ConsistencyPolicy) {
	results := schema.Set{
		F: resourceAzureRMCosmosDBConsistencyPolicyHash,
	}

	result := map[string]interface{}{}
	result["consistency_level"] = string(policy.DefaultConsistencyLevel)
	result["max_interval_in_seconds"] = int(*policy.MaxIntervalInSeconds)
	result["max_staleness_prefix"] = int(*policy.MaxStalenessPrefix)
	results.Add(result)

	d.Set("consistency_policy", &results)
}

func flattenAndSetAzureRmCosmosDBFailoverPolicy(d *schema.ResourceData, list *[]documentdb.FailoverPolicy) {
	results := schema.Set{
		F: resourceAzureRMCosmosDBFailoverPolicyHash,
	}

	for _, i := range *list {
		result := map[string]interface{}{
			"id":       *i.ID,
			"location": azureRMNormalizeLocation(*i.LocationName),
			"priority": int(*i.FailoverPriority),
		}

		results.Add(result)
	}

	d.Set("failover_policy", &results)
}

func resourceAzureRMCosmosDBConsistencyPolicyHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	consistencyLevel := m["consistency_level"].(string)
	maxInterval := m["max_interval_in_seconds"].(int)
	maxStalenessPrefix := m["max_staleness_prefix"].(int)

	buf.WriteString(fmt.Sprintf("%s-%d-%d", consistencyLevel, maxInterval, maxStalenessPrefix))

	return hashcode.String(buf.String())
}

func resourceAzureRMCosmosDBFailoverPolicyHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	locationName := m["location"].(string)
	location := azureRMNormalizeLocation(locationName)
	priority := int32(m["priority"].(int))

	buf.WriteString(fmt.Sprintf("%s-%d", location, priority))

	return hashcode.String(buf.String())
}

func validateAzureRmCosmosDBName(v interface{}, k string) (ws []string, errors []error) {
	value := v.(string)

	r, _ := regexp.Compile("[a-z0-9-]")
	if !r.MatchString(value) {
		errors = append(errors, fmt.Errorf("CosmosDB Name can only contain lower-case characters, numbers and the `-` character."))
	}

	length := len(value)
	if length > 50 || 3 > length {
		errors = append(errors, fmt.Errorf("CosmosDB Name can only be between 3 and 50 seconds."))
	}

	return
}
<commit_msg>Defaulting the Consistency Level fields<commit_after>package azurerm

import (
	"bytes"
	"fmt"
	"log"
	"net/http"
	"regexp"

	"github.com/Azure/azure-sdk-for-go/arm/documentdb"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceArmCosmosDB() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmCosmosDBCreateUpdate,
		Read:   resourceArmCosmosDBRead,
		Update: resourceArmCosmosDBCreateUpdate,
		Delete: resourceArmCosmosDBDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateAzureRmCosmosDBName,
			},

			"location": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: azureRMNormalizeLocation,
			},

			"resource_group_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"offer_type": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(documentdb.Standard),
				}, true),
			},

			"ip_range_filter": {
				Type:     schema.TypeString,
				Optional: true,
			},

			"consistency_policy": {
				Type:     schema.TypeSet,
				Required: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"consistency_level": {
							Type:             schema.TypeString,
							Required:         true,
							DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
							ValidateFunc: validation.StringInSlice([]string{
								string(documentdb.BoundedStaleness),
								string(documentdb.Eventual),
								string(documentdb.Session),
								string(documentdb.Strong),
							}, true),
						},

						"max_interval_in_seconds": {
							Type:         schema.TypeInt,
							Optional:     true,
							Default:      5,
							ValidateFunc: validation.IntBetween(1, 100),
						},

						"max_staleness_prefix": {
							Type:         schema.TypeInt,
							Optional:     true,
							Default:      100,
							ValidateFunc: validation.IntBetween(1, 2147483647),
						},
					},
				},
				Set: resourceAzureRMCosmosDBConsistencyPolicyHash,
			},

			"failover_policy": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"id": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},

						"location": {
							Type:      schema.TypeString,
							Required:  true,
							StateFunc: azureRMNormalizeLocation,
						},

						"priority": {
							Type:     schema.TypeInt,
							Required: true,
						},
					},
				},
				Set: resourceAzureRMCosmosDBFailoverPolicyHash,
			},

			"primary_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"secondary_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"primary_readonly_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"secondary_readonly_master_key": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceArmCosmosDBCreateUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).documentDBClient
	log.Printf("[INFO] preparing arguments for AzureRM Cosmos DB creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	offerType := d.Get("offer_type").(string)
	ipRangeFilter := d.Get("ip_range_filter").(string)

	consistencyPolicy := expandAzureRmCosmosDBConsistencyPolicy(d)
	failoverPolicies, err := expandAzureRmCosmosDBFailoverPolicies(name, d)
	if err != nil {
		return err
	}
	tags := d.Get("tags").(map[string]interface{})

	parameters := documentdb.DatabaseAccountCreateUpdateParameters{
		Location: &location,
		DatabaseAccountCreateUpdateProperties: &documentdb.DatabaseAccountCreateUpdateProperties{
			ConsistencyPolicy:        &consistencyPolicy,
			DatabaseAccountOfferType: &offerType,
			Locations:                &failoverPolicies,
			IPRangeFilter:            &ipRangeFilter,
		},
		Tags: expandTags(tags),
	}

	_, error := client.CreateOrUpdate(resGroup, name, parameters, make(chan struct{}))
	err = <-error
	if err != nil {
		return err
	}

	read, err := client.Get(resGroup, name)
	if err != nil {
		return err
	}

	if read.ID == nil {
		return fmt.Errorf("Cannot read Document DB instance %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	return resourceArmCosmosDBRead(d, meta)
}

func resourceArmCosmosDBRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).documentDBClient
	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["databaseAccounts"]

	resp, err := client.Get(resGroup, name)
	if err != nil {
		return fmt.Errorf("Error making Read request on AzureRM CosmosDB %s: %s", name, err)
	}
	if resp.StatusCode == http.StatusNotFound {
		d.SetId("")
		return nil
	}

	d.Set("name", resp.Name)
	d.Set("location", azureRMNormalizeLocation(*resp.Location))
	d.Set("resource_group_name", resGroup)
	d.Set("offer_type", string(resp.DatabaseAccountOfferType))
	d.Set("ip_range_filter", resp.IPRangeFilter)
	flattenAndSetAzureRmCosmosDBConsistencyPolicy(d, resp.ConsistencyPolicy)
	flattenAndSetAzureRmCosmosDBFailoverPolicy(d, resp.FailoverPolicies)

	keys, err := client.ListKeys(resGroup, name)
	if err != nil {
		log.Printf("[ERROR] Unable to List Write keys for CosmosDB %s: %s", name, err)
	} else {
		d.Set("primary_master_key", keys.PrimaryMasterKey)
		d.Set("secondary_master_key", keys.SecondaryMasterKey)
	}

	readonlyKeys, err := client.ListReadOnlyKeys(resGroup, name)
	if err != nil {
		log.Printf("[ERROR] Unable to List read-only keys for CosmosDB %s: %s", name, err)
	} else {
		d.Set("primary_readonly_master_key", readonlyKeys.PrimaryReadonlyMasterKey)
		d.Set("secondary_readonly_master_key", readonlyKeys.SecondaryReadonlyMasterKey)
	}

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmCosmosDBDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient).documentDBClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["databaseAccounts"]

	deleteResp, error := client.Delete(resGroup, name, make(chan struct{}))
	resp := <-deleteResp
	err = <-error

	if err != nil {
		if resp.StatusCode == http.StatusNotFound {
			return nil
		}

		return fmt.Errorf("Error issuing AzureRM delete request for CosmosDB instance '%s': %+v", name, err)
	}

	return nil
}

func expandAzureRmCosmosDBConsistencyPolicy(d *schema.ResourceData) documentdb.ConsistencyPolicy {
	inputs := d.Get("consistency_policy").(*schema.Set).List()
	input := inputs[0].(map[string]interface{})

	consistencyLevel := input["consistency_level"].(string)

	policy := documentdb.ConsistencyPolicy{
		DefaultConsistencyLevel: documentdb.DefaultConsistencyLevel(consistencyLevel),
	}

	if stalenessPrefix := input["max_staleness_prefix"].(int); stalenessPrefix > 0 {
		maxStalenessPrefix := int64(stalenessPrefix)
		policy.MaxStalenessPrefix = &maxStalenessPrefix
	}

	if maxInterval := input["max_interval_in_seconds"].(int); maxInterval > 0 {
		maxIntervalInSeconds := int32(maxInterval)
		policy.MaxIntervalInSeconds = &maxIntervalInSeconds
	}

	return policy
}

func expandAzureRmCosmosDBFailoverPolicies(databaseName string, d *schema.ResourceData) ([]documentdb.Location, error) {
	input := d.Get("failover_policy").(*schema.Set).List()
	locations := make([]documentdb.Location, 0, len(input))

	for _, configRaw := range input {
		data := configRaw.(map[string]interface{})

		locationName := azureRMNormalizeLocation(data["location"].(string))
		id := fmt.Sprintf("%s-%s", databaseName, locationName)
		failoverPriority := int32(data["priority"].(int))

		location := documentdb.Location{
			ID:               &id,
			LocationName:     &locationName,
			FailoverPriority: &failoverPriority,
		}

		locations = append(locations, location)
	}

	containsWriteLocation := false
	writeFailoverPriority := int32(0)
	for _, location := range locations {
		if *location.FailoverPriority == writeFailoverPriority {
			containsWriteLocation = true
			break
		}
	}

	// all priorities must be unique
	locationIds := make(map[int]struct{}, len(locations))
	for _, location := range locations {
		priority := int(*location.FailoverPriority)
		if _, ok := locationIds[priority]; ok {
			err := fmt.Errorf("Each CosmosDB Failover Policy needs to be unique")
			return nil, err
		}

		locationIds[priority] = struct{}{}
	}

	if !containsWriteLocation {
		err := fmt.Errorf("CosmosDB Failover Policy should contain a Write Location (Location '0')")
		return nil, err
	}

	return locations, nil
}

func flattenAndSetAzureRmCosmosDBConsistencyPolicy(d *schema.ResourceData, policy *documentdb.ConsistencyPolicy) {
	results := schema.Set{
		F: resourceAzureRMCosmosDBConsistencyPolicyHash,
	}

	result := map[string]interface{}{}
	result["consistency_level"] = string(policy.DefaultConsistencyLevel)
	result["max_interval_in_seconds"] = int(*policy.MaxIntervalInSeconds)
	result["max_staleness_prefix"] = int(*policy.MaxStalenessPrefix)
	results.Add(result)

	d.Set("consistency_policy", &results)
}

func flattenAndSetAzureRmCosmosDBFailoverPolicy(d *schema.ResourceData, list *[]documentdb.FailoverPolicy) {
	results := schema.Set{
		F: resourceAzureRMCosmosDBFailoverPolicyHash,
	}

	for _, i := range *list {
		result := map[string]interface{}{
			"id":       *i.ID,
			"location": azureRMNormalizeLocation(*i.LocationName),
			"priority": int(*i.FailoverPriority),
		}

		results.Add(result)
	}

	d.Set("failover_policy", &results)
}

func resourceAzureRMCosmosDBConsistencyPolicyHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	consistencyLevel := m["consistency_level"].(string)
	maxInterval := m["max_interval_in_seconds"].(int)
	maxStalenessPrefix := m["max_staleness_prefix"].(int)

	buf.WriteString(fmt.Sprintf("%s-%d-%d", consistencyLevel, maxInterval, maxStalenessPrefix))

	return hashcode.String(buf.String())
}

func resourceAzureRMCosmosDBFailoverPolicyHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	locationName := m["location"].(string)
	location := azureRMNormalizeLocation(locationName)
	priority := int32(m["priority"].(int))

	buf.WriteString(fmt.Sprintf("%s-%d", location, priority))

	return hashcode.String(buf.String())
}

func validateAzureRmCosmosDBName(v interface{}, k string) (ws []string, errors []error) {
	value := v.(string)

	r, _ := regexp.Compile("[a-z0-9-]")
	if !r.MatchString(value) {
		errors = append(errors, fmt.Errorf("CosmosDB Name can only contain lower-case characters, numbers and the `-` character."))
	}

	length := len(value)
	if length > 50 || 3 > length {
		errors = append(errors, fmt.Errorf("CosmosDB Name can only be between 3 and 50 seconds."))
	}

	return
}
<|endoftext|>