<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"gopkg.in/src-d/enry.v1"
	"gopkg.in/src-d/enry.v1/data"
)

var (
	version = "undefined"
	build   = "undefined"
	commit  = "undefined"
)

func main() {
	flag.Usage = usage
	breakdownFlag := flag.Bool("breakdown", false, "")
	jsonFlag := flag.Bool("json", false, "")
	showVersion := flag.Bool("version", false, "Show the enry version information")
	onlyProg := flag.Bool("prog", false, "Only show programming file types in output")
	countMode := flag.String("mode", "file", "the method used to count file size. Available options are: file, line and byte")
	limitKB := flag.Int64("limit", 16*1024, "Analyse first N KB of the file (-1 means no limit)")
	flag.Parse()
	limit := (*limitKB) * 1024

	if *showVersion {
		fmt.Println(version)
		return
	}

	root, err := filepath.Abs(flag.Arg(0))
	if err != nil {
		log.Fatal(err)
	}

	fileInfo, err := os.Stat(root)
	if err != nil {
		log.Fatal(err)
	}

	if fileInfo.Mode().IsRegular() {
		err = printFileAnalysis(root, limit)
		if err != nil {
			fmt.Println(err)
		}
		return
	}

	out := make(map[string][]string, 0)
	err = filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			log.Println(err)
			return filepath.SkipDir
		}

		if !f.Mode().IsDir() && !f.Mode().IsRegular() {
			return nil
		}

		relativePath, err := filepath.Rel(root, path)
		if err != nil {
			log.Println(err)
			return nil
		}

		if relativePath == "." {
			return nil
		}

		if f.IsDir() {
			relativePath = relativePath + "/"
		}

		if enry.IsVendor(relativePath) || enry.IsDotFile(relativePath) ||
			enry.IsDocumentation(relativePath) || enry.IsConfiguration(relativePath) {
			if f.IsDir() {
				return filepath.SkipDir
			}

			return nil
		}

		if f.IsDir() {
			return nil
		}

		language, ok := enry.GetLanguageByExtension(path)
		if !ok {
			if language, ok = enry.GetLanguageByFilename(path); !ok {
				content, err := readFile(path, limit)
				if err != nil {
					log.Println(err)
					return nil
				}

				language = enry.GetLanguage(filepath.Base(path), content)
				if language == enry.OtherLanguage {
					return nil
				}
			}
		}

		// If we are displaying only prog. and language is not prog. skip it.
		if *onlyProg && enry.GetLanguageType(language) != enry.Programming {
			return nil
		}

		out[language] = append(out[language], relativePath)
		return nil
	})

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

	var buf bytes.Buffer
	switch {
	case *jsonFlag && !*breakdownFlag:
		printJson(out, &buf)
	case *jsonFlag && *breakdownFlag:
		printBreakDown(out, &buf)
	case *breakdownFlag:
		printPercents(out, &buf, *countMode)
		buf.WriteByte('\n')
		printBreakDown(out, &buf)
	default:
		printPercents(out, &buf, *countMode)
	}

	fmt.Print(buf.String())
}

func usage() {
	fmt.Fprintf(
		os.Stderr,
		`  %[1]s %[2]s build: %[3]s commit: %[4]s, based on linguist commit: %[5]s
  %[1]s, A simple (and faster) implementation of github/linguist
  usage: %[1]s [-mode=(file|line|byte)] [-prog] <path>
         %[1]s [-mode=(file|line|byte)] [-prog] [-json] [-breakdown] <path>
         %[1]s [-mode=(file|line|byte)] [-prog] [-json] [-breakdown]
         %[1]s [-version]
`,
		os.Args[0], version, build, commit, data.LinguistCommit[:7],
	)
}

func printBreakDown(out map[string][]string, buff *bytes.Buffer) {
	for name, language := range out {
		writeStringLn(name, buff)
		for _, file := range language {
			writeStringLn(file, buff)
		}

		writeStringLn("", buff)
	}
}

func printJson(out map[string][]string, buf *bytes.Buffer) {
	json.NewEncoder(buf).Encode(out)
}

// filelistError represents a failed operation that took place across multiple files.
type filelistError []string

func (e filelistError) Error() string {
	return fmt.Sprintf("Could not process the following files:\n%s", strings.Join(e, "\n"))
}

func printPercents(fSummary map[string][]string, buff *bytes.Buffer, mode string) {
	// Select the way we quantify 'amount' of code.
	var reducer func([]string) (float64, filelistError)
	switch mode {
	case "file":
		reducer = fileCountValues
	case "line":
		reducer = lineCountValues
	case "byte":
		reducer = byteCountValues
	default:
		reducer = fileCountValues
	}

	// Reduce the list of files to a quantity of file type.
	var (
		total           float64
		keys            []string
		unreadableFiles filelistError
		fileValues      = make(map[string]float64)
	)
	for fType, files := range fSummary {
		val, err := reducer(files)
		if err != nil {
			unreadableFiles = append(unreadableFiles, err...)
		}
		fileValues[fType] = val
		keys = append(keys, fType)
		total += val
	}

	// Slice the keys by their quantity (file count, line count, byte size, etc.).
	sort.Slice(keys, func(i, j int) bool {
		return fileValues[keys[i]] > fileValues[keys[j]]
	})

	// Calculate and write percentages of each file type.
	for _, fType := range keys {
		val := fileValues[fType]
		percent := val / total * 100.0
		buff.WriteString(fmt.Sprintf("%.2f%%\t%s\n", percent, fType))
		if unreadableFiles != nil {
			buff.WriteString(fmt.Sprintf("\n%s", unreadableFiles.Error()))
		}
	}
}

func fileCountValues(files []string) (float64, filelistError) {
	return float64(len(files)), nil
}

func lineCountValues(files []string) (float64, filelistError) {
	var filesErr filelistError
	var t float64
	for _, fName := range files {
		l, _ := getLines(fName, nil)
		t += float64(l)
	}
	return t, filesErr
}

func byteCountValues(files []string) (float64, filelistError) {
	var filesErr filelistError
	var t float64
	for _, fName := range files {
		f, err := os.Open(fName)
		if err != nil {
			filesErr = append(filesErr, fName)
			continue
		}
		fi, err := f.Stat()
		f.Close()
		if err != nil {
			filesErr = append(filesErr, fName)
			continue
		}
		t += float64(fi.Size())
	}
	return t, filesErr
}

func printFileAnalysis(file string, limit int64) error {
	data, err := readFile(file, limit)
	if err != nil {
		return err
	}

	isSample := limit > 0 && len(data) == int(limit)

	full := data
	if isSample {
		// communicate to getLines that we don't have full contents
		full = nil
	}

	totalLines, nonBlank := getLines(file, full)

	// functions below can work on a sample
	fileType := getFileType(file, data)
	language := enry.GetLanguage(file, data)
	mimeType := enry.GetMimeType(file, language)

	fmt.Printf(
		`%s: %d lines (%d sloc)
  type:      %s
  mime_type: %s
  language:  %s
`,
		filepath.Base(file), totalLines, nonBlank, fileType, mimeType, language,
	)
	return nil
}

func readFile(path string, limit int64) ([]byte, error) {
	if limit <= 0 {
		return ioutil.ReadFile(path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	st, err := f.Stat()
	if err != nil {
		return nil, err
	}
	size := st.Size()
	if limit > 0 && size > limit {
		size = limit
	}
	buf := bytes.NewBuffer(nil)
	buf.Grow(int(size))
	_, err = io.Copy(buf, io.LimitReader(f, limit))
	return buf.Bytes(), err
}

func getLines(file string, content []byte) (total, blank int) {
	var r io.Reader
	if content != nil {
		r = bytes.NewReader(content)
	} else {
		// file not loaded to memory - stream it
		f, err := os.Open(file)
		if err != nil {
			fmt.Println(err)
			return
		}
		defer f.Close()
		r = f
	}
	br := bufio.NewReader(r)
	lastBlank := true
	empty := true
	for {
		data, prefix, err := br.ReadLine()
		if err == io.EOF {
			break
		} else if err != nil {
			fmt.Println(err)
			break
		}
		if prefix {
			continue
		}
		empty = false
		total++
		lastBlank = len(data) == 0
		if lastBlank {
			blank++
		}
	}
	if !empty && lastBlank {
		total++
		blank++
	}
	nonBlank := total - blank
	return total, nonBlank
}

func getFileType(file string, content []byte) string {
	switch {
	case enry.IsImage(file):
		return "Image"
	case enry.IsBinary(content):
		return "Binary"
	default:
		return "Text"
	}
}

func writeStringLn(s string, buff *bytes.Buffer) {
	buff.WriteString(s)
	buff.WriteByte('\n')
}
<commit_msg>fix json output for a single file; fixes #159<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"gopkg.in/src-d/enry.v1"
	"gopkg.in/src-d/enry.v1/data"
)

var (
	version = "undefined"
	build   = "undefined"
	commit  = "undefined"
)

func main() {
	flag.Usage = usage
	breakdownFlag := flag.Bool("breakdown", false, "")
	jsonFlag := flag.Bool("json", false, "")
	showVersion := flag.Bool("version", false, "Show the enry version information")
	onlyProg := flag.Bool("prog", false, "Only show programming file types in output")
	countMode := flag.String("mode", "file", "the method used to count file size. Available options are: file, line and byte")
	limitKB := flag.Int64("limit", 16*1024, "Analyse first N KB of the file (-1 means no limit)")
	flag.Parse()
	limit := (*limitKB) * 1024

	if *showVersion {
		fmt.Println(version)
		return
	}

	root, err := filepath.Abs(flag.Arg(0))
	if err != nil {
		log.Fatal(err)
	}

	fileInfo, err := os.Stat(root)
	if err != nil {
		log.Fatal(err)
	}

	if fileInfo.Mode().IsRegular() {
		err = printFileAnalysis(root, limit, *jsonFlag)
		if err != nil {
			fmt.Println(err)
		}
		return
	}

	out := make(map[string][]string, 0)
	err = filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			log.Println(err)
			return filepath.SkipDir
		}

		if !f.Mode().IsDir() && !f.Mode().IsRegular() {
			return nil
		}

		relativePath, err := filepath.Rel(root, path)
		if err != nil {
			log.Println(err)
			return nil
		}

		if relativePath == "." {
			return nil
		}

		if f.IsDir() {
			relativePath = relativePath + "/"
		}

		if enry.IsVendor(relativePath) || enry.IsDotFile(relativePath) ||
			enry.IsDocumentation(relativePath) || enry.IsConfiguration(relativePath) {
			if f.IsDir() {
				return filepath.SkipDir
			}

			return nil
		}

		if f.IsDir() {
			return nil
		}

		language, ok := enry.GetLanguageByExtension(path)
		if !ok {
			if language, ok = enry.GetLanguageByFilename(path); !ok {
				content, err := readFile(path, limit)
				if err != nil {
					log.Println(err)
					return nil
				}

				language = enry.GetLanguage(filepath.Base(path), content)
				if language == enry.OtherLanguage {
					return nil
				}
			}
		}

		// If we are displaying only prog. and language is not prog. skip it.
		if *onlyProg && enry.GetLanguageType(language) != enry.Programming {
			return nil
		}

		out[language] = append(out[language], relativePath)
		return nil
	})

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

	var buf bytes.Buffer
	switch {
	case *jsonFlag && !*breakdownFlag:
		printJson(out, &buf)
	case *jsonFlag && *breakdownFlag:
		printBreakDown(out, &buf)
	case *breakdownFlag:
		printPercents(out, &buf, *countMode)
		buf.WriteByte('\n')
		printBreakDown(out, &buf)
	default:
		printPercents(out, &buf, *countMode)
	}

	fmt.Print(buf.String())
}

func usage() {
	fmt.Fprintf(
		os.Stderr,
		`  %[1]s %[2]s build: %[3]s commit: %[4]s, based on linguist commit: %[5]s
  %[1]s, A simple (and faster) implementation of github/linguist
  usage: %[1]s [-mode=(file|line|byte)] [-prog] <path>
         %[1]s [-mode=(file|line|byte)] [-prog] [-json] [-breakdown] <path>
         %[1]s [-mode=(file|line|byte)] [-prog] [-json] [-breakdown]
         %[1]s [-version]
`,
		os.Args[0], version, build, commit, data.LinguistCommit[:7],
	)
}

