Skip to content

Commit

Permalink
feat: Introducing --adopt flag to detect template block contents for …
Browse files Browse the repository at this point in the history
…existing code (#174)

The heuristics are documented in the blocks.go file in the adoptBlocks
function, but the very simple version is looking for matching lines
before/after a block in a template in the target file, if it so exists,
and if so, pull "what will be the block content" out of that file in as
"block content" for the stencil render (usually the first time stencil
is run on a repo, or after a template change introducing blocks where
there aren't already any).
  • Loading branch information
deregtd authored Oct 30, 2024
1 parent 00060b3 commit 89cbf30
Show file tree
Hide file tree
Showing 34 changed files with 559 additions and 152 deletions.
6 changes: 5 additions & 1 deletion cmd/stencil/stencil.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func NewStencilAction(log slogext.Logger) cli.ActionFunc {
return fmt.Errorf("failed to parse stencil.yaml: %w", err)
}

return stencil.NewCommand(log, manifest, c.Bool("dry-run")).Run(c.Context)
return stencil.NewCommand(log, manifest, c.Bool("dry-run"), c.Bool("adopt")).Run(c.Context)
}
}

Expand All @@ -88,6 +88,10 @@ func NewStencil(log slogext.Logger) *cli.App {
Usage: "Enables debug logging for version resolution, template renderer, and other useful information",
Aliases: []string{"d"},
},
&cli.BoolFlag{
Name: "adopt",
Usage: "Uses heuristics to detect code that should go into blocks to assist with first-time adoption of templates",
},
},
Commands: []*cli.Command{
NewDescribeCommand(),
Expand Down
2 changes: 1 addition & 1 deletion cmd/stencil/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func NewUpgradeCommand(log slogext.Logger) *cli.Command {
return fmt.Errorf("failed to parse stencil.yaml: %w", err)
}

return stencil.NewCommand(log, manifest, c.Bool("dry-run")).Upgrade(c.Context)
return stencil.NewCommand(log, manifest, c.Bool("dry-run"), c.Bool("adopt")).Upgrade(c.Context)
},
}
}
37 changes: 0 additions & 37 deletions docs/funcs/module_Call.md

This file was deleted.

25 changes: 0 additions & 25 deletions docs/funcs/module_Export.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/funcs/stencil.Debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ Debug logs the provided arguments under the DEBUG log level (must run
stencil with --debug).

```go
{{- $_ := stencil.Debug "I'm a log!" }}
{{- stencil.Debug "I'm a log!" }}
```
2 changes: 1 addition & 1 deletion docs/guide/basic-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ arbitrary number of files with a single template. This is done with the

