func (f FileContents) Save(dir string, overwrite bool) error {
	if !overwrite {
		for name := range f {
			path := filepath.Join(dir, name)
			if _, err := os.Stat(path); err == nil {
				return errors.New(name + " already existed; save aborted")
			}
		}
	}
	for name := range f {
		path := filepath.Join(dir, name)
		dirsToCreate := filepath.Dir(path)
		if err := os.MkdirAll(dirsToCreate, os.ModePerm); err != nil {
			return errors.New("Couldn't create directories for " + path + ": " + err.Error())
		}
	}
	for name, contents := range f {
		path := filepath.Join(dir, name)
		if err := ioutil.WriteFile(path, contents, 0644); err != nil {
			return errors.New("Couldn't save " + path + ": " + err.Error())
		}
	}
	return nil
}
Save saves the given FileContents to the filesystem, creating any implied
directories. Dir is the prefix to join with each path in FileContents; "" is
fine. Will error if overwite is not true and any of the files to create
already exist.
package stub
import (
	"errors"
	"go/format"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)
type Options struct {
	Name string
	DisplayName string
	Description       string
	MinNumPlayers     int
	MaxNumPlayers     int
	DefaultNumPlayers int
	SuppressTest  bool
	SuppressPhase bool
	SuppressCurrentPlayer bool
	SuppressMovesStubs bool
	SuppressReflectedName               bool
	SuppressComponentsStubs             bool
	SuppressClientRenderGame            bool
	SuppressClientRenderPlayerInfo      bool
	EnableExampleDeck                   bool
	EnableExampleDynamicComponentValues bool
	EnableExampleEndState               bool
	EnableExampleComputedProperties     bool
	EnableExampleConstants              bool
	EnableExampleVariants               bool
	EnableExampleClient                 bool
	EnableExampleMoves                  bool
	EnableSeatPlayer                    bool
	EnableInactivePlayer                bool
}
type FileContents map[string][]byte
func (o *Options) SuppressClient() {
	o.SuppressClientRenderGame = true
	o.SuppressClientRenderPlayerInfo = true
}
func (o *Options) SuppressExtras() {
	o.SuppressTest = true
	o.SuppressPhase = true
	o.SuppressCurrentPlayer = true
	o.SuppressMovesStubs = true
	o.SuppressComponentsStubs = true
}
func (o *Options) EnableTutorials() {
	o.EnableExampleDeck = true
	o.EnableExampleDynamicComponentValues = true
	o.EnableExampleEndState = true
	o.EnableExampleComputedProperties = true
	o.EnableExampleConstants = true
	o.EnableExampleVariants = true
	o.EnableExampleClient = true
	o.EnableExampleMoves = true
	o.EnableSeatPlayer = true
	o.EnableInactivePlayer = true
}
func nameLegal(gameName string) bool {
	matches, err := regexp.Match("^[a-zA-Z0-9]*$", []byte(gameName))
	if err != nil {
		log.Println("Regexmp illegal: " + err.Error())
		return false
	}
	return matches
}
func (o *Options) Validate() error {
	o.Name = strings.TrimSpace(o.Name)
	o.Name = strings.ToLower(o.Name)
	if o.Name == "" {
		return errors.New("No name provided")
	}
	if !nameLegal(o.Name) {
		return errors.New("illegal characters in gamename. Must be a valid go package name")
	}
	if o.MinNumPlayers != 0 && o.MaxNumPlayers != 0 {
		if o.MaxNumPlayers < o.MinNumPlayers {
			return errors.New("Max num players less than min")
		}
		if o.DefaultNumPlayers != 0 {
			if o.DefaultNumPlayers < o.MinNumPlayers || o.DefaultNumPlayers > o.MaxNumPlayers {
				return errors.New("Default num players not within min/max range")
			}
		}
	}
	if o.EnableExampleDynamicComponentValues {
		o.EnableExampleDeck = true
	}
	if o.EnableExampleEndState {
		o.EnableExampleDeck = true
	}
	if o.EnableExampleComputedProperties {
		o.EnableExampleDeck = true
	}
	if o.EnableExampleConstants {
		o.EnableExampleDeck = true
	}
	if o.EnableExampleClient {
		o.EnableExampleDeck = true
	}
	if o.EnableExampleMoves {
		o.EnableExampleDeck = true
	}
	if o.EnableExampleMoves {
		o.SuppressPhase = false
	}
	if o.EnableExampleClient {
		o.SuppressClientRenderGame = false
		o.SuppressClientRenderPlayerInfo = false
	}
	if o.EnableExampleDeck {
		o.SuppressComponentsStubs = false
	}
	if o.EnableInactivePlayer {
		o.EnableSeatPlayer = true
	}
	if o.EnableSeatPlayer {
		o.SuppressPhase = false
	}
	return nil
}
var spaceReducer *regexp.Regexp
var titleCaseReplacer *strings.Replacer
func Generate(opt *Options) (FileContents, error) {
	if err := opt.Validate(); err != nil {
		return nil, errors.New("Options didn't validate: " + err.Error())
	}
	templates, err := DefaultTemplateSet(opt)
	if err != nil {
		return nil, errors.New("Default Template Set errored: " + err.Error())
	}
	if templates == nil {
		return nil, errors.New("No templates returned")
	}
	files, err := templates.Generate(opt)
	if err != nil {
		return nil, errors.New("Couldn't generate file contents: " + err.Error())
	}
	if err := files.Format(); err != nil {
		return nil, errors.New("Couldn't go fmt generated file contents: " + err.Error())
	}
	return files, nil
}
func (f FileContents) Format() error {
	newContent := make(map[string][]byte)
	for filename, rawSource := range f {
		if strings.ToLower(filepath.Ext(filename)) != ".go" {
			continue
		}
		transformedSource, err := format.Source(rawSource)
		if err != nil {
			return errors.New("Couldn't format go code for " + filename + ": " + err.Error())
		}
		newContent[filename] = transformedSource
	}
	for name, content := range newContent {
		f[name] = content
	}
	return nil
}