Skip to content
This repository has been archived by the owner on Mar 6, 2024. It is now read-only.

Optional steps #4

Merged
merged 4 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 71 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/tomakado/projector.svg)](https://pkg.go.dev/github.com/tomakado/projector)
[![Go Report Card](https://goreportcard.com/badge/github.com/tomakado/projector)](https://goreportcard.com/report/github.com/tomakado/projector)
[![Coverage Status](https://coveralls.io/repos/github/tomakado/projector/badge.svg?branch=master)](https://coveralls.io/github/tomakado/projector?branch=master)
![License](https://img.shields.io/badge/license-MIT-green)


<img src="doc/logo.png" align="right" />

Expand All @@ -10,12 +7,20 @@ Projector has some builtin templates, but you also can use your custom templates

Currently Projector is in active development stage, therefore breaking changes may occur.

# Features

* Single binary, no extra dependencies
* Builtin templates that allow you to start quickly
* Simple template manifest format
* Custom templates
* Optional steps for flexibility
---
<div align="center">

[![Go Reference](https://pkg.go.dev/badge/github.com/tomakado/projector.svg)](https://pkg.go.dev/github.com/tomakado/projector)
[![Go Report Card](https://goreportcard.com/badge/github.com/tomakado/projector)](https://goreportcard.com/report/github.com/tomakado/projector)
[![Coverage Status](https://coveralls.io/repos/github/tomakado/projector/badge.svg?branch=master)](https://coveralls.io/github/tomakado/projector?branch=master)
![License](https://img.shields.io/badge/license-MIT-green)

</div>

# Installation

Expand All @@ -27,11 +32,10 @@ There are two ways to get Projector right now:

# Usage

## Projector CLI
Get general usage help with `-h` or `--help` flags:
```
❯ projector -h
A flexible, language and framework agnostic tool that allows you to generate projects from templates.
❯ projector -h
A flexible, language and framework agnostic tool that allows you to generate projects from templates.
Projector has some builtin templates, but you can use your custom templates or import third-party templates
from GitHub.

Expand All @@ -43,6 +47,7 @@ Available Commands:
create Create project using specified template
help Help about any command
info Show meta information about template
init Create template manifest in current directory (like `create projector` command)
list List builtin and cached templates
validate Validate manifest without performing actions (dry run)

Expand All @@ -53,6 +58,7 @@ Flags:
Use "projector [command] --help" for more information about a command.
```

## Create project
Create project with `create` command:
```
projector create go/hello-world --author "tomakado <hi@ildarkarymov.ru>" && \
Expand All @@ -62,13 +68,34 @@ projector create go/hello-world --author "tomakado <hi@ildarkarymov.ru>" && \
```
where `go/hello-world` is template you want to use. You also can use custom manifest file by passing it with `--manifest` or `-m` flags.

## Optional steps
Template may have optional steps omitted by default. Use `-i` or `--i` flags to include them:
```
projector create go/hello-world --author "tomakado <hi@ildarkarymov.ru>" && \
--name "my-awesome-app" && \
--package "github.com/tomakado/go-helloworld" && \
--include=makefile,dockerfile
./hello-world/
```

Use `--all` flag to include all optional steps:
```
projector create go/hello-world --author "tomakado <hi@ildarkarymov.ru>" && \
--name "my-awesome-app" && \
--package "github.com/tomakado/go-helloworld" && \
--all
./hello-world/
```

## Listing available templates
List all available locally templates with `projector list`:
```
❯ projector list
go/hello-world
go/http
```

## Getting info about template
Get a bit more info about concrete template with `projector info [template]`:
```
❯ projector info go/hello-world
Expand All @@ -77,12 +104,14 @@ URL: https://github.com/tomakado/projector
Description: Basic program to get started with Go
```

## Template validation
Validate custom manifest file with `projector validate --manifest=[path-to-custom-manifest-file]`:
```
❯ projector validate -m projector.toml
Manifest is valid ✅
```

## Template debugging
Debug your template with `--verbose` flag:
```
❯ projector create go/hello-world -a "tomakado <hi@ildarkarymov.ru>" -n "my-awesome-app" -p "github.com/tomakado/go-helloworld" ./hello-world --verbose
Expand Down Expand Up @@ -126,6 +155,31 @@ Debug your template with `--verbose` flag:
2022/02/19 18:22:32 writing rendered file to "main.go"
```

## Сustom template

You can just create file with name `projector.toml` and start filling, but Projector has template for... templates:
```
projector create projector --name="projector-demo" ./projector-demo
```

that generates file with following content:
```toml
name="projector-demo"
author="tomakado"
version="snapshot"
url="https://github.com/tomakado/projector-demo"
description="Enter your template description here"

[[steps]]
name="hello world"
shell="echo \"hello, world!\""
```

Also there is shortcut for it (you have to be inside target directory):
```
projector init
```

## Manifest

Manifest is a file that describes how Projector should act to generate project. Projector uses [TOML](https://toml.io) format to store manifest on disk.
Expand Down Expand Up @@ -168,11 +222,12 @@ The first, top-level section containing fields `name`, `author`, `version`, `url
#### `step`
_Step_ is self-sufficient action performed by Projector to generate project. Projector “executes” steps sequentially, one by one. Inside `shell` field `text/template` syntax is supported, so you can use values exposed to [Template Context](#template-context) inside shell script.

| Field | Description |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Name of step. Required. |
| `files` | Array of files to generate. See [`file`](#file) for more info. Required if `shell` is not set. |
| `shell` | Shell script to execute. `text/template` supported (see [Template Context](#template-context) for more info). Required if `files` is not set. |
| Field | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Name of step. Required. |
| `optional` | Defines if step is optional. If `true` step will be omitted if not included via `-i` flag. Optional. Default: `false`. |
| `files` | Array of files to generate. See [`file`](#file) for more info. Required if `shell` is not set. |
| `shell` | Shell script to execute. `text/template` supported (see [Template Context](#template-context) for more info). Required if `files` is not set. |

#### `file`
_File_ in terms of Projector manifest is something like task of following kind:
Expand All @@ -195,12 +250,13 @@ _File_ in terms of Projector manifest is something like task of following kind:
| `ProjectName` | Name of project. |
| `ProjectPackage` | Package name for project. E.g. in Go it would something like `github.com/owner/module`. |
| `Manifest` | Reference to manifest. See [Manifest](#manifest) for info. |
| `OptionalSteps` | Slice of optional step names. |

# Backlog

There are some features I'd like to implement in Projector:

- [ ] Use multiple templates to generate single project (e.g., for having separate templates for Dockerfile, frontend, “service” level, etc.)
- [ ] Optional steps
- [ ] Nice animated output of current step
- [ ] User-friendly error messages
- [ ] Import third-party templates from GitHub
Expand Down
14 changes: 9 additions & 5 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ var (
Args: cobra.MinimumNArgs(1),
RunE: runCreate,
}
cfg projector.Config
pathToManifest string
cfg projector.Config
pathToManifest string
includeAllSteps bool
)

func init() {
Expand All @@ -31,6 +32,8 @@ func init() {
)
createCmd.Flags().StringVarP(&cfg.ProjectAuthor, "author", "a", "", "project author (default current OS user)")
createCmd.Flags().StringVarP(&pathToManifest, "manifest", "m", "", "path to custom template manifest")
createCmd.Flags().StringSliceVarP(&cfg.OptionalSteps, "include", "i", []string{}, "optional steps to include")
createCmd.Flags().BoolVar(&includeAllSteps, "all", false, "include all optional steps (overrides --include)")
}

func runCreate(_ *cobra.Command, args []string) error {
Expand All @@ -50,9 +53,10 @@ func runCreate(_ *cobra.Command, args []string) error {

return projector.Create(
projector.CreateConfig{
Config: &cfg,
Provider: p,
PathToManifest: pathToManifest,
Config: &cfg,
Provider: p,
PathToManifest: pathToManifest,
IncludeAllSteps: includeAllSteps,
},
)
}
3 changes: 3 additions & 0 deletions cmd/resources/templates/go/http/Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
lint:
golangci-lint run ./...

run:
go run ./cmd/
35 changes: 23 additions & 12 deletions cmd/resources/templates/go/http/projector.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,32 @@ version="1.0.0"
url="https://github.com/tomakado/projector"

[[steps]]
name="init go module and git repository"
name="init"
shell="go mod init {{ .ProjectPackage }} && git init"
[[steps.files]]
path="gitignore"
output=".gitignore"
[[steps.files]]
path="gitignore"
output=".gitignore"

[[steps]]
name="install chi router"
name="chi"
shell="go get github.com/go-chi/chi/v5"
[[steps.files]]
path="cmd/main_chi.go.tpl"
output="cmd/main.go"

[[steps]]
name="create basic http server"
[[steps.files]]
path="cmd/main.go.tpl"
output="cmd/main.go"
[[steps.files]]
path="Makefile"
output="Makefile"
name="linter"
optional=true
shell="go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"

[[steps]]
name="goreleaser"
optional=true
shell="go install github.com/goreleaser/goreleaser@latest && goreleaser init"

[[steps]]
name="makefile"
optional=true
[[steps.files]]
path="Makefile"
output="Makefile"
1 change: 1 addition & 0 deletions pkg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ type Config struct {
ProjectName string
ProjectPackage string
Manifest *manifest.Manifest
OptionalSteps []string
ManifestPath string
}
18 changes: 15 additions & 3 deletions pkg/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
)

type CreateConfig struct {
Config *Config
Provider provider
PathToManifest string
Config *Config
Provider provider
IncludeAllSteps bool
PathToManifest string
}

func Create(cfg CreateConfig) error {
Expand Down Expand Up @@ -47,5 +48,16 @@ func Create(cfg CreateConfig) error {
)
}

if cfg.IncludeAllSteps {
var optionalSteps []string
for _, step := range cfg.Config.Manifest.Steps {
if step.IsOptional {
stepToAppend := step.Name
optionalSteps = append(optionalSteps, stepToAppend)
}
}
cfg.Config.OptionalSteps = optionalSteps
}

return Generate(cfg.Config, cfg.Provider)
}
36 changes: 34 additions & 2 deletions pkg/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,25 @@ func Generate(config *Config, provider provider) error {
type Generator struct {
config *Config
provider provider

// optionalSteps is string set with included optional step names
optionalSteps map[string]struct{}
}

func NewGenerator(config *Config, provider provider) *Generator {
return &Generator{
config: config,
provider: provider,
config: config,
provider: provider,
optionalSteps: map[string]struct{}{},
}
}

// Generate traverses steps in project template manifest and performs actions defined inside each of them.
func (g *Generator) Generate() error {
if err := g.makeOptionalStepSet(g.config.OptionalSteps); err != nil {
return fmt.Errorf("makeOptionalStepSet: %w", err)
}

verbose.Printf("initializing working directory %q", g.config.WorkingDirectory)
if err := os.MkdirAll(g.config.WorkingDirectory, os.ModePerm); err != nil {
return fmt.Errorf("failed to mkdir %q: %w", g.config.WorkingDirectory, err)
Expand All @@ -54,6 +62,14 @@ func (g *Generator) Generate() error {
for i, step := range g.config.Manifest.Steps {
verbose.Printf("step %q, %d of %d", step.Name, (i + 1), len(g.config.Manifest.Steps))

if step.IsOptional {
if _, ok := g.optionalSteps[step.Name]; !ok {
verbose.Printf("step %q is optional and not included to config, skipping", step.Name)
continue
}
verbose.Printf("step %q is optional but included to config", step.Name)
}

if step.Files != nil {
if err := g.ProcessFiles(step.Files); err != nil {
return fmt.Errorf("[step %q] generate files: %w", step.Name, err)
Expand Down Expand Up @@ -178,3 +194,19 @@ func (g *Generator) RunShell(rawSh string) error {

return nil
}

func (g *Generator) makeOptionalStepSet(steps []string) error {
verbose.Printf("resolving optional steps: %v", steps)
for _, stepName := range steps {
if _, err := g.config.Manifest.Steps.Get(stepName); err != nil {
return fmt.Errorf("getStepByName: %w", err)
}

if _, ok := g.optionalSteps[stepName]; !ok {
g.optionalSteps[stepName] = struct{}{}
}
}

verbose.Printf("built optional step set: %v", g.optionalSteps)
return nil
}
Loading