<commit_before>package main

import (
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/kusubooru/shimmie2-tools/bulk"
)

var fns = template.FuncMap{
	"last": func(s []string) string {
		if len(s) == 0 {
			return ""
		}
		return s[len(s)-1]
	},
}

var templates = template.Must(template.New("").Funcs(fns).ParseGlob("web/*.tmpl"))

var (
	directory   = flag.String("dir", ".", "the directory that contains the images")
	csvFilename = flag.String("csv", "bulk.csv", "the name of the CSV file")
	pathPrefix  = flag.String("prefix", "", "the path that should be prefixed before the directory and the image name on the CSV file")
	port        = flag.String("port", "8080", "server port")
)

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: bulk-add-csv-ui [Options...]\n")
	fmt.Fprintf(os.Stderr, "Options:\n")
	flag.PrintDefaults()
}

type model struct {
	Err         error
	Prefix      string
	Dir         string
	CSVFilename string
	Images      []bulk.Image
}

var globalModel *model

func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(2)
	}
}

func run() error {
	flag.Usage = usage
	flag.Parse()

	d, err := filepath.Abs(*directory)
	if err != nil {
		return err
	}
	*directory = d

	m, err := loadFromCSVFile(*directory, *csvFilename)
	if err != nil {
		return err
	}
	globalModel = m

	if *pathPrefix != "" {
		globalModel.Prefix = *pathPrefix
	}

	http.Handle("/", http.HandlerFunc(indexHandler))
	http.Handle("/load", http.HandlerFunc(loadHandler))
	http.Handle("/update", http.HandlerFunc(updateHandler))
	http.Handle("/img/", http.HandlerFunc(serveImage))
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))

	go func() {
		localURL := fmt.Sprintf("http://localhost:%v", *port)
		if err := browserOpen(localURL); err != nil {
			fmt.Fprintf(os.Stderr, "Error: could not open browser, please visit %v manually.\n", localURL)
		}
	}()

	fmt.Println("Starting server at :" + *port)
	if err := http.ListenAndServe(":"+*port, nil); err != nil {
		return err
	}

	return nil
}

func loadFromCSVFile(dir, csvFilename string) (*model, error) {
	m := &model{Dir: dir, CSVFilename: csvFilename}

	// Loading images from folder
	images, err := bulk.LoadImages(dir)
	if err != nil {
		return nil, err
	}

	f, err := os.OpenFile(filepath.Join(dir, csvFilename), os.O_RDONLY|os.O_CREATE, 0644)
	if err != nil {
		return nil, err
	}
	defer func() {
		if cerr := f.Close(); cerr != nil && err == nil {
			err = cerr
		}
	}()

	// Loading CSV image data
	imagesWithInfo, err := bulk.LoadCSV(f)
	if err != nil {
		return nil, err
	}
	m.Images = bulk.Combine(images, imagesWithInfo)

	// Getting current prefix
	if _, err = f.Seek(0, 0); err != nil {
		return nil, err
	}
	cp, err := bulk.CurrentPrefix(dir, f)
	if err != nil {
		return nil, err
	}
	m.Prefix = cp

	return m, nil
}

func loadHandler(w http.ResponseWriter, r *http.Request) {
	f, h, err := r.FormFile("csvFilename")
	if err != nil {
		http.Error(w, fmt.Sprintf("could not parse multipart file: %v", err), http.StatusInternalServerError)
		return
	}
	defer func() {
		if cerr := f.Close(); cerr != nil {
			log.Printf("Error: could not close multipart file: %v\n", cerr)
		}
	}()

	// TODO: Extract load logic to function.
	img, err := bulk.LoadCSV(f)
	if err != nil {
		globalModel.Err = fmt.Errorf("Error: could not load image info from CSV File: %v", err)
		render(w, "index", globalModel)
		return
	}
	globalModel.CSVFilename = h.Filename
	globalModel.Images = bulk.Combine(globalModel.Images, img)
	if _, err = f.Seek(0, 0); err != nil {
		http.Error(w, fmt.Sprintf("could not seek multipart file: %v", err), http.StatusInternalServerError)
		return
	}
	prefix, err := bulk.CurrentPrefix(globalModel.Dir, f)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not read current prefix from multipart file: %v", err), http.StatusInternalServerError)
		return
	}
	err = bulk.Save(globalModel.Images, globalModel.Dir, globalModel.CSVFilename, prefix)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not save file: %v", err), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/", http.StatusFound)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	m, err := loadFromCSVFile(globalModel.Dir, globalModel.CSVFilename)
	if err != nil {
		globalModel.Err = fmt.Errorf("Error: could not load from CSV File: %v", err)
	} else {
		globalModel = m
	}

	render(w, "index", globalModel)
}

func render(w http.ResponseWriter, tmpl string, model interface{}) {
	if err := templates.ExecuteTemplate(w, tmpl+".tmpl", model); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func updateHandler(w http.ResponseWriter, r *http.Request) {

	if err := r.ParseForm(); err != nil {
		http.Error(w, "could not parse form", http.StatusInternalServerError)
		return
	}

	// prefix
	globalModel.Prefix = r.PostForm["prefix"][0]
	// csvFilename
	globalModel.CSVFilename = r.PostForm["csvFilename"][0]
	for _, img := range globalModel.Images {
		// tags
		areaTags := r.PostForm[fmt.Sprintf("image[%d].tags", img.ID)]
		globalModel.Images[img.ID].Tags = strings.Fields(areaTags[0])
		// source
		globalModel.Images[img.ID].Source = r.PostForm[fmt.Sprintf("image[%d].source", img.ID)][0]
		// rating
		rating := r.PostForm[fmt.Sprintf("image[%d].rating", img.ID)]
		if len(rating) != 0 {
			globalModel.Images[img.ID].Rating = rating[0]
		}
	}

	if err := saveToCSVFile(globalModel); err != nil {
		globalModel.Err = fmt.Errorf("Error: could not save to CSV file: %v", err)
		render(w, "index", globalModel)
	} else {
		globalModel.Err = nil
		http.Redirect(w, r, "/", http.StatusFound)
	}
}

func saveToCSVFile(m *model) error {
	return bulk.Save(m.Images, m.Dir, m.CSVFilename, m.Prefix)
}

func serveImage(w http.ResponseWriter, r *http.Request) {
	idStr := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
	id, err := strconv.Atoi(idStr)
	if err != nil {
		http.Error(w, fmt.Sprintf("%v is not a valid image ID", idStr), http.StatusBadRequest)
		return
	}
	img := bulk.FindByID(globalModel.Images, id)
	if img == nil {
		http.Error(w, fmt.Sprintf("no image found with ID: %v", id), http.StatusNotFound)
		return
	}
	p := filepath.Join(*directory, img.Name)

	f, err := os.Open(p)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not open image %v", p), http.StatusInternalServerError)
		return
	}
	defer func() {
		if cerr := f.Close(); cerr != nil {
			log.Printf("Error: could not close image file: %v\n", cerr)
		}
	}()
	data, err := ioutil.ReadAll(f)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not read image %v", p), http.StatusInternalServerError)
		return
	}
	_, err = w.Write(data)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not write image bytes: %v", err), http.StatusInternalServerError)
		return
	}
}

func browserOpen(input string) error {
	var err error
	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", input).Start()
	case "windows":
		err = exec.Command("cmd", "/C", "start", "", input).Start()
	case "darwin":
		err = exec.Command("open", input).Start()
	default:
		err = fmt.Errorf("unsupported platform")
	}
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Fix http.Error message in serveImage<commit_after>package main

import (
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/kusubooru/shimmie2-tools/bulk"
)

var fns = template.FuncMap{
	"last": func(s []string) string {
		if len(s) == 0 {
			return ""
		}
		return s[len(s)-1]
	},
}

var templates = template.Must(template.New("").Funcs(fns).ParseGlob("web/*.tmpl"))

var (
	directory   = flag.String("dir", ".", "the directory that contains the images")
	csvFilename = flag.String("csv", "bulk.csv", "the name of the CSV file")
	pathPrefix  = flag.String("prefix", "", "the path that should be prefixed before the directory and the image name on the CSV file")
	port        = flag.String("port", "8080", "server port")
)

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: bulk-add-csv-ui [Options...]\n")
	fmt.Fprintf(os.Stderr, "Options:\n")
	flag.PrintDefaults()
}

type model struct {
	Err         error
	Prefix      string
	Dir         string
	CSVFilename string
	Images      []bulk.Image
}

var globalModel *model

func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(2)
	}
}

func run() error {
	flag.Usage = usage
	flag.Parse()

	d, err := filepath.Abs(*directory)
	if err != nil {
		return err
	}
	*directory = d

	m, err := loadFromCSVFile(*directory, *csvFilename)
	if err != nil {
		return err
	}
	globalModel = m

	if *pathPrefix != "" {
		globalModel.Prefix = *pathPrefix
	}

	http.Handle("/", http.HandlerFunc(indexHandler))
	http.Handle("/load", http.HandlerFunc(loadHandler))
	http.Handle("/update", http.HandlerFunc(updateHandler))
	http.Handle("/img/", http.HandlerFunc(serveImage))
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))

	go func() {
		localURL := fmt.Sprintf("http://localhost:%v", *port)
		if err := browserOpen(localURL); err != nil {
			fmt.Fprintf(os.Stderr, "Error: could not open browser, please visit %v manually.\n", localURL)
		}
	}()

	fmt.Println("Starting server at :" + *port)
	if err := http.ListenAndServe(":"+*port, nil); err != nil {
		return err
	}

	return nil
}

func loadFromCSVFile(dir, csvFilename string) (*model, error) {
	m := &model{Dir: dir, CSVFilename: csvFilename}

	// Loading images from folder
	images, err := bulk.LoadImages(dir)
	if err != nil {
		return nil, err
	}

	f, err := os.OpenFile(filepath.Join(dir, csvFilename), os.O_RDONLY|os.O_CREATE, 0644)
	if err != nil {
		return nil, err
	}
	defer func() {
		if cerr := f.Close(); cerr != nil && err == nil {
			err = cerr
		}
	}()

	// Loading CSV image data
	imagesWithInfo, err := bulk.LoadCSV(f)
	if err != nil {
		return nil, err
	}
	m.Images = bulk.Combine(images, imagesWithInfo)

	// Getting current prefix
	if _, err = f.Seek(0, 0); err != nil {
		return nil, err
	}
	cp, err := bulk.CurrentPrefix(dir, f)
	if err != nil {
		return nil, err
	}
	m.Prefix = cp

	return m, nil
}

func loadHandler(w http.ResponseWriter, r *http.Request) {
	f, h, err := r.FormFile("csvFilename")
	if err != nil {
		http.Error(w, fmt.Sprintf("could not parse multipart file: %v", err), http.StatusInternalServerError)
		return
	}
	defer func() {
		if cerr := f.Close(); cerr != nil {
			log.Printf("Error: could not close multipart file: %v\n", cerr)
		}
	}()

	// TODO: Extract load logic to function.
	img, err := bulk.LoadCSV(f)
	if err != nil {
		globalModel.Err = fmt.Errorf("Error: could not load image info from CSV File: %v", err)
		render(w, "index", globalModel)
		return
	}
	globalModel.CSVFilename = h.Filename
	globalModel.Images = bulk.Combine(globalModel.Images, img)
	if _, err = f.Seek(0, 0); err != nil {
		http.Error(w, fmt.Sprintf("could not seek multipart file: %v", err), http.StatusInternalServerError)
		return
	}
	prefix, err := bulk.CurrentPrefix(globalModel.Dir, f)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not read current prefix from multipart file: %v", err), http.StatusInternalServerError)
		return
	}
	err = bulk.Save(globalModel.Images, globalModel.Dir, globalModel.CSVFilename, prefix)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not save file: %v", err), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/", http.StatusFound)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	m, err := loadFromCSVFile(globalModel.Dir, globalModel.CSVFilename)
	if err != nil {
		globalModel.Err = fmt.Errorf("Error: could not load from CSV File: %v", err)
	} else {
		globalModel = m
	}

	render(w, "index", globalModel)
}