func printBreakDown(out map[string][]string, buff *bytes.Buffer) {
	for name, language := range out {
		writeStringLn(name, buff)
		for _, file := range language {
			writeStringLn(file, buff)
		}

		writeStringLn("", buff)
	}
}

func printJson(out map[string][]string, buf *bytes.Buffer) {
	json.NewEncoder(buf).Encode(out)
}

// filelistError represents a failed operation that took place across multiple files.
type filelistError []string

func (e filelistError) Error() string {
	return fmt.Sprintf("Could not process the following files:\n%s", strings.Join(e, "\n"))
}

func printPercents(fSummary map[string][]string, buff *bytes.Buffer, mode string) {
	// Select the way we quantify 'amount' of code.
	var reducer func([]string) (float64, filelistError)
	switch mode {
	case "file":
		reducer = fileCountValues
	case "line":
		reducer = lineCountValues
	case "byte":
		reducer = byteCountValues
	default:
		reducer = fileCountValues
	}

	// Reduce the list of files to a quantity of file type.
	var (
		total           float64
		keys            []string
		unreadableFiles filelistError
		fileValues      = make(map[string]float64)
	)
	for fType, files := range fSummary {
		val, err := reducer(files)
		if err != nil {
			unreadableFiles = append(unreadableFiles, err...)
		}
		fileValues[fType] = val
		keys = append(keys, fType)
		total += val
	}

	// Slice the keys by their quantity (file count, line count, byte size, etc.).
	sort.Slice(keys, func(i, j int) bool {
		return fileValues[keys[i]] > fileValues[keys[j]]
	})

	// Calculate and write percentages of each file type.
	for _, fType := range keys {
		val := fileValues[fType]
		percent := val / total * 100.0
		buff.WriteString(fmt.Sprintf("%.2f%%\t%s\n", percent, fType))
		if unreadableFiles != nil {
			buff.WriteString(fmt.Sprintf("\n%s", unreadableFiles.Error()))
		}
	}
}

func fileCountValues(files []string) (float64, filelistError) {
	return float64(len(files)), nil
}

func lineCountValues(files []string) (float64, filelistError) {
	var filesErr filelistError
	var t float64
	for _, fName := range files {
		l, _ := getLines(fName, nil)
		t += float64(l)
	}
	return t, filesErr
}

func byteCountValues(files []string) (float64, filelistError) {
	var filesErr filelistError
	var t float64
	for _, fName := range files {
		f, err := os.Open(fName)
		if err != nil {
			filesErr = append(filesErr, fName)
			continue
		}
		fi, err := f.Stat()
		f.Close()
		if err != nil {
			filesErr = append(filesErr, fName)
			continue
		}
		t += float64(fi.Size())
	}
	return t, filesErr
}

func printFileAnalysis(file string, limit int64, isJSON bool) error {
	data, err := readFile(file, limit)
	if err != nil {
		return err
	}

	isSample := limit > 0 && len(data) == int(limit)

	full := data
	if isSample {
		// communicate to getLines that we don't have full contents
		full = nil
	}

	totalLines, nonBlank := getLines(file, full)

	// functions below can work on a sample
	fileType := getFileType(file, data)
	language := enry.GetLanguage(file, data)
	mimeType := enry.GetMimeType(file, language)

	if isJSON {
		return json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
			"filename":    filepath.Base(file),
			"lines":       nonBlank,
			"total_lines": totalLines,
			"type":        fileType,
			"mime":        mimeType,
			"language":    language,
		})
	}

	fmt.Printf(
		`%s: %d lines (%d sloc)
  type:      %s
  mime_type: %s
  language:  %s
`,
		filepath.Base(file), totalLines, nonBlank, fileType, mimeType, language,
	)
	return nil
}

func readFile(path string, limit int64) ([]byte, error) {
	if limit <= 0 {
		return ioutil.ReadFile(path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	st, err := f.Stat()
	if err != nil {
		return nil, err
	}
	size := st.Size()
	if limit > 0 && size > limit {
		size = limit
	}
	buf := bytes.NewBuffer(nil)
	buf.Grow(int(size))
	_, err = io.Copy(buf, io.LimitReader(f, limit))
	return buf.Bytes(), err
}

func getLines(file string, content []byte) (total, blank int) {
	var r io.Reader
	if content != nil {
		r = bytes.NewReader(content)
	} else {
		// file not loaded to memory - stream it
		f, err := os.Open(file)
		if err != nil {
			fmt.Println(err)
			return
		}
		defer f.Close()
		r = f
	}
	br := bufio.NewReader(r)
	lastBlank := true
	empty := true
	for {
		data, prefix, err := br.ReadLine()
		if err == io.EOF {
			break
		} else if err != nil {
			fmt.Println(err)
			break
		}
		if prefix {
			continue
		}
		empty = false
		total++
		lastBlank = len(data) == 0
		if lastBlank {
			blank++
		}
	}
	if !empty && lastBlank {
		total++
		blank++
	}
	nonBlank := total - blank
	return total, nonBlank
}

func getFileType(file string, content []byte) string {
	switch {
	case enry.IsImage(file):
		return "Image"
	case enry.IsBinary(content):
		return "Binary"
	default:
		return "Text"
	}
}

func writeStringLn(s string, buff *bytes.Buffer) {
	buff.WriteString(s)
	buff.WriteByte('\n')
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/yosssi/gcss"
)

const validArgsLen = 1

var exit = os.Exit

func main() {
	v := flag.Bool("v", false, "Print the version and exit.")

	flag.Parse()

	if *v {
		fmt.Println(gcss.Version)
		exit(0)
		return
	}

	args := flag.Args()
	argsL := len(args)

	if argsL < validArgsLen {
		writeTo(os.Stderr, "A GCSS file path should be specified.")
		exit(1)
		return
	}

	if argsL > validArgsLen {
		writeTo(os.Stderr, "The number of the command line args should be 1.")
		exit(1)
		return
	}

	pathc, errc := gcss.Compile(args[0])

	select {
	case path := <-pathc:
		writeTo(os.Stdout, "compiled "+path)
	case err := <-errc:
		writeTo(os.Stderr, err.Error())
		exit(1)
		return
	}
}

// writeTo writes s to w.
func writeTo(w io.Writer, s string) {
	w.Write([]byte(s + "\n"))
}
<commit_msg>Update cmd/gcss/main.go<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/yosssi/gcss"
)

const validArgsLen = 1

var exit = os.Exit

func main() {
	v := flag.Bool("v", false, "Print the version and exit.")

	flag.Parse()

	if *v {
		fmt.Println(gcss.Version)
		exit(0)
		return
	}

	args := flag.Args()
	argsL := len(args)

	if argsL < validArgsLen {
		writeTo(os.Stderr, "GCSS file path should be specified.")
		exit(1)
		return
	}

	if argsL > validArgsLen {
		writeTo(os.Stderr, "The number of the command line args should be 1.")
		exit(1)
		return
	}

	pathc, errc := gcss.Compile(args[0])

	select {
	case path := <-pathc:
		writeTo(os.Stdout, "compiled "+path)
	case err := <-errc:
		writeTo(os.Stderr, err.Error())
		exit(1)
		return
	}
}

// writeTo writes s to w.
func writeTo(w io.Writer, s string) {
	w.Write([]byte(s + "\n"))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/yuin/gopher-lua"
	"github.com/yuin/gopher-lua/parse"
	"os"
)

func main() {
	var opt_e, opt_l string
	var opt_i, opt_v, opt_dt, opt_dc bool
	var opt_m int
	flag.StringVar(&opt_e, "e", "", "")
	flag.StringVar(&opt_l, "l", "", "")
	flag.IntVar(&opt_m, "mx", 0, "")
	flag.BoolVar(&opt_i, "i", false, "")
	flag.BoolVar(&opt_v, "v", false, "")
	flag.BoolVar(&opt_dt, "dt", false, "")
	flag.BoolVar(&opt_dc, "dc", false, "")
	flag.Usage = func() {
		fmt.Println(`usage: glua.exe [options] [script [args]].
Available options are:
  -e stat  execute string 'stat'
  -l name  require library 'name'
  -mx MB   memory limit(default: unlimited)
  -dt      dump AST trees
  -dc      dump VM codes
  -i       enter interactive mode after executing 'script'
  -v       show version information
`)
	}
	flag.Parse()
	if len(opt_e) == 0 && !opt_i && !opt_v && flag.NArg() == 0 {
		opt_i = true
	}

	status := 0

	L := lua.NewState()
	defer L.Close()
	if opt_m > 0 {
		L.SetMx(opt_m)
	}

	if opt_v || opt_i {
		fmt.Println(lua.PackageCopyRight)
	}

	if len(opt_l) > 0 {
		if err := L.DoFile(opt_l); err != nil {
			fmt.Println(err.Error())
		}
	}

	if nargs := flag.NArg(); nargs > 0 {
		script := flag.Arg(0)
		argtb := L.NewTable()
		for i := 1; i < nargs; i++ {
			L.RawSet(argtb, lua.LNumber(i), lua.LString(flag.Arg(i)))
		}
		L.SetGlobal("arg", argtb)
		if opt_dt || opt_dc {
			file, err := os.Open(script)
			if err != nil {
				fmt.Println(err.Error())
				os.Exit(1)
			}
			chunk, err2 := parse.Parse(file, script)
			if err2 != nil {
				fmt.Println(err2.Error())
				os.Exit(1)
			}
			if opt_dt {
				fmt.Println(parse.Dump(chunk))
			}
			if opt_dc {
				proto, err3 := lua.Compile(chunk, script)
				if err3 != nil {
					fmt.Println(err3.Error())
					os.Exit(1)
				}
				fmt.Println(proto.String())
			}
		}
		if err := L.DoFile(script); err != nil {
			fmt.Println(err.Error())
			status = 1
		}
	}

	if len(opt_e) > 0 {
		if err := L.DoString(opt_e); err != nil {
			fmt.Println(err.Error())
			status = 1
		}
	}

	if opt_i {
		reader := bufio.NewReader(os.Stdin)
		for {
			fmt.Print("> ")
			if buf, err := reader.ReadString('\n'); err != nil {
			} else {
				if err2 := L.DoString(buf); err2 != nil {
					fmt.Println(err2.Error())
				}
			}
		}
	}
	os.Exit(status)
}
<commit_msg>glua: Terminate on EOF<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/yuin/gopher-lua"
	"github.com/yuin/gopher-lua/parse"
	"io"
	"os"
)

func main() {
	var opt_e, opt_l string
	var opt_i, opt_v, opt_dt, opt_dc bool
	var opt_m int
	flag.StringVar(&opt_e, "e", "", "")
	flag.StringVar(&opt_l, "l", "", "")
	flag.IntVar(&opt_m, "mx", 0, "")
	flag.BoolVar(&opt_i, "i", false, "")
	flag.BoolVar(&opt_v, "v", false, "")
	flag.BoolVar(&opt_dt, "dt", false, "")
	flag.BoolVar(&opt_dc, "dc", false, "")
	flag.Usage = func() {
		fmt.Println(`usage: glua.exe [options] [script [args]].
Available options are:
  -e stat  execute string 'stat'
  -l name  require library 'name'
  -mx MB   memory limit(default: unlimited)
  -dt      dump AST trees
  -dc      dump VM codes
  -i       enter interactive mode after executing 'script'
  -v       show version information
`)
	}
	flag.Parse()
	if len(opt_e) == 0 && !opt_i && !opt_v && flag.NArg() == 0 {
		opt_i = true
	}

	status := 0

	L := lua.NewState()
	defer L.Close()
	if opt_m > 0 {
		L.SetMx(opt_m)
	}

	if opt_v || opt_i {
		fmt.Println(lua.PackageCopyRight)
	}

	if len(opt_l) > 0 {
		if err := L.DoFile(opt_l); err != nil {
			fmt.Println(err.Error())
		}
	}

	if nargs := flag.NArg(); nargs > 0 {
		script := flag.Arg(0)
		argtb := L.NewTable()
		for i := 1; i < nargs; i++ {
			L.RawSet(argtb, lua.LNumber(i), lua.LString(flag.Arg(i)))
		}
		L.SetGlobal("arg", argtb)
		if opt_dt || opt_dc {
			file, err := os.Open(script)
			if err != nil {
				fmt.Println(err.Error())
				os.Exit(1)
			}
			chunk, err2 := parse.Parse(file, script)
			if err2 != nil {
				fmt.Println(err2.Error())
				os.Exit(1)
			}
			if opt_dt {
				fmt.Println(parse.Dump(chunk))
			}
			if opt_dc {
				proto, err3 := lua.Compile(chunk, script)
				if err3 != nil {
					fmt.Println(err3.Error())
					os.Exit(1)
				}
				fmt.Println(proto.String())
			}
		}
		if err := L.DoFile(script); err != nil {
			fmt.Println(err.Error())
			status = 1
		}
	}

	if len(opt_e) > 0 {
		if err := L.DoString(opt_e); err != nil {
			fmt.Println(err.Error())
			status = 1
		}
	}

	if opt_i {
		reader := bufio.NewReader(os.Stdin)
		for {
			fmt.Print("> ")
			if buf, err := reader.ReadString('\n'); err == io.EOF {
				break
			} else if err != nil {
			} else {
				if err := L.DoString(buf); err != nil {
					fmt.Println(err.Error())
				}
			}
		}
	}
	os.Exit(status)
}
<|endoftext|>


