Skip to content

Commit 2dcca34

Browse files
committed
Add support for custom placeholders
Signed-off-by: Mathieu Frenette <silphid@users.noreply.github.com>
1 parent 4b28c02 commit 2dcca34

10 files changed

Lines changed: 71 additions & 34 deletions

File tree

.prettierrc.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
overrides:
2+
- files: "*.yaml"
3+
options:
4+
bracketSpacing: false

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -448,11 +448,18 @@ As the conditional for `if` steps is always a template expression, _do not_ encl
448448

449449
## Special placeholders
450450

451-
Because the PROJECT variable is typically used pervasively throughout templates in the form of `{{.PROJECT}}` and `{{.PROJECT | upper}}`, we have introduced the special placeholders `projekt` and `PROJEKT`, which can be used anywhere in file/dir names and templates without any adornments.
451+
Placeholders are a lightweight alternative to go template expressions, which can be used as plain text anywhere in file/dir names and template files. Because placeholders are processed using plain search-and-replace, ensure they have improbable names that don't risk conflicting with anything else (ie: "projekt").
452452

453-
For example, the text "MY PROJEKT FILE.TXT" is equivalent to "MY {{.PROJECT | upper}} FILE.TXT".
453+
For example, you can define the following placeholders in your template spec:
454454

455-
Currently, those two placeholders are hardcoded and are the only ones supported, but we plan to add support for defining your own in the template spec.
455+
```yaml
456+
placeholders:
457+
projekt: "{{ .PROJECT | lower }}"
458+
Projekt: "{{ .PROJECT | title }}"
459+
PROJEKT: "{{ .PROJECT | upper }}"
460+
```
461+
462+
You can then use these placeholders anywhere without any adornments. For example, the text "MY PROJEKT FILE.TXT" is equivalent to "MY {{.PROJECT | upper}} FILE.TXT".
456463

457464
This feature was inspired by the way we were previously creating new projects by duplicating an existing project and doing a search-and-replace for the project name in different case variants. That strategy was very simple and effective, as long as the project name was a very distinct string that did not appear in any other undesired contexts, hence our choice of `projekt` as something that you are (hopefully!) very
458465
unlikely to encounter in your project for any other reason than those placeholders!
@@ -486,10 +493,3 @@ To associate a template with an existing project that was not initially generate
486493
- Add more example templates, for go, node...
487494
- Fix `choice` step to pre-select current value, if any.
488495
- Allow special `.tmpl` and `.notmpl` extensions to be placed before actual extension (ie: `file.tmpl.txt`), to allow file editor to recognize them better during template editing.
489-
- Allow to customize placeholders in spec file:
490-
491-
```
492-
placeholders:
493-
projekt: {{.PROJECT | lower}}
494-
PROJEKT: {{.PROJECT | upper}},
495-
```

examples/.prettierrc.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
overrides:
2+
- files: "*.yaml"
3+
options:
4+
bracketSpacing: false

examples/templates/hello-world/spec.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ version: 0.2.0
44
# Description displayed to user during template selection
55
description: The customary Hello World example
66

7+
# Placeholders are a lightweight alternative to go template expressions, which can be used as
8+
# plain text anywhere in file/dir names and template files. Because placeholders are processed
9+
# using plain search-and-replace, ensure they have improbable names that don't risk conflicting
10+
# with anything else (ie: "projekt").
11+
placeholders:
12+
projekt: "{{.PROJECT | lower}}"
13+
Projekt: "{{.PROJECT | title}}"
14+
PROJEKT: "{{.PROJECT | upper}}"
15+
716
# Actions are sets of steps that can be invoked by user by their name
817
actions:
918
# By convention, the "create" action is in charge of scaffolding the project initially

examples/templates/hello-world/src/README.md.tmpl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
The PROJEKT project is managed by the {{.TEAM}} team.
44

5-
`PROJEKT` is a special placeholder equivalent to {{.PROJECT | upper}}
5+
The `PROJEKT` placeholder is defined in spec as equivalent to {{.PROJECT | upper}}
66

77
Project name: {{.PROJECT}}
88
Project name in uppercase: {{.PROJECT | upper}}
99
Placeholder lowercase: projekt
10+
Placeholder titlecase: Projekt
1011
Placeholder uppercase: PROJEKT
1112

1213
It was created with:

src/cmd/internal/internal.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,10 @@ func (c context) IsVarOverriden(name string) bool {
9797
}
9898