func render(w http.ResponseWriter, tmpl string, model interface{}) {
	if err := templates.ExecuteTemplate(w, tmpl+".tmpl", model); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func updateHandler(w http.ResponseWriter, r *http.Request) {

	if err := r.ParseForm(); err != nil {
		http.Error(w, "could not parse form", http.StatusInternalServerError)
		return
	}

	// prefix
	globalModel.Prefix = r.PostForm["prefix"][0]
	// csvFilename
	globalModel.CSVFilename = r.PostForm["csvFilename"][0]
	for _, img := range globalModel.Images {
		// tags
		areaTags := r.PostForm[fmt.Sprintf("image[%d].tags", img.ID)]
		globalModel.Images[img.ID].Tags = strings.Fields(areaTags[0])
		// source
		globalModel.Images[img.ID].Source = r.PostForm[fmt.Sprintf("image[%d].source", img.ID)][0]
		// rating
		rating := r.PostForm[fmt.Sprintf("image[%d].rating", img.ID)]
		if len(rating) != 0 {
			globalModel.Images[img.ID].Rating = rating[0]
		}
	}

	if err := saveToCSVFile(globalModel); err != nil {
		globalModel.Err = fmt.Errorf("Error: could not save to CSV file: %v", err)
		render(w, "index", globalModel)
	} else {
		globalModel.Err = nil
		http.Redirect(w, r, "/", http.StatusFound)
	}
}

func saveToCSVFile(m *model) error {
	return bulk.Save(m.Images, m.Dir, m.CSVFilename, m.Prefix)
}

func serveImage(w http.ResponseWriter, r *http.Request) {
	idStr := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
	id, err := strconv.Atoi(idStr)
	if err != nil {
		http.Error(w, fmt.Sprintf("%v is not a valid image ID", idStr), http.StatusBadRequest)
		return
	}
	img := bulk.FindByID(globalModel.Images, id)
	if img == nil {
		http.Error(w, fmt.Sprintf("no image found with ID: %v", id), http.StatusNotFound)
		return
	}
	p := filepath.Join(*directory, img.Name)

	f, err := os.Open(p)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not open image: %v", err), http.StatusInternalServerError)
		return
	}
	defer func() {
		if cerr := f.Close(); cerr != nil {
			log.Printf("Error: could not close image file: %v\n", cerr)
		}
	}()
	data, err := ioutil.ReadAll(f)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not read image: %v", err), http.StatusInternalServerError)
		return
	}
	_, err = w.Write(data)
	if err != nil {
		http.Error(w, fmt.Sprintf("could not write image bytes: %v", err), http.StatusInternalServerError)
		return
	}
}

func browserOpen(input string) error {
	var err error
	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", input).Start()
	case "windows":
		err = exec.Command("cmd", "/C", "start", "", input).Start()
	case "darwin":
		err = exec.Command("open", input).Start()
	default:
		err = fmt.Errorf("unsupported platform")
	}
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// Utility scans spectrum and shows channels on which Crazyflies found
package main

import (
	"log"

	"github.com/krasin/crazyradio"
)

func main() {
	st, err := crazyradio.Start(nil)
	if err != nil {
		log.Fatal(err)
	}
	addr, err := st.Scan()
	if err != nil {
		log.Fatalf("Scan failed: %v", err)
	}
	log.Printf("Found crazyflies: %v", addr)
}
<commit_msg>Fix nil reference panic in crazyradio-scan. Pass usb.Hub to station.Start, instead of nil<commit_after>// Utility scans spectrum and shows channels on which Crazyflies found
package main

import (
	"log"

	"github.com/krasin/crazyradio"
	"github.com/krasin/crazyradio/usb"
)

func main() {
	st, err := crazyradio.Start(usb.Hub)
	if err != nil {
		log.Fatal(err)
	}
	addr, err := st.Scan()
	if err != nil {
		log.Fatalf("Scan failed: %v", err)
	}
	log.Printf("Found crazyflies: %v", addr)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"time"

	"github.com/Akagi201/cryptotrader/eosforce"
	log "github.com/sirupsen/logrus"
)

type AccountBalance struct {
	Staked    string  `json:"staked"`
	Available float64 `json:"avalable"`
	Created   string  `json:"created"`
	Unstaking string  `json:"unstaking"`
	Name      string  `json:"name"`
}

var balances []AccountBalance
var fuckingGuys []string

func main() {

	content, _ := ioutil.ReadFile(Opts.AccountFile)
	err := json.Unmarshal(content, &balances)
	if err != nil {
		log.Fatalf("json file parse json failed, err: %v", err)
	}

	c := eosforce.New([]string{"docker", "exec", "eosforce", "cleos"}, Opts.RpcScheme, Opts.RpcHost)

	for {
		for i, v := range balances {
			available, err := c.GetAvailable(context.Background(), v.Name)
			if err != nil {
				log.Fatalf("eosforce get available balance failed, err: %v", err)
			}
			if available != v.Available {
				log.Infof("Fucking Guy %v is dumping! before: %v, after: %v, diff: %v", v.Name, v.Available, available, v.Available-available)
				balances[i].Available = available
				fuckingGuys = append(fuckingGuys, v.Name)
			}
			time.Sleep(1 * time.Second)
		}
		log.Infof("Fucking Guys: %v", fuckingGuys)
		time.Sleep(2*time.Minute)
	}
}
<commit_msg>feat(eosforcemonitor): add callbackbot<commit_after>package main

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"time"
	"net/http"

	"github.com/Akagi201/cryptotrader/eosforce"
	log "github.com/sirupsen/logrus"
	"fmt"
	"bytes"
)

type AccountBalance struct {
	Staked    string  `json:"staked"`
	Available float64 `json:"avalable"`
	Created   string  `json:"created"`
	Unstaking string  `json:"unstaking"`
	Name      string  `json:"name"`
}

var balances []AccountBalance
var fuckingGuys []string

func main() {

	content, _ := ioutil.ReadFile(Opts.AccountFile)
	err := json.Unmarshal(content, &balances)
	if err != nil {
		log.Fatalf("json file parse json failed, err: %v", err)
	}

	c := eosforce.New([]string{"docker", "exec", "eosforce", "cleos"}, Opts.RpcScheme, Opts.RpcHost)

	for {
		for i, v := range balances {
			available, err := c.GetAvailable(context.Background(), v.Name)
			if err != nil {
				log.Fatalf("eosforce get available balance failed, err: %v", err)
			}
			if available != v.Available {
				log.Infof("Fucking Guy %v is dumping! before: %v, after: %v, diff: %v", v.Name, v.Available, available, v.Available-available)
				balances[i].Available = available
				fuckingGuys = append(fuckingGuys, v.Name)
				func(){
					post := fmt.Sprintf("Fucking Guy %v is dumping! before: %v, after: %v, diff: %v", v.Name, v.Available, available, v.Available-available)
					req, _ := http.NewRequest("POST", "http://dev.chainpool.io:3000/eosforce-monitor", bytes.NewBuffer([]byte(post)))
					req.Header.Set("Content-Type", "application/json")
					client := &http.Client{}
					resp, _ := client.Do(req)
					defer resp.Body.Close()
				}()
			}
			time.Sleep(1 * time.Second)
		}
		log.Infof("Fucking Guys: %v", fuckingGuys)
		func(){
			post := fmt.Sprintf("Fucking Guys: %v", fuckingGuys)
			req, _ := http.NewRequest("POST", "http://dev.chainpool.io:3000/eosforce-monitor", bytes.NewBuffer([]byte(post)))
			req.Header.Set("Content-Type", "application/json")
			client := &http.Client{}
			resp, _ := client.Do(req)
			defer resp.Body.Close()
		}()
		time.Sleep(2*time.Minute)
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package commands

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

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/loggo"
	"gopkg.in/juju/charm.v6-unstable"
	"gopkg.in/juju/charmrepo.v2-unstable"
	"gopkg.in/juju/charmrepo.v2-unstable/csclient"
	"gopkg.in/macaroon-bakery.v1/httpbakery"
	"gopkg.in/macaroon.v1"

	"github.com/juju/juju/api"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/environs/configstore"
)

// destroyPreparedEnviron destroys the environment and logs an error
// if it fails.
var destroyPreparedEnviron = destroyPreparedEnvironProductionFunc

var logger = loggo.GetLogger("juju.cmd.juju")

func destroyPreparedEnvironProductionFunc(
	ctx *cmd.Context,
	env environs.Environ,
	store configstore.Storage,
	action string,
) {
	ctx.Infof("%s failed, destroying environment", action)
	if err := environs.Destroy(env, store); err != nil {
		logger.Errorf("the environment could not be destroyed: %v", err)
	}
}

var destroyEnvInfo = destroyEnvInfoProductionFunc

func destroyEnvInfoProductionFunc(
	ctx *cmd.Context,
	cfgName string,
	store configstore.Storage,
	action string,
) {
	ctx.Infof("%s failed, cleaning up the environment.", action)
	if err := environs.DestroyInfo(cfgName, store); err != nil {
		logger.Errorf("the environment jenv file could not be cleaned up: %v", err)
	}
}

// environFromName loads an existing environment or prepares a new
// one. If there are no errors, it returns the environ and a closure to
// clean up in case we need to further up the stack. If an error has
// occurred, the environment and cleanup function will be nil, and the
// error will be filled in.
var environFromName = environFromNameProductionFunc

func environFromNameProductionFunc(
	ctx *cmd.Context,
	envName string,
	action string,
	ensureNotBootstrapped func(environs.Environ) error,
) (env environs.Environ, cleanup func(), err error) {

	store, err := configstore.Default()
	if err != nil {
		return nil, nil, err
	}

	envExisted := false
	if environInfo, err := store.ReadInfo(envName); err == nil {
		envExisted = true
		logger.Warningf(
			"ignoring environments.yaml: using bootstrap config in %s",
			environInfo.Location(),
		)
	} else if !errors.IsNotFound(err) {
		return nil, nil, err
	}

	cleanup = func() {
		// Distinguish b/t removing the jenv file or tearing down the
		// environment. We want to remove the jenv file if preparation
		// was not successful. We want to tear down the environment
		// only in the case where the environment didn't already
		// exist.
		if env == nil {
			logger.Debugf("Destroying environment info.")
			destroyEnvInfo(ctx, envName, store, action)
		} else if !envExisted && ensureNotBootstrapped(env) != environs.ErrAlreadyBootstrapped {
			logger.Debugf("Destroying environment.")
			destroyPreparedEnviron(ctx, env, store, action)
		}
	}

	if env, err = environs.PrepareFromName(envName, envcmd.BootstrapContext(ctx), store); err != nil {
		return nil, cleanup, err
	}

	return env, cleanup, err
}

type resolveCharmStoreEntityParams struct {
	urlStr          string
	requestedSeries string
	forceSeries     bool
	csParams        charmrepo.NewCharmStoreParams
	repoPath        string
	conf            *config.Config
}

// resolveCharmStoreEntityURL resolves the given charm or bundle URL string
// by looking it up in the appropriate charm repository.
// If it is a charm store URL, the given csParams will
// be used to access the charm store repository.
// If it is a local charm or bundle URL, the local charm repository at
// the given repoPath will be used. The given configuration
// will be used to add any necessary attributes to the repo
// and to return the charm's supported series if possible.
//
// resolveCharmStoreEntityURL also returns the charm repository holding
// the charm or bundle.
func resolveCharmStoreEntityURL(args resolveCharmStoreEntityParams) (*charm.URL, []string, charmrepo.Interface, error) {
	url, err := charm.ParseURL(args.urlStr)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	repo, err := charmrepo.InferRepository(url, args.csParams, args.repoPath)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	repo = config.SpecializeCharmRepo(repo, args.conf)

	if url.Schema == "local" && url.Series == "" {
		if defaultSeries, ok := args.conf.DefaultSeries(); ok {
			url.Series = defaultSeries
		}
		if url.Series == "" {
			possibleURL := *url
			possibleURL.Series = config.LatestLtsSeries()
			logger.Errorf("The series is not specified in the environment (default-series) or with the charm. Did you mean:\n\t%s", &possibleURL)
			return nil, nil, nil, errors.Errorf("cannot resolve series for charm: %q", url)
		}
	}
	resultUrl, supportedSeries, err := repo.Resolve(url)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	return resultUrl, supportedSeries, repo, nil
}

func isSeriesSupported(requestedSeries string, supportedSeries []string) bool {
	for _, series := range supportedSeries {
		if series == requestedSeries {
			return true
		}
	}
	return false
}

// termsAgreementError returns err as a *termsAgreementError
// if it has a "terms agreement request" error code, otherwise
// it returns err unchanged.
func termsAgreementError(err error) error {
	e, ok := err.(*httpbakery.DischargeError)
	if !ok {
		return nil
	}
	if e.Reason == nil {
		return err
	}
	code := "term agreement required"
	if e.Reason.Code != httpbakery.ErrorCode(code) {
		return err
	}
	magicMarker := code + ":"
	index := strings.LastIndex(e.Reason.Message, magicMarker)
	if index == -1 {
		return err
	}
	return &termsRequiredError{strings.Fields(e.Reason.Message[index+len(magicMarker):])}
}

type termsRequiredError struct {
	Terms []string
}

func (e *termsRequiredError) Error() string {
	return fmt.Sprintf("please agree to terms %q", strings.Join(e.Terms, " "))
}

// addCharmFromURL calls the appropriate client API calls to add the
// given charm URL to state. For non-public charm URLs, this function also
// handles the macaroon authorization process using the given csClient.
// The resulting charm URL of the added charm is displayed on stdout.
func addCharmFromURL(client *api.Client, curl *charm.URL, repo charmrepo.Interface, csclient *csClient) (*charm.URL, error) {
	switch curl.Schema {
	case "local":
		ch, err := repo.Get(curl)
		if err != nil {
			return nil, err
		}
		stateCurl, err := client.AddLocalCharm(curl, ch)
		if err != nil {
			return nil, err
		}
		curl = stateCurl
	case "cs":
		if err := client.AddCharm(curl); err != nil {
			if !params.IsCodeUnauthorized(err) {
				return nil, errors.Trace(err)
			}
			m, err := csclient.authorize(curl)
			if err != nil {
				return nil, termsAgreementError(errors.Cause(err))
			}
			if err := client.AddCharmWithAuthorization(curl, m); err != nil {
				return nil, errors.Trace(err)
			}
		}
	default:
		return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
	}
	return curl, nil
}

// csClient gives access to the charm store server and provides parameters
// for connecting to the charm store.
type csClient struct {
	params charmrepo.NewCharmStoreParams
}

// newCharmStoreClient is called to obtain a charm store client
// including the parameters for connecting to the charm store, and
// helpers to save the local authorization cookies and to authorize
// non-public charm deployments. It is defined as a variable so it can
// be changed for testing purposes.
var newCharmStoreClient = func(client *http.Client) *csClient {
	return &csClient{
		params: charmrepo.NewCharmStoreParams{
			HTTPClient:   client,
			VisitWebPage: httpbakery.OpenWebBrowser,
		},
	}
}

// authorize acquires and return the charm store delegatable macaroon to be
// used to add the charm corresponding to the given URL.
// The macaroon is properly attenuated so that it can only be used to deploy
// the given charm URL.
func (c *csClient) authorize(curl *charm.URL) (*macaroon.Macaroon, error) {
	client := csclient.New(csclient.Params{
		URL:          c.params.URL,
		HTTPClient:   c.params.HTTPClient,
		VisitWebPage: c.params.VisitWebPage,
	})
	endpoint := "/delegatable-macaroon"
	if curl != nil {
		query := url.Values{}
		query.Add("id", curl.String())
		endpoint = endpoint + "?" + query.Encode()
	}
	var m *macaroon.Macaroon
	if err := client.Get(endpoint, &m); err != nil {
		return nil, errors.Trace(err)
	}
	if err := m.AddFirstPartyCaveat("is-entity " + curl.String()); err != nil {
		return nil, errors.Trace(err)
	}
	return m, nil
}
<commit_msg>cmd/juju/common: No longer need to add FPC<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package commands

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

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/loggo"
	"gopkg.in/juju/charm.v6-unstable"
	"gopkg.in/juju/charmrepo.v2-unstable"
	"gopkg.in/juju/charmrepo.v2-unstable/csclient"
	"gopkg.in/macaroon-bakery.v1/httpbakery"
	"gopkg.in/macaroon.v1"

	"github.com/juju/juju/api"
	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/cmd/envcmd"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/environs/configstore"
)

