Skip to content
Permalink
Browse files

'new' command and set flags

  • Loading branch information...
Depado committed Apr 15, 2019
1 parent 6801759 commit 01883dff4b6bc32a0394613af46624725937e322
Showing with 237 additions and 53 deletions.
  1. +99 −13 README.md
  2. +1 −0 cmd/flags.go
  3. +15 −37 cmd/new.go
  4. +1 −0 cmd/qk/main.go
  5. +35 −0 conf/input.go
  6. +15 −1 renderer/analyze.go
  7. +2 −2 renderer/render.go
  8. +11 −0 utils/colors.go
  9. +58 −0 utils/survey.go
112 README.md
@@ -113,7 +113,7 @@ Quokka supports various providers to download the templates. It supports
or using a local directory.

```
Quokka (qk) is a template engine that enables to render local or distant
Quokka (qk) is a template engine that enables to render local or distant
templates/boilerplates in a user friendly way. When given a URL/Git repository
or a path to a local Quokka template, quokka will ask for the required values
in an interactive way except if an inpute file is given to the CLI.
@@ -128,31 +128,82 @@ Available Commands:
version Show build and version
Flags:
-d, --debug debug mode
--debug Enable or disable debug mode
--git.depth int depth of git clone in case of git provider (default 1)
-h, --help help for qk
-i, --input string specify an input values file to automate template rendering
-k, --keep do not delete the template when operation is complete
-o, --output string specify the directory where the template should be downloaded or cloned
-p, --path string specify if the template is actually stored in a sub-directory of the downloaded file
-e, --set strings specify values on the command line
-y, --yes Automatically accept
Use "qk [command] --help" for more information about a command.
```

<!-- ## Commands
Some templates may define additional commands that will run once the template
has been rendered. If you wish to activate this behavior, you can pass the
`-c` or `--commands` flag. These commands can be anything, and may harm your
system so make sure you are ok with that. -->

## Keeping the template

When downloading or cloning a template, `quokka` will create a temporary
When downloading or cloning a template, Quokka will create a temporary
directory and delete it once the operation completes. If you want to keep
the template (to play with it, or simply to keep a copy), make sure you pass
the `--keep` option. This option pairs well with the `--output` option which
defines where the template should be downloaded/cloned.
the `-k/--keep` option. This option pairs well with the `-o/--output` option
which defines where the template should be downloaded/cloned.

## Input file

The rendering of a Quokka template can be automated if the template was designed
with this in mind and if an input file is provided on the command line.

Since there is no clear way for specifying overriding values (for example a
variable that applies to a single file and overrides an already existing
variable in the root config), the input values will also fill the overriding
variables.

The format of the input file is also yaml. The following example demonstrates
how an input file could be used:

`.quokka.yml`
```yaml
name: "Quokka Template"
description: "New Quokka Template"
version: "0.1.0"
variables:
slack:
confirm: true
prompt: "Add Slack integration?"
variables:
channel:
required: true
webhook:
required: true
```

`input.yml`
```yaml
slack: true
slack_channel: "#mychan"
slack_webhook: "complexurl
```
If this input file is given to Quokka, it won't prompt for these three
variables, thus requiring no input from the user to render the template.
## Set
Additionally, you can provide Quokka with the `-e/--set` flag (multiple time if
you wish). This works the same way as the input file but has a higher priority,
meaning that if you pass both an input file and a `-e` flag that defines a
variable, the one passed on the command line will have a higher priority.
The `--set` flags work by providing it with a `key=value` style kind of string.
If we take the example above using the input file, we can effectively replace
the `slack_channel` variable by doing so:
```sh
$ qk template/ output -i input.yml --set "slack_channel=#anotherchan"
$ # Or
$ qk template/ output -i input.yml -e "slack_channel=#anotherchan"
```

## Examples

