Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom plain templates #930

Merged
merged 2 commits into from
Jan 17, 2025
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
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 @@ -144,6 +144,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}
Loading