// destroyPreparedEnviron destroys the environment and logs an error
// if it fails.
var destroyPreparedEnviron = destroyPreparedEnvironProductionFunc

var logger = loggo.GetLogger("juju.cmd.juju")

func destroyPreparedEnvironProductionFunc(
	ctx *cmd.Context,
	env environs.Environ,
	store configstore.Storage,
	action string,
) {
	ctx.Infof("%s failed, destroying environment", action)
	if err := environs.Destroy(env, store); err != nil {
		logger.Errorf("the environment could not be destroyed: %v", err)
	}
}

var destroyEnvInfo = destroyEnvInfoProductionFunc

func destroyEnvInfoProductionFunc(
	ctx *cmd.Context,
	cfgName string,
	store configstore.Storage,
	action string,
) {
	ctx.Infof("%s failed, cleaning up the environment.", action)
	if err := environs.DestroyInfo(cfgName, store); err != nil {
		logger.Errorf("the environment jenv file could not be cleaned up: %v", err)
	}
}

// environFromName loads an existing environment or prepares a new
// one. If there are no errors, it returns the environ and a closure to
// clean up in case we need to further up the stack. If an error has
// occurred, the environment and cleanup function will be nil, and the
// error will be filled in.
var environFromName = environFromNameProductionFunc

func environFromNameProductionFunc(
	ctx *cmd.Context,
	envName string,
	action string,
	ensureNotBootstrapped func(environs.Environ) error,
) (env environs.Environ, cleanup func(), err error) {

	store, err := configstore.Default()
	if err != nil {
		return nil, nil, err
	}

	envExisted := false
	if environInfo, err := store.ReadInfo(envName); err == nil {
		envExisted = true
		logger.Warningf(
			"ignoring environments.yaml: using bootstrap config in %s",
			environInfo.Location(),
		)
	} else if !errors.IsNotFound(err) {
		return nil, nil, err
	}

	cleanup = func() {
		// Distinguish b/t removing the jenv file or tearing down the
		// environment. We want to remove the jenv file if preparation
		// was not successful. We want to tear down the environment
		// only in the case where the environment didn't already
		// exist.
		if env == nil {
			logger.Debugf("Destroying environment info.")
			destroyEnvInfo(ctx, envName, store, action)
		} else if !envExisted && ensureNotBootstrapped(env) != environs.ErrAlreadyBootstrapped {
			logger.Debugf("Destroying environment.")
			destroyPreparedEnviron(ctx, env, store, action)
		}
	}

	if env, err = environs.PrepareFromName(envName, envcmd.BootstrapContext(ctx), store); err != nil {
		return nil, cleanup, err
	}

	return env, cleanup, err
}

type resolveCharmStoreEntityParams struct {
	urlStr          string
	requestedSeries string
	forceSeries     bool
	csParams        charmrepo.NewCharmStoreParams
	repoPath        string
	conf            *config.Config
}

// resolveCharmStoreEntityURL resolves the given charm or bundle URL string
// by looking it up in the appropriate charm repository.
// If it is a charm store URL, the given csParams will
// be used to access the charm store repository.
// If it is a local charm or bundle URL, the local charm repository at
// the given repoPath will be used. The given configuration
// will be used to add any necessary attributes to the repo
// and to return the charm's supported series if possible.
//
// resolveCharmStoreEntityURL also returns the charm repository holding
// the charm or bundle.
func resolveCharmStoreEntityURL(args resolveCharmStoreEntityParams) (*charm.URL, []string, charmrepo.Interface, error) {
	url, err := charm.ParseURL(args.urlStr)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	repo, err := charmrepo.InferRepository(url, args.csParams, args.repoPath)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	repo = config.SpecializeCharmRepo(repo, args.conf)

	if url.Schema == "local" && url.Series == "" {
		if defaultSeries, ok := args.conf.DefaultSeries(); ok {
			url.Series = defaultSeries
		}
		if url.Series == "" {
			possibleURL := *url
			possibleURL.Series = config.LatestLtsSeries()
			logger.Errorf("The series is not specified in the environment (default-series) or with the charm. Did you mean:\n\t%s", &possibleURL)
			return nil, nil, nil, errors.Errorf("cannot resolve series for charm: %q", url)
		}
	}
	resultUrl, supportedSeries, err := repo.Resolve(url)
	if err != nil {
		return nil, nil, nil, errors.Trace(err)
	}
	return resultUrl, supportedSeries, repo, nil
}

func isSeriesSupported(requestedSeries string, supportedSeries []string) bool {
	for _, series := range supportedSeries {
		if series == requestedSeries {
			return true
		}
	}
	return false
}

// termsAgreementError returns err as a *termsAgreementError
// if it has a "terms agreement request" error code, otherwise
// it returns err unchanged.
func termsAgreementError(err error) error {
	e, ok := err.(*httpbakery.DischargeError)
	if !ok {
		return nil
	}
	if e.Reason == nil {
		return err
	}
	code := "term agreement required"
	if e.Reason.Code != httpbakery.ErrorCode(code) {
		return err
	}
	magicMarker := code + ":"
	index := strings.LastIndex(e.Reason.Message, magicMarker)
	if index == -1 {
		return err
	}
	return &termsRequiredError{strings.Fields(e.Reason.Message[index+len(magicMarker):])}
}

type termsRequiredError struct {
	Terms []string
}

func (e *termsRequiredError) Error() string {
	return fmt.Sprintf("please agree to terms %q", strings.Join(e.Terms, " "))
}

// addCharmFromURL calls the appropriate client API calls to add the
// given charm URL to state. For non-public charm URLs, this function also
// handles the macaroon authorization process using the given csClient.
// The resulting charm URL of the added charm is displayed on stdout.
func addCharmFromURL(client *api.Client, curl *charm.URL, repo charmrepo.Interface, csclient *csClient) (*charm.URL, error) {
	switch curl.Schema {
	case "local":
		ch, err := repo.Get(curl)
		if err != nil {
			return nil, err
		}
		stateCurl, err := client.AddLocalCharm(curl, ch)
		if err != nil {
			return nil, err
		}
		curl = stateCurl
	case "cs":
		if err := client.AddCharm(curl); err != nil {
			if !params.IsCodeUnauthorized(err) {
				return nil, errors.Trace(err)
			}
			m, err := csclient.authorize(curl)
			if err != nil {
				return nil, termsAgreementError(errors.Cause(err))
			}
			if err := client.AddCharmWithAuthorization(curl, m); err != nil {
				return nil, errors.Trace(err)
			}
		}
	default:
		return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
	}
	return curl, nil
}

// csClient gives access to the charm store server and provides parameters
// for connecting to the charm store.
type csClient struct {
	params charmrepo.NewCharmStoreParams
}

// newCharmStoreClient is called to obtain a charm store client
// including the parameters for connecting to the charm store, and
// helpers to save the local authorization cookies and to authorize
// non-public charm deployments. It is defined as a variable so it can
// be changed for testing purposes.
var newCharmStoreClient = func(client *http.Client) *csClient {
	return &csClient{
		params: charmrepo.NewCharmStoreParams{
			HTTPClient:   client,
			VisitWebPage: httpbakery.OpenWebBrowser,
		},
	}
}

