diff --git a/.gitignore b/.gitignore index a1338d6..575efa5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ +vendor/ diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..e994db8 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,9 @@ +# Credits + +## Development Lead + +- Elf M. Sternberg [elfsternberg](https://github.com/elfsternberg) + +## Contributors + +None yet. Why not be the first? \ No newline at end of file diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md new file mode 100644 index 0000000..558af4a --- /dev/null +++ b/CODE-OF-CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..019cc1e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# Contributing + +Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. + +You can contribute in many ways: + +## Types of Contributions + +### Report Bugs + +Report bugs at https://github.com/elfsternberg/formulaic/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +### Fix Bugs + +Look through the GitHub issues for bugs. Anything tagged with "bug" +is open to whoever wants to implement it. + +### Implement Features + +Look through the GitHub issues for features. Anything tagged with "feature" +is open to whoever wants to implement it. + +### Write Documentation + +formulaic could always use more documentation, whether as part of the +official formulaic docs, in docstrings, or even on the web in blog posts, +articles, and such. + +### Submit Feedback + +The best way to send feedback is to file an issue at https://github.com/elfsternberg/formulaic/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +## Get Started! + +Ready to contribute? Here's how to set up `formulaic` for local development. + +1. Fork the `formulaic` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/formulaic.git + +3. Create a branch for local development:: + + $ git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +4. When you're done making changes, check that your changes pass the tests:: + + $ make test + +6. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +7. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.md. \ No newline at end of file diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..e0c33e1 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,113 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/fsnotify/fsnotify" + packages = ["."] + revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" + version = "v1.4.7" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/hcl" + packages = [ + ".", + "hcl/ast", + "hcl/parser", + "hcl/scanner", + "hcl/strconv", + "hcl/token", + "json/parser", + "json/scanner", + "json/token" + ] + revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8" + +[[projects]] + name = "github.com/magiconair/properties" + packages = ["."] + revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" + version = "v1.7.6" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/mapstructure" + packages = ["."] + revision = "00c29f56e2386353d58c599509e8dc3801b0d716" + +[[projects]] + name = "github.com/pelletier/go-toml" + packages = ["."] + revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" + version = "v1.1.0" + +[[projects]] + name = "github.com/spf13/afero" + packages = [ + ".", + "mem" + ] + revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c" + version = "v1.0.2" + +[[projects]] + name = "github.com/spf13/cast" + packages = ["."] + revision = "8965335b8c7107321228e3e3702cab9832751bac" + version = "v1.2.0" + +[[projects]] + branch = "master" + name = "github.com/spf13/jwalterweatherman" + packages = ["."] + revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" + +[[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" + version = "v1.0.0" + +[[projects]] + name = "github.com/spf13/viper" + packages = ["."] + revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" + version = "v1.0.0" + +[[projects]] + name = "github.com/urfave/cli" + packages = ["."] + revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" + version = "v1.20.0" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "f6cff0780e542efa0c8e864dc8fa522808f6a598" + +[[projects]] + name = "golang.org/x/text" + packages = [ + "internal/gen", + "internal/triegen", + "internal/ucd", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[[projects]] + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5" + version = "v2.1.1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "d8cfff552a8b5944b1ef528b7188d2f8c501391574026859edcab1406ff3ba63" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..6e6e601 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,43 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/spf13/viper" + version = "1.0.0" + +[[constraint]] + name = "github.com/gobwas/glob" + +[[constraint]] + name = "github.com/urfave/cli" + +[[constraint]] + name = "github.com/Masterminds/vcs" + +[prune] + go-tests = true + unused-packages = true diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..21b0e10 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +BIN_NAME=formulaic + +VERSION := $(shell grep "const Version " version.go | sed -E 's/.*"(.+)"$$/\1/') +GIT_COMMIT=$(shell git rev-parse HEAD) +GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) +IMAGE_NAME := "elfsternberg/formulaic" + +.PHONY default +default: help + +.PHONY build +build: ### Compile the formulaic binary + @echo "building ${BIN_NAME} ${VERSION}" + @echo "GOPATH=${GOPATH}" + go build -ldflags "-X main.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X main.VersionPrerelease=DEV" -o bin/${BIN_NAME} + +.PHONY test +test: ### Run the unit tests + go test $(glide nv) + +.PHONY get-dep +get-deps: ### Install the dependencies needed to build the project + glide install + +.PHONY clean +clean: ### Clean the directory tree + @test ! -e bin/${BIN_NAME} || rm bin/${BIN_NAME} + +.PHONY help +help: ### Display this help message + @echo 'Management commands for formulaic:' + @echo + @echo 'Usage:' + @perl -nl scripts/show-help.pl $(MAKEFILE_LIST) + + diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..76ee8b5 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,25 @@ +As this is my third Go project and remarkably like my git-linter project, I've +decided to try and keep the same sort of documentation path I chose for that +project. + +# Strategies + +formulaic uses a variety of strategies that it passes through to understand +what to do. + +1. Parse the command line. + +2. Read in the configuration file, in the following order (so later +keys override earlier keys): + + a. From formulaic.[toml|json|yaml] + b. From $HOME/.formulaic/config.[toml|json|yaml] + c. As specified as a filename on the command line + d. As specified as key-value pairs on the command line. + +3. Find or pull down the requested template. + +4. Unless otherwise specified, make queries as specified in the formulaic + configuration. + +5. Generate the project diff --git a/README.md b/README.md new file mode 100644 index 0000000..93a67d1 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# formulaic + +A Cookiecutter Clone in Go + +## Getting started + +This project requires Go to be installed. On OS X with Homebrew you can just run `brew install go`. + +Running it then should be as simple as: + +```console +$ make +$ ./bin/formulaic +``` + +### Testing + +``make test`` \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a0e0a78 --- /dev/null +++ b/TODO.md @@ -0,0 +1,16 @@ +* Re-arrange to use the command feature. +* Add the --update and --nokeep options to fetch +* Global configuration file +* Command: 'list' - List the existing local templates +** Option: '-r' - List remote templates. (Make this, DO NOT ENABLE) +* Command: 'fetch' - Get a template, do not run it. +* Feature: DESCRIPTION.md in the template root for the list option +* URL Hash as a replay target, to avoid trying to encode the URL as path +* See if the Go template language has date handlers, figure out how to + parse that. +* Does Viper return TOML, JSON, and YAML files in order? Consider + Progressive Parsing of configuration (earlier keys with hard values can + be substituted for later keys with templated values.) +* Cascading rules for applying keys and values to templates +* Add TGZ, tar.bz, tar.xz, zip to list of capabilities. +* Figure out your list of commands, including the default. diff --git a/commands/commands.go b/commands/commands.go new file mode 100644 index 0000000..fa41c48 --- /dev/null +++ b/commands/commands.go @@ -0,0 +1,48 @@ +package commands + +func listLocalTemplates(spath *string) ([]string, error) { + templatePath := filepath.Join(spath, "templates") + templateStat, err := os.Stat(templatePath) + + if os.IsNotExist(err) { + os.Mkdir(templatePath, 0755) + return [] + } + + if err != nil { + return nil, cliNewExitError(err, 1) + } + + + + + + + + + + +func List(spath *string) error { + + + path, err := filepath.Abs(spath) + if err != nil { + return nil, cli.NewExitError(fmt.Sprintf("%s: not a valid path", path), 1) + } + + pathstats, err := os.Stat(path) + if os.IsNotExist(err) { + os.Mkdir(path) + return path + } + + if error != nil { + return nil, cli.NewExitError(fmt.Sprintf("%s: not a valid path", path), 1) + } + + if !pathstats.IsDir() { + return nil, cli.NewExitError(fmt.Sprintf("%s: not a directory", path), 1) + } + path, nil +} + diff --git a/commands/template.go b/commands/template.go new file mode 100644 index 0000000..4bf4579 --- /dev/null +++ b/commands/template.go @@ -0,0 +1,8 @@ +package commands + +import ( + "github.com/Masterminds/vcs" +) + +func getTemplate(url *string) (*string, error) { + diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..04fd027 --- /dev/null +++ b/config/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "time" + + "github.com/spf13/viper" +) + +// Provider defines a set of read-only methods for accessing the application +// configuration params as defined in one of the config files. +type Provider interface { + ConfigFileUsed() string + Get(key string) interface{} + GetBool(key string) bool + GetDuration(key string) time.Duration + GetFloat64(key string) float64 + GetInt(key string) int + GetInt64(key string) int64 + GetSizeInBytes(key string) uint + GetString(key string) string + GetStringMap(key string) map[string]interface{} + GetStringMapString(key string) map[string]string + GetStringMapStringSlice(key string) map[string][]string + GetStringSlice(key string) []string + GetTime(key string) time.Time + InConfig(key string) bool + IsSet(key string) bool +} + +var defaultConfig *viper.Viper + +func Config() Provider { + return defaultConfig +} + +func LoadConfigProvider(appName string) Provider { + return readViperConfig(appName) +} + +func init() { + defaultConfig = readViperConfig("FORMULAIC") +} + +func readViperConfig(appName string) *viper.Viper { + v := viper.New() + v.SetEnvPrefix(appName) + v.AutomaticEnv() + + // global defaults + + + return v +} diff --git a/formulaic b/formulaic new file mode 100755 index 0000000..5d73a63 Binary files /dev/null and b/formulaic differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..12bbd6e --- /dev/null +++ b/main.go @@ -0,0 +1,159 @@ +package main + +import ( + "fmt" + "github.com/urfave/cli" + "formulaic/commands" + "log" + "os" + "os/user" + "path/filepath" + "time" +) + +func formRoot() (*string, error) { + usr, err := user.Current() + if err != nil { + return nil, cli.NewExitError("Could not determine current user.", 1) + } + + rootpath, err := filepath.Abs(usr.HomeDir + "/.formulaic/") + if err != nil { + return nil, cli.NewExitError(err, 1) + } + + rootstat, err := os.Stat(rootpath) + if os.IsNotExist(err) { + os.Mkdir(rootpath, 0755) + return &rootpath, nil + } + + if !rootstat.IsDir() { + return nil, cli.NewExitError(fmt.Sprintf("%s: not a directory", rootpath), 1) + } + + return &rootpath, nil +} + + +func main() { + app := cli.NewApp() + root, err := formRoot() + + if err != nil { + log.Fatal(err) + } + + branchFlag := cli.StringFlag{ + Name: "checkout, b", + Usage: "branch, tag or commit to checkout", + } + + updateFlag := cli.BoolFlag{ + Name: "update, u", + Usage: "update the template local template if it is already present", + } + + app.Name = "formulaic" + app.Version = "0.2" + app.Compiled = time.Now() + app.Copyright = "Copyright © 2018 Elf M. Sternberg" + app.Usage = "generate a new project from a project template" + + app.Authors = []cli.Author{ + { + Name: "Elf M. Sternberg", + Email: "elf.sternberg@gmail.com", + }, + } + + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "verbose, V", + Usage: "print debugging information", + }, + } + + app.Commands = []cli.Command{ + { + Name: "build", + Aliases: []string{"b"}, + Usage: "build a new project from a formulaic template", + UsageText: "formulaic build [options] [template name or url] [key=value ...]", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "specify alternative formulaic config file", + Value: "formulaic.[toml|json|yaml]", + }, + cli.StringFlag{ + Name: "path, o", + Usage: "specify path to directory into which project will be written", + Value: ".", + }, + cli.BoolFlag{ + Name: "force, f", + Usage: "overwrite if output directory exists", + }, + updateFlag, + branchFlag, + cli.BoolFlag{ + Name: "noprompt, x", + Usage: "do not prompt for input, use the formulaic config file only", + }, + cli.BoolFlag{ + Name: "replay, r", + Usage: "use replay file, if any", + }, + }, + Action: func(c *cli.Context) error { + fmt.Printf("Build.") + return nil + }, + }, + { + Name: "list", + Aliases: []string{"l"}, + Usage: "list all formulaic templates in your cache, with optional filter", + UsageText: "formulaic list [options] [search expression]", + Flags: []cli.Flag{ + /* This is commented out because I know that + maintaining a template repository will never be a + priority for me. + */ + /* + cli.BoolFlag{ + Name: "remote, r", + Usage: "show remote templates from template repository", + }, + */ + cli.BoolFlag{ + Name: "long, l", + Usage: "show the long description, if available", + }, + cli.BoolFlag{ + Name: "prompts, p", + Usage: "show the template prompts, if available", + }, + }, + Action: func(c *cli.Context) error { + return commands.List(root) + }, + }, + { + Name: "fetch", + Aliases: []string{"f"}, + Usage: "fetch and store, but do not run, a template", + UsageText: "formulaic fetch [options] [template url]", + Flags: []cli.Flag{ + updateFlag, + }, + Action: func(c *cli.Context) error { + fmt.Printf("Fetch.") + return nil + }, + }, + } + + app.Run(os.Args) +} diff --git a/scripts/show-help.pl b/scripts/show-help.pl new file mode 100644 index 0000000..02143dc --- /dev/null +++ b/scripts/show-help.pl @@ -0,0 +1,3 @@ +#!/usr/bin/env perl -nl +# Extracts help entries and shows them. +m{^([\w\-]+\:).*?###(.*)$} && printf("make %-18s %s\n", $1, $2); diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..1b7bed5 --- /dev/null +++ b/version/version.go @@ -0,0 +1,12 @@ +package main + +// The git commit that was compiled. This will be filled in by the compiler. +var GitCommit string + +// The main version number that is being run at the moment. +const Version = "0.1.0" + +// A pre-release marker for the version. If this is "" (empty string) +// then it means that it is a final release. Otherwise, this is a pre-release +// such as "dev" (in development) +var VersionPrerelease = ""