Skip to content
Open
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
14 changes: 12 additions & 2 deletions docs/user/reference/config/overlays.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ successfully makes a replacement to at least one matching file.
|------|-------------|-----------------|--------------------------------|
| `file-prepend-lines` | Prepends lines to a file | `file`, `lines` | Glob pattern for files to transform |
| `file-search-replace` | Regex-based search and replace on a file | `file`, `regex` | Glob pattern for files to transform |
| `file-add` | Copies a new file from a source location; **fails if destination already exists** | `file`, `source` | Name of destination file |
| `file-add` | Copies a new file from a source location; **fails if destination already exists** | `file` (required); `source` (optional, defaults to `file`) | Name of destination file |
| `file-remove` | Removes a file | `file` | Glob pattern for files to remove |
| `file-rename` | Renames a file within the same directory | `file`, `replacement` | Name of file to rename |

Expand All @@ -60,7 +60,7 @@ successfully makes a replacement to at least one matching file.
| Replacement | `replacement` | Literal replacement text; capture group references like `$1` are **not** expanded. Omit or leave empty to delete matched text. | `spec-search-replace`, `file-search-replace`, `file-rename` |
| Lines | `lines` | Array of text lines to insert | `spec-prepend-lines`, `spec-append-lines`, `file-prepend-lines` |
| File | `file` | The name of the non-spec file to modify or add | `file-prepend-lines`, `file-search-replace`, `file-add`, `file-remove`, `file-rename`, `patch-add` (optional), `patch-remove` |
| Source | `source` | Path to source file for `file-add` and `patch-add`; relative paths are relative to the config file | `file-add`, `patch-add` |
| Source | `source` | Path to source file for `file-add` and `patch-add`; relative paths are relative to the config file. For `file-add`, defaults to the value of `file` when omitted. | `file-add` (optional), `patch-add` |

> **Note:** For `file-rename`, the `replacement` field is a **filename only** (not a path). The file is renamed within its current directory.

Expand Down Expand Up @@ -201,6 +201,16 @@ source = "files/mypackage/extra-config.conf"
description = "Add custom configuration file"
```

When the destination filename matches the source filename, `source` can be
omitted and defaults to the value of `file`:

```toml
[[components.mypackage.overlays]]
type = "file-add"
file = "extra-config.conf"
description = "Add custom configuration file"
```

### Removing a File

```toml
Expand Down
17 changes: 12 additions & 5 deletions internal/projectconfig/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ type ComponentOverlay struct {
// For overlays that reference lines of text, the lines of text to use.
Lines []string `toml:"lines,omitempty" json:"lines,omitempty" jsonschema:"title=Lines,description=The lines of text to use"`
// For overlays that require a source file as input, indicates a path to that file; relative paths are relative to
// the config file that defines the overlay.
Source string `toml:"source,omitempty" json:"source,omitempty" jsonschema:"title=Source,description=For overlays that require a source file as input, indicates a path to that file; relative paths are relative to the config file that defines the overlay"`
// the config file that defines the overlay. For `file-add` overlays, this defaults to the value of `file` when
// omitted.
Source string `toml:"source,omitempty" json:"source,omitempty" jsonschema:"title=Source,description=For overlays that require a source file as input, indicates a path to that file; relative paths are relative to the config file that defines the overlay. For file-add overlays, defaults to the value of file when omitted."`
}

// WithAbsolutePaths returns a copy of the overlay with config-relative file paths converted to absolute
Expand All @@ -53,6 +54,13 @@ func (c *ComponentOverlay) WithAbsolutePaths(referenceDir string) (result *Compo
// here.
result = deep.MustCopy(c)

// For `file-add` overlays, `source` defaults to the value of `file` when omitted, so that
// users don't have to repeat the same value when the destination filename matches the
// source filename (the common case). Apply the default before absolutizing.
if result.Type == ComponentOverlayAddFile && result.Source == "" {
result.Source = result.Filename
}

// Fix up paths.
result.Source = makeAbsolute(referenceDir, result.Source)

Expand Down Expand Up @@ -231,9 +239,8 @@ func (c *ComponentOverlay) Validate() error {
return err
}

if c.Source == "" {
return missingField("source")
}
// `source` is optional and defaults to the value of `file` when omitted
// (see ComponentOverlay.WithAbsolutePaths).
case ComponentOverlayRemoveFile:
if err := requireRelativePath("file", c.Filename); err != nil {
return err
Expand Down
56 changes: 53 additions & 3 deletions internal/projectconfig/overlay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,12 @@ func TestComponentOverlay_Validate(t *testing.T) {
errorContains: "file",
},
{
name: "file-add missing source",
name: "file-add missing source is valid (defaults to file)",
overlay: projectconfig.ComponentOverlay{
Type: projectconfig.ComponentOverlayAddFile,
Filename: "new-file.txt",
},
errorExpected: true,
errorContains: "source",
errorExpected: false,
},
// Description included in error
{
Expand Down Expand Up @@ -441,3 +440,54 @@ func TestComponentOverlay_ModifiesSpec(t *testing.T) {
})
}
}

func TestComponentOverlay_WithAbsolutePaths(t *testing.T) {
const testRefDir = "/ref/dir"

t.Run("file-add uses explicit source when provided", func(t *testing.T) {
overlay := projectconfig.ComponentOverlay{
Type: projectconfig.ComponentOverlayAddFile,
Filename: "dest.txt",
Source: "custom/source.txt",
}

result := overlay.WithAbsolutePaths(testRefDir)

assert.Equal(t, "/ref/dir/custom/source.txt", result.Source)
assert.Equal(t, "dest.txt", result.Filename)
})

t.Run("file-add defaults source to file when omitted", func(t *testing.T) {
overlay := projectconfig.ComponentOverlay{
Type: projectconfig.ComponentOverlayAddFile,
Filename: "dest.txt",
}

result := overlay.WithAbsolutePaths(testRefDir)

assert.Equal(t, "/ref/dir/dest.txt", result.Source)
assert.Equal(t, "dest.txt", result.Filename)
})

t.Run("file-add source default does not mutate original overlay", func(t *testing.T) {
overlay := projectconfig.ComponentOverlay{
Type: projectconfig.ComponentOverlayAddFile,
Filename: "dest.txt",
}

_ = overlay.WithAbsolutePaths(testRefDir)

assert.Empty(t, overlay.Source, "original overlay should not be mutated")
})

t.Run("non file-add overlays do not default source from file", func(t *testing.T) {
overlay := projectconfig.ComponentOverlay{
Type: projectconfig.ComponentOverlayAddPatch,
Filename: "dest.patch",
}

result := overlay.WithAbsolutePaths(testRefDir)

assert.Empty(t, result.Source, "patch-add should not default source from file")
})
}
Loading