// authorize acquires and return the charm store delegatable macaroon to be
// used to add the charm corresponding to the given URL.
// The macaroon is properly attenuated so that it can only be used to deploy
// the given charm URL.
func (c *csClient) authorize(curl *charm.URL) (*macaroon.Macaroon, error) {
	client := csclient.New(csclient.Params{
		URL:          c.params.URL,
		HTTPClient:   c.params.HTTPClient,
		VisitWebPage: c.params.VisitWebPage,
	})
	endpoint := "/delegatable-macaroon"
	if curl != nil {
		query := url.Values{}
		query.Add("id", curl.String())
		endpoint = endpoint + "?" + query.Encode()
	}
	var m *macaroon.Macaroon
	if err := client.Get(endpoint, &m); err != nil {
		return nil, errors.Trace(err)
	}
	return m, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 cmd

import (
	"bytes"
	"errors"
	"fmt"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"text/template"
	"time"

	"github.com/spf13/cobra"
	"k8s.io/klog/v2"
	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/kapi"
	"k8s.io/minikube/pkg/minikube/browser"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/mustload"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/reason"
	"k8s.io/minikube/pkg/minikube/service"
	"k8s.io/minikube/pkg/minikube/style"
	"k8s.io/minikube/pkg/minikube/tunnel/kic"
)

const defaultServiceFormatTemplate = "http://{{.IP}}:{{.Port}}"

var (
	namespace          string
	all                bool
	https              bool
	serviceURLMode     bool
	serviceURLFormat   string
	serviceURLTemplate *template.Template
	wait               int
	interval           int
)

// serviceCmd represents the service command
var serviceCmd = &cobra.Command{
	Use:   "service [flags] SERVICE",
	Short: "Returns a URL to connect to a service",
	Long:  `Returns the Kubernetes URL(s) for service(s) in your local cluster. In the case of multiple URLs they will be printed one at a time.`,
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		t, err := template.New("serviceURL").Parse(serviceURLFormat)
		if err != nil {
			exit.Error(reason.InternalFormatUsage, "The value passed to --format is invalid", err)
		}
		serviceURLTemplate = t

		RootCmd.PersistentPreRun(cmd, args)
	},
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) == 0 && !all || (len(args) > 0 && all) {
			exit.Message(reason.Usage, "You must specify service name(s) or --all")
		}

		svcArgs := make(map[string]bool)
		for _, v := range args {
			svcArgs[v] = true
		}

		cname := ClusterFlagValue()
		co := mustload.Healthy(cname)

		var services service.URLs
		services, err := service.GetServiceURLs(co.API, co.Config.Name, namespace, serviceURLTemplate)
		if err != nil {
			out.FatalT("Failed to get service URL: {{.error}}", out.V{"error": err})
			out.ErrT(style.Notice, "Check that minikube is running and that you have specified the correct namespace (-n flag) if required.")
			os.Exit(reason.ExSvcUnavailable)
		}

		if len(args) >= 1 {
			var newServices service.URLs
			for _, svc := range services {
				if _, ok := svcArgs[svc.Name]; ok {
					newServices = append(newServices, svc)
				}
			}
			services = newServices
		}

		if len(services) == 0 {
			exit.Message(reason.SvcNotFound, `Service '{{.service}}' was not found in '{{.namespace}}' namespace.
You may select another namespace by using 'minikube service {{.service}} -n <namespace>'. Or list out all the services using 'minikube service list'`, out.V{"service": args[0], "namespace": namespace})
		}

		var data [][]string
		for _, svc := range services {
			openUrls, err := service.WaitForService(co.API, co.Config.Name, namespace, svc.Name, serviceURLTemplate, serviceURLMode, https, wait, interval)

			if err != nil {
				var s *service.SVCNotFoundError
				if errors.As(err, &s) {
					exit.Message(reason.SvcNotFound, `Service '{{.service}}' was not found in '{{.namespace}}' namespace.
You may select another namespace by using 'minikube service {{.service}} -n <namespace>'. Or list out all the services using 'minikube service list'`, out.V{"service": svc, "namespace": namespace})
				}
				exit.Error(reason.SvcTimeout, "Error opening service", err)
			}

			if len(openUrls) == 0 {
				data = append(data, []string{svc.Namespace, svc.Name, "No node port"})
			} else {
				servicePortNames := strings.Join(svc.PortNames, "\n")
				serviceURLs := strings.Join(openUrls, "\n")

				// if we are running Docker on OSX we empty the internal service URLs
				if runtime.GOOS == "darwin" && co.Config.Driver == oci.Docker {
					serviceURLs = ""
				}

				data = append(data, []string{svc.Namespace, svc.Name, servicePortNames, serviceURLs})

				if serviceURLMode && !driver.NeedsPortForward(co.Config.Driver) {
					out.String(fmt.Sprintf("%s\n", serviceURLs))
				}
			}
		}

		if driver.NeedsPortForward(co.Config.Driver) && driver.IsKIC(co.Config.Driver) && services != nil {
			startKicServiceTunnel(services, cname, co.Config.Driver)
		} else if driver.NeedsPortForward(co.Config.Driver) && driver.IsQEMU(co.Config.Driver) && services != nil {
			startQemuServiceTunnel(services, cname, co.Config.Driver)
		} else if !serviceURLMode {
			openURLs(data)
		}
	},
}

func init() {
	serviceCmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "The service namespace")
	serviceCmd.Flags().BoolVar(&serviceURLMode, "url", false, "Display the Kubernetes service URL in the CLI instead of opening it in the default browser")
	serviceCmd.Flags().BoolVar(&all, "all", false, "Forwards all services in a namespace (defaults to \"false\")")
	serviceCmd.Flags().BoolVar(&https, "https", false, "Open the service URL with https instead of http (defaults to \"false\")")
	serviceCmd.Flags().IntVar(&wait, "wait", service.DefaultWait, "Amount of time to wait for a service in seconds")
	serviceCmd.Flags().IntVar(&interval, "interval", service.DefaultInterval, "The initial time interval for each check that wait performs in seconds")

	serviceCmd.PersistentFlags().StringVar(&serviceURLFormat, "format", defaultServiceFormatTemplate, "Format to output service URL in. This format will be applied to each url individually and they will be printed one at a time.")
}

func startKicServiceTunnel(services service.URLs, configName, driverName string) {
	ctrlC := make(chan os.Signal, 1)
	signal.Notify(ctrlC, os.Interrupt)

	clientset, err := kapi.Client(configName)
	if err != nil {
		exit.Error(reason.InternalKubernetesClient, "error creating clientset", err)
	}

	var data [][]string
	for _, svc := range services {
		port, err := oci.ForwardedPort(driverName, configName, 22)
		if err != nil {
			exit.Error(reason.DrvPortForward, "error getting ssh port", err)
		}
		sshPort := strconv.Itoa(port)
		sshKey := filepath.Join(localpath.MiniPath(), "machines", configName, "id_rsa")

		serviceTunnel := kic.NewServiceTunnel(sshPort, sshKey, clientset.CoreV1(), serviceURLMode)
		urls, err := serviceTunnel.Start(svc.Name, namespace)

		if err != nil {
			exit.Error(reason.SvcTunnelStart, "error starting tunnel", err)
		}
		// mutate response urls to HTTPS if needed
		urls, err = mutateURLs(svc.Name, urls)

		if err != nil {
			exit.Error(reason.SvcTunnelStart, "error creatings urls", err)
		}

		defer serviceTunnel.Stop()
		svc.URLs = urls
		data = append(data, []string{namespace, svc.Name, "", strings.Join(urls, "\n")})
	}

	time.Sleep(1 * time.Second)

	if !serviceURLMode {
		service.PrintServiceList(os.Stdout, data)
	} else {
		for _, row := range data {
			out.String(fmt.Sprintf("%s\n", row[3]))
		}
	}

	if !serviceURLMode {
		openURLs(data)
	}

	out.WarningT("Because you are using a Docker driver on {{.operating_system}}, the terminal needs to be open to run it.", out.V{"operating_system": runtime.GOOS})

	<-ctrlC
}

func startQemuServiceTunnel(services service.URLs, configName, driverName string) {
}

func mutateURLs(serviceName string, urls []string) ([]string, error) {
	formattedUrls := make([]string, 0)
	for _, rawURL := range urls {
		var doc bytes.Buffer
		parsedURL, err := url.Parse(rawURL)
		if err != nil {
			exit.Error(reason.SvcTunnelStart, "No valid URL found for tunnel.", err)
		}
		port, err := strconv.Atoi(parsedURL.Port())
		if err != nil {
			exit.Error(reason.SvcTunnelStart, "No valid port found for tunnel.", err)
		}
		err = serviceURLTemplate.Execute(&doc, struct {
			IP   string
			Port int32
			Name string
		}{
			parsedURL.Hostname(),
			int32(port),
			serviceName,
		})

		if err != nil {
			return nil, err
		}

		httpsURL, _ := service.OptionallyHTTPSFormattedURLString(doc.String(), https)
		formattedUrls = append(formattedUrls, httpsURL)
	}

	return formattedUrls, nil
}

func openURLs(urls [][]string) {
	for _, u := range urls {

		if len(u) < 4 {
			klog.Warning("No URL found")
			continue
		}

		_, err := url.Parse(u[3])
		if err != nil {
			klog.Warningf("failed to parse url %q: %v (will not open)", u[3], err)
			out.String(fmt.Sprintf("%s\n", u))
			continue
		}

		if serviceURLMode {
			out.String(fmt.Sprintf("%s\n", u))
			continue
		}

		out.Styled(style.Celebrate, "Opening service {{.namespace_name}}/{{.service_name}} in default browser...", out.V{"namespace_name": namespace, "service_name": u[1]})
		if err := browser.OpenURL(u[3]); err != nil {
			exit.Error(reason.HostBrowser, fmt.Sprintf("open url failed: %s", u), err)
		}
	}
}
<commit_msg>revert changes<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 cmd

import (
	"bytes"
	"errors"
	"fmt"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"text/template"
	"time"

	"github.com/spf13/cobra"
	"k8s.io/klog/v2"
	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/kapi"
	"k8s.io/minikube/pkg/minikube/browser"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/mustload"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/reason"
	"k8s.io/minikube/pkg/minikube/service"
	"k8s.io/minikube/pkg/minikube/style"
	"k8s.io/minikube/pkg/minikube/tunnel/kic"
)

const defaultServiceFormatTemplate = "http://{{.IP}}:{{.Port}}"

var (
	namespace          string
	all                bool
	https              bool
	serviceURLMode     bool
	serviceURLFormat   string
	serviceURLTemplate *template.Template
	wait               int
	interval           int
)

// serviceCmd represents the service command
var serviceCmd = &cobra.Command{
	Use:   "service [flags] SERVICE",
	Short: "Returns a URL to connect to a service",
	Long:  `Returns the Kubernetes URL(s) for service(s) in your local cluster. In the case of multiple URLs they will be printed one at a time.`,
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		t, err := template.New("serviceURL").Parse(serviceURLFormat)
		if err != nil {
			exit.Error(reason.InternalFormatUsage, "The value passed to --format is invalid", err)
		}
		serviceURLTemplate = t

		RootCmd.PersistentPreRun(cmd, args)
	},
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) == 0 && !all || (len(args) > 0 && all) {
			exit.Message(reason.Usage, "You must specify service name(s) or --all")
		}

		svcArgs := make(map[string]bool)
		for _, v := range args {
			svcArgs[v] = true
		}

		cname := ClusterFlagValue()
		co := mustload.Healthy(cname)

		// Bail cleanly for qemu2 until implemented
		if driver.IsQEMU(co.Config.Driver) {
			exit.Message(reason.Unimplemented, "minikube service is not currently implemented with the qemu2 driver. See https://github.com/kubernetes/minikube/issues/14146 for details.")
		}

		var services service.URLs
		services, err := service.GetServiceURLs(co.API, co.Config.Name, namespace, serviceURLTemplate)
		if err != nil {
			out.FatalT("Failed to get service URL: {{.error}}", out.V{"error": err})
			out.ErrT(style.Notice, "Check that minikube is running and that you have specified the correct namespace (-n flag) if required.")
			os.Exit(reason.ExSvcUnavailable)
		}

		if len(args) >= 1 {
			var newServices service.URLs
			for _, svc := range services {
				if _, ok := svcArgs[svc.Name]; ok {
					newServices = append(newServices, svc)
				}
			}
			services = newServices
		}

		if len(services) == 0 {
			exit.Message(reason.SvcNotFound, `Service '{{.service}}' was not found in '{{.namespace}}' namespace.
You may select another namespace by using 'minikube service {{.service}} -n <namespace>'. Or list out all the services using 'minikube service list'`, out.V{"service": args[0], "namespace": namespace})
		}

		var data [][]string
		for _, svc := range services {
			openUrls, err := service.WaitForService(co.API, co.Config.Name, namespace, svc.Name, serviceURLTemplate, serviceURLMode, https, wait, interval)

			if err != nil {
				var s *service.SVCNotFoundError
				if errors.As(err, &s) {
					exit.Message(reason.SvcNotFound, `Service '{{.service}}' was not found in '{{.namespace}}' namespace.
You may select another namespace by using 'minikube service {{.service}} -n <namespace>'. Or list out all the services using 'minikube service list'`, out.V{"service": svc, "namespace": namespace})
				}
				exit.Error(reason.SvcTimeout, "Error opening service", err)
			}

			if len(openUrls) == 0 {
				data = append(data, []string{svc.Namespace, svc.Name, "No node port"})
			} else {
				servicePortNames := strings.Join(svc.PortNames, "\n")
				serviceURLs := strings.Join(openUrls, "\n")

				// if we are running Docker on OSX we empty the internal service URLs
				if runtime.GOOS == "darwin" && co.Config.Driver == oci.Docker {
					serviceURLs = ""
				}

				data = append(data, []string{svc.Namespace, svc.Name, servicePortNames, serviceURLs})

				if serviceURLMode && !driver.NeedsPortForward(co.Config.Driver) {
					out.String(fmt.Sprintf("%s\n", serviceURLs))
				}
			}
		}

		if driver.NeedsPortForward(co.Config.Driver) && driver.IsKIC(co.Config.Driver) && services != nil {
			startKicServiceTunnel(services, cname, co.Config.Driver)
		} else if driver.NeedsPortForward(co.Config.Driver) && driver.IsQEMU(co.Config.Driver) && services != nil {
			startQemuServiceTunnel(services, cname, co.Config.Driver)
		} else if !serviceURLMode {
			openURLs(data)
		}
	},
}