9999
// GetPlaceholders returns a map of special placeholders that can be used instead
100-
// of go template expression, for lighter weight templating, especially for the
101-
// project's name, which appears everywhere. For now, the only supported placeholder
102-
// is PROJECT, but we will eventually make placeholders configurable in spec file.
100+
// of go template expressions, for more lightweight templating, especially for the
101+
// project's name, which appears everywhere.
103102
func (c context) GetPlaceholders() map[string]string {
104-
value, _ := c.project.Vars["PROJECT"]
105-
str, ok := value.(string)
106-
if !ok {
107-
return nil
108-
}
109-
return map[string]string{
110-
"projekt": strings.ToLower(str),
111-
"PROJEKT": strings.ToUpper(str),
112-
}
103+
return c.spec.Placeholders
113104
}
114105

115106
// GetEvalVars returns a dictionary of the project's variable names mapped to

src/internal/evaluation/evaluation.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Context interface {
1818
GetEvalVars() map[string]interface{}
1919

2020
// GetPlaceholders returns a map of special placeholders that can be used instead
21-
// of go template expression, for lighter weight templating, especially for the
21+
// of go template expressions, for more lightweight templating, especially for the
2222
// project's name, which appears everywhere.
2323
GetPlaceholders() map[string]string
2424

@@ -64,8 +64,8 @@ func EvalTemplate(context Context, text string) (string, error) {
6464
text = strings.ReplaceAll(text, tripleClose, doubleOpen+"`"+doubleClose+"`"+doubleClose)
6565

6666
// Perform replacement of placeholders
67-
for search, replace := range context.GetPlaceholders() {
68-
text = strings.ReplaceAll(text, search, replace)
67+
for placeholderName, placeholderValue := range context.GetPlaceholders() {
68+
text = strings.ReplaceAll(text, placeholderName, placeholderValue)
6969
}
7070

7171
// Render go template

src/internal/exec/exec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type Context interface {
1717
IsVarOverriden(name string) bool
1818

1919
// GetPlaceholders returns a map of special placeholders that can be used instead
20-
// of go template expression, for lighter weight templating, especially for the
20+
// of go template expressions, for more lightweight templating, especially for the
2121
// project's name, which appears everywhere.
2222
GetPlaceholders() map[string]string
2323

src/internal/spec/spec.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ import (
1919

2020
// Spec represents a template specification file found in the root of the template's dir
2121
type Spec struct {
22-
Name string
23-
Version string
24-
Description string
25-
Actions map[string]Action
22+
Name string
23+
Version string
24+
Description string
25+
Placeholders map[string]string
26+
Actions map[string]Action
2627
}
2728

2829
// Load loads spec object from a template directory
@@ -58,6 +59,15 @@ func loadFromMap(_map yaml.Map, templateDir string) (*Spec, error) {
5859
return nil, err
5960
}
6061

62+
// Load placeholders
63+
placeholders, ok, err := getOptionalMap(_map, "placeholders")
64+
if err != nil {
65+
return nil, err
66+
}
67+
if ok {
68+
spec.Placeholders, err = loadPlaceholders(placeholders)
69+
}
70+
6171
// Load actions
6272
actions, err := getRequiredMap(_map, "actions")
6373
if err != nil {
@@ -71,6 +81,18 @@ func loadFromMap(_map yaml.Map, templateDir string) (*Spec, error) {
7181
return spec, nil
7282
}
7383

84+
func loadPlaceholders(_map yaml.Map) (map[string]string, error) {
85+
placeholders := make(map[string]string, len(_map))
86+
for key, node := range _map {
87+
value, ok := getString(node)
88+
if !ok {
89+
return nil, fmt.Errorf("value for placeholder %q must be a string", key)
90+
}
91+
placeholders[key] = value
92+
}
93+
return placeholders, nil
94+
}
95+
7496
func loadActions(node yaml.Map, templateDir string) (ActionMap, error) {
7597
var actions []Action
7698
for name, value := range node {

src/internal/spec/spec_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,10 @@ func TestLoadSpec(t *testing.T) {
364364
Buffer: `
365365
version: 0.2.0
366366
description: Description
367-
import:
368-
common: common
369-
go: go/common
367+
placeholders:
368+
projekt: {{.PROJECT | lower}}
369+
Projekt: {{.PROJECT | title}}
370+
PROJEKT: {{.PROJECT | upper}}
370371
actions:
371372
action1:
372373
- if: Condition 1
@@ -382,6 +383,11 @@ actions:
382383
Name: "template_name",
383384
Version: "0.2.0",
384385
Description: "Description",
386+
Placeholders: map[string]string{
387+
"projekt": "{{.PROJECT | lower}}",
388+
"Projekt": "{{.PROJECT | title}}",
389+
"PROJEKT": "{{.PROJECT | upper}}",
390+
},
385391
Actions: ActionMap{
386392
"action1": Action{
387393
Name: "action1",

0 commit comments

Comments
 (0)