func FromURL(urlstr string, cache bool) (chklst Checklist, err error) {
	log.Debug("Creating/checking remote checklist dir")
	if err := os.MkdirAll(remoteCheckDir, 0775); err != nil {
		log.WithFields(log.Fields{
			"dir":   remoteCheckDir,
			"error": err.Error(),
		}).Warn("Could not create remote check directory")
		remoteCheckDir = "./.remote-checks"
		if err := os.MkdirAll(remoteCheckDir, 0755); err != nil {
			errutil.CouldntWriteError(remoteCheckDir, err)
		}
	}
	log.Debug("Using " + remoteCheckDir + " for remote check storage")
	pathSanitize := func(str string) (filename string) {
		filename = str
		disallowed := []string{
			`/`, `?`, `%`, `*`, `:`, `|`, `"`, `<`, `^`, `>`, `.`, `\`, ` `,
		}
		for _, c := range disallowed {
			filename = strings.Replace(filename, c, "", -1)
		}
		return filename
	}
	filename := pathSanitize(urlstr) + ".yaml"
	fullpath := filepath.Join(remoteCheckDir, filename)
	// only create it if it doesn't exist or we aren't using the cached copy
	if _, err := os.Stat(fullpath); err != nil || !cache {
		log.Info("Fetching remote checklist")
		body := chkutil.URLToBytes(urlstr, true) // secure connection
		log.Debug("Writing remote checklist to cache")
		chkutil.BytesToFile(body, fullpath)
		return FromBytes(body)
	}
	log.WithFields(log.Fields{
		"path": fullpath,
	}).Info("Using local copy of remote checklist")
	return FromFile(fullpath)
}
FromURL reads data retrieved from the URL and parses its utf8
encoded yaml data, turning it into a checklist struct. It also optionally
caches this data at remoteCheckDir, currently "/var/run/distributive/".
package checklists
import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"github.com/CiscoCloud/distributive/chkutil"
	"github.com/CiscoCloud/distributive/errutil"
	log "github.com/Sirupsen/logrus"
	"github.com/ghodss/yaml"
)
var remoteCheckDir = "/var/run/distributive/"
type Checklist struct {
	Name   string
	Checks []*CheckWrapper  
	Origin string           
}
func (chklst *Checklist) MakeReport() (anyFailed bool, report string) {
	if chklst == nil {  
		log.Warn("Nil checklist passed to makeReport. Please report this bug.")
		return
	}
	log.Debug("Making report for " + chklst.Name)
	codes := make(chan int)
	msgs := make(chan string)
	for _, chk := range chklst.Checks {
		log.Info("Running check " + chk.ID())
		go func(chk *CheckWrapper, codes chan int, msgs chan string) {
			log.Debug("Running check " + chk.ID())
			code, msg, err := chk.Status()
			if err != nil {
				log.WithFields(log.Fields{
					"ID":    chk.ID(),
					"error": err.Error(),
				}).Warn("There was an error running a check")
			}
			codes <- code
			msgs <- msg
		}(chk, codes, msgs)
	}
	total := len(chklst.Checks)
	passed := 0
	failed := 0
	other := 0
	for _ = range chklst.Checks {
		code := <-codes
		switch code {
		case 0:
			passed++
		case 1:
			failed++
		default:
			other++
		}
	}
	close(codes)
	report += "↴\nTotal: " + fmt.Sprint(total)
	report += "\nPassed: " + fmt.Sprint(passed)
	report += "\nFailed: " + fmt.Sprint(failed)
	report += "\nOther: " + fmt.Sprint(other)
	for _ = range chklst.Checks {
		if msg := <-msgs; msg != "" {
			report += "\n" + msg
		}
	}
	close(msgs)
	return (failed > 0), report
}
type CheckYAML struct {
	ID string `json:"id"`
	Parameters []string `json:"parameters"`
}
type ChecklistYAML struct {
	Name      string      `json:"name"`
	Checklist []CheckYAML `json"checklist"`
}
func FromBytes(data []byte) (chklst Checklist, err error) {
	var chklstYAML ChecklistYAML
	err = yaml.Unmarshal(data, &chklstYAML)
	if err != nil {
		return chklst, err
	}
	chklst.Name = chklstYAML.Name
	for _, chkYAML := range chklstYAML.Checklist {
		chkStruct := constructCheck(chkYAML)
		if chkStruct == nil {
			log.WithFields(log.Fields{
				"check":     chkYAML.ID,
				"checklist": chklst.Name,
				"params":    chkYAML.Parameters,
			}).Fatal("Unable to parse check")
		}
		_, err := chkStruct.New(chkYAML.Parameters)
		if err != nil {
			log.WithFields(log.Fields{
				"check":  chkYAML.ID,
				"params": chkYAML.Parameters,
				"error":  err.Error(),
			}).Fatal("Error while constructing check")
		}
		chklst.Checks = append(chklst.Checks, chkStruct)
	}
	if len(chklst.Checks) < 1 {
		log.WithFields(log.Fields{
			"checklist": chklst.Name,
		}).Fatal("Checklist had no checks associated with it!")
	}
	return chklst, nil
}
func FromFile(path string) (chklst Checklist, err error) {
	log.Debugf("Creating checklist from %s", path)
	return FromBytes(chkutil.FileToBytes(path))
}
func FromStdin() (chklst Checklist, err error) {
	stdinAsBytes := func() (data []byte) {
		data, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			log.WithFields(log.Fields{
				"error": err.Error(),
			}).Fatal("Couldn't read from stdin")
		} else if len(data) < 1 {
			log.Fatal("Stdin was empty!")
		}
		return data
	}
	log.Debug("Creating checklist from stdin")
	return FromBytes(stdinAsBytes())
}
func FromDirectory(dirpath string) (chklsts []Checklist, err error) {
	log.Debug("Creating checklist(s) from " + dirpath)
	paths := chkutil.GetFilesWithExtension(dirpath, ".yaml")
	paths = append(paths, chkutil.GetFilesWithExtension(dirpath, ".yml")...)
	paths = append(paths, chkutil.GetFilesWithExtension(dirpath, ".json")...)
	for _, path := range paths {
		chklst, err := FromFile(path)
		if err != nil {
			return chklsts, err
		}
		chklsts = append(chklsts, chklst)
	}
	return chklsts, nil
}