copy API

copy

package

API reference for the copy package.

S
struct

rule

cmd/rfw/plugins/copy/copy.go:20-23
type rule struct

Fields

Name Type Description
From string json:"from"
To string json:"to"
S
struct

plugin

cmd/rfw/plugins/copy/copy.go:25-27
type plugin struct

Methods

Name
Method

Returns

string
func (*plugin) Name() string
{ return "copy" }
Priority
Method

Returns

int
func (*plugin) Priority() int
{ return 0 }
Build
Method

Parameters

Returns

err error
func (*plugin) Build(raw json.RawMessage) (err error)
{
	cfg := struct {
		Files []rule `json:"files"`
	}{}
	if len(raw) > 0 {
		if err := json.Unmarshal(raw, &cfg); err != nil {
			return err
		}
	}
	p.rules = cfg.Files
	projectRoot, err := os.OpenRoot(".")
	if err != nil {
		return err
	}
	defer func() {
		if closeErr := projectRoot.Close(); err == nil {
			err = closeErr
		}
	}()
	for _, r := range p.rules {
		if err := validateRule(r); err != nil {
			return err
		}
		matches, err := doublestar.Glob(os.DirFS("."), r.From)
		if err != nil {
			return err
		}
		base, _ := doublestar.SplitPattern(r.From)
		base = filepath.FromSlash(base)
		for _, m := range matches {
			path := filepath.FromSlash(m)
			info, err := os.Stat(path)
			if err != nil {
				return err
			}
			if info.IsDir() {
				continue
			}
			rel, err := filepath.Rel(base, path)
			if err != nil {
				return err
			}
			dst := filepath.Join(r.To, rel)
			destinationDirectory := filepath.Dir(dst)
			if err := projectRoot.MkdirAll(destinationDirectory, 0o755); err != nil {
				return err
			}
			if err := copyFile(projectRoot, path, dst); err != nil {
				return err
			}
			logging.Log.Info("copied file", logging.F("plugin", "copy"), logging.F("path", dst))
		}
	}
	return nil
}
ShouldRebuild
Method

Parameters

path string

Returns

bool
func (*plugin) ShouldRebuild(path string) bool
{
	for _, r := range p.rules {
		if ok, _ := doublestar.PathMatch(r.From, path); ok {
			return true
		}
	}
	return false
}

Fields

Name Type Description
rules []rule
F
function

init

cmd/rfw/plugins/copy/copy.go:29-29
func init()

{ plugins.Register(&plugin{}) }
F
function

validateRule

Parameters

copyRule

Returns

error
cmd/rfw/plugins/copy/copy.go:100-114
func validateRule(copyRule rule) error

{
	for field, value := range map[string]string{"from": copyRule.From, "to": copyRule.To} {
		if value == "" || filepath.IsAbs(value) {
			return fmt.Errorf("copy %s path %q must be project-relative", field, value)
		}
		for _, segment := range strings.FieldsFunc(filepath.Clean(value), func(char rune) bool {
			return char == '/' || char == '\\'
		}) {
			if segment == ".." {
				return fmt.Errorf("copy %s path %q escapes the project", field, value)
			}
		}
	}
	return nil
}
F
function

copyFile

Parameters

projectRoot
src
string
dst
string

Returns

error
cmd/rfw/plugins/copy/copy.go:116-131
func copyFile(projectRoot *os.Root, src, dst string) error

{
	in, err := projectRoot.Open(src)
	if err != nil {
		return err
	}
	if err := projectRoot.Remove(dst); err != nil && !errors.Is(err, os.ErrNotExist) {
		return errors.Join(err, in.Close())
	}
	out, err := projectRoot.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
	if err != nil {
		return errors.Join(err, in.Close())
	}
	_, copyErr := io.Copy(out, in)
	closeErr := errors.Join(out.Close(), in.Close())
	return errors.Join(copyErr, closeErr)
}
F
function

TestBuildAndShouldRebuild

TestBuildAndShouldRebuild ensures files matching patterns are copied
and rebuilds are triggered for matched paths.

Parameters

cmd/rfw/plugins/copy/copy_test.go:14-69
func TestBuildAndShouldRebuild(t *testing.T)

{
	p := &plugin{}
	tmp := t.TempDir()

	t.Chdir(tmp)

	srcRoot := filepath.Join("examples", "components")
	if err := os.MkdirAll(filepath.Join(srcRoot, "templates"), 0o750); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	fileA := filepath.Join(srcRoot, "comp.txt")
	if err := os.WriteFile(fileA, []byte("a"), 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}
	fileB := filepath.Join(srcRoot, "templates", "tpl.txt")
	if err := os.WriteFile(fileB, []byte("b"), 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}

	destRoot := filepath.Join("build", "static", "examples", "components")
	cfg := struct {
		Files []struct {
			From string `json:"from"`
			To   string `json:"to"`
		} `json:"files"`
	}{
		Files: []struct {
			From string `json:"from"`
			To   string `json:"to"`
		}{{
			From: filepath.Join(srcRoot, "**", "*"),
			To:   destRoot,
		}},
	}
	raw, err := json.Marshal(cfg)
	if err != nil {
		t.Fatal(err)
	}
	if err := p.Build(raw); err != nil {
		t.Fatalf("Build: %v", err)
	}

	if data, err := os.ReadFile("build/static/examples/components/comp.txt"); err != nil || string(data) != "a" {
		t.Fatalf("comp.txt not copied: %v %s", err, data)
	}
	if data, err := os.ReadFile("build/static/examples/components/templates/tpl.txt"); err != nil || string(data) != "b" {
		t.Fatalf("tpl.txt not copied: %v %s", err, data)
	}

	if !p.ShouldRebuild(fileA) {
		t.Fatalf("expected ShouldRebuild true for %s", fileA)
	}
	if p.ShouldRebuild(filepath.Join("other.txt")) {
		t.Fatalf("unexpected rebuild for unrelated file")
	}
}
F
function

TestBuildRejectsPathsOutsideProject

Parameters

cmd/rfw/plugins/copy/copy_test.go:71-88
func TestBuildRejectsPathsOutsideProject(t *testing.T)

{
	p := &plugin{}
	for _, copyRule := range []rule{
		{From: "../*", To: "build"},
		{From: "*", To: "../build"},
		{From: "*", To: filepath.Join(t.TempDir(), "build")},
	} {
		raw, err := json.Marshal(struct {
			Files []rule `json:"files"`
		}{Files: []rule{copyRule}})
		if err != nil {
			t.Fatal(err)
		}
		if err := p.Build(raw); err == nil {
			t.Errorf("expected rule %+v to be rejected", copyRule)
		}
	}
}