@@ -162,11 +213,46 @@ $ qk git@github.com:Depado/quokka.git output --path _example/license
$ # Clone the template in a specific directory, render it in a specific directory and keep the template
$ qk git@github.com:Depado/quokka.git myamazingproject --path _example/cleanarch --keep --output "template"
$ # Reuse the downloaded template
$ quokka template/ myotherproject
$ qk template/ myotherproject
$ # Pass an input file to Quokka
$ qk template/ output -i in.yml
```

# Template Creation

## New command

If `quokka` is installed, simply run `quokka new <path>`. This will ask for
basic information such as the template name, description and version with some
sane defaults (version number for example is set to `0.1.0` by default).
You can also pass these values as flags on the command line.

This command will check if the output directory and a `.quokka.yml` file already
exist. This command is in charge of creating a new directory and creating the
initial `.quokka.yml` file with those basic information, helping you getting
started with Quokka template development.

<details><summary>Command Line Help</summary>

```
$ qk new --help
Create a new quokka template
Usage:
qk new [output] <options> [flags]
Flags:
-d, --description string description of the new template
-h, --help help for new
-n, --name string name of the new template
-v, --version string version of the new template
Global Flags:
--debug Enable or disable debug mode
-y, --yes Automatically accept
```
</details>

## The root `.quokka.yml` file

To configure your template, place a `.quokka.yml` at the root of your template.
@@ -17,6 +17,7 @@ func AddRendererFlags(c *cobra.Command) {
c.Flags().BoolP("keep", "k", false, "do not delete the template when operation is complete")
c.Flags().StringP("path", "p", "", "specify if the template is actually stored in a sub-directory of the downloaded file")
c.Flags().StringP("output", "o", "", "specify the directory where the template should be downloaded or cloned")
c.Flags().StringSliceP("set", "e", []string{}, "specify values on the command line")
// Git options
c.Flags().Int("git.depth", 1, "depth of git clone in case of git provider")

@@ -3,55 +3,33 @@ package cmd
import (
"fmt"
"os"
"path/filepath"

"gopkg.in/AlecAivazis/survey.v1"

_ "github.com/Depado/quokka/conf" // Import conf to ensure package init for survey
"github.com/Depado/quokka/utils"
)

func askIfNotString(in *string, name, message, def string, debug bool) {
var err error
if *in == "" {
if err = survey.AskOne(&survey.Input{
Message: message,
Default: def,
}, in, nil); err != nil {
utils.ErrPrintln("Canceled operation")
os.Exit(0)
}
} else if debug {
utils.OkPrintln(utils.Green.Sprint(name), "already filled:", *in)
}
}

// NewQuokkaTemplate will create a new Quokka template with default params
func NewQuokkaTemplate(path, name, description, version string, yes, debug bool) {
var err error
var fd *os.File

if _, err = os.Stat(path); !os.IsNotExist(err) {
if yes {
utils.OkPrintln("Output destination already exists but 'yes' option was used")
} else {
var confirmed bool
prompt := &survey.Confirm{
Message: "The output destination already exists. Continue ?",
}
survey.AskOne(prompt, &confirmed, nil) // nolint: errcheck
if !confirmed {
utils.ErrPrintln("Canceled operation")
os.Exit(0)
}
}
} else {
if !utils.ConfirmFileExists(path, true, yes, debug) {
if err = os.MkdirAll(path, os.ModePerm); err != nil {
utils.FatalPrintln("Unable to create directory")
}
}
qf := filepath.Join(path, ".quokka.yml")
utils.ConfirmFileExists(qf, false, yes, debug)

askIfNotString(&name, "name", "Template name?", "Quokka Template", debug)
askIfNotString(&description, "description", "Template description?", "New Quokka Template", debug)
askIfNotString(&version, "version", "Template version?", "0.1.0", debug)
utils.AskIfEmptyString(&name, "name", "Template name?", "Quokka Template", debug)
utils.AskIfEmptyString(&description, "description", "Template description?", "New Quokka Template", debug)
utils.AskIfEmptyString(&version, "version", "Template version?", "0.1.0", debug)

fmt.Printf("Name: %s\nDescription: %s\nVersion: %s\n", name, description, version)
if fd, err = os.Create(qf); err != nil {
utils.FatalPrintln("Unable to create file")
}
defer fd.Close()
if _, err = fd.WriteString(fmt.Sprintf("name: \"%s\"\ndescription: \"%s\"\nversion: \"%s\"\n", name, description, version)); err != nil {
utils.FatalPrintln("Unable to write in file")
}
}
@@ -37,6 +37,7 @@ var rootc = &cobra.Command{
viper.GetString("output"),
viper.GetString("path"),
viper.GetString("input"),
viper.GetStringSlice("set"),
viper.GetBool("keep"),
viper.GetInt("git.depth"),
viper.GetBool("yes"),
@@ -1,14 +1,35 @@
package conf

import (
"fmt"
"io/ioutil"
"strings"

"gopkg.in/yaml.v2"
)

// InputCtx is the input context
type InputCtx yaml.MapSlice

// MergeCtx will merge two InputCtx into a single one
func MergeCtx(a, b InputCtx) InputCtx {
out := a
for _, v := range b {
var found bool
for i, base := range out {
if base.Key == v.Key {
out[i].Value = v.Value
found = true
}
}
if !found {
out = append(out, v)
}
}

return out
}

// GetInputContext will return a map of string to interface{} that will then
// be used to determine whether or not a value from the root config file
// has already been filled
@@ -20,3 +41,17 @@ func GetInputContext(path string) (InputCtx, error) {
}
return out, yaml.Unmarshal(input, &out)
}

// GetSetContext will return the map of string to interface{} that contains the
// set flags passed on the command line parsed
func GetSetContext(set []string) (InputCtx, error) {
out := InputCtx{}
for _, s := range set {
tmp := strings.SplitN(s, "=", 2)
if len(tmp) != 2 {
return out, fmt.Errorf("invalid set option: %s", s)
}
out = append(out, yaml.MapItem{Key: tmp[0], Value: tmp[1]})
}
return out, nil
}
@@ -6,6 +6,7 @@ import (

"github.com/Depado/quokka/conf"
"github.com/Depado/quokka/utils"
"github.com/davecgh/go-spew/spew"
"github.com/fatih/color"
)

@@ -46,15 +47,28 @@ func HandleRootConfig(dir string, ctx conf.InputCtx) *conf.Root {
// Analyze is a work in progress function to analyze the template directory
// and gather information about where the configuration files are stored and to
// which templates they should apply.
func Analyze(dir, output, input string) {
func Analyze(dir, output, input string, set []string) {
var err error
var ctx conf.InputCtx

if input != "" {
if ctx, err = conf.GetInputContext(input); err != nil {
utils.FatalPrintln("Could not parse input file:", err)
}
utils.OkPrintln("Input file", utils.Green.Sprint(input), "found")
}
if set != nil {
setCtx, err := conf.GetSetContext(set)
if err != nil {
utils.FatalPrintln("Could not parse set flags:", err)
}
ctx = conf.MergeCtx(ctx, setCtx)
spew.Dump(ctx)
utils.OkPrintln("Command line set merged in context")
}

spew.Dump(ctx)

root := HandleRootConfig(dir, ctx)
var candidates []*conf.File

@@ -9,7 +9,7 @@ import (
)

// Render is the main render function
func Render(template, output, toutput, path, input string, keep bool, depth int, yes bool) {
func Render(template, output, toutput, path, input string, set []string, keep bool, depth int, yes bool) {
var err error
var tpath string

@@ -44,5 +44,5 @@ func Render(template, output, toutput, path, input string, keep bool, depth int,
utils.OkPrintln("Removed template", utils.Green.Sprint(path))
}(path)
}
Analyze(tpath, output, input)
Analyze(tpath, output, input, set)
}
@@ -11,6 +11,9 @@ import (
// Green is a simple green foreground color
var Green = color.New(color.FgGreen)

// Yellow is a simple yellow foreground color
var Yellow = color.New(color.FgYellow)

// OkPrefix is the prefix that should prefix output when everything is ok
var OkPrefix = Green.Sprint("»")

@@ -38,7 +41,15 @@ func ErrSprintln(opts ...interface{}) string {
}

// FatalPrintln prints out information with a red prefix and exits the program
// with an error status code
func FatalPrintln(opts ...interface{}) {
log.Println(append([]interface{}{ErrPrefix}, opts...)...)
os.Exit(1)
}

// ExitPrintln prints out information with a red prefix and exits the program
// with an acceptable status code
func ExitPrintln(opts ...interface{}) {
log.Println(append([]interface{}{ErrPrefix}, opts...)...)
os.Exit(0)
}

0 comments on commit 01883df

Please sign in to comment.
You can’t perform that action at this time.