func init() {
	serviceCmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "The service namespace")
	serviceCmd.Flags().BoolVar(&serviceURLMode, "url", false, "Display the Kubernetes service URL in the CLI instead of opening it in the default browser")
	serviceCmd.Flags().BoolVar(&all, "all", false, "Forwards all services in a namespace (defaults to \"false\")")
	serviceCmd.Flags().BoolVar(&https, "https", false, "Open the service URL with https instead of http (defaults to \"false\")")
	serviceCmd.Flags().IntVar(&wait, "wait", service.DefaultWait, "Amount of time to wait for a service in seconds")
	serviceCmd.Flags().IntVar(&interval, "interval", service.DefaultInterval, "The initial time interval for each check that wait performs in seconds")

	serviceCmd.PersistentFlags().StringVar(&serviceURLFormat, "format", defaultServiceFormatTemplate, "Format to output service URL in. This format will be applied to each url individually and they will be printed one at a time.")
}

func startKicServiceTunnel(services service.URLs, configName, driverName string) {
	ctrlC := make(chan os.Signal, 1)
	signal.Notify(ctrlC, os.Interrupt)

	clientset, err := kapi.Client(configName)
	if err != nil {
		exit.Error(reason.InternalKubernetesClient, "error creating clientset", err)
	}

	var data [][]string
	for _, svc := range services {
		port, err := oci.ForwardedPort(driverName, configName, 22)
		if err != nil {
			exit.Error(reason.DrvPortForward, "error getting ssh port", err)
		}
		sshPort := strconv.Itoa(port)
		sshKey := filepath.Join(localpath.MiniPath(), "machines", configName, "id_rsa")

		serviceTunnel := kic.NewServiceTunnel(sshPort, sshKey, clientset.CoreV1(), serviceURLMode)
		urls, err := serviceTunnel.Start(svc.Name, namespace)

		if err != nil {
			exit.Error(reason.SvcTunnelStart, "error starting tunnel", err)
		}
		// mutate response urls to HTTPS if needed
		urls, err = mutateURLs(svc.Name, urls)

		if err != nil {
			exit.Error(reason.SvcTunnelStart, "error creatings urls", err)
		}

		defer serviceTunnel.Stop()
		svc.URLs = urls
		data = append(data, []string{namespace, svc.Name, "", strings.Join(urls, "\n")})
	}

	time.Sleep(1 * time.Second)

	if !serviceURLMode {
		service.PrintServiceList(os.Stdout, data)
	} else {
		for _, row := range data {
			out.String(fmt.Sprintf("%s\n", row[3]))
		}
	}

	if !serviceURLMode {
		openURLs(data)
	}

	out.WarningT("Because you are using a Docker driver on {{.operating_system}}, the terminal needs to be open to run it.", out.V{"operating_system": runtime.GOOS})

	<-ctrlC
}

func startQemuServiceTunnel(services service.URLs, configName, driverName string) {
}

func mutateURLs(serviceName string, urls []string) ([]string, error) {
	formattedUrls := make([]string, 0)
	for _, rawURL := range urls {
		var doc bytes.Buffer
		parsedURL, err := url.Parse(rawURL)
		if err != nil {
			exit.Error(reason.SvcTunnelStart, "No valid URL found for tunnel.", err)
		}
		port, err := strconv.Atoi(parsedURL.Port())
		if err != nil {
			exit.Error(reason.SvcTunnelStart, "No valid port found for tunnel.", err)
		}
		err = serviceURLTemplate.Execute(&doc, struct {
			IP   string
			Port int32
			Name string
		}{
			parsedURL.Hostname(),
			int32(port),
			serviceName,
		})

		if err != nil {
			return nil, err
		}

		httpsURL, _ := service.OptionallyHTTPSFormattedURLString(doc.String(), https)
		formattedUrls = append(formattedUrls, httpsURL)
	}

	return formattedUrls, nil
}

func openURLs(urls [][]string) {
	for _, u := range urls {

		if len(u) < 4 {
			klog.Warning("No URL found")
			continue
		}

		_, err := url.Parse(u[3])
		if err != nil {
			klog.Warningf("failed to parse url %q: %v (will not open)", u[3], err)
			out.String(fmt.Sprintf("%s\n", u))
			continue
		}

		if serviceURLMode {
			out.String(fmt.Sprintf("%s\n", u))
			continue
		}

		out.Styled(style.Celebrate, "Opening service {{.namespace_name}}/{{.service_name}} in default browser...", out.V{"namespace_name": namespace, "service_name": u[1]})
		if err := browser.OpenURL(u[3]); err != nil {
			exit.Error(reason.HostBrowser, fmt.Sprintf("open url failed: %s", u), err)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative 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 (
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"strconv"
	"testing"
	"time"

	"go.uber.org/atomic"

	network "knative.dev/networking/pkg"
	"knative.dev/serving/pkg/queue/readiness"
)

func TestProbeQueueInvalidPort(t *testing.T) {
	t.Cleanup(func() { os.Unsetenv(queuePortEnvVar) })
	for _, port := range []string{"-1", "0", "66000"} {
		os.Setenv(queuePortEnvVar, port)
		if rv := standaloneProbeMain(1, http.DefaultTransport); rv != 1 {
			t.Error("Unexpected return code", rv)
		}
	}
}

func TestProbeQueueConnectionFailure(t *testing.T) {
	if err := probeQueueHealthPath(1, 12345, http.DefaultTransport); err == nil {
		t.Error("Expected error, got nil")
	}
}

func TestProbeQueueNotReady(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		probed.Store(true)
		w.WriteHeader(http.StatusBadRequest)
	})

	err := probeQueueHealthPath(100*time.Millisecond, port, http.DefaultTransport)

	if err == nil || err.Error() != "probe returned not ready" {
		t.Error("Unexpected not ready error:", err)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeShuttingDown(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		probed.Store(true)
		w.WriteHeader(http.StatusGone)
	})

	err := probeQueueHealthPath(time.Second, port, http.DefaultTransport)

	if err == nil || err.Error() != "failed to probe: failing probe deliberately for shutdown" {
		t.Error("Unexpected error:", err)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeQueueShuttingDownFailsFast(t *testing.T) {
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusGone)
	})

	start := time.Now()
	if err := probeQueueHealthPath(1, port, http.DefaultTransport); err == nil {
		t.Error("probeQueueHealthPath did not fail")
	}

	// if fails due to timeout and not cancelation, then it took too long.
	if time.Since(start) >= 1*time.Second {
		t.Error("took too long to fail")
	}
}

func TestProbeQueueReady(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		probed.Store(true)
		w.WriteHeader(http.StatusOK)
	})

	t.Cleanup(func() { os.Unsetenv(queuePortEnvVar) })
	os.Setenv(queuePortEnvVar, strconv.Itoa(port))

	if rv := standaloneProbeMain(0 /*use default*/, nil); rv != 0 {
		t.Error("Unexpected return value from standaloneProbeMain:", rv)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeQueueTimeout(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, r *http.Request) {
		probed.Store(true)

		select {
		case <-time.After(time.Second):
		case <-r.Context().Done():
		}

		w.WriteHeader(http.StatusOK)
	})

	t.Cleanup(func() { os.Unsetenv(queuePortEnvVar) })
	os.Setenv(queuePortEnvVar, strconv.Itoa(port))

	if rv := standaloneProbeMain(300*time.Millisecond, nil); rv == 0 {
		t.Error("Unexpected return value from standaloneProbeMain:", rv)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeQueueDelayedReady(t *testing.T) {
	var count atomic.Int64
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		if count.Inc() < 3 {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		w.WriteHeader(http.StatusOK)
	})

	if err := probeQueueHealthPath(readiness.PollTimeout, port, http.DefaultTransport); err != nil {
		t.Errorf("probeQueueHealthPath(%d) = %s", port, err)
	}
}

func newProbeTestServer(t *testing.T, f func(w http.ResponseWriter, r *http.Request)) (port int) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get(network.UserAgentKey) == network.QueueProxyUserAgent {
			f(w, r)
		}
	}))
	t.Cleanup(ts.Close)

	u, err := url.Parse(ts.URL)
	if err != nil {
		t.Fatalf("%s is not a valid URL: %v", ts.URL, err)
	}

	port, err = strconv.Atoi(u.Port())
	if err != nil {
		t.Fatalf("Failed to convert port(%s) to int: %v", u.Port(), err)
	}

	return port
}
<commit_msg>Bump request timeout in TestProbeQueueNotReady to fix flake (#11050)<commit_after>/*
Copyright 2020 The Knative 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 (
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"strconv"
	"testing"
	"time"

	"go.uber.org/atomic"

	network "knative.dev/networking/pkg"
	"knative.dev/serving/pkg/queue/readiness"
)

func TestProbeQueueInvalidPort(t *testing.T) {
	t.Cleanup(func() { os.Unsetenv(queuePortEnvVar) })
	for _, port := range []string{"-1", "0", "66000"} {
		os.Setenv(queuePortEnvVar, port)
		if rv := standaloneProbeMain(1, http.DefaultTransport); rv != 1 {
			t.Error("Unexpected return code", rv)
		}
	}
}

func TestProbeQueueConnectionFailure(t *testing.T) {
	if err := probeQueueHealthPath(1, 12345, http.DefaultTransport); err == nil {
		t.Error("Expected error, got nil")
	}
}

func TestProbeQueueNotReady(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		probed.Store(true)
		w.WriteHeader(http.StatusBadRequest)
	})

	err := probeQueueHealthPath(300*time.Millisecond, port, http.DefaultTransport)

	if err == nil || err.Error() != "probe returned not ready" {
		t.Error("Unexpected not ready error:", err)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeShuttingDown(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		probed.Store(true)
		w.WriteHeader(http.StatusGone)
	})

	err := probeQueueHealthPath(time.Second, port, http.DefaultTransport)

	if err == nil || err.Error() != "failed to probe: failing probe deliberately for shutdown" {
		t.Error("Unexpected error:", err)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeQueueShuttingDownFailsFast(t *testing.T) {
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusGone)
	})

	start := time.Now()
	if err := probeQueueHealthPath(1, port, http.DefaultTransport); err == nil {
		t.Error("probeQueueHealthPath did not fail")
	}

	// if fails due to timeout and not cancelation, then it took too long.
	if time.Since(start) >= 1*time.Second {
		t.Error("took too long to fail")
	}
}