```go
# This is important! We do not want to create a greeter.go file
{{- $_ := file.Skip "Generates multiple files" }}
{{- file.Skip "Generates multiple files" }}

{{- define "greeter" -}}
{{- $greeting := .greeting }}
Expand Down
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ features:
- icon: 🧱
title: <a href="/reference/modules">Modular</a>
details: Templates can be composed through importable modules allowing easy customization
- icon: 📈
title: <a href="/reference/adopt">Adopt Existing Projects</a>
details: Templatize existing patterns and automatically adopt code blocks into Stencil template modules
- icon: 🛠️
title: <a href="/reference/native-extensions">Native Extensions</a>
details: Need to interface with an API or implement custom parsing/merging logic? Stencil supports native extensions in <i>any</i> language to implement that logic
Expand Down
53 changes: 53 additions & 0 deletions docs/reference/adopt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
order: 5
---

# Adopt Existing Projects

Often times, your projects have already been built using a template or code generation of some sort, so there is some level of standardization in place. We have added some simple heuristics to stencil to detect what customization content should go inside blocks as you adopt these existing projects into stencil. This system is designed to help automate bringing stencil into established codebases and save the majority of the time taken to manually reconcile code that goes into blocks as you one-by-one convert your projects to use stencil.

## Usage

Once you have created a stencil.yaml file in an existing project's repository directory, you can run `stencil --adopt` to enable the heuristics to detect block content. Do not run --adopt multiple times, as it will multiply the block start/end lines. Run it once, compare the diff, and do any remaining manual reconciliation needed.

## Example

Let's say you have a template for a YAML file that looks like:

```yaml
global:
deploymentEnvironment: prod
## <<Stencil::Block(version)>>
{{- if empty (trim (file.Block "version")) }}
version: latest
{{- else }}
{{ file.Block "version"}}
{{- end }}
## <</Stencil::Block>>
somechart:
somestuff: 4
```
And you have an existing YAML file that looks like:
```yaml
global:
deploymentEnvironment: prod
version: xyz
somechart:
somestuff: 4
```
If you run `stencil --adopt`, then the heuristics will detect the line in between `deploymentEnvironment: prod` and `somechart:` (the lines above and below the block start/finish) and insert `version: xyz` into the contents of that block during the first render, and you'll end up with the following file:

```yaml
global:
deploymentEnvironment: prod
## <<Stencil::Block(version)>>
version: xyz
## <</Stencil::Block>>
somechart:
somestuff: 4
```

The heuristics look for increasing numbers of lines above and below the blocks until it gets down to the point of only having one matching potential-block, and then fills that block in. If you find other example scenarios that are not handled well by this, feel free to open an issue on [GitHub](https://github.com/rgst-io/stencil/issues) with the example, and we'll see what we can do to improve it.
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
Expand Down
9 changes: 7 additions & 2 deletions internal/cmd/stencil/stencil.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ type Command struct {

// dryRun denotes if we should write files to disk or not
dryRun bool

// adopt denotes if we should use heuristics to detect code that should go
// into blocks to assist with first-time adoption of templates
adopt bool
}

// printVersion is a command line friendly version of
Expand All @@ -69,7 +73,7 @@ func printVersion(v *resolver.Version) string {
}

// NewCommand creates a new stencil command
func NewCommand(log slogext.Logger, s *configuration.Manifest, dryRun bool) *Command {
func NewCommand(log slogext.Logger, s *configuration.Manifest, dryRun, adopt bool) *Command {
l, err := stencil.LoadLockfile("")
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.WithError(err).Warn("failed to load lockfile")
Expand All @@ -80,6 +84,7 @@ func NewCommand(log slogext.Logger, s *configuration.Manifest, dryRun bool) *Com
manifest: s,
log: log,
dryRun: dryRun,
adopt: adopt,
}
}

Expand Down Expand Up @@ -255,7 +260,7 @@ func (c *Command) Run(ctx context.Context) error {

// runWithModules runs the stencil command with the given modules
func (c *Command) runWithModules(ctx context.Context, mods []*modules.Module) error {
st := codegen.NewStencil(c.manifest, c.lock, mods, c.log)
st := codegen.NewStencil(c.manifest, c.lock, mods, c.log, c.adopt)
defer st.Close()

c.log.Info("Loading native extensions")
Expand Down
10 changes: 5 additions & 5 deletions internal/cmd/stencil/stencil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestResolveModulesShouldUseModulesFromLockfile(t *testing.T) {
Modules: []*configuration.TemplateRepository{{
Name: "github.com/rgst-io/stencil-golang",
}},
}, false)
}, false, false)
s.lock = &stencil.Lockfile{
Modules: []*stencil.LockfileModuleEntry{{
Name: "github.com/rgst-io/stencil-golang",
Expand All @@ -54,7 +54,7 @@ func TestResolveModulesShouldUpgradeWhenExplicitlyAsked(t *testing.T) {
Name: "github.com/rgst-io/stencil-golang",
Version: "v0.5.0", // 3c3213721335c53fd78f4fede1b3704801616615
}},
}, false)
}, false, false)
s.lock = &stencil.Lockfile{
Modules: []*stencil.LockfileModuleEntry{{
Name: "github.com/rgst-io/stencil-golang",
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestResolveShouldNotUpgradeOtherModulesWhenUpgradingOne(t *testing.T) {
Name: "github.com/rgst-io/stencil-module",
},
},
}, false)
}, false, false)
s.lock = &stencil.Lockfile{
Modules: []*stencil.LockfileModuleEntry{{
Name: "github.com/rgst-io/stencil-golang",
Expand Down Expand Up @@ -134,7 +134,7 @@ func TestResolveModulesShouldUpdateReplacements(t *testing.T) {
Replacements: map[string]string{
"github.com/rgst-io/stencil-golang": filepath.Join("testdata", "stub-module"),
},
}, false)
}, false, false)
s.lock = &stencil.Lockfile{
Modules: []*stencil.LockfileModuleEntry{{
Name: "github.com/rgst-io/stencil-golang",
Expand Down Expand Up @@ -165,7 +165,7 @@ func TestResolveModulesShouldAllowAdds(t *testing.T) {
Name: "github.com/rgst-io/stencil-module",
},
},
}, false)
}, false, false)
s.lock = &stencil.Lockfile{
Modules: []*stencil.LockfileModuleEntry{{
Name: "github.com/rgst-io/stencil-module",
Expand Down
Loading

0 comments on commit 89cbf30

Please sign in to comment.