<commit_before>/*
Copyright The Helm 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 main // import "helm.sh/helm/v3/cmd/helm"

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"github.com/spf13/cobra"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"

	"helm.sh/helm/v3/internal/experimental/registry"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/repo"
)

var globalUsage = `The Kubernetes package manager

Common actions for Helm:

- helm search:    search for charts
- helm pull:      download a chart to your local directory to view
- helm install:   upload the chart to Kubernetes
- helm list:      list releases of charts

Environment variables:

| Name                               | Description                                                                       |
|------------------------------------|-----------------------------------------------------------------------------------|
| $HELM_CACHE_HOME                   | set an alternative location for storing cached files.                             |
| $HELM_CONFIG_HOME                  | set an alternative location for storing Helm configuration.                       |
| $HELM_DATA_HOME                    | set an alternative location for storing Helm data.                                |
| $HELM_DEBUG                        | indicate whether or not Helm is running in Debug mode                             |
| $HELM_DRIVER                       | set the backend storage driver. Values are: configmap, secret, memory, postgres   |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use.                      |
| $HELM_MAX_HISTORY                  | set the maximum number of helm release history.                                   |
| $HELM_NAMESPACE                    | set the namespace used for the helm operations.                                   |
| $HELM_NO_PLUGINS                   | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.                        |
| $HELM_PLUGINS                      | set the path to the plugins directory                                             |
| $HELM_REGISTRY_CONFIG              | set the path to the registry config file.                                         |
| $HELM_REPOSITORY_CACHE             | set the path to the repository cache directory                                    |
| $HELM_REPOSITORY_CONFIG            | set the path to the repositories file.                                            |
| $KUBECONFIG                        | set an alternative Kubernetes configuration file (default "~/.kube/config")       |
| $HELM_KUBEAPISERVER                | set the Kubernetes API Server Endpoint for authentication                         |
| $HELM_KUBEASGROUPS                 | set the Username to impersonate for the operation.                                |
| $HELM_KUBEASUSER                   | set the Groups to use for impoersonation using a comma-separated list.            |
| $HELM_KUBECONTEXT                  | set the name of the kubeconfig context.                                           |
| $HELM_KUBETOKEN                    | set the Bearer KubeToken used for authentication.                                 |

Helm stores cache, configuration, and data based on the following configuration order:

- If a HELM_*_HOME environment variable is set, it will be used
- Otherwise, on systems supporting the XDG base directory specification, the XDG variables will be used
- When no other location is set a default location will be used based on the operating system

By default, the default directories depend on the Operating System. The defaults are listed below:

| Operating System | Cache Path                | Configuration Path             | Data Path               |
|------------------|---------------------------|--------------------------------|-------------------------|
| Linux            | $HOME/.cache/helm         | $HOME/.config/helm             | $HOME/.local/share/helm |
| macOS            | $HOME/Library/Caches/helm | $HOME/Library/Preferences/helm | $HOME/Library/helm      |
| Windows          | %TEMP%\helm               | %APPDATA%\helm                 | %APPDATA%\helm          |
`

func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) (*cobra.Command, error) {
	cmd := &cobra.Command{
		Use:          "helm",
		Short:        "The Helm package manager for Kubernetes.",
		Long:         globalUsage,
		SilenceUsage: true,
		// This breaks completion for 'helm help <TAB>'
		// The Cobra release following 1.0 will fix this
		//ValidArgsFunction: noCompletions, // Disable file completion
	}
	flags := cmd.PersistentFlags()

	settings.AddFlags(flags)
	addKlogFlags(flags)

	// Setup shell completion for the namespace flag
	err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if client, err := actionConfig.KubernetesClientSet(); err == nil {
			// Choose a long enough timeout that the user notices somethings is not working
			// but short enough that the user is not made to wait very long
			to := int64(3)
			cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug)

			nsNames := []string{}
			if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil {
				for _, ns := range namespaces.Items {
					if strings.HasPrefix(ns.Name, toComplete) {
						nsNames = append(nsNames, ns.Name)
					}
				}
				return nsNames, cobra.ShellCompDirectiveNoFileComp
			}
		}
		return nil, cobra.ShellCompDirectiveDefault
	})

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

	// Setup shell completion for the kube-context flag
	err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		cobra.CompDebugln("About to get the different kube-contexts", settings.Debug)

		loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
		if len(settings.KubeConfig) > 0 {
			loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: settings.KubeConfig}
		}
		if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
			loadingRules,
			&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
			ctxs := []string{}
			for name := range config.Contexts {
				if strings.HasPrefix(name, toComplete) {
					ctxs = append(ctxs, name)
				}
			}
			return ctxs, cobra.ShellCompDirectiveNoFileComp
		}
		return nil, cobra.ShellCompDirectiveNoFileComp
	})

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

	// We can safely ignore any errors that flags.Parse encounters since
	// those errors will be caught later during the call to cmd.Execution.
	// This call is required to gather configuration information prior to
	// execution.
	flags.ParseErrorsWhitelist.UnknownFlags = true
	flags.Parse(args)

	// Add subcommands
	cmd.AddCommand(
		// chart commands
		newCreateCmd(out),
		newDependencyCmd(out),
		newPullCmd(out),
		newShowCmd(out),
		newLintCmd(out),
		newPackageCmd(out),
		newRepoCmd(out),
		newSearchCmd(out),
		newVerifyCmd(out),

		// release commands
		newGetCmd(actionConfig, out),
		newHistoryCmd(actionConfig, out),
		newInstallCmd(actionConfig, out),
		newListCmd(actionConfig, out),
		newReleaseTestCmd(actionConfig, out),
		newRollbackCmd(actionConfig, out),
		newStatusCmd(actionConfig, out),
		newTemplateCmd(actionConfig, out),
		newUninstallCmd(actionConfig, out),
		newUpgradeCmd(actionConfig, out),

		newCompletionCmd(out),
		newEnvCmd(out),
		newPluginCmd(out),
		newVersionCmd(out),

		// Hidden documentation generator command: 'helm docs'
		newDocsCmd(out),
	)

	// Add *experimental* subcommands
	registryClient, err := registry.NewClient(
		registry.ClientOptDebug(settings.Debug),
		registry.ClientOptWriter(out),
		registry.ClientOptCredentialsFile(settings.RegistryConfig),
	)
	if err != nil {
		return nil, err
	}
	actionConfig.RegistryClient = registryClient
	cmd.AddCommand(
		newRegistryCmd(actionConfig, out),
		newChartCmd(actionConfig, out),
	)

	// Find and add plugins
	loadPlugins(cmd, out)

	// Check permissions on critical files
	checkPerms()

	// Check for expired repositories
	checkForExpiredRepos(settings.RepositoryConfig)

	return cmd, nil
}

func checkForExpiredRepos(repofile string) {

	expiredRepos := []struct {
		name string
		old  string
		new  string
	}{
		{
			name: "stable",
			old:  "kubernetes-charts.storage.googleapis.com",
			new:  "https://charts.helm.sh/stable",
		},
		{
			name: "incubator",
			old:  "kubernetes-charts-incubator.storage.googleapis.com",
			new:  "https://charts.helm.sh/incubator",
		},
	}

	// parse repo file.
	// Ignore the error because it is okay for a repo file to be unparseable at this
	// stage. Later checks will trap the error and respond accordingly.
	repoFile, err := repo.LoadFile(repofile)
	if err != nil {
		return
	}

	for _, exp := range expiredRepos {
		r := repoFile.Get(exp.name)
		if r == nil {
			return
		}

		if url := r.URL; strings.Contains(url, exp.old) {
			fmt.Fprintf(
				os.Stderr,
				"WARNING: %q is deprecated for %q and will be deleted Nov. 13, 2020.\nWARNING: You should switch to %q\n",
				exp.old,
				exp.name,
				exp.new,
			)
		}
	}

}
<commit_msg>Updating descriptions<commit_after>/*
Copyright The Helm 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 main // import "helm.sh/helm/v3/cmd/helm"

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"github.com/spf13/cobra"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"

	"helm.sh/helm/v3/internal/experimental/registry"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/repo"
)

var globalUsage = `The Kubernetes package manager

Common actions for Helm:

- helm search:    search for charts
- helm pull:      download a chart to your local directory to view
- helm install:   upload the chart to Kubernetes
- helm list:      list releases of charts

Environment variables:

| Name                               | Description                                                                       |
|------------------------------------|-----------------------------------------------------------------------------------|
| $HELM_CACHE_HOME                   | set an alternative location for storing cached files.                             |
| $HELM_CONFIG_HOME                  | set an alternative location for storing Helm configuration.                       |
| $HELM_DATA_HOME                    | set an alternative location for storing Helm data.                                |
| $HELM_DEBUG                        | indicate whether or not Helm is running in Debug mode                             |
| $HELM_DRIVER                       | set the backend storage driver. Values are: configmap, secret, memory, postgres   |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use.                      |
| $HELM_MAX_HISTORY                  | set the maximum number of helm release history.                                   |
| $HELM_NAMESPACE                    | set the namespace used for the helm operations.                                   |
| $HELM_NO_PLUGINS                   | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.                        |
| $HELM_PLUGINS                      | set the path to the plugins directory                                             |
| $HELM_REGISTRY_CONFIG              | set the path to the registry config file.                                         |
| $HELM_REPOSITORY_CACHE             | set the path to the repository cache directory                                    |
| $HELM_REPOSITORY_CONFIG            | set the path to the repositories file.                                            |
| $KUBECONFIG                        | set an alternative Kubernetes configuration file (default "~/.kube/config")       |
| $HELM_KUBEAPISERVER                | set the Kubernetes API Server Endpoint for authentication                         |
| $HELM_KUBEASGROUPS                 | set the Groups to use for impersonation using a comma-separated list.             |
| $HELM_KUBEASUSER                   | set the Username to impersonate for the operation.                                |
| $HELM_KUBECONTEXT                  | set the name of the kubeconfig context.                                           |
| $HELM_KUBETOKEN                    | set the Bearer KubeToken used for authentication.                                 |

Helm stores cache, configuration, and data based on the following configuration order:

- If a HELM_*_HOME environment variable is set, it will be used
- Otherwise, on systems supporting the XDG base directory specification, the XDG variables will be used
- When no other location is set a default location will be used based on the operating system

By default, the default directories depend on the Operating System. The defaults are listed below:

| Operating System | Cache Path                | Configuration Path             | Data Path               |
|------------------|---------------------------|--------------------------------|-------------------------|
| Linux            | $HOME/.cache/helm         | $HOME/.config/helm             | $HOME/.local/share/helm |
| macOS            | $HOME/Library/Caches/helm | $HOME/Library/Preferences/helm | $HOME/Library/helm      |
| Windows          | %TEMP%\helm               | %APPDATA%\helm                 | %APPDATA%\helm          |
`

func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) (*cobra.Command, error) {
	cmd := &cobra.Command{
		Use:          "helm",
		Short:        "The Helm package manager for Kubernetes.",
		Long:         globalUsage,
		SilenceUsage: true,
		// This breaks completion for 'helm help <TAB>'
		// The Cobra release following 1.0 will fix this
		//ValidArgsFunction: noCompletions, // Disable file completion
	}
	flags := cmd.PersistentFlags()

	settings.AddFlags(flags)
	addKlogFlags(flags)

	// Setup shell completion for the namespace flag
	err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		if client, err := actionConfig.KubernetesClientSet(); err == nil {
			// Choose a long enough timeout that the user notices somethings is not working
			// but short enough that the user is not made to wait very long
			to := int64(3)
			cobra.CompDebugln(fmt.Sprintf("About to call kube client for namespaces with timeout of: %d", to), settings.Debug)

			nsNames := []string{}
			if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil {
				for _, ns := range namespaces.Items {
					if strings.HasPrefix(ns.Name, toComplete) {
						nsNames = append(nsNames, ns.Name)
					}
				}
				return nsNames, cobra.ShellCompDirectiveNoFileComp
			}
		}
		return nil, cobra.ShellCompDirectiveDefault
	})

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

	// Setup shell completion for the kube-context flag
	err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		cobra.CompDebugln("About to get the different kube-contexts", settings.Debug)

		loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
		if len(settings.KubeConfig) > 0 {
			loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: settings.KubeConfig}
		}
		if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
			loadingRules,
			&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
			ctxs := []string{}
			for name := range config.Contexts {
				if strings.HasPrefix(name, toComplete) {
					ctxs = append(ctxs, name)
				}
			}
			return ctxs, cobra.ShellCompDirectiveNoFileComp
		}
		return nil, cobra.ShellCompDirectiveNoFileComp
	})

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

	// We can safely ignore any errors that flags.Parse encounters since
	// those errors will be caught later during the call to cmd.Execution.
	// This call is required to gather configuration information prior to
	// execution.
	flags.ParseErrorsWhitelist.UnknownFlags = true
	flags.Parse(args)

	// Add subcommands
	cmd.AddCommand(
		// chart commands
		newCreateCmd(out),
		newDependencyCmd(out),
		newPullCmd(out),
		newShowCmd(out),
		newLintCmd(out),
		newPackageCmd(out),
		newRepoCmd(out),
		newSearchCmd(out),
		newVerifyCmd(out),

		// release commands
		newGetCmd(actionConfig, out),
		newHistoryCmd(actionConfig, out),
		newInstallCmd(actionConfig, out),
		newListCmd(actionConfig, out),
		newReleaseTestCmd(actionConfig, out),
		newRollbackCmd(actionConfig, out),
		newStatusCmd(actionConfig, out),
		newTemplateCmd(actionConfig, out),
		newUninstallCmd(actionConfig, out),
		newUpgradeCmd(actionConfig, out),

		newCompletionCmd(out),
		newEnvCmd(out),
		newPluginCmd(out),
		newVersionCmd(out),

		// Hidden documentation generator command: 'helm docs'
		newDocsCmd(out),
	)

	// Add *experimental* subcommands
	registryClient, err := registry.NewClient(
		registry.ClientOptDebug(settings.Debug),
		registry.ClientOptWriter(out),
		registry.ClientOptCredentialsFile(settings.RegistryConfig),
	)
	if err != nil {
		return nil, err
	}
	actionConfig.RegistryClient = registryClient
	cmd.AddCommand(
		newRegistryCmd(actionConfig, out),
		newChartCmd(actionConfig, out),
	)

	// Find and add plugins
	loadPlugins(cmd, out)

	// Check permissions on critical files
	checkPerms()

	// Check for expired repositories
	checkForExpiredRepos(settings.RepositoryConfig)

	return cmd, nil
}

func checkForExpiredRepos(repofile string) {

	expiredRepos := []struct {
		name string
		old  string
		new  string
	}{
		{
			name: "stable",
			old:  "kubernetes-charts.storage.googleapis.com",
			new:  "https://charts.helm.sh/stable",
		},
		{
			name: "incubator",
			old:  "kubernetes-charts-incubator.storage.googleapis.com",
			new:  "https://charts.helm.sh/incubator",
		},
	}

	// parse repo file.
	// Ignore the error because it is okay for a repo file to be unparseable at this
	// stage. Later checks will trap the error and respond accordingly.
	repoFile, err := repo.LoadFile(repofile)
	if err != nil {
		return
	}

	for _, exp := range expiredRepos {
		r := repoFile.Get(exp.name)
		if r == nil {
			return
		}

		if url := r.URL; strings.Contains(url, exp.old) {
			fmt.Fprintf(
				os.Stderr,
				"WARNING: %q is deprecated for %q and will be deleted Nov. 13, 2020.\nWARNING: You should switch to %q\n",
				exp.old,
				exp.name,
				exp.new,
			)
		}
	}

}
<|endoftext|>


<commit_before>package data

import (
	"context"
	"encoding/json"
	"net/http"
	"regexp"
	"sort"
	"strconv"
	"time"

	"github.com/tidepool-org/platform/errors"
	"github.com/tidepool-org/platform/id"
	"github.com/tidepool-org/platform/metadata"
	"github.com/tidepool-org/platform/page"
	"github.com/tidepool-org/platform/request"
	"github.com/tidepool-org/platform/structure"
	structureValidator "github.com/tidepool-org/platform/structure/validator"
	timeZone "github.com/tidepool-org/platform/time/zone"
)

// TODO: This is a migration in progress from upload.Upload to DataSet. Some structures
// have been duplicated.

type DataSetAccessor interface {
	ListUserDataSets(ctx context.Context, userID string, filter *DataSetFilter, pagination *page.Pagination) (DataSets, error)
	CreateUserDataSet(ctx context.Context, userID string, create *DataSetCreate) (*DataSet, error)
	GetDataSet(ctx context.Context, id string) (*DataSet, error)
	UpdateDataSet(ctx context.Context, id string, update *DataSetUpdate) (*DataSet, error)
	DeleteDataSet(ctx context.Context, id string) error
}

const (
	ComputerTimeFormat = "2006-01-02T15:04:05"
	TimeFormat         = time.RFC3339Nano
	DeviceTimeFormat   = "2006-01-02T15:04:05"

	DataSetTypeContinuous = "continuous"
	DataSetTypeNormal     = "normal" // TODO: Normal?

	DataSetStateClosed = "closed"
	DataSetStateOpen   = "open"

	DeviceTagBGM         = "bgm"
	DeviceTagCGM         = "cgm"
	DeviceTagInsulinPump = "insulin-pump"

	TimeProcessingAcrossTheBoardTimeZone = "across-the-board-timezone" // TODO: Rename to across-the-board-time-zone or alternative
	TimeProcessingNone                   = "none"
	TimeProcessingUTCBootstrapping       = "utc-bootstrapping" // TODO: Rename to utc-bootstrap or alternative
)

func DataSetTypes() []string {
	return []string{
		DataSetTypeContinuous,
		DataSetTypeNormal,
	}
}

func DataSetStates() []string {
	return []string{
		DataSetStateClosed,
		DataSetStateOpen,
	}
}

func DeviceTags() []string {
	return []string{
		DeviceTagBGM,
		DeviceTagCGM,
		DeviceTagInsulinPump,
	}
}

func TimeProcessings() []string {
	return []string{
		TimeProcessingAcrossTheBoardTimeZone,
		TimeProcessingNone,
		TimeProcessingUTCBootstrapping,
	}
}

// TODO: Add OAuth client id to DataSetClient when available
// TODO: Pull from OAuth rather than be dependent upon client to complete

type DataSetClient struct {
	Name    *string                 `json:"name,omitempty" bson:"name,omitempty"`
	Version *string                 `json:"version,omitempty" bson:"version,omitempty"`
	Private *map[string]interface{} `json:"private,omitempty" bson:"private,omitempty"`
}

func NewDataSetClient() *DataSetClient {
	return &DataSetClient{}
}

func (d *DataSetClient) Parse(parser structure.ObjectParser) {
	if ptr := parser.String("name"); ptr != nil {
		d.Name = ptr
	}
	if ptr := parser.String("version"); ptr != nil {
		d.Version = ptr
	}
	if ptr := parser.Object("private"); ptr != nil {
		d.Private = ptr
	}
}

func (d *DataSetClient) Validate(validator structure.Validator) {
	validator.String("name", d.Name).NotEmpty()
	validator.String("version", d.Version).NotEmpty() // TODO: Semver validation
}

type DataSetFilter struct {
	ClientName *string
	Deleted    *bool
	DeviceID   *string
}

func NewDataSetFilter() *DataSetFilter {
	return &DataSetFilter{}
}

func (d *DataSetFilter) Parse(parser structure.ObjectParser) {
	d.ClientName = parser.String("client.name")
	d.Deleted = parser.Bool("deleted")
	d.DeviceID = parser.String("deviceId")
}

func (d *DataSetFilter) Validate(validator structure.Validator) {
	validator.String("client.name", d.ClientName).NotEmpty()
	validator.String("deviceId", d.DeviceID).NotEmpty()
}

func (d *DataSetFilter) MutateRequest(req *http.Request) error {
	parameters := map[string]string{}
	if d.ClientName != nil {
		parameters["client.name"] = *d.ClientName
	}
	if d.Deleted != nil {
		parameters["deleted"] = strconv.FormatBool(*d.Deleted)
	}
	if d.DeviceID != nil {
		parameters["deviceId"] = *d.DeviceID
	}
	return request.NewParametersMutator(parameters).MutateRequest(req)
}

type DataSetCreate struct {
	Client              *DataSetClient          `json:"client,omitempty"`
	DataSetType         *string                 `json:"dataSetType,omitempty"`
	Deduplicator        *DeduplicatorDescriptor `json:"deduplicator,omitempty"`
	DeviceID            *string                 `json:"deviceId,omitempty"`
	DeviceManufacturers *[]string               `json:"deviceManufacturers,omitempty"`
	DeviceModel         *string                 `json:"deviceModel,omitempty"`
	DeviceSerialNumber  *string                 `json:"deviceSerialNumber,omitempty"`
	DeviceTags          *[]string               `json:"deviceTags,omitempty"`
	Time                *time.Time              `json:"time,omitempty"`
	Type                string                  `json:"type,omitempty"`
	TimeProcessing      *string                 `json:"timeProcessing,omitempty"`
	TimeZoneName        *string                 `json:"timezone,omitempty"`
	TimeZoneOffset      *int                    `json:"timezoneOffset,omitempty"`
}

func NewDataSetCreate() *DataSetCreate {
	return &DataSetCreate{
		Type: "upload",
	}
}

func (d *DataSetCreate) Parse(parser structure.ObjectParser) {
	if clientParser := parser.WithReferenceObjectParser("client"); clientParser.Exists() {
		d.Client = NewDataSetClient()
		d.Client.Parse(clientParser)
		clientParser.NotParsed()
	}
	d.DataSetType = parser.String("dataSetType")
	d.Deduplicator = ParseDeduplicatorDescriptor(parser.WithReferenceObjectParser("deduplicator"))
	d.DeviceID = parser.String("deviceId")
	d.DeviceManufacturers = parser.StringArray("deviceManufacturers")
	d.DeviceModel = parser.String("deviceModel")
	d.DeviceSerialNumber = parser.String("deviceSerialNumber")
	d.DeviceTags = parser.StringArray("deviceTags")
	d.Time = parser.Time("time", TimeFormat)
	d.TimeProcessing = parser.String("timeProcessing")
	d.TimeZoneName = parser.String("timezone")
	d.TimeZoneOffset = parser.Int("timezoneOffset")
}

func (d *DataSetCreate) Validate(validator structure.Validator) {
	if d.Client != nil {
		d.Client.Validate(validator.WithReference("client"))
	}
	validator.String("dataSetType", d.DataSetType).OneOf(DataSetTypes()...)
	if d.Deduplicator != nil {
		d.Deduplicator.Validate(validator.WithReference("deduplicator"))
	}
	validator.String("deviceId", d.DeviceID).NotEmpty()
	validator.StringArray("deviceManufacturers", d.DeviceManufacturers).NotEmpty()
	validator.String("deviceModel", d.DeviceModel).NotEmpty()
	validator.String("deviceSerialNumber", d.DeviceSerialNumber).NotEmpty()
	validator.StringArray("deviceTags", d.DeviceTags).NotEmpty().EachOneOf(DeviceTags()...)
	validator.Time("time", d.Time).NotZero()
	validator.String("timeProcessing", d.TimeProcessing).OneOf(TimeProcessings()...)
	validator.String("timezone", d.TimeZoneName).Using(timeZone.NameValidator)
	validator.Int("timezoneOffset", d.TimeZoneOffset).InRange(-12*60, 14*60)
}

func (d *DataSetCreate) Normalize(normalizer structure.Normalizer) {
	if d.Deduplicator != nil {
		d.Deduplicator.Normalize(normalizer.WithReference("deduplicator"))
	}
	if d.DeviceManufacturers != nil {
		sort.Strings(*d.DeviceManufacturers)
	}
	if d.DeviceTags != nil {
		sort.Strings(*d.DeviceTags)
	}
}

type DataSetUpdate struct {
	Active             *bool                   `json:"-"`
	DeviceID           *string                 `json:"deviceId,omitempty"`
	DeviceModel        *string                 `json:"deviceModel,omitempty"`
	DeviceSerialNumber *string                 `json:"deviceSerialNumber,omitempty"`
	Deduplicator       *DeduplicatorDescriptor `json:"-"`
	State              *string                 `json:"state,omitempty"`
	Time               *time.Time              `json:"time,omitempty"`
	TimeZoneName       *string                 `json:"timezone,omitempty"`
	TimeZoneOffset     *int                    `json:"timezoneOffset,omitempty"`
}

func NewDataSetUpdate() *DataSetUpdate {
	return &DataSetUpdate{}
}

func (d *DataSetUpdate) Parse(parser structure.ObjectParser) {
	d.DeviceID = parser.String("deviceId")
	d.DeviceModel = parser.String("deviceModel")
	d.DeviceSerialNumber = parser.String("deviceSerialNumber")
	d.State = parser.String("state")
	d.Time = parser.Time("time", TimeFormat)
	d.TimeZoneName = parser.String("timezone")
	d.TimeZoneOffset = parser.Int("timezoneOffset")
}

func (d *DataSetUpdate) Validate(validator structure.Validator) {
	validator.String("deviceId", d.DeviceID).NotEmpty()
	validator.String("deviceModel", d.DeviceModel).LengthGreaterThan(1)
	validator.String("deviceSerialNumber", d.DeviceSerialNumber).LengthGreaterThan(1)
	validator.String("state", d.State).OneOf(DataSetStates()...)
	validator.Time("time", d.Time).NotZero()
	validator.String("timezone", d.TimeZoneName).Using(timeZone.NameValidator)
	validator.Int("timezoneOffset", d.TimeZoneOffset).InRange(-12*60, 14*60)
}

func (d *DataSetUpdate) IsEmpty() bool {
	return d.Active == nil && d.DeviceID == nil && d.DeviceModel == nil && d.DeviceSerialNumber == nil &&
		d.Deduplicator == nil && d.State == nil && d.Time == nil && d.TimeZoneName == nil && d.TimeZoneOffset == nil
}

func NewSetID() string {
	return id.Must(id.New(16))
}

func IsValidSetID(value string) bool {
	return ValidateSetID(value) == nil
}

func SetIDValidator(value string, errorReporter structure.ErrorReporter) {
	errorReporter.ReportError(ValidateSetID(value))
}

func ValidateSetID(value string) error {
	if value == "" {
		return structureValidator.ErrorValueEmpty()
	} else if !setIDExpression.MatchString(value) {
		return ErrorValueStringAsSetIDNotValid(value)
	}
	return nil
}

func ErrorValueStringAsSetIDNotValid(value string) error {
	return errors.Preparedf(structureValidator.ErrorCodeValueNotValid, "value is not valid", "value %q is not valid as data set id", value)
}

var setIDExpression = regexp.MustCompile("^(upid_[0-9a-f]{12}|upid_[0-9a-f]{32}|[0-9a-f]{32})$") // TODO: Want just "[0-9a-f]{32}"

type DataSet struct {
	Active              bool                    `json:"-" bson:"_active"`
	Annotations         *metadata.MetadataArray `json:"annotations,omitempty" bson:"annotations,omitempty"`
	ArchivedDataSetID   *string                 `json:"-" bson:"archivedDatasetId,omitempty"`
	ArchivedTime        *time.Time              `json:"-" bson:"archivedTime,omitempty"`
	ByUser              *string                 `json:"byUser,omitempty" bson:"byUser,omitempty"`
	Client              *DataSetClient          `json:"client,omitempty" bson:"client,omitempty"`
	ClockDriftOffset    *int                    `json:"clockDriftOffset,omitempty" bson:"clockDriftOffset,omitempty"`
	ComputerTime        *time.Time              `json:"computerTime,omitempty" bson:"computerTime,omitempty"`
	ConversionOffset    *int                    `json:"conversionOffset,omitempty" bson:"conversionOffset,omitempty"`
	CreatedTime         *time.Time              `json:"createdTime,omitempty" bson:"createdTime,omitempty"`
	CreatedUserID       *string                 `json:"createdUserId,omitempty" bson:"createdUserId,omitempty"`
	DataSetType         *string                 `json:"dataSetType,omitempty" bson:"dataSetType,omitempty"`
	DataState           *string                 `json:"-" bson:"_dataState,omitempty"` // TODO: Deprecated DataState (after data migration)
	Deduplicator        *DeduplicatorDescriptor `json:"deduplicator,omitempty" bson:"_deduplicator,omitempty"`
	DeletedTime         *time.Time              `json:"deletedTime,omitempty" bson:"deletedTime,omitempty"`
	DeletedUserID       *string                 `json:"deletedUserId,omitempty" bson:"deletedUserId,omitempty"`
	DeviceID            *string                 `json:"deviceId,omitempty" bson:"deviceId,omitempty"`
	DeviceManufacturers *[]string               `json:"deviceManufacturers,omitempty" bson:"deviceManufacturers,omitempty"`
	DeviceModel         *string                 `json:"deviceModel,omitempty" bson:"deviceModel,omitempty"`
	DeviceSerialNumber  *string                 `json:"deviceSerialNumber,omitempty" bson:"deviceSerialNumber,omitempty"`
	DeviceTags          *[]string               `json:"deviceTags,omitempty" bson:"deviceTags,omitempty"`
	DeviceTime          *time.Time              `json:"deviceTime,omitempty" bson:"deviceTime,omitempty"`
	GUID                *string                 `json:"guid,omitempty" bson:"guid,omitempty"`
	ID                  *string                 `json:"id,omitempty" bson:"id,omitempty"`
	ModifiedTime        *time.Time              `json:"modifiedTime,omitempty" bson:"modifiedTime,omitempty"`
	ModifiedUserID      *string                 `json:"modifiedUserId,omitempty" bson:"modifiedUserId,omitempty"`
	Payload             *metadata.Metadata      `json:"payload,omitempty" bson:"payload,omitempty"`
	Source              *string                 `json:"source,omitempty" bson:"source,omitempty"`
	State               *string                 `json:"-" bson:"_state,omitempty"`
	Time                *time.Time              `json:"time,omitempty" bson:"time,omitempty"`
	TimeProcessing      *string                 `json:"timeProcessing,omitempty" bson:"timeProcessing,omitempty"`
	TimeZoneName        *string                 `json:"timezone,omitempty" bson:"timezone,omitempty"`
	TimeZoneOffset      *int                    `json:"timezoneOffset,omitempty" bson:"timezoneOffset,omitempty"`
	Type                string                  `json:"type,omitempty" bson:"type,omitempty"`
	UploadID            *string                 `json:"uploadId,omitempty" bson:"uploadId,omitempty"`
	UserID              *string                 `json:"-" bson:"_userId,omitempty"`
	Version             *string                 `json:"version,omitempty" bson:"version,omitempty"`
	VersionInternal     int                     `json:"-" bson:"_version,omitempty"`
}

// custom marshalling for DeviceTime
type DeviceTime time.Time

func (t DeviceTime) MarshalJSON() (text []byte, err error) {
	return []byte(`"` + time.Time(t).Format(DeviceTimeFormat) + `"`), nil
}

func (d DataSet) MarshalJSON() ([]byte, error) {
	type Alias DataSet
	dataSet := &struct {
		DeviceTime DeviceTime `json:"deviceTime,omitempty" bson:"deviceTime,omitempty"`
		Alias
	}{DeviceTime(*d.DeviceTime), (Alias)(d)}

	return json.Marshal(dataSet)
}

type DataSets []*DataSet
<commit_msg>remove DeviceTime marshalling from data_set<commit_after>package data

import (
	"context"
	"encoding/json"
	"net/http"
	"regexp"
	"sort"
	"strconv"
	"time"

	"github.com/tidepool-org/platform/errors"
	"github.com/tidepool-org/platform/id"
	"github.com/tidepool-org/platform/metadata"
	"github.com/tidepool-org/platform/page"
	"github.com/tidepool-org/platform/request"
	"github.com/tidepool-org/platform/structure"
	structureValidator "github.com/tidepool-org/platform/structure/validator"
	timeZone "github.com/tidepool-org/platform/time/zone"
)

// TODO: This is a migration in progress from upload.Upload to DataSet. Some structures
// have been duplicated.

type DataSetAccessor interface {
	ListUserDataSets(ctx context.Context, userID string, filter *DataSetFilter, pagination *page.Pagination) (DataSets, error)
	CreateUserDataSet(ctx context.Context, userID string, create *DataSetCreate) (*DataSet, error)
	GetDataSet(ctx context.Context, id string) (*DataSet, error)
	UpdateDataSet(ctx context.Context, id string, update *DataSetUpdate) (*DataSet, error)
	DeleteDataSet(ctx context.Context, id string) error
}

const (
	ComputerTimeFormat = "2006-01-02T15:04:05"
	TimeFormat         = time.RFC3339Nano
	DeviceTimeFormat   = "2006-01-02T15:04:05"

	DataSetTypeContinuous = "continuous"
	DataSetTypeNormal     = "normal" // TODO: Normal?

	DataSetStateClosed = "closed"
	DataSetStateOpen   = "open"

	DeviceTagBGM         = "bgm"
	DeviceTagCGM         = "cgm"
	DeviceTagInsulinPump = "insulin-pump"

	TimeProcessingAcrossTheBoardTimeZone = "across-the-board-timezone" // TODO: Rename to across-the-board-time-zone or alternative
	TimeProcessingNone                   = "none"
	TimeProcessingUTCBootstrapping       = "utc-bootstrapping" // TODO: Rename to utc-bootstrap or alternative
)

func DataSetTypes() []string {
	return []string{
		DataSetTypeContinuous,
		DataSetTypeNormal,
	}
}

func DataSetStates() []string {
	return []string{
		DataSetStateClosed,
		DataSetStateOpen,
	}
}

func DeviceTags() []string {
	return []string{
		DeviceTagBGM,
		DeviceTagCGM,
		DeviceTagInsulinPump,
	}
}

func TimeProcessings() []string {
	return []string{
		TimeProcessingAcrossTheBoardTimeZone,
		TimeProcessingNone,
		TimeProcessingUTCBootstrapping,
	}
}

// TODO: Add OAuth client id to DataSetClient when available
// TODO: Pull from OAuth rather than be dependent upon client to complete

type DataSetClient struct {
	Name    *string                 `json:"name,omitempty" bson:"name,omitempty"`
	Version *string                 `json:"version,omitempty" bson:"version,omitempty"`
	Private *map[string]interface{} `json:"private,omitempty" bson:"private,omitempty"`
}

func NewDataSetClient() *DataSetClient {
	return &DataSetClient{}
}

func (d *DataSetClient) Parse(parser structure.ObjectParser) {
	if ptr := parser.String("name"); ptr != nil {
		d.Name = ptr
	}
	if ptr := parser.String("version"); ptr != nil {
		d.Version = ptr
	}
	if ptr := parser.Object("private"); ptr != nil {
		d.Private = ptr
	}
}

func (d *DataSetClient) Validate(validator structure.Validator) {
	validator.String("name", d.Name).NotEmpty()
	validator.String("version", d.Version).NotEmpty() // TODO: Semver validation
}

type DataSetFilter struct {
	ClientName *string
	Deleted    *bool
	DeviceID   *string
}

func NewDataSetFilter() *DataSetFilter {
	return &DataSetFilter{}
}

func (d *DataSetFilter) Parse(parser structure.ObjectParser) {
	d.ClientName = parser.String("client.name")
	d.Deleted = parser.Bool("deleted")
	d.DeviceID = parser.String("deviceId")
}

func (d *DataSetFilter) Validate(validator structure.Validator) {
	validator.String("client.name", d.ClientName).NotEmpty()
	validator.String("deviceId", d.DeviceID).NotEmpty()
}

func (d *DataSetFilter) MutateRequest(req *http.Request) error {
	parameters := map[string]string{}
	if d.ClientName != nil {
		parameters["client.name"] = *d.ClientName
	}
	if d.Deleted != nil {
		parameters["deleted"] = strconv.FormatBool(*d.Deleted)
	}
	if d.DeviceID != nil {
		parameters["deviceId"] = *d.DeviceID
	}
	return request.NewParametersMutator(parameters).MutateRequest(req)
}

type DataSetCreate struct {
	Client              *DataSetClient          `json:"client,omitempty"`
	DataSetType         *string                 `json:"dataSetType,omitempty"`
	Deduplicator        *DeduplicatorDescriptor `json:"deduplicator,omitempty"`
	DeviceID            *string                 `json:"deviceId,omitempty"`
	DeviceManufacturers *[]string               `json:"deviceManufacturers,omitempty"`
	DeviceModel         *string                 `json:"deviceModel,omitempty"`
	DeviceSerialNumber  *string                 `json:"deviceSerialNumber,omitempty"`
	DeviceTags          *[]string               `json:"deviceTags,omitempty"`
	Time                *time.Time              `json:"time,omitempty"`
	Type                string                  `json:"type,omitempty"`
	TimeProcessing      *string                 `json:"timeProcessing,omitempty"`
	TimeZoneName        *string                 `json:"timezone,omitempty"`
	TimeZoneOffset      *int                    `json:"timezoneOffset,omitempty"`
}

func NewDataSetCreate() *DataSetCreate {
	return &DataSetCreate{
		Type: "upload",
	}
}

func (d *DataSetCreate) Parse(parser structure.ObjectParser) {
	if clientParser := parser.WithReferenceObjectParser("client"); clientParser.Exists() {
		d.Client = NewDataSetClient()
		d.Client.Parse(clientParser)
		clientParser.NotParsed()
	}
	d.DataSetType = parser.String("dataSetType")
	d.Deduplicator = ParseDeduplicatorDescriptor(parser.WithReferenceObjectParser("deduplicator"))
	d.DeviceID = parser.String("deviceId")
	d.DeviceManufacturers = parser.StringArray("deviceManufacturers")
	d.DeviceModel = parser.String("deviceModel")
	d.DeviceSerialNumber = parser.String("deviceSerialNumber")
	d.DeviceTags = parser.StringArray("deviceTags")
	d.Time = parser.Time("time", TimeFormat)
	d.TimeProcessing = parser.String("timeProcessing")
	d.TimeZoneName = parser.String("timezone")
	d.TimeZoneOffset = parser.Int("timezoneOffset")
}

func (d *DataSetCreate) Validate(validator structure.Validator) {
	if d.Client != nil {
		d.Client.Validate(validator.WithReference("client"))
	}
	validator.String("dataSetType", d.DataSetType).OneOf(DataSetTypes()...)
	if d.Deduplicator != nil {
		d.Deduplicator.Validate(validator.WithReference("deduplicator"))
	}
	validator.String("deviceId", d.DeviceID).NotEmpty()
	validator.StringArray("deviceManufacturers", d.DeviceManufacturers).NotEmpty()
	validator.String("deviceModel", d.DeviceModel).NotEmpty()
	validator.String("deviceSerialNumber", d.DeviceSerialNumber).NotEmpty()
	validator.StringArray("deviceTags", d.DeviceTags).NotEmpty().EachOneOf(DeviceTags()...)
	validator.Time("time", d.Time).NotZero()
	validator.String("timeProcessing", d.TimeProcessing).OneOf(TimeProcessings()...)
	validator.String("timezone", d.TimeZoneName).Using(timeZone.NameValidator)
	validator.Int("timezoneOffset", d.TimeZoneOffset).InRange(-12*60, 14*60)
}

func (d *DataSetCreate) Normalize(normalizer structure.Normalizer) {
	if d.Deduplicator != nil {
		d.Deduplicator.Normalize(normalizer.WithReference("deduplicator"))
	}
	if d.DeviceManufacturers != nil {
		sort.Strings(*d.DeviceManufacturers)
	}
	if d.DeviceTags != nil {
		sort.Strings(*d.DeviceTags)
	}
}

type DataSetUpdate struct {
	Active             *bool                   `json:"-"`
	DeviceID           *string                 `json:"deviceId,omitempty"`
	DeviceModel        *string                 `json:"deviceModel,omitempty"`
	DeviceSerialNumber *string                 `json:"deviceSerialNumber,omitempty"`
	Deduplicator       *DeduplicatorDescriptor `json:"-"`
	State              *string                 `json:"state,omitempty"`
	Time               *time.Time              `json:"time,omitempty"`
	TimeZoneName       *string                 `json:"timezone,omitempty"`
	TimeZoneOffset     *int                    `json:"timezoneOffset,omitempty"`
}

func NewDataSetUpdate() *DataSetUpdate {
	return &DataSetUpdate{}
}

func (d *DataSetUpdate) Parse(parser structure.ObjectParser) {
	d.DeviceID = parser.String("deviceId")
	d.DeviceModel = parser.String("deviceModel")
	d.DeviceSerialNumber = parser.String("deviceSerialNumber")
	d.State = parser.String("state")
	d.Time = parser.Time("time", TimeFormat)
	d.TimeZoneName = parser.String("timezone")
	d.TimeZoneOffset = parser.Int("timezoneOffset")
}

func (d *DataSetUpdate) Validate(validator structure.Validator) {
	validator.String("deviceId", d.DeviceID).NotEmpty()
	validator.String("deviceModel", d.DeviceModel).LengthGreaterThan(1)
	validator.String("deviceSerialNumber", d.DeviceSerialNumber).LengthGreaterThan(1)
	validator.String("state", d.State).OneOf(DataSetStates()...)
	validator.Time("time", d.Time).NotZero()
	validator.String("timezone", d.TimeZoneName).Using(timeZone.NameValidator)
	validator.Int("timezoneOffset", d.TimeZoneOffset).InRange(-12*60, 14*60)
}

func (d *DataSetUpdate) IsEmpty() bool {
	return d.Active == nil && d.DeviceID == nil && d.DeviceModel == nil && d.DeviceSerialNumber == nil &&
		d.Deduplicator == nil && d.State == nil && d.Time == nil && d.TimeZoneName == nil && d.TimeZoneOffset == nil
}

func NewSetID() string {
	return id.Must(id.New(16))
}

func IsValidSetID(value string) bool {
	return ValidateSetID(value) == nil
}

func SetIDValidator(value string, errorReporter structure.ErrorReporter) {
	errorReporter.ReportError(ValidateSetID(value))
}

func ValidateSetID(value string) error {
	if value == "" {
		return structureValidator.ErrorValueEmpty()
	} else if !setIDExpression.MatchString(value) {
		return ErrorValueStringAsSetIDNotValid(value)
	}
	return nil
}

func ErrorValueStringAsSetIDNotValid(value string) error {
	return errors.Preparedf(structureValidator.ErrorCodeValueNotValid, "value is not valid", "value %q is not valid as data set id", value)
}

var setIDExpression = regexp.MustCompile("^(upid_[0-9a-f]{12}|upid_[0-9a-f]{32}|[0-9a-f]{32})$") // TODO: Want just "[0-9a-f]{32}"

type DataSet struct {
	Active              bool                    `json:"-" bson:"_active"`
	Annotations         *metadata.MetadataArray `json:"annotations,omitempty" bson:"annotations,omitempty"`
	ArchivedDataSetID   *string                 `json:"-" bson:"archivedDatasetId,omitempty"`
	ArchivedTime        *time.Time              `json:"-" bson:"archivedTime,omitempty"`
	ByUser              *string                 `json:"byUser,omitempty" bson:"byUser,omitempty"`
	Client              *DataSetClient          `json:"client,omitempty" bson:"client,omitempty"`
	ClockDriftOffset    *int                    `json:"clockDriftOffset,omitempty" bson:"clockDriftOffset,omitempty"`
	ComputerTime        *string                 `json:"computerTime,omitempty" bson:"computerTime,omitempty"`
	ConversionOffset    *int                    `json:"conversionOffset,omitempty" bson:"conversionOffset,omitempty"`
	CreatedTime         *time.Time              `json:"createdTime,omitempty" bson:"createdTime,omitempty"`
	CreatedUserID       *string                 `json:"createdUserId,omitempty" bson:"createdUserId,omitempty"`
	DataSetType         *string                 `json:"dataSetType,omitempty" bson:"dataSetType,omitempty"`
	DataState           *string                 `json:"-" bson:"_dataState,omitempty"` // TODO: Deprecated DataState (after data migration)
	Deduplicator        *DeduplicatorDescriptor `json:"deduplicator,omitempty" bson:"_deduplicator,omitempty"`
	DeletedTime         *time.Time              `json:"deletedTime,omitempty" bson:"deletedTime,omitempty"`
	DeletedUserID       *string                 `json:"deletedUserId,omitempty" bson:"deletedUserId,omitempty"`
	DeviceID            *string                 `json:"deviceId,omitempty" bson:"deviceId,omitempty"`
	DeviceManufacturers *[]string               `json:"deviceManufacturers,omitempty" bson:"deviceManufacturers,omitempty"`
	DeviceModel         *string                 `json:"deviceModel,omitempty" bson:"deviceModel,omitempty"`
	DeviceSerialNumber  *string                 `json:"deviceSerialNumber,omitempty" bson:"deviceSerialNumber,omitempty"`
	DeviceTags          *[]string               `json:"deviceTags,omitempty" bson:"deviceTags,omitempty"`
	DeviceTime          *string                 `json:"deviceTime,omitempty" bson:"deviceTime,omitempty"`
	GUID                *string                 `json:"guid,omitempty" bson:"guid,omitempty"`
	ID                  *string                 `json:"id,omitempty" bson:"id,omitempty"`
	ModifiedTime        *time.Time              `json:"modifiedTime,omitempty" bson:"modifiedTime,omitempty"`
	ModifiedUserID      *string                 `json:"modifiedUserId,omitempty" bson:"modifiedUserId,omitempty"`
	Payload             *metadata.Metadata      `json:"payload,omitempty" bson:"payload,omitempty"`
	Source              *string                 `json:"source,omitempty" bson:"source,omitempty"`
	State               *string                 `json:"-" bson:"_state,omitempty"`
	Time                *time.Time              `json:"time,omitempty" bson:"time,omitempty"`
	TimeProcessing      *string                 `json:"timeProcessing,omitempty" bson:"timeProcessing,omitempty"`
	TimeZoneName        *string                 `json:"timezone,omitempty" bson:"timezone,omitempty"`
	TimeZoneOffset      *int                    `json:"timezoneOffset,omitempty" bson:"timezoneOffset,omitempty"`
	Type                string                  `json:"type,omitempty" bson:"type,omitempty"`
	UploadID            *string                 `json:"uploadId,omitempty" bson:"uploadId,omitempty"`
	UserID              *string                 `json:"-" bson:"_userId,omitempty"`
	Version             *string                 `json:"version,omitempty" bson:"version,omitempty"`
	VersionInternal     int                     `json:"-" bson:"_version,omitempty"`
}

type DataSets []*DataSet
<|endoftext|>


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

package driverhub

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/bazelbuild/rules_webtesting/go/launcher/diagnostics"
	"github.com/bazelbuild/rules_webtesting/go/launcher/errors"
	"github.com/bazelbuild/rules_webtesting/go/launcher/webdriver"
	"github.com/bazelbuild/rules_webtesting/go/metadata/capabilities"
	"github.com/gorilla/mux"
)

// WebDriverSession is an http.Handler for forwarding requests to a WebDriver session.
type WebDriverSession struct {
	*mux.Router
	diagnostics.Diagnostics
	hub *WebDriverHub
	webdriver.WebDriver
	ID            int
	handler       HandlerFunc
	sessionPath   string
	RequestedCaps capabilities.Spec

	mu      sync.RWMutex
	stopped bool
}

// A handlerProvider wraps another HandlerFunc to create a new HandlerFunc.
// If the second return value is false, then the provider did not construct a new HandlerFunc.
// TODO(juangj): Update type of caps to capabilities.Spec.
type handlerProvider func(session *WebDriverSession, caps map[string]interface{}, base HandlerFunc) (HandlerFunc, bool)

// HandlerFunc is a func for handling a request to a WebDriver session.
type HandlerFunc func(context.Context, Request) (Response, error)

// Request wraps a request to a WebDriver session.
type Request struct {
	// HTTP Method for this request (e.g. http.MethodGet, ...).
	Method string
	// Path of the request after the session id.
	Path []string
	// Any HTTP headers sent with the request.
	Header http.Header
	// The body of the request.
	Body []byte
}

// Response describes what response should be returned for a request to WebDriver session.
type Response struct {
	// HTTP status code to return (e.g. http.StatusOK, ...).
	Status int
	// Any HTTP Headers that should be included in the response.
	Header http.Header
	// The body of the response.
	Body []byte
}

var providers = []handlerProvider{}

// HandlerProviderFunc adds additional handlers that will wrap any previously defined handlers.
//
// It is important to note that later added handlers will wrap earlier added handlers.
// E.g. if you call as follows:
//   HandlerProviderFunc(hp1)
//   HandlerProviderFunc(hp2)
//   HandlerProviderFunc(hp3)
//
// The generated handler will be constructed as follows:
//   hp3(session, caps, hp2(session, caps, hp1(session, caps, base)))
// where base is the a default function that forwards commands to WebDriver unchanged.
func HandlerProviderFunc(provider handlerProvider) {
	providers = append(providers, provider)
}

func createHandler(session *WebDriverSession, caps capabilities.Spec) HandlerFunc {
	handler := createBaseHandler(session.WebDriver)

	for _, provider := range providers {
		// TODO(juangj): Modify all handler providers to deal with capabilities.Specs.
		if h, ok := provider(session, caps.OSSCaps, handler); ok {
			handler = h
		}
	}
	return handler
}

// CreateSession creates a WebDriverSession object.
func CreateSession(id int, hub *WebDriverHub, driver webdriver.WebDriver, caps capabilities.Spec) (*WebDriverSession, error) {
	sessionPath := fmt.Sprintf("/wd/hub/session/%s", driver.SessionID())
	session := &WebDriverSession{ID: id, Diagnostics: hub.Diagnostics, hub: hub, WebDriver: driver, sessionPath: sessionPath, Router: mux.NewRouter(), RequestedCaps: caps}

	session.handler = createHandler(session, caps)
	// Route for commands for this session.
	session.PathPrefix(sessionPath).HandlerFunc(session.defaultHandler)
	// Route for commands for some other session. If this happens, the hub has
	// done something wrong.
	session.PathPrefix("/wd/hub/session/{sessionID}").HandlerFunc(session.wrongSession)
	// Route for all other paths that aren't WebDriver commands. This also implies
	// that the hub has done something wrong.
	session.PathPrefix("/").HandlerFunc(session.unknownCommand)

	return session, nil
}

// Name is the name of the component used in error messages.
func (s *WebDriverSession) Name() string {
	return "WebDriver Session Handler"
}

func (s *WebDriverSession) wrongSession(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	s.Severe(errors.New(s.Name(), "request routed to wrong session handler"))
	unknownError(w, fmt.Errorf("request for session %q was routed to handler for %q", vars["sessionID"], s.SessionID()))
}

func (s *WebDriverSession) unknownCommand(w http.ResponseWriter, r *http.Request) {
	s.Severe(errors.New(s.Name(), "unknown command routed to session handler"))
	unknownCommand(w, r)
}

// Quit can be called by handlers to quit this session.
func (s *WebDriverSession) Quit(ctx context.Context, _ Request) (Response, error) {
	if err := s.quit(ctx, capabilities.CanReuseSession(s.RequestedCaps)); err != nil {
		return ResponseFromError(err)
	}

	return Response{
		Status: http.StatusOK,
		Body:   []byte(`{"status": 0}`),
	}, nil
}

// Quit can be called by handlers to quit this session.
func (s *WebDriverSession) quit(ctx context.Context, reusable bool) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.stopped = true

	var wdErr error

	if !reusable {
		wdErr = s.WebDriver.Quit(ctx)
		if wdErr != nil {
			s.Warning(wdErr)
		}
	}

	envErr := s.hub.Env.StopSession(ctx, s.ID)
	if envErr != nil {
		s.Warning(envErr)
	}

	s.hub.RemoveSession(s.SessionID())

	if wdErr != nil {
		return wdErr
	}
	if envErr != nil {
		return envErr
	}

	if reusable {
		s.hub.AddReusableSession(s)
	}

	return nil
}

func (s *WebDriverSession) commandPathTokens(path string) []string {
	commandPath := strings.Trim(strings.TrimPrefix(path, s.sessionPath), "/")
	if commandPath == "" {
		return []string{}
	}
	return strings.Split(commandPath, "/")
}

// Unpause makes the session usable again and associates it with the given session id.
func (s *WebDriverSession) Unpause(id int) {
	s.mu.Lock()
	s.stopped = false
	s.ID = id
	s.mu.Unlock()
}

func (s *WebDriverSession) defaultHandler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	vars := mux.Vars(r)
	pathTokens := s.commandPathTokens(r.URL.Path)

	s.mu.Lock()
	stopped := s.stopped
	s.mu.Unlock()

	if stopped {
		invalidSessionID(w, vars["sessionID"])
		return
	}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		unknownError(w, err)
		return
	}

	req := Request{
		Method: r.Method,
		Path:   pathTokens,
		Header: r.Header,
		Body:   body,
	}
	resp, err := s.handler(ctx, req)
	if err != nil {
		if ctx.Err() == context.Canceled {
			log.Printf("[%s] request %+v was canceled.", s.Name(), req)
			return
		}
		if ctx.Err() == context.DeadlineExceeded {
			s.Warning(errors.New(s.Name(), fmt.Errorf("request %+v exceeded deadline", req)))
			timeout(w, r.URL.Path)
			return
		}
		s.Severe(errors.New(s.Name(), err))
		unknownError(w, err)
		return
	}

	if len(resp.Body) != 0 {
		w.Header().Set("Content-Type", contentType)
	}
	if resp.Header != nil {
		// Copy response headers from resp to w
		for k, vs := range resp.Header {
			w.Header().Del(k)
			for _, v := range vs {
				w.Header().Add(k, v)
			}
		}
	}

	// TODO(fisherii): needed to play nice with Dart Sync WebDriver. Delete when Dart Sync WebDriver is deleted.
	w.Header().Set("Transfer-Encoding", "identity")
	w.Header().Set("Content-Length", strconv.Itoa(len(resp.Body)))

	// Copy status code from resp to w
	w.WriteHeader(resp.Status)

	// Write body from resp to w
	w.Write(resp.Body)
}

func createBaseHandler(driver webdriver.WebDriver) HandlerFunc {
	client := &http.Client{}

	return func(ctx context.Context, rq Request) (Response, error) {
		url, err := driver.CommandURL(rq.Path...)
		if err != nil {
			return Response{}, err
		}

		req, err := http.NewRequest(rq.Method, url.String(), bytes.NewReader(rq.Body))
		if err != nil {
			return Response{}, err
		}
		req = req.WithContext(ctx)
		if rq.Header != nil {
			req.Header = rq.Header
		}

		resp, err := client.Do(req)
		if err != nil {
			return Response{}, err
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return Response{}, err
		}
		return Response{resp.StatusCode, resp.Header, body}, nil
	}
}

// ResponseFromError generates a Response object for err.
func ResponseFromError(err error) (Response, error) {
	body, e := webdriver.MarshalError(err)
	return Response{
		Status: webdriver.ErrorHTTPStatus(err),
		Body:   body,
	}, e
}

// SuccessfulResponse generate a response object indicating success.
func SuccessfulResponse(value interface{}) (Response, error) {
	body := map[string]interface{}{
		"status": 0,
	}

	if value != nil {
		body["value"] = value
	}

	bytes, err := json.Marshal(body)
	return Response{
		Status: http.StatusOK,
		Body:   bytes,
	}, err
}
<commit_msg>Add Metadata field to WebDriverSession. (#164)<commit_after>// Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package driverhub

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/bazelbuild/rules_webtesting/go/launcher/diagnostics"
	"github.com/bazelbuild/rules_webtesting/go/launcher/errors"
	"github.com/bazelbuild/rules_webtesting/go/launcher/webdriver"
	"github.com/bazelbuild/rules_webtesting/go/metadata"
	"github.com/bazelbuild/rules_webtesting/go/metadata/capabilities"
	"github.com/gorilla/mux"
)

// WebDriverSession is an http.Handler for forwarding requests to a WebDriver session.
type WebDriverSession struct {
	*mux.Router
	diagnostics.Diagnostics
	hub *WebDriverHub
	webdriver.WebDriver
	ID            int
	handler       HandlerFunc
	sessionPath   string
	RequestedCaps capabilities.Spec
	Metadata      *metadata.Metadata

	mu      sync.RWMutex
	stopped bool
}

// A handlerProvider wraps another HandlerFunc to create a new HandlerFunc.
// If the second return value is false, then the provider did not construct a new HandlerFunc.
// TODO(juangj): Update type of caps to capabilities.Spec.
type handlerProvider func(session *WebDriverSession, caps map[string]interface{}, base HandlerFunc) (HandlerFunc, bool)

// HandlerFunc is a func for handling a request to a WebDriver session.
type HandlerFunc func(context.Context, Request) (Response, error)

// Request wraps a request to a WebDriver session.
type Request struct {
	// HTTP Method for this request (e.g. http.MethodGet, ...).
	Method string
	// Path of the request after the session id.
	Path []string
	// Any HTTP headers sent with the request.
	Header http.Header
	// The body of the request.
	Body []byte
}

// Response describes what response should be returned for a request to WebDriver session.
type Response struct {
	// HTTP status code to return (e.g. http.StatusOK, ...).
	Status int
	// Any HTTP Headers that should be included in the response.
	Header http.Header
	// The body of the response.
	Body []byte
}

var providers = []handlerProvider{}

// HandlerProviderFunc adds additional handlers that will wrap any previously defined handlers.
//
// It is important to note that later added handlers will wrap earlier added handlers.
// E.g. if you call as follows:
//   HandlerProviderFunc(hp1)
//   HandlerProviderFunc(hp2)
//   HandlerProviderFunc(hp3)
//
// The generated handler will be constructed as follows:
//   hp3(session, caps, hp2(session, caps, hp1(session, caps, base)))
// where base is the a default function that forwards commands to WebDriver unchanged.
func HandlerProviderFunc(provider handlerProvider) {
	providers = append(providers, provider)
}

func createHandler(session *WebDriverSession, caps capabilities.Spec) HandlerFunc {
	handler := createBaseHandler(session.WebDriver)

	for _, provider := range providers {
		// TODO(juangj): Modify all handler providers to deal with capabilities.Specs.
		if h, ok := provider(session, caps.OSSCaps, handler); ok {
			handler = h
		}
	}
	return handler
}

// CreateSession creates a WebDriverSession object.
func CreateSession(id int, hub *WebDriverHub, driver webdriver.WebDriver, caps capabilities.Spec) (*WebDriverSession, error) {
	sessionPath := fmt.Sprintf("/wd/hub/session/%s", driver.SessionID())
	session := &WebDriverSession{
		ID:            id,
		Diagnostics:   hub.Diagnostics,
		hub:           hub,
		WebDriver:     driver,
		sessionPath:   sessionPath,
		Router:        mux.NewRouter(),
		RequestedCaps: caps,
		Metadata:      hub.Metadata,
	}

	session.handler = createHandler(session, caps)
	// Route for commands for this session.
	session.PathPrefix(sessionPath).HandlerFunc(session.defaultHandler)
	// Route for commands for some other session. If this happens, the hub has
	// done something wrong.
	session.PathPrefix("/wd/hub/session/{sessionID}").HandlerFunc(session.wrongSession)
	// Route for all other paths that aren't WebDriver commands. This also implies
	// that the hub has done something wrong.
	session.PathPrefix("/").HandlerFunc(session.unknownCommand)

	return session, nil
}

// Name is the name of the component used in error messages.
func (s *WebDriverSession) Name() string {
	return "WebDriver Session Handler"
}

func (s *WebDriverSession) wrongSession(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	s.Severe(errors.New(s.Name(), "request routed to wrong session handler"))
	unknownError(w, fmt.Errorf("request for session %q was routed to handler for %q", vars["sessionID"], s.SessionID()))
}

func (s *WebDriverSession) unknownCommand(w http.ResponseWriter, r *http.Request) {
	s.Severe(errors.New(s.Name(), "unknown command routed to session handler"))
	unknownCommand(w, r)
}

// Quit can be called by handlers to quit this session.
func (s *WebDriverSession) Quit(ctx context.Context, _ Request) (Response, error) {
	if err := s.quit(ctx, capabilities.CanReuseSession(s.RequestedCaps)); err != nil {
		return ResponseFromError(err)
	}

	return Response{
		Status: http.StatusOK,
		Body:   []byte(`{"status": 0}`),
	}, nil
}

// Quit can be called by handlers to quit this session.
func (s *WebDriverSession) quit(ctx context.Context, reusable bool) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.stopped = true

	var wdErr error

	if !reusable {
		wdErr = s.WebDriver.Quit(ctx)
		if wdErr != nil {
			s.Warning(wdErr)
		}
	}

	envErr := s.hub.Env.StopSession(ctx, s.ID)
	if envErr != nil {
		s.Warning(envErr)
	}

	s.hub.RemoveSession(s.SessionID())

	if wdErr != nil {
		return wdErr
	}
	if envErr != nil {
		return envErr
	}

	if reusable {
		s.hub.AddReusableSession(s)
	}

	return nil
}

func (s *WebDriverSession) commandPathTokens(path string) []string {
	commandPath := strings.Trim(strings.TrimPrefix(path, s.sessionPath), "/")
	if commandPath == "" {
		return []string{}
	}
	return strings.Split(commandPath, "/")
}

// Unpause makes the session usable again and associates it with the given session id.
func (s *WebDriverSession) Unpause(id int) {
	s.mu.Lock()
	s.stopped = false
	s.ID = id
	s.mu.Unlock()
}

func (s *WebDriverSession) defaultHandler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	vars := mux.Vars(r)
	pathTokens := s.commandPathTokens(r.URL.Path)

	s.mu.Lock()
	stopped := s.stopped
	s.mu.Unlock()

	if stopped {
		invalidSessionID(w, vars["sessionID"])
		return
	}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		unknownError(w, err)
		return
	}

	req := Request{
		Method: r.Method,
		Path:   pathTokens,
		Header: r.Header,
		Body:   body,
	}
	resp, err := s.handler(ctx, req)
	if err != nil {
		if ctx.Err() == context.Canceled {
			log.Printf("[%s] request %+v was canceled.", s.Name(), req)
			return
		}
		if ctx.Err() == context.DeadlineExceeded {
			s.Warning(errors.New(s.Name(), fmt.Errorf("request %+v exceeded deadline", req)))
			timeout(w, r.URL.Path)
			return
		}
		s.Severe(errors.New(s.Name(), err))
		unknownError(w, err)
		return
	}

	if len(resp.Body) != 0 {
		w.Header().Set("Content-Type", contentType)
	}
	if resp.Header != nil {
		// Copy response headers from resp to w
		for k, vs := range resp.Header {
			w.Header().Del(k)
			for _, v := range vs {
				w.Header().Add(k, v)
			}
		}
	}

	// TODO(fisherii): needed to play nice with Dart Sync WebDriver. Delete when Dart Sync WebDriver is deleted.
	w.Header().Set("Transfer-Encoding", "identity")
	w.Header().Set("Content-Length", strconv.Itoa(len(resp.Body)))

	// Copy status code from resp to w
	w.WriteHeader(resp.Status)

	// Write body from resp to w
	w.Write(resp.Body)
}

func createBaseHandler(driver webdriver.WebDriver) HandlerFunc {
	client := &http.Client{}

	return func(ctx context.Context, rq Request) (Response, error) {
		url, err := driver.CommandURL(rq.Path...)
		if err != nil {
			return Response{}, err
		}

		req, err := http.NewRequest(rq.Method, url.String(), bytes.NewReader(rq.Body))
		if err != nil {
			return Response{}, err
		}
		req = req.WithContext(ctx)
		if rq.Header != nil {
			req.Header = rq.Header
		}

		resp, err := client.Do(req)
		if err != nil {
			return Response{}, err
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return Response{}, err
		}
		return Response{resp.StatusCode, resp.Header, body}, nil
	}
}

// ResponseFromError generates a Response object for err.
func ResponseFromError(err error) (Response, error) {
	body, e := webdriver.MarshalError(err)
	return Response{
		Status: webdriver.ErrorHTTPStatus(err),
		Body:   body,
	}, e
}

// SuccessfulResponse generate a response object indicating success.
func SuccessfulResponse(value interface{}) (Response, error) {
	body := map[string]interface{}{
		"status": 0,
	}

	if value != nil {
		body["value"] = value
	}

	bytes, err := json.Marshal(body)
	return Response{
		Status: http.StatusOK,
		Body:   bytes,
	}, err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"runtime/pprof"

	cmds "github.com/jbenet/go-ipfs/commands"
	cmdsCli "github.com/jbenet/go-ipfs/commands/cli"
	cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
	"github.com/jbenet/go-ipfs/config"
	"github.com/jbenet/go-ipfs/core/commands"
	"github.com/jbenet/go-ipfs/daemon"
	u "github.com/jbenet/go-ipfs/util"
)

// log is the command logger
var log = u.Logger("cmd/ipfs")

func main() {
	args := os.Args[1:]
	root := Root

	req, err := cmdsCli.Parse(args, root)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// if the CLI-specific root doesn't contain the command, use the general root
	if len(req.Path()) == 0 {
		root = commands.Root
		req, err = cmdsCli.Parse(args, root)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	cmd, err := root.Get(req.Path())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	options, err := getOptions(req, root)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if help, found := options.Option("help"); found && help.(bool) {
		fmt.Println(cmd.Help)
		os.Exit(0)
	}

	if debug, found := options.Option("debug"); found && debug.(bool) {
		u.Debug = true

		// if debugging, setup profiling.
		if u.Debug {
			ofi, err := os.Create("cpu.prof")
			if err != nil {
				fmt.Println(err)
				return
			}
			pprof.StartCPUProfile(ofi)
			defer ofi.Close()
			defer pprof.StopCPUProfile()
		}
	}

	configPath, err := getConfigRoot(options)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	conf, err := getConfig(configPath)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	ctx := req.Context()
	ctx.ConfigRoot = configPath
	ctx.Config = conf

	var res cmds.Response
	if root == Root {
		res = root.Call(req)

	} else {
		local, found := options.Option("local")

		if (!found || !local.(bool)) && daemon.Locked(configPath) {
			res, err = cmdsHttp.Send(req)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}

		} else {
			// TODO: spin up node
			res = root.Call(req)
		}
	}

	if res.Error() != nil {
		fmt.Println(res.Error().Error())

		if cmd.Help != "" && res.Error().Code == cmds.ErrClient {
			// TODO: convert from markdown to ANSI terminal format?
			fmt.Println(cmd.Help)
		}

		os.Exit(1)
	}

	_, err = io.Copy(os.Stdout, res)
	if err != nil {
		fmt.Println(err.Error())
	}
}

func getOptions(req cmds.Request, root *cmds.Command) (cmds.Request, error) {
	tempReq := cmds.NewRequest(req.Path(), req.Options(), nil, nil)

	options, err := root.GetOptions(tempReq.Path())
	if err != nil {
		return nil, err
	}

	err = tempReq.ConvertOptions(options)
	if err != nil {
		return nil, err
	}

	return tempReq, nil
}

func getConfigRoot(req cmds.Request) (string, error) {
	if opt, found := req.Option("config"); found {
		return opt.(string), nil
	}

	configPath, err := config.PathRoot()
	if err != nil {
		return "", err
	}
	return configPath, nil
}

func getConfig(path string) (*config.Config, error) {
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
<commit_msg>commands: Gave Requests a reference to the command they are being called on<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"runtime/pprof"

	cmds "github.com/jbenet/go-ipfs/commands"
	cmdsCli "github.com/jbenet/go-ipfs/commands/cli"
	cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
	"github.com/jbenet/go-ipfs/config"
	"github.com/jbenet/go-ipfs/core/commands"
	"github.com/jbenet/go-ipfs/daemon"
	u "github.com/jbenet/go-ipfs/util"
)

// log is the command logger
var log = u.Logger("cmd/ipfs")

func main() {
	args := os.Args[1:]
	root := Root

	req, err := cmdsCli.Parse(args, root)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// if the CLI-specific root doesn't contain the command, use the general root
	if len(req.Path()) == 0 {
		root = commands.Root
		req, err = cmdsCli.Parse(args, root)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	cmd, err := root.Get(req.Path())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	options, err := getOptions(req, root)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if help, found := options.Option("help"); found && help.(bool) {
		fmt.Println(cmd.Help)
		os.Exit(0)
	}

	if debug, found := options.Option("debug"); found && debug.(bool) {
		u.Debug = true

		// if debugging, setup profiling.
		if u.Debug {
			ofi, err := os.Create("cpu.prof")
			if err != nil {
				fmt.Println(err)
				return
			}
			pprof.StartCPUProfile(ofi)
			defer ofi.Close()
			defer pprof.StopCPUProfile()
		}
	}

	configPath, err := getConfigRoot(options)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	conf, err := getConfig(configPath)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	ctx := req.Context()
	ctx.ConfigRoot = configPath
	ctx.Config = conf

	var res cmds.Response
	if root == Root {
		res = root.Call(req)

	} else {
		local, found := options.Option("local")

		if (!found || !local.(bool)) && daemon.Locked(configPath) {
			res, err = cmdsHttp.Send(req)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}

		} else {
			// TODO: spin up node
			res = root.Call(req)
		}
	}

	if res.Error() != nil {
		fmt.Println(res.Error().Error())

		if cmd.Help != "" && res.Error().Code == cmds.ErrClient {
			// TODO: convert from markdown to ANSI terminal format?
			fmt.Println(cmd.Help)
		}

		os.Exit(1)
	}

	_, err = io.Copy(os.Stdout, res)
	if err != nil {
		fmt.Println(err.Error())
	}
}

func getOptions(req cmds.Request, root *cmds.Command) (cmds.Request, error) {
	tempReq := cmds.NewRequest(req.Path(), req.Options(), nil, nil, nil)

	options, err := root.GetOptions(tempReq.Path())
	if err != nil {
		return nil, err
	}

	err = tempReq.ConvertOptions(options)
	if err != nil {
		return nil, err
	}

	return tempReq, nil
}

func getConfigRoot(req cmds.Request) (string, error) {
	if opt, found := req.Option("config"); found {
		return opt.(string), nil
	}

	configPath, err := config.PathRoot()
	if err != nil {
		return "", err
	}
	return configPath, nil
}

func getConfig(path string) (*config.Config, error) {
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
<|endoftext|>