func TestProbeQueueReady(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		probed.Store(true)
		w.WriteHeader(http.StatusOK)
	})

	t.Cleanup(func() { os.Unsetenv(queuePortEnvVar) })
	os.Setenv(queuePortEnvVar, strconv.Itoa(port))

	if rv := standaloneProbeMain(0 /*use default*/, nil); rv != 0 {
		t.Error("Unexpected return value from standaloneProbeMain:", rv)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeQueueTimeout(t *testing.T) {
	var probed atomic.Bool
	port := newProbeTestServer(t, func(w http.ResponseWriter, r *http.Request) {
		probed.Store(true)

		select {
		case <-time.After(time.Second):
		case <-r.Context().Done():
		}

		w.WriteHeader(http.StatusOK)
	})

	t.Cleanup(func() { os.Unsetenv(queuePortEnvVar) })
	os.Setenv(queuePortEnvVar, strconv.Itoa(port))

	if rv := standaloneProbeMain(300*time.Millisecond, nil); rv == 0 {
		t.Error("Unexpected return value from standaloneProbeMain:", rv)
	}

	if !probed.Load() {
		t.Error("Expected the queue proxy server to be probed")
	}
}

func TestProbeQueueDelayedReady(t *testing.T) {
	var count atomic.Int64
	port := newProbeTestServer(t, func(w http.ResponseWriter, _ *http.Request) {
		if count.Inc() < 3 {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		w.WriteHeader(http.StatusOK)
	})

	if err := probeQueueHealthPath(readiness.PollTimeout, port, http.DefaultTransport); err != nil {
		t.Errorf("probeQueueHealthPath(%d) = %s", port, err)
	}
}

func newProbeTestServer(t *testing.T, f func(w http.ResponseWriter, r *http.Request)) (port int) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get(network.UserAgentKey) == network.QueueProxyUserAgent {
			f(w, r)
		}
	}))
	t.Cleanup(ts.Close)

	u, err := url.Parse(ts.URL)
	if err != nil {
		t.Fatalf("%s is not a valid URL: %v", ts.URL, err)
	}

	port, err = strconv.Atoi(u.Port())
	if err != nil {
		t.Fatalf("Failed to convert port(%s) to int: %v", u.Port(), err)
	}

	return port
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"io/ioutil"
	"os"
	"path"
	"testing"
	"time"

	"github.com/brandur/sorg"
	_ "github.com/brandur/sorg/testing"
	_ "github.com/lib/pq"
	assert "github.com/stretchr/testify/require"
)

var db *sql.DB

func init() {
	err := sorg.CreateTargetDirs()
	if err != nil {
		panic(err)
	}

	db, err = sql.Open("postgres", "postgres://localhost/sorg-test?sslmode=disable")
	if err != nil {
		panic(err)
	}
}

func TestCompileJavascripts(t *testing.T) {
	dir, err := ioutil.TempDir("", "javascripts")

	file1 := dir + "/file1.js"
	file2 := dir + "/file2.js"
	file3 := dir + "/file3.js"
	out := dir + "/app.js"

	err = ioutil.WriteFile(file1, []byte(`function() { return "file1" }`), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file2, []byte(`function() { return "file2" }`), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file3, []byte(`function() { return "file3" }`), 0755)
	assert.NoError(t, err)

	err = compileJavascripts([]string{file1, file2, file3}, out)
	assert.NoError(t, err)

	actual, err := ioutil.ReadFile(out)
	assert.NoError(t, err)

	expected := `/* file1.js */

(function() {

function() { return "file1" }

}).call(this);

/* file2.js */

(function() {

function() { return "file2" }

}).call(this);

/* file3.js */

(function() {

function() { return "file3" }

}).call(this);

`
	assert.Equal(t, expected, string(actual))
}

func TestCompilePhotos(t *testing.T) {
	//
	// No database
	//

	photos, err := compilePhotos(nil)
	assert.NoError(t, err)
	assert.Equal(t, []*Photo(nil), photos)

	//
	// With empty database
	//

	photos, err = compilePhotos(db)
	assert.NoError(t, err)
	assert.Equal(t, []*Photo(nil), photos)

	//
	// With results
	//

	// TODO: insert photos
	//photos, err = compilePhotos(db)
	//assert.NoError(t, err)
}

func TestCompileReading(t *testing.T) {
	//
	// No database
	//

	err := compileReading(nil)
	assert.NoError(t, err)

	//
	// With empty database
	//

	err = compileReading(db)
	assert.NoError(t, err)

	//
	// With results
	//

	// TODO: insert reading
	//err = compileReading(db)
	//assert.NoError(t, err)
}

func TestCompileRuns(t *testing.T) {
	//
	// No database
	//

	err := compileRuns(nil)
	assert.NoError(t, err)

	//
	// With empty database
	//

	err = compileRuns(db)
	assert.NoError(t, err)

	//
	// With results
	//

	// TODO: insert runs
	//err = compileRuns(db)
	//assert.NoError(t, err)
}

func TestCompileStylesheets(t *testing.T) {
	dir, err := ioutil.TempDir("", "stylesheets")

	file1 := dir + "/file1.sass"
	file2 := dir + "/file2.sass"
	file3 := dir + "/file3.css"
	out := dir + "/app.css"

	// The syntax of the first and second files is GCSS and the third is in
	// CSS.
	err = ioutil.WriteFile(file1, []byte("p\n  margin: 10px"), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file2, []byte("p\n  padding: 10px"), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file3, []byte("p {\n  border: 10px;\n}"), 0755)
	assert.NoError(t, err)

	err = compileStylesheets([]string{file1, file2, file3}, out)
	assert.NoError(t, err)

	actual, err := ioutil.ReadFile(out)
	assert.NoError(t, err)

	// Note that the first two files have no spacing in the output because they
	// go through the GCSS compiler.
	expected := `/* file1.sass */

p{margin:10px;}

/* file2.sass */

p{padding:10px;}

/* file3.css */

p {
  border: 10px;
}

`
	assert.Equal(t, expected, string(actual))
}

func TestCompileTwitter(t *testing.T) {
	//
	// No database
	//

	err := compileTwitter(nil)
	assert.NoError(t, err)

	//
	// With empty database
	//

	err = compileTwitter(db)
	assert.NoError(t, err)

	//
	// With results
	//

	now := time.Now()
	tweet := &Tweet{
		Content:    "Hello, world!",
		OccurredAt: &now,
		Slug:       "1234",
	}
	insertTweet(t, tweet, false)

	err = compileTwitter(db)
	assert.NoError(t, err)
}

func TestEnsureSymlink(t *testing.T) {
	dir, err := ioutil.TempDir("", "symlink")
	assert.NoError(t, err)

	source := path.Join(dir, "source")
	err = ioutil.WriteFile(source, []byte("source"), 0755)
	assert.NoError(t, err)

	dest := path.Join(dir, "symlink-dest")

	//
	// Case 1: Symlink does not exist
	//

	err = ensureSymlink(source, dest)
	assert.NoError(t, err)

	actual, err := os.Readlink(dest)
	assert.Equal(t, source, actual)

	//
	// Case 2: Symlink does exist
	//
	// Consists solely of re-running the previous test case.
	//

	err = ensureSymlink(source, dest)
	assert.NoError(t, err)

	actual, err = os.Readlink(dest)
	assert.Equal(t, source, actual)

	//
	// Case 3: Symlink file exists, but source doesn't
	//

	err = os.RemoveAll(dest)
	assert.NoError(t, err)

	source = path.Join(dir, "source")
	err = ioutil.WriteFile(source, []byte("source"), 0755)
	assert.NoError(t, err)

	err = ensureSymlink(source, dest)
	assert.NoError(t, err)

	actual, err = os.Readlink(dest)
	assert.Equal(t, source, actual)
}

func TestGetLocals(t *testing.T) {
	locals := getLocals("Title", map[string]interface{}{
		"Foo": "Bar",
	})

	assert.Equal(t, "Bar", locals["Foo"])
	assert.Equal(t, sorg.Release, locals["Release"])
	assert.Equal(t, "Title", locals["Title"])
}

func TestIsHidden(t *testing.T) {
	assert.Equal(t, true, isHidden(".gitkeep"))
	assert.Equal(t, false, isHidden("article"))
}

func TestSplitFrontmatter(t *testing.T) {
	frontmatter, content, err := splitFrontmatter(`---
foo: bar
---

other`)
	assert.NoError(t, err)
	assert.Equal(t, "foo: bar", frontmatter)
	assert.Equal(t, "other", content)

	frontmatter, content, err = splitFrontmatter(`other`)
	assert.NoError(t, err)
	assert.Equal(t, "", frontmatter)
	assert.Equal(t, "other", content)

	frontmatter, content, err = splitFrontmatter(`---
foo: bar
---
`)
	assert.NoError(t, err)
	assert.Equal(t, "foo: bar", frontmatter)
	assert.Equal(t, "", content)

	frontmatter, content, err = splitFrontmatter(`foo: bar
---
`)
	assert.Equal(t, errBadFrontmatter, err)
}

func insertTweet(t *testing.T, tweet *Tweet, reply bool) {
	_, err := db.Exec(`
		INSERT INTO events
			(content, occurred_at, metadata, slug, type)
		VALUES
			($1, $2, hstore('reply', $3), $4, $5)
	`, tweet.Content, tweet.OccurredAt, reply, tweet.Slug, "twitter")
	assert.NoError(t, err)
}
<commit_msg>Fix test involing output directory<commit_after>package main

import (
	"database/sql"
	"io/ioutil"
	"os"
	"path"
	"testing"
	"time"

	"github.com/brandur/sorg"
	_ "github.com/brandur/sorg/testing"
	_ "github.com/lib/pq"
	assert "github.com/stretchr/testify/require"
)

var db *sql.DB

func init() {
	conf.TargetDir = "./public"
	err := sorg.CreateOutputDirs(conf.TargetDir)
	if err != nil {
		panic(err)
	}

	db, err = sql.Open("postgres", "postgres://localhost/sorg-test?sslmode=disable")
	if err != nil {
		panic(err)
	}
}

func TestCompileJavascripts(t *testing.T) {
	dir, err := ioutil.TempDir("", "javascripts")

	file1 := dir + "/file1.js"
	file2 := dir + "/file2.js"
	file3 := dir + "/file3.js"
	out := dir + "/app.js"

	err = ioutil.WriteFile(file1, []byte(`function() { return "file1" }`), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file2, []byte(`function() { return "file2" }`), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file3, []byte(`function() { return "file3" }`), 0755)
	assert.NoError(t, err)

	err = compileJavascripts([]string{file1, file2, file3}, out)
	assert.NoError(t, err)

	actual, err := ioutil.ReadFile(out)
	assert.NoError(t, err)

	expected := `/* file1.js */

(function() {

function() { return "file1" }

}).call(this);

/* file2.js */

(function() {

function() { return "file2" }

}).call(this);

/* file3.js */

(function() {

function() { return "file3" }

}).call(this);

`
	assert.Equal(t, expected, string(actual))
}

func TestCompilePhotos(t *testing.T) {
	//
	// No database
	//

	photos, err := compilePhotos(nil)
	assert.NoError(t, err)
	assert.Equal(t, []*Photo(nil), photos)

	//
	// With empty database
	//

	photos, err = compilePhotos(db)
	assert.NoError(t, err)
	assert.Equal(t, []*Photo(nil), photos)

	//
	// With results
	//

	// TODO: insert photos
	//photos, err = compilePhotos(db)
	//assert.NoError(t, err)
}

func TestCompileReading(t *testing.T) {
	//
	// No database
	//

	err := compileReading(nil)
	assert.NoError(t, err)

	//
	// With empty database
	//

	err = compileReading(db)
	assert.NoError(t, err)

	//
	// With results
	//

	// TODO: insert reading
	//err = compileReading(db)
	//assert.NoError(t, err)
}

func TestCompileRuns(t *testing.T) {
	//
	// No database
	//

	err := compileRuns(nil)
	assert.NoError(t, err)

	//
	// With empty database
	//

	err = compileRuns(db)
	assert.NoError(t, err)

	//
	// With results
	//

	// TODO: insert runs
	//err = compileRuns(db)
	//assert.NoError(t, err)
}

func TestCompileStylesheets(t *testing.T) {
	dir, err := ioutil.TempDir("", "stylesheets")

	file1 := dir + "/file1.sass"
	file2 := dir + "/file2.sass"
	file3 := dir + "/file3.css"
	out := dir + "/app.css"

	// The syntax of the first and second files is GCSS and the third is in
	// CSS.
	err = ioutil.WriteFile(file1, []byte("p\n  margin: 10px"), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file2, []byte("p\n  padding: 10px"), 0755)
	assert.NoError(t, err)

	err = ioutil.WriteFile(file3, []byte("p {\n  border: 10px;\n}"), 0755)
	assert.NoError(t, err)

	err = compileStylesheets([]string{file1, file2, file3}, out)
	assert.NoError(t, err)

	actual, err := ioutil.ReadFile(out)
	assert.NoError(t, err)

	// Note that the first two files have no spacing in the output because they
	// go through the GCSS compiler.
	expected := `/* file1.sass */

p{margin:10px;}

/* file2.sass */

p{padding:10px;}

/* file3.css */

p {
  border: 10px;
}

`
	assert.Equal(t, expected, string(actual))
}

