Skip to content

Commit

Permalink
feat: add custom plain templates (#930)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrexox authored Jan 17, 2025
1 parent 4711444 commit dcbf0d4
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 29 deletions.
11 changes: 6 additions & 5 deletions docs/mdbook/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,23 @@
- [Configuration](./configuration/README.md)
- [`assert_lefthook_installed`](./configuration/assert_lefthook_installed.md)
- [`colors`](./configuration/colors.md)
- [`no_tty`](./configuration/no_tty.md)
- [`extends`](./configuration/extends.md)
- [`lefthook`](./configuration/lefthook.md)
- [`min_version`](./configuration/min_version.md)
- [`no_tty`](./configuration/no_tty.md)
- [`output`](./configuration/output.md)
- [`skip_output`](./configuration/skip_output.md)
- [`source_dir`](./configuration/source_dir.md)
- [`source_dir_local`](./configuration/source_dir_local.md)
- [`rc`](./configuration/rc.md)
- [`skip_lfs`](./configuration/skip_lfs.md)
- [`remotes`](./configuration/remotes.md)
- [`git_url`](./configuration/git_url.md)
- [`ref`](./configuration/ref.md)
- [`refetch`](./configuration/refetch.md)
- [`refetch_frequency`](./configuration/refetch_frequency.md)
- [`configs`](./configuration/configs.md)
- [`skip_output`](./configuration/skip_output.md)
- [`source_dir`](./configuration/source_dir.md)
- [`source_dir_local`](./configuration/source_dir_local.md)
- [`skip_lfs`](./configuration/skip_lfs.md)
- [`templates`](./configuration/templates.md)
- [{Git hook name}](./configuration/Hook.md)
- [`files`](./configuration/files-global.md)
- [`parallel`](./configuration/parallel.md)
Expand Down
11 changes: 6 additions & 5 deletions docs/mdbook/configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ Lefthook also merges an extra config with the name `lefthook-local`. All support

- [`assert_lefthook_installed`](./assert_lefthook_installed.md)
- [`colors`](./colors.md)
- [`no_tty`](./no_tty.md)
- [`extends`](./extends.md)
- [`lefthook`](./lefthook.md)
- [`min_version`](./min_version.md)
- [`no_tty`](./no_tty.md)
- [`output`](./output.md)
- [`skip_output`](./skip_output.md)
- [`source_dir`](./source_dir.md)
- [`source_dir_local`](./source_dir_local.md)
- [`rc`](./rc.md)
- [`skip_lfs`](./skip_lfs.md)
- [`remotes`](./remotes.md)
- [`git_url`](./git_url.md)
- [`ref`](./ref.md)
- [`refetch`](./refetch.md)
- [`refetch_frequency`](./refetch_frequency.md)
- [`configs`](./configs.md)
- [`skip_output`](./skip_output.md)
- [`source_dir`](./source_dir.md)
- [`source_dir_local`](./source_dir_local.md)
- [`skip_lfs`](./skip_lfs.md)
- [`templates`](./templates.md)
- [{Git hook name}](./Hook.md) (e.g. `pre-commit`)
- [`files` (global)](./files-global.md)
- [`parallel`](./parallel.md)
Expand Down
43 changes: 43 additions & 0 deletions docs/mdbook/configuration/templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## `templates`

Provide custom replacement for templates in `run` values.

With `templates` you can specify what can be overridden via `lefthook-local.yml` without a need to overwrite every jobs in your configuration.

## Example

### Override with lefthook-local.yml

```yml
# lefthook.yml

templates:
dip: # empty

pre-commit:
jobs:
# Will run: `bundle exec rubocop file1 file2 file3 ...`
- run: {dip} bundle exec rubocop {staged_files}
```
```yml
# lefthook.yml

templates:
dip: dip # Will run: `dip bundle exec rubocop file1 file2 file3 ...`
```
### Reduce redundancy
```yml
# lefthook.yml

templates:
wrapper: docker-compose run --rm -v $(pwd):/app service

pre-commit:
jobs:
- run: {wrapper} yarn format
- run: {wrapper} yarn lint
- run: {wrapper} yarn test
```
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type Config struct {

Remotes []*Remote `json:"remotes,omitempty" jsonschema:"description=Provide multiple remote configs to use lefthook configurations shared across projects. Lefthook will automatically download and merge configurations into main config." mapstructure:"remotes,omitempty"`

Templates map[string]string `json:"templates,omitempty" jsonschema:"description=Custom templates for replacements in run commands." mapstructure:"templates,omitempty"`

// Deprecated: use Remotes
Remote *Remote `json:"remote,omitempty" jsonschema:"description=Deprecated: use remotes" mapstructure:"-"`

Expand Down
1 change: 1 addition & 0 deletions internal/lefthook/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func (l *Lefthook) Run(hookName string, args RunArgs, gitArgs []string) error {
LogSettings: logSettings,
DisableTTY: cfg.NoTTY || args.NoTTY,
SkipLFS: cfg.SkipLFS || args.SkipLFS,
Templates: cfg.Templates,
Files: args.Files,
Force: args.Force,
RunOnlyCommands: args.RunOnlyCommands,
Expand Down
26 changes: 15 additions & 11 deletions internal/lefthook/runner/jobs/build_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (

var surroundingQuotesRegexp = regexp.MustCompile(`^'(.*)'$`)

// template is stats for template replacements in a command string.
type template struct {
// fileTemplate contains for template replacements in a command string.
type filesTemplate struct {
files []string
cnt int
}
Expand Down Expand Up @@ -67,7 +67,7 @@ func buildCommand(params *Params) (*Job, error) {
config.SubFiles: cmdFiles,
}

templates := make(map[string]*template)
filesTemplates := make(map[string]*filesTemplate)

filterParams := filters.Params{
Glob: params.Glob,
Expand All @@ -81,8 +81,8 @@ func buildCommand(params *Params) (*Job, error) {
continue
}

templ := &template{cnt: cnt}
templates[filesType] = templ
templ := &filesTemplate{cnt: cnt}
filesTemplates[filesType] = templ

files, err := fn()
if err != nil {
Expand All @@ -100,7 +100,7 @@ func buildCommand(params *Params) (*Job, error) {
// Checking substitutions and skipping execution if it is empty.
//
// Special case for `files` option: return if the result of files command is empty.
if !params.Force && len(filesCmd) > 0 && templates[config.SubFiles] == nil {
if !params.Force && len(filesCmd) > 0 && filesTemplates[config.SubFiles] == nil {
files, err := filesFns[config.SubFiles]()
if err != nil {
return nil, fmt.Errorf("error calling replace command for %s: %w", config.SubFiles, err)
Expand All @@ -116,15 +116,19 @@ func buildCommand(params *Params) (*Job, error) {
runString := params.Run
runString = replacePositionalArguments(runString, params.GitArgs)

for keyword, replacement := range params.Templates {
runString = strings.ReplaceAll(runString, "{"+keyword+"}", replacement)
}

maxlen := system.MaxCmdLen()
result := replaceInChunks(runString, templates, maxlen)
result := replaceInChunks(runString, filesTemplates, maxlen)

if params.Force || len(result.Files) != 0 {
return result, nil
}

if config.HookUsesStagedFiles(params.HookName) {
ok, err := canSkipJob(params, filterParams, templates[config.SubStagedFiles], params.Repo.StagedFilesWithDeleted)
ok, err := canSkipJob(params, filterParams, filesTemplates[config.SubStagedFiles], params.Repo.StagedFilesWithDeleted)
if err != nil {
return nil, err
}
Expand All @@ -134,7 +138,7 @@ func buildCommand(params *Params) (*Job, error) {
}

if config.HookUsesPushFiles(params.HookName) {
ok, err := canSkipJob(params, filterParams, templates[config.SubPushFiles], params.Repo.PushFiles)
ok, err := canSkipJob(params, filterParams, filesTemplates[config.SubPushFiles], params.Repo.PushFiles)
if err != nil {
return nil, err
}
Expand All @@ -146,7 +150,7 @@ func buildCommand(params *Params) (*Job, error) {
return result, nil
}

func canSkipJob(params *Params, filterParams filters.Params, template *template, filesFn func() ([]string, error)) (bool, error) {
func canSkipJob(params *Params, filterParams filters.Params, template *filesTemplate, filesFn func() ([]string, error)) (bool, error) {
if template != nil {
return len(template.files) == 0, nil
}
Expand Down Expand Up @@ -184,7 +188,7 @@ func escapeFiles(files []string) []string {
return filesEsc
}

func replaceInChunks(str string, templates map[string]*template, maxlen int) *Job {
func replaceInChunks(str string, templates map[string]*filesTemplate, maxlen int) *Job {
if len(templates) == 0 {
return &Job{
Execs: []string{str},
Expand Down
14 changes: 7 additions & 7 deletions internal/lefthook/runner/jobs/build_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ func Test_getNChars(t *testing.T) {
func Test_replaceInChunks(t *testing.T) {
for i, tt := range [...]struct {
str string
templates map[string]*template
templates map[string]*filesTemplate
maxlen int
job *Job
}{
{
str: "echo {staged_files}",
templates: map[string]*template{
templates: map[string]*filesTemplate{
"{staged_files}": {
files: []string{"file1", "file2", "file3"},
cnt: 1,
Expand All @@ -88,7 +88,7 @@ func Test_replaceInChunks(t *testing.T) {
},
{
str: "echo {staged_files}",
templates: map[string]*template{
templates: map[string]*filesTemplate{
"{staged_files}": {
files: []string{"file1", "file2", "file3"},
cnt: 1,
Expand All @@ -106,7 +106,7 @@ func Test_replaceInChunks(t *testing.T) {
},
{
str: "echo {files} && git add {files}",
templates: map[string]*template{
templates: map[string]*filesTemplate{
"{files}": {
files: []string{"file1", "file2", "file3"},
cnt: 2,
Expand All @@ -123,7 +123,7 @@ func Test_replaceInChunks(t *testing.T) {
},
{
str: "echo {files} && git add {files}",
templates: map[string]*template{
templates: map[string]*filesTemplate{
"{files}": {
files: []string{"file1", "file2", "file3"},
cnt: 2,
Expand All @@ -139,7 +139,7 @@ func Test_replaceInChunks(t *testing.T) {
},
{
str: "echo {push_files} && git add {files}",
templates: map[string]*template{
templates: map[string]*filesTemplate{
"{push_files}": {
files: []string{"push-file"},
cnt: 1,
Expand All @@ -160,7 +160,7 @@ func Test_replaceInChunks(t *testing.T) {
},
{
str: "echo {push_files} && git add {files}",
templates: map[string]*template{
templates: map[string]*filesTemplate{
"{push_files}": {
files: []string{"push1", "push2", "push3"},
cnt: 1,
Expand Down
1 change: 1 addition & 0 deletions internal/lefthook/runner/jobs/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Params struct {
Files string
FileTypes []string
Tags []string
Templates map[string]string
Exclude interface{}
Only interface{}
Skip interface{}
Expand Down
1 change: 1 addition & 0 deletions internal/lefthook/runner/run_jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func (r *Runner) runSingleJob(ctx context.Context, domain *domain, id string, jo
Exclude: exclude,
Only: job.Only,
Skip: job.Skip,
Templates: r.Templates,
})
if err != nil {
r.logSkip(name, err.Error())
Expand Down
2 changes: 2 additions & 0 deletions internal/lefthook/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Options struct {
RunOnlyCommands []string
RunOnlyJobs []string
SourceDirs []string
Templates map[string]string
}

// Runner responds for actual execution and handling the results.
Expand Down Expand Up @@ -467,6 +468,7 @@ func (r *Runner) runCommand(ctx context.Context, name string, command *config.Co
Exclude: command.Exclude,
Only: command.Only,
Skip: command.Skip,
Templates: r.Templates,
})
if err != nil {
r.logSkip(name, err.Error())
Expand Down
9 changes: 8 additions & 1 deletion schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@
"type": "object"
}
},
"$comment": "Last updated on 2025.01.14.",
"$comment": "Last updated on 2025.01.16.",
"properties": {
"min_version": {
"type": "string",
Expand Down Expand Up @@ -473,6 +473,13 @@
"type": "array",
"description": "Provide multiple remote configs to use lefthook configurations shared across projects. Lefthook will automatically download and merge configurations into main config."
},
"templates": {
"additionalProperties": {
"type": "string"
},
"type": "object",
"description": "Custom templates for replacements in run commands."
},
"remote": {
"$ref": "#/$defs/Remote",
"description": "Deprecated: use remotes"
Expand Down
14 changes: 14 additions & 0 deletions testdata/templates.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
exec git init
exec lefthook run test
stdout '^\s*hello\s*$'

-- lefthook.yml --
templates:
message: hello

output:
- execution_out

test:
jobs:
- run: echo {message}

0 comments on commit dcbf0d4

Please sign in to comment.