func TestCompileTwitter(t *testing.T) {
	//
	// No database
	//

	err := compileTwitter(nil)
	assert.NoError(t, err)

	//
	// With empty database
	//

	err = compileTwitter(db)
	assert.NoError(t, err)

	//
	// With results
	//

	now := time.Now()
	tweet := &Tweet{
		Content:    "Hello, world!",
		OccurredAt: &now,
		Slug:       "1234",
	}
	insertTweet(t, tweet, false)

	err = compileTwitter(db)
	assert.NoError(t, err)
}

func TestEnsureSymlink(t *testing.T) {
	dir, err := ioutil.TempDir("", "symlink")
	assert.NoError(t, err)

	source := path.Join(dir, "source")
	err = ioutil.WriteFile(source, []byte("source"), 0755)
	assert.NoError(t, err)

	dest := path.Join(dir, "symlink-dest")

	//
	// Case 1: Symlink does not exist
	//

	err = ensureSymlink(source, dest)
	assert.NoError(t, err)

	actual, err := os.Readlink(dest)
	assert.Equal(t, source, actual)

	//
	// Case 2: Symlink does exist
	//
	// Consists solely of re-running the previous test case.
	//

	err = ensureSymlink(source, dest)
	assert.NoError(t, err)

	actual, err = os.Readlink(dest)
	assert.Equal(t, source, actual)

	//
	// Case 3: Symlink file exists, but source doesn't
	//

	err = os.RemoveAll(dest)
	assert.NoError(t, err)

	source = path.Join(dir, "source")
	err = ioutil.WriteFile(source, []byte("source"), 0755)
	assert.NoError(t, err)

	err = ensureSymlink(source, dest)
	assert.NoError(t, err)

	actual, err = os.Readlink(dest)
	assert.Equal(t, source, actual)
}

func TestGetLocals(t *testing.T) {
	locals := getLocals("Title", map[string]interface{}{
		"Foo": "Bar",
	})

	assert.Equal(t, "Bar", locals["Foo"])
	assert.Equal(t, sorg.Release, locals["Release"])
	assert.Equal(t, "Title", locals["Title"])
}

func TestIsHidden(t *testing.T) {
	assert.Equal(t, true, isHidden(".gitkeep"))
	assert.Equal(t, false, isHidden("article"))
}

func TestSplitFrontmatter(t *testing.T) {
	frontmatter, content, err := splitFrontmatter(`---
foo: bar
---

other`)
	assert.NoError(t, err)
	assert.Equal(t, "foo: bar", frontmatter)
	assert.Equal(t, "other", content)

	frontmatter, content, err = splitFrontmatter(`other`)
	assert.NoError(t, err)
	assert.Equal(t, "", frontmatter)
	assert.Equal(t, "other", content)

	frontmatter, content, err = splitFrontmatter(`---
foo: bar
---
`)
	assert.NoError(t, err)
	assert.Equal(t, "foo: bar", frontmatter)
	assert.Equal(t, "", content)

	frontmatter, content, err = splitFrontmatter(`foo: bar
---
`)
	assert.Equal(t, errBadFrontmatter, err)
}

func insertTweet(t *testing.T, tweet *Tweet, reply bool) {
	_, err := db.Exec(`
		INSERT INTO events
			(content, occurred_at, metadata, slug, type)
		VALUES
			($1, $2, hstore('reply', $3), $4, $5)
	`, tweet.Content, tweet.OccurredAt, reply, tweet.Slug, "twitter")
	assert.NoError(t, err)
}
<|endoftext|>


<commit_before>//
// xlsx2json package wraps the github.com/tealag/xlsx package (used under a BSD License) and  a fork of Robert Krimen's Otto
// Javascript engine (under an MIT License) providing an scriptable xlsx2json exporter, explorer and importer utility.
//
//
// Overview: A command line utility designed to take a XML based Excel file
// and turn each row into a JSON blob. The JSON blob returned for
// each row can be processed via a JavaScript callback allowing for
// flexible translations for spreadsheet to JSON output.
//
// @author R. S. Doiel, <rsdoiel@gmail.com>
//
// Copyright (c) 2016, R. S. Doiel
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"strings"

	// 3rd Party packages
	"github.com/chzyer/readline"

	// My packages
	"github.com/rsdoiel/xlsx2json"
)

var (
	showhelp      bool
	sheetNo       int
	inputFilename *string
	jsFilename    *string
	jsCallback    *string
)

func usage() {
	fmt.Println(`

 USAGE: xlsx2json [OPTIONS] EXCEL_FILENAME

 OVERVIEW

 Read a .xlsx file and return each row as a JSON object (or array of objects).
 If a JavaScript file and callback name are provided then that will be used to
 generate the resulting JSON object per row.

 JAVASCRIPT

 The callback function in JavaScript should return an object that looks like

     {"path": ..., "source": ..., "error": ...}

 The "path" property should contain the desired filename to use for storing
 the JSON blob. If it is empty the output will only be displayed to standard out.

 The "source" property should be the final version of the object you want to
 turn into a JSON blob.

 The "error" property is a string and if the string is not empty it will be
 used as an error message and cause the processing to stop.

 A simple JavaScript Examples:

    // Counter i is used to name the JSON output files.
    var i = 0;

    // callback is the default name looked for when processing.
    // the command line option -callback lets you used a different name.
    function callback(row) {
        i += 1;
        if (i > 10) {
            // Stop if processing more than 10 rows.
            return {"error": "too many rows..."}
        }
        return {
            "path": "data/" + i + ".json",
            "source": row,
            "error": ""
        }
    }

 OPTIONS
`)
	flag.PrintDefaults()
	fmt.Println(`

 Examples

    xlsx2json myfile.xlsx

    xlsx2json -js row2obj.js -callback row2obj myfile.xlsx

	xlsx2json -repl myfile.xlsx

`)
	os.Exit(0)
}

func init() {
	flag.BoolVar(&showhelp, "h", false, "display this help message")
	flag.BoolVar(&showhelp, "help", false, "display this help message")
	flag.BoolVar(&showVersion, "v", false, "display version information")
	flag.BoolVar(&xlsx2json.UseRepl, "i", false, "Run with an interactive repl")
	flag.IntVar(&sheetNo, "sheet", 0, "Specify the number of the sheet to process")
	jsFilename = flag.String("js", "", "The name of the JavaScript file containing callback function")
	jsCallback = flag.String("callback", "callback", "The name of the JavaScript function to use as a callback")
}

func main() {
	var (
		inputFilename string
	)
	flag.Parse()

	if showhelp == true {
		usage()
	}
	if showVersion == true {
		fmt.Printf("Version %s\n", xlsx2json.Version)
	}

	args := flag.Args()
	if len(args) > 0 {
		inputFilename = args[0]
	}
	if inputFilename == "" {
		// Read Excel file from standard
		log.Fatalf("Need to provide an xlsx file for input, -i")
	}
	vm, output, err := xlsx2json.Run(inputFilename, sheetNo, *jsFilename, *jsCallback)
	if err != nil {
		log.Fatal(err)
	}
	if xlsx2json.UseRepl == true {
		//FIXME: merge output as an array
		vm.Object(fmt.Sprintf(`Spreadsheet = [%s]`, strings.Join(output, ",")))
		rl, err := readline.New("> ")
		if err != nil {
			log.Fatal(err)
		}
		defer rl.Close()
		for xlsx2json.UseRepl == true {
			jsSrc, err := rl.Readline()
			if err != nil { // io.EOF, readline.ErrInterrupt
				break
			}
			if len(strings.Trim(jsSrc, " ")) > 0 {
				if script, err := vm.Compile("repl", jsSrc); err != nil {
					fmt.Printf("Compile error, %s\n", err)
				} else {
					out, err := vm.Eval(script)
					switch {
					case err != nil:
						fmt.Printf("Runtime error, %s\n", err)
					default:
						if xlsx2json.UseRepl == true {
							fmt.Println(out.String())
						}
					}
				}
			}
		}
		//FIXME: update output to reflect the contents of Spreadsheet from the JS VM
		if value, err := vm.Get("Spreadsheet"); err == nil {
			data, err := value.Export()
			if err != nil {
				log.Fatal(err)
			}
			src, err := json.Marshal(data)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("%s", src)
			os.Exit(0)
		} else {
			log.Fatal(err)
		}
	}
	fmt.Printf(`[%s]`, strings.Join(output, ","))
}
<commit_msg>adding version number flag<commit_after>//
// xlsx2json package wraps the github.com/tealag/xlsx package (used under a BSD License) and  a fork of Robert Krimen's Otto
// Javascript engine (under an MIT License) providing an scriptable xlsx2json exporter, explorer and importer utility.
//
//
// Overview: A command line utility designed to take a XML based Excel file
// and turn each row into a JSON blob. The JSON blob returned for
// each row can be processed via a JavaScript callback allowing for
// flexible translations for spreadsheet to JSON output.
//
// @author R. S. Doiel, <rsdoiel@gmail.com>
//
// Copyright (c) 2016, R. S. Doiel
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"strings"

	// 3rd Party packages
	"github.com/chzyer/readline"

	// My packages
	"github.com/rsdoiel/xlsx2json"
)

var (
	showhelp      bool
	showVersion   bool
	sheetNo       int
	inputFilename *string
	jsFilename    *string
	jsCallback    *string
)

func usage() {
	fmt.Println(`

 USAGE: xlsx2json [OPTIONS] EXCEL_FILENAME

 OVERVIEW

 Read a .xlsx file and return each row as a JSON object (or array of objects).
 If a JavaScript file and callback name are provided then that will be used to
 generate the resulting JSON object per row.

 JAVASCRIPT

 The callback function in JavaScript should return an object that looks like

     {"path": ..., "source": ..., "error": ...}

 The "path" property should contain the desired filename to use for storing
 the JSON blob. If it is empty the output will only be displayed to standard out.

 The "source" property should be the final version of the object you want to
 turn into a JSON blob.

 The "error" property is a string and if the string is not empty it will be
 used as an error message and cause the processing to stop.

 A simple JavaScript Examples:

    // Counter i is used to name the JSON output files.
    var i = 0;

    // callback is the default name looked for when processing.
    // the command line option -callback lets you used a different name.
    function callback(row) {
        i += 1;
        if (i > 10) {
            // Stop if processing more than 10 rows.
            return {"error": "too many rows..."}
        }
        return {
            "path": "data/" + i + ".json",
            "source": row,
            "error": ""
        }
    }

 OPTIONS
`)
	flag.PrintDefaults()
	fmt.Printf(`

 Examples

    xlsx2json myfile.xlsx

    xlsx2json -js row2obj.js -callback row2obj myfile.xlsx

	xlsx2json -repl myfile.xlsx

Version %s
`, xlsx2json.Version)
	os.Exit(0)
}

func init() {
	flag.BoolVar(&showhelp, "h", false, "display this help message")
	flag.BoolVar(&showhelp, "help", false, "display this help message")
	flag.BoolVar(&showVersion, "v", false, "display version information")
	flag.BoolVar(&xlsx2json.UseRepl, "i", false, "Run with an interactive repl")
	flag.IntVar(&sheetNo, "sheet", 0, "Specify the number of the sheet to process")
	jsFilename = flag.String("js", "", "The name of the JavaScript file containing callback function")
	jsCallback = flag.String("callback", "callback", "The name of the JavaScript function to use as a callback")
}

func main() {
	var (
		inputFilename string
	)
	flag.Parse()

	if showhelp == true {
		usage()
	}
	if showVersion == true {
		fmt.Printf("Version %s\n", xlsx2json.Version)
		os.Exit(0)
	}

	args := flag.Args()
	if len(args) > 0 {
		inputFilename = args[0]
	}
	if inputFilename == "" {
		// Read Excel file from standard
		log.Fatalf("Need to provide an xlsx file for input, -i")
	}
	vm, output, err := xlsx2json.Run(inputFilename, sheetNo, *jsFilename, *jsCallback)
	if err != nil {
		log.Fatal(err)
	}
	if xlsx2json.UseRepl == true {
		//FIXME: merge output as an array
		vm.Object(fmt.Sprintf(`Spreadsheet = [%s]`, strings.Join(output, ",")))
		rl, err := readline.New("> ")
		if err != nil {
			log.Fatal(err)
		}
		defer rl.Close()
		for xlsx2json.UseRepl == true {
			jsSrc, err := rl.Readline()
			if err != nil { // io.EOF, readline.ErrInterrupt
				break
			}
			if len(strings.Trim(jsSrc, " ")) > 0 {
				if script, err := vm.Compile("repl", jsSrc); err != nil {
					fmt.Printf("Compile error, %s\n", err)
				} else {
					out, err := vm.Eval(script)
					switch {
					case err != nil:
						fmt.Printf("Runtime error, %s\n", err)
					default:
						if xlsx2json.UseRepl == true {
							fmt.Println(out.String())
						}
					}
				}
			}
		}
		//FIXME: update output to reflect the contents of Spreadsheet from the JS VM
		if value, err := vm.Get("Spreadsheet"); err == nil {
			data, err := value.Export()
			if err != nil {
				log.Fatal(err)
			}
			src, err := json.Marshal(data)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("%s", src)
			os.Exit(0)
		} else {
			log.Fatal(err)
		}
	}
	fmt.Printf(`[%s]`, strings.Join(output, ","))
}
<|endoftext|>


<commit_before>package rest

import (
	"fmt"
	"text/template"

	"goa.design/goa.v2/codegen"
	"goa.design/goa.v2/codegen/files"
	"goa.design/goa.v2/design"
	"goa.design/goa.v2/design/rest"
)

type (
	// pathData contains the data necessary to render the path template.
	pathData struct {
		// ServiceName
		ServiceName string
		// EndpointName
		EndpointName string
		// Routes describes all the possible paths for an action
		Routes []*pathRoute
	}

	// pathRoute contains the data to render a path for a specific route.
	pathRoute struct {
		// Path is the fullpath converted to printf compatible layout
		Path string
		// PathParams are all the path parameters in this route
		PathParams []string
		// Arguments describe the arguments used in the route
		Arguments []*pathArgument
	}

	// pathArgument contains the name and data type of the path arguments
	pathArgument struct {
		// Name is the name of the argument variable
		Name string
		// Type describes the datatype of the argument
		Type design.DataType
	}

	// pathFile
	pathFile struct {
		sections []*codegen.Section
	}
)

var pathTmpl = template.Must(template.New("path").
	Funcs(template.FuncMap{
		"add":       codegen.Add,
		"goTypeRef": codegen.GoTypeRef,
		"goify":     codegen.Goify,
	}).
	Parse(pathT))

// PathFile returns the path file.
func PathFile(api *design.APIExpr, r *rest.RootExpr) codegen.File {
	title := fmt.Sprintf("%s HTTP request path constructors", api.Name)
	sections := []*codegen.Section{
		codegen.Header(title, "http", []*codegen.ImportSpec{
			{Path: "fmt"},
			{Path: "net/url"},
			{Path: "strconv"},
			{Path: "strings"},
		}),
	}

	for _, res := range r.Resources {
		for _, a := range res.Actions {
			sections = append(sections, Path(a))
		}
	}

	return &pathFile{sections}
}

// Path returns a path section for the specified action
func Path(a *rest.ActionExpr) *codegen.Section {
	return &codegen.Section{
		Template: pathTmpl,
		Data:     buildPathData(a),
	}
}

func (e *pathFile) Sections(_ string) []*codegen.Section {
	return e.sections
}

func (e *pathFile) OutputPath(reserved map[string]bool) string {
	return files.UniquePath("gen/transport/http/paths%d.go", reserved)
}

func buildPathData(a *rest.ActionExpr) *pathData {
	pd := pathData{
		ServiceName:  a.Service.Name,
		EndpointName: a.Name,
		Routes:       make([]*pathRoute, len(a.Routes)),
	}

	for i, r := range a.Routes {
		pd.Routes[i] = &pathRoute{
			Path:       rest.WildcardRegex.ReplaceAllString(r.FullPath(), "/%v"),
			PathParams: r.Params(),
			Arguments:  generatePathArguments(r),
		}
	}
	return &pd
}

func generatePathArguments(r *rest.RouteExpr) []*pathArgument {
	params := r.Params()
	obj := design.AsObject(r.Action.PathParams().Type)
	args := make([]*pathArgument, len(params))
	for i, name := range params {
		args[i] = &pathArgument{
			Name: name,
			Type: obj[name].Type,
		}
	}
	return args
}

const pathT = `{{range $i, $route := .Routes -}}
// {{$.EndpointName}}{{$.ServiceName}}Path{{if ne $i 0}}{{add $i 1}}{{end}} returns the URL path to the {{$.ServiceName}} service {{$.EndpointName}} HTTP endpoint.
func {{$.EndpointName}}{{$.ServiceName}}Path{{if ne $i 0}}{{add $i 1}}{{end}}({{template "arguments" .Arguments}}) string {
{{- if .Arguments}}
	{{template "slice_conversion" .Arguments -}}
	return fmt.Sprintf("{{ .Path }}"{{template "fmt_params" .Arguments}})
{{- else}}
	return "{{ .Path }}"
{{- end}}
}

{{end}}

{{- define "arguments" -}}
{{range $i, $arg := . -}}
{{if ne $i 0}}, {{end}}{{goify .Name false}} {{goTypeRef .Type}}
{{- end}}
{{- end}}

{{- define "fmt_params" -}}
{{range . -}}
, {{if eq .Type.Name "array"}}strings.Join(encoded{{goify .Name true}}, ","){{else}}{{goify .Name false}}{{end}}
{{- end}}
{{- end}}

{{- define "slice_conversion" -}}
{{range $i, $arg := .}}
	{{- if eq .Type.Name "array" -}}
	encoded{{goify .Name true}} := make([]string, len({{goify .Name false}}))
	for i, v := range {{goify .Name false}} {
		encoded{{goify .Name true}}[i] = {{if eq .Type.ElemType.Type.Name "string"}}url.QueryEscape(v)
	{{else if eq .Type.ElemType.Type.Name "int" "int32"}}strconv.FormatInt(int64(v), 10)
	{{else if eq .Type.ElemType.Type.Name "int64"}}strconv.FormatInt(v, 10)
	{{else if eq .Type.ElemType.Type.Name "uint" "uint32"}}strconv.FormatUint(uint64(v), 10)
	{{else if eq .Type.ElemType.Type.Name "uint64"}}strconv.FormatUint(v, 10)
	{{else if eq .Type.ElemType.Type.Name "float32"}}strconv.FormatFloat(float64(v), 'f', -1, 32)
	{{else if eq .Type.ElemType.Type.Name "float64"}}strconv.FormatFloat(v, 'f', -1, 64)
	{{else if eq .Type.ElemType.Type.Name "boolean"}}strconv.FormatBool(v)
	{{else}}url.QueryEscape(fmt.Sprintf("%v", v))
	{{end -}}
	}

	{{end}}
{{- end}}
{{- end}}`
<commit_msg>Make another pass at the paths file.<commit_after>package rest

import (
	"fmt"
	"text/template"

	"goa.design/goa.v2/codegen"
	"goa.design/goa.v2/codegen/files"
	"goa.design/goa.v2/design"
	"goa.design/goa.v2/design/rest"
)

type (
	// pathData contains the data necessary to render the path template.
	pathData struct {
		// ServiceName is the name of the service defined in the design.
		ServiceName string
		// EndpointName is the name of the endpoint defined in the design.
		EndpointName string
		// Routes describes all the possible paths for an action.
		Routes []*pathRoute
	}

	// pathRoute contains the data to render a path for a specific route.
	pathRoute struct {
		// Path is the fullpath converted to printf compatible layout.
		Path string
		// PathParams are all the path parameters in this route.
		PathParams []string
		// Arguments describe the arguments used in the route.
		Arguments []*pathArgument
	}

	// pathArgument contains the name and data type of the path arguments.
	pathArgument struct {
		// Name is the name of the argument variable.
		Name string
		// Type describes the datatype of the argument.
		Type design.DataType
	}

	// pathFile is the codegen file that generates the path constructors.
	pathFile struct {
		sections []*codegen.Section
	}
)

var pathTmpl = template.Must(template.New("path").
	Funcs(template.FuncMap{
		"add":       codegen.Add,
		"goTypeRef": codegen.GoTypeRef,
		"goify":     codegen.Goify,
		"isArray":   design.IsArray,
	}).
	Parse(pathT))

// PathFile returns the path file.
func PathFile(api *design.APIExpr, r *rest.RootExpr) codegen.File {
	title := fmt.Sprintf("%s HTTP request path constructors", api.Name)
	sections := []*codegen.Section{
		codegen.Header(title, "http", []*codegen.ImportSpec{
			{Path: "fmt"},
			{Path: "net/url"},
			{Path: "strconv"},
			{Path: "strings"},
		}),
	}

	for _, res := range r.Resources {
		for _, a := range res.Actions {
			sections = append(sections, Path(a))
		}
	}

	return &pathFile{sections}
}

// Path returns a path section for the specified action
func Path(a *rest.ActionExpr) *codegen.Section {
	return &codegen.Section{
		Template: pathTmpl,
		Data:     buildPathData(a),
	}
}

func (e *pathFile) Sections(_ string) []*codegen.Section {
	return e.sections
}

func (e *pathFile) OutputPath(reserved map[string]bool) string {
	return files.UniquePath("gen/transport/http/paths%d.go", reserved)
}

func buildPathData(a *rest.ActionExpr) *pathData {
	pd := pathData{
		ServiceName:  a.Service.Name,
		EndpointName: a.Name,
		Routes:       make([]*pathRoute, len(a.Routes)),
	}

	for i, r := range a.Routes {
		pd.Routes[i] = &pathRoute{
			Path:       rest.WildcardRegex.ReplaceAllString(r.FullPath(), "/%v"),
			PathParams: r.Params(),
			Arguments:  generatePathArguments(r),
		}
	}
	return &pd
}

func generatePathArguments(r *rest.RouteExpr) []*pathArgument {
	params := r.Params()
	obj := design.AsObject(r.Action.PathParams().Type)
	args := make([]*pathArgument, len(params))
	for i, name := range params {
		args[i] = &pathArgument{
			Name: name,
			Type: obj[name].Type,
		}
	}
	return args
}

const pathT = `{{ range $i, $route := .Routes -}}
// {{ goify $.EndpointName true }}{{ goify $.ServiceName true }}Path{{ if ne $i 0 }}{{ add $i 1 }}{{ end }} returns the URL path to the {{ $.ServiceName }} service {{ $.EndpointName }} HTTP endpoint.
func {{ goify $.EndpointName true }}{{ goify $.ServiceName true }}Path{{ if ne $i 0 }}{{ add $i 1 }}{{ end }}({{ template "arguments" .Arguments }}) string {
{{- if .Arguments }}
	{{ template "slice_conversion" .Arguments -}}
	return fmt.Sprintf("{{ .Path }}"{{ template "fmt_params" .Arguments }})
{{- else }}
	return "{{  .Path  }}"
{{- end }}
}

{{ end }}

{{- define "arguments" -}}
{{ range $i, $arg := . -}}
{{ if ne $i 0 }}, {{ end }}{{ goify .Name false }} {{ goTypeRef .Type }}
{{- end }}
{{- end }}

{{- define "fmt_params" -}}
{{ range . -}}
, {{ if isArray .Type }}strings.Join(encoded{{ goify .Name true }}, ",")
  {{- else }}{{ goify .Name false }}{{ end }}
{{- end }}
{{- end }}

{{- define "slice_conversion" -}}
{{ range $i, $arg := . }}
	{{- if isArray .Type -}}
	encoded{{ goify .Name true }} := make([]string, len({{ goify .Name false }}))
	{{- $elemType := .Type.ElemType.Type.Name }}
	for i, v := range {{ goify .Name false }} {
		encoded{{ goify .Name true }}[i] = {{ if eq $elemType "string" }}url.QueryEscape(v)
	{{ else if eq $elemType "int" "int32"   }}strconv.FormatInt(int64(v), 10)
	{{ else if eq $elemType "int64"         }}strconv.FormatInt(v, 10)
	{{ else if eq $elemType "uint" "uint32" }}strconv.FormatUint(uint64(v), 10)
	{{ else if eq $elemType "uint64"        }}strconv.FormatUint(v, 10)
	{{ else if eq $elemType "float32"       }}strconv.FormatFloat(float64(v), 'f', -1, 32)
	{{ else if eq $elemType "float64"       }}strconv.FormatFloat(v, 'f', -1, 64)
	{{ else if eq $elemType "boolean"       }}strconv.FormatBool(v)
	{{ else }}url.QueryEscape(fmt.Sprintf("%v", v))
	{{ end -}}
	}

	{{ end }}
{{- end }}
{{- end }}`
<|endoftext|>