From aff0b44f520aca6b1a77f6a195072dc10f69b8c8 Mon Sep 17 00:00:00 2001 From: Nicolas Paul Date: Sun, 1 Oct 2023 23:03:40 +0200 Subject: Flatten structure Related to #13 --- cmd/svgu/svgu.go | 139 --------------------------- pkg/config/lib/prelude/prelude.go | 126 ------------------------- pkg/config/starlark.go | 66 ------------- pkg/templates/templates.go | 115 ----------------------- pkg/templates/templates_test.go | 191 -------------------------------------- pkg/types/index.go | 113 ---------------------- pkg/types/module.go | 110 ---------------------- prelude.go | 127 +++++++++++++++++++++++++ starlark.go | 65 +++++++++++++ svgu.go | 138 +++++++++++++++++++++++++++ templates.go | 117 +++++++++++++++++++++++ templates_test.go | 191 ++++++++++++++++++++++++++++++++++++++ types.go | 176 +++++++++++++++++++++++++++++++++++ 13 files changed, 814 insertions(+), 860 deletions(-) delete mode 100644 cmd/svgu/svgu.go delete mode 100644 pkg/config/lib/prelude/prelude.go delete mode 100644 pkg/config/starlark.go delete mode 100644 pkg/templates/templates.go delete mode 100644 pkg/templates/templates_test.go delete mode 100644 pkg/types/index.go delete mode 100644 pkg/types/module.go create mode 100644 prelude.go create mode 100644 starlark.go create mode 100644 svgu.go create mode 100644 templates.go create mode 100644 templates_test.go create mode 100644 types.go diff --git a/cmd/svgu/svgu.go b/cmd/svgu/svgu.go deleted file mode 100644 index 1b66db0..0000000 --- a/cmd/svgu/svgu.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright Nicolas Paul (2023) -// -// * Nicolas Paul -// -// This software is a computer program whose purpose is to allow the hosting -// and sharing of Go modules using a personal domain. -// -// This software is governed by the CeCILL license under French law and -// abiding by the rules of distribution of free software. You can use, -// modify and/ or redistribute the software under the terms of the CeCILL -// license as circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, -// modify and redistribute granted by the license, users are provided only -// with a limited warranty and the software's author, the holder of the -// economic rights, and the successive licensors have only limited -// liability. -// -// In this respect, the user's attention is drawn to the risks associated -// with loading, using, modifying and/or developing or reproducing the -// software by the user in light of its specific status of free software, -// that may mean that it is complicated to manipulate, and that also -// therefore means that it is reserved for developers and experienced -// professionals having in-depth computer knowledge. Users are therefore -// encouraged to load and test the software's suitability as regards their -// requirements in conditions enabling the security of their systems and/or -// data to be ensured and, more generally, to use and operate it in the -// same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -// The svgu tool. -package main // import "go.nc0.fr/svgu" - -import ( - "flag" - "log" - "os" - "path/filepath" - "sync" - - "go.nc0.fr/svgu/pkg/config" - "go.nc0.fr/svgu/pkg/types" -) - -var ( - cfg = flag.String("c", "DOMAINS.star", "the configuration file to use.") - out = flag.String("o", "dst", "output directory") - verbose = flag.Bool("v", false, "prints additional information logs") -) - -func main() { - log.SetFlags(0) - log.SetPrefix("svgu: ") - flag.Parse() - - // Check if the configuration file exists. - if *verbose { - log.Printf("checking if configuration file %q exists", *cfg) - } - - if cfg, err := filepath.Abs(*cfg); err != nil { - log.Fatalf("could not get absolute path of %s: %v", cfg, err) - } - - if cfgfd, err := os.Stat(*cfg); os.IsNotExist(err) || cfgfd.IsDir() { - log.Fatalf("configuration file %q does not exist", *cfg) - } else if err != nil { - log.Fatalf("could not stat %q: %v", *cfg, err) - } - - // Check if the output directory exists. - if *verbose { - log.Printf("checking if output directory %q exists", *out) - } - - if out, err := filepath.Abs(*out); err != nil { - log.Fatalf("could not get absolute path of %s: %v", out, err) - } - - if outfd, err := os.Stat(*out); outfd != nil && outfd.IsDir() { - log.Fatalf("output directory %q already exists", *out) - } else if err != nil && !os.IsNotExist(err) { - log.Fatalf("could not stat %q: %v", *out, err) - } - - // Execute the configuration file and get the registered modules. - if *verbose { - log.Printf("executing configuration file %q", *cfg) - } - - idx, err := config.ExecConfig(*cfg) - if err != nil { - log.Fatalf("could not execute configuration file %q: %v", *cfg, err) - } - - // Create the output directory. - if *verbose { - log.Printf("creating output directory %q", *out) - } - - if err := os.MkdirAll(*out, 0755); err != nil { - log.Fatalf("could not create output directory %q: %v", *out, err) - } - - // Generate the index file. - if *verbose { - log.Printf("generating index file") - } - - if err := idx.GenerateFile(*out); err != nil { - log.Fatalf("could not generate index file: %v", err) - } - - // Generate the modules. - if *verbose { - log.Printf("generating modules") - } - - var wg sync.WaitGroup - var mu sync.Mutex - for _, mod := range idx.Modules { - wg.Add(1) - go func(m *types.Module) { - defer wg.Done() - defer mu.Unlock() - - mu.Lock() - if err := m.GenerateFile(*out, idx.Domain); err != nil { - log.Fatalf("could not generate module %q: %v", m.Path, err) - } - }(mod) - } - - wg.Wait() - log.Println("done") -} diff --git a/pkg/config/lib/prelude/prelude.go b/pkg/config/lib/prelude/prelude.go deleted file mode 100644 index 16f9f4d..0000000 --- a/pkg/config/lib/prelude/prelude.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright Nicolas Paul (2023) -// -// * Nicolas Paul -// -// This software is a computer program whose purpose is to allow the hosting -// and sharing of Go modules using a personal domain. -// -// This software is governed by the CeCILL license under French law and -// abiding by the rules of distribution of free software. You can use, -// modify and/ or redistribute the software under the terms of the CeCILL -// license as circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, -// modify and redistribute granted by the license, users are provided only -// with a limited warranty and the software's author, the holder of the -// economic rights, and the successive licensors have only limited -// liability. -// -// In this respect, the user's attention is drawn to the risks associated -// with loading, using, modifying and/or developing or reproducing the -// software by the user in light of its specific status of free software, -// that may mean that it is complicated to manipulate, and that also -// therefore means that it is reserved for developers and experienced -// professionals having in-depth computer knowledge. Users are therefore -// encouraged to load and test the software's suitability as regards their -// requirements in conditions enabling the security of their systems and/or -// data to be ensured and, more generally, to use and operate it in the -// same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package prelude - -import ( - "fmt" - "go.nc0.fr/svgu/pkg/types" - "go.starlark.net/starlark" - "strings" -) - -// https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names -const invalidName string = "..\\/<>:\"|?* \t\n\r\b\findex" - -// Registered represents the index of registered modules. -var Registered types.Index - -// InternIndex represents the built-in function "index". -// index(domain) initializes a new index with the given domain. -func InternIndex(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, - kwargs []starlark.Tuple) (starlark.Value, error) { - var domain string - if err := starlark.UnpackArgs("index", args, kwargs, - "domain", &domain); err != nil { - return nil, err - } - - Registered.SetDomain(domain) - - return starlark.None, nil -} - -// InternModule represents the built-in function "module". -// module(name, vcs, repo, dir, file) registers a new module into the -// index. -func InternModule(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, - kwargs []starlark.Tuple) (starlark.Value, error) { - - var name, vcs, repo, dir, file string - if err := starlark.UnpackArgs("module", args, kwargs, "name", - &name, "vcs", &vcs, "repo", &repo, "dir", &dir, "file", &file); err != nil { - return nil, err - } - - if Registered.Domain == "" { - return nil, fmt.Errorf("index not initialized") - } - - if name == "" { - return nil, fmt.Errorf("module name cannot be empty") - } - - if vcs == "" { - return nil, fmt.Errorf("module %q vcs cannot be empty", name) - } - - if repo == "" { - return nil, fmt.Errorf("module %q repo cannot be empty", name) - } - - // Check for name conditions. - if strings.Contains(invalidName, name) { - return nil, fmt.Errorf("module %q name is invalid", name) - } - - if Registered.CheckModule(name) { - return nil, fmt.Errorf("module %q already exists", name) - } - - var v types.Vcs - switch vcs { - case "git": - v = types.VcsGit - case "hg": - v = types.VcsMercurial - case "svn": - v = types.VcsSubversion - case "fossil": - v = types.VcsFossil - case "bzr": - v = types.VcsBazaar - default: - return nil, fmt.Errorf("unknown vcs %q", vcs) - } - - Registered.AddModule(name, &types.Module{ - Path: name, - Vcs: v, - Repo: repo, - Dir: dir, - File: file, - }) - - return starlark.None, nil -} diff --git a/pkg/config/starlark.go b/pkg/config/starlark.go deleted file mode 100644 index bb65465..0000000 --- a/pkg/config/starlark.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright Nicolas Paul (2023) -// -// * Nicolas Paul -// -// This software is a computer program whose purpose is to allow the hosting -// and sharing of Go modules using a personal domain. -// -// This software is governed by the CeCILL license under French law and -// abiding by the rules of distribution of free software. You can use, -// modify and/ or redistribute the software under the terms of the CeCILL -// license as circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, -// modify and redistribute granted by the license, users are provided only -// with a limited warranty and the software's author, the holder of the -// economic rights, and the successive licensors have only limited -// liability. -// -// In this respect, the user's attention is drawn to the risks associated -// with loading, using, modifying and/or developing or reproducing the -// software by the user in light of its specific status of free software, -// that may mean that it is complicated to manipulate, and that also -// therefore means that it is reserved for developers and experienced -// professionals having in-depth computer knowledge. Users are therefore -// encouraged to load and test the software's suitability as regards their -// requirements in conditions enabling the security of their systems and/or -// data to be ensured and, more generally, to use and operate it in the -// same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package config - -import ( - "fmt" - "go.nc0.fr/svgu/pkg/config/lib/prelude" - "go.nc0.fr/svgu/pkg/types" - "go.starlark.net/starlark" -) - -// ExecConfig configures the Starlark environment and executes the given -// configuration file "fl". -// The function returns a list of registered modules, or an error if something -// went wrong. -func ExecConfig(fl string) (*types.Index, error) { - th := &starlark.Thread{ - Name: "exec " + fl, - } - - env := starlark.StringDict{ - "index": starlark.NewBuiltin("index", prelude.InternIndex), - "module": starlark.NewBuiltin("module", prelude.InternModule), - } - - prelude.Registered = types.Index{ - Domain: "", - Modules: make(map[string]*types.Module), - } - if _, err := starlark.ExecFile(th, fl, nil, env); err != nil { - return &types.Index{}, err - } - - return &prelude.Registered, nil -} diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go deleted file mode 100644 index 4c227a5..0000000 --- a/pkg/templates/templates.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright Nicolas Paul (2023) -// -// * Nicolas Paul -// -// This software is a computer program whose purpose is to allow the hosting -// and sharing of Go modules using a personal domain. -// -// This software is governed by the CeCILL license under French law and -// abiding by the rules of distribution of free software. You can use, -// modify and/ or redistribute the software under the terms of the CeCILL -// license as circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, -// modify and redistribute granted by the license, users are provided only -// with a limited warranty and the software's author, the holder of the -// economic rights, and the successive licensors have only limited -// liability. -// -// In this respect, the user's attention is drawn to the risks associated -// with loading, using, modifying and/or developing or reproducing the -// software by the user in light of its specific status of free software, -// that may mean that it is complicated to manipulate, and that also -// therefore means that it is reserved for developers and experienced -// professionals having in-depth computer knowledge. Users are therefore -// encouraged to load and test the software's suitability as regards their -// requirements in conditions enabling the security of their systems and/or -// data to be ensured and, more generally, to use and operate it in the -// same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package templates - -import ( - "fmt" - "html/template" - "io" -) - -// HTML document templates -var ( - // indexTmpl is the HTML template for the root of the static site. - // It redirects to a site configured by the user. - // - // {{.Count}} corresponds to the timer—in seconds— before redirecting. - // {{.Redirect}} is the URL to redirect visitors to. - indexTmpl = template.Must( - template.New("index").Parse(` - - - -

Nothing to see, redirecting here.`)) - - // moduleTmpl is the HTML template for module-specific documents. - // They include information for the Go toolchain to find and download - // the module source code. It also redirects users to the module's - // documentation on pkg.go.dev. - // - // {{.Prefix}} is the module's prefix, a.k.a. import path, - // e.g. example.com/foo - // {{.Vcs}} is the version control system used in the codebase - // {{.Home}} is the repository's home. - // {{.Dir}} is a URL template to a page listing the files inside a package - // {{.File}} is a URL template listing the lines of a file - // {{.Doc}} is the URL of the module's documentation on pkg.go.dev - // - // Templates support a specific set of substitutions which are documented - // here: https://github.com/golang/gddo/wiki/Source-Code-Links - moduleTmpl = template.Must( - template.New("module").Parse(` - - - - -

There is nothing to see, redirecting here.`)) -) - -// ExecIndex constructs an HTML document for the index of the generated site -// in the given writer "w." -// The document redirects visitor to the specified "url" after "count" seconds. -func ExecIndex(w io.Writer, url string, count uint8) error { - return indexTmpl.Execute(w, struct { - Redirect string - Count uint8 - }{ - Redirect: url, - Count: count, - }) -} - -// ExecModule constructs an HTML document for a module indexed on the domain -// in the given writer "w." The "prefix" corresponds to the import path of the -// module, "vcs" to the version control system used — git, bazaar..., "home" -// is the repository's home and "dir"/"file" are URL templates as documented -// by GoDoc: https://github.com/golang/gddo/wiki/Source-Code-Links. -func ExecModule(w io.Writer, prefix, vcs, - home, dir, file string) error { - return moduleTmpl.Execute(w, struct { - Prefix string - Vcs string - Home string - Dir string - File string - Doc string - }{ - Prefix: prefix, - Vcs: vcs, - Home: home, - Dir: dir, - File: file, - Doc: fmt.Sprintf("https://pkg.go.dev/%s", prefix), - }) -} diff --git a/pkg/templates/templates_test.go b/pkg/templates/templates_test.go deleted file mode 100644 index 34c2c82..0000000 --- a/pkg/templates/templates_test.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright Nicolas Paul (2023) -// -// * Nicolas Paul -// -// This software is a computer program whose purpose is to allow the hosting -// and sharing of Go modules using a personal domain. -// -// This software is governed by the CeCILL license under French law and -// abiding by the rules of distribution of free software. You can use, -// modify and/ or redistribute the software under the terms of the CeCILL -// license as circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, -// modify and redistribute granted by the license, users are provided only -// with a limited warranty and the software's author, the holder of the -// economic rights, and the successive licensors have only limited -// liability. -// -// In this respect, the user's attention is drawn to the risks associated -// with loading, using, modifying and/or developing or reproducing the -// software by the user in light of its specific status of free software, -// that may mean that it is complicated to manipulate, and that also -// therefore means that it is reserved for developers and experienced -// professionals having in-depth computer knowledge. Users are therefore -// encouraged to load and test the software's suitability as regards their -// requirements in conditions enabling the security of their systems and/or -// data to be ensured and, more generally, to use and operate it in the -// same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package templates - -import ( - "bytes" - "io" - "testing" -) - -func TestExecIndex(t *testing.T) { - type args struct { - url string - count uint8 - } - tests := []struct { - name string - args args - want *bytes.Buffer - wantErr bool - }{ - { - name: "valid url waiting 5 seconds", - args: args{ - url: "https://example.com", - count: 5, - }, - want: bytes.NewBufferString(` - - - -

Nothing to see, redirecting here.`), - wantErr: false, - }, - { - name: "valid url waiting 0 second", - args: args{ - url: "https://example.com", - count: 0, - }, - want: bytes.NewBufferString(` - - - -

Nothing to see, redirecting here.`), - wantErr: false, - }, - { - name: "not the same url", - args: args{ - url: "https://example.com", - count: 0, - }, - want: bytes.NewBufferString(` - - - -

Nothing to see, redirecting here.`), - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var buf bytes.Buffer - err := ExecIndex(&buf, tt.args.url, tt.args.count) - - if !tt.wantErr && err != nil { - t.Errorf("ExecIndex() error = %v", err) - } - - if !tt.wantErr && !bytes.Equal(buf.Bytes(), tt.want.Bytes()) { - t.Errorf("ExecIndex() = %v, want %v", buf, tt.want) - } - }) - } -} - -func TestExecModule(t *testing.T) { - type args struct { - w *io.Writer - prefix string - vcs string - home string - dir string - file string - } - tests := []struct { - name string - args args - want *bytes.Buffer - wantErr bool - }{ - { - name: "module example.com/foo hosted on github", - args: args{ - prefix: "example.com/foo", - vcs: "git", - home: "https://github.com/example/foo", - dir: "https://github.com/example/foo/tree/master{/dir}", - file: "https://github.com/example/foo/blob/master{/dir}/{file}#L{line}", - }, - want: bytes.NewBufferString(` - - - - -

There is nothing to see, redirecting here.`), - wantErr: false, - }, - { - name: "module example.com/foo hosted on gitlab", - args: args{ - prefix: "example.com/foo", - vcs: "git", - home: "https://gitlab.com/example/foo", - dir: "https://gitlab.com/example/foo/-/tree/master{/dir}", - file: "https://gitlab.com/example/foo/-/blob/master{/dir}/{file}#L{line}", - }, - want: bytes.NewBufferString(` - - - - -

There is nothing to see, redirecting here.`), - wantErr: false, - }, - { - name: "module example.com/foo hosted on Source Hut with Mercurial", - args: args{ - prefix: "example.com/foo", - vcs: "hg", - home: "https://hg.sr.ht/~example/foo", - dir: "https://hg.sr.ht/~example/foo/tree/master{/dir}", - file: "https://hg.sr.ht/~example/foo/browse/master{/dir}/{file}#{line}", - }, - want: bytes.NewBufferString(` - - - - -

There is nothing to see, redirecting here.`), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var buf bytes.Buffer - err := ExecModule(&buf, tt.args.prefix, tt.args.vcs, tt.args.home, - tt.args.dir, tt.args.file) - - if !tt.wantErr && err != nil { - t.Errorf("ExecIndex() error = %v", err) - } - - if !tt.wantErr && !bytes.Equal(buf.Bytes(), tt.want.Bytes()) { - t.Errorf("ExecIndex() = %v, want %v", buf, tt.want) - } - }) - } -} diff --git a/pkg/types/index.go b/pkg/types/index.go deleted file mode 100644 index 3abf527..0000000 --- a/pkg/types/index.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright Nicolas Paul (2023) -// -// * Nicolas Paul -// -// This software is a computer program whose purpose is to allow the hosting -// and sharing of Go modules using a personal domain. -// -// This software is governed by the CeCILL license under French law and -// abiding by the rules of distribution of free software. You can use, -// modify and/ or redistribute the software under the terms of the CeCILL -// license as circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, -// modify and redistribute granted by the license, users are provided only -// with a limited warranty and the software's author, the holder of the -// economic rights, and the successive licensors have only limited -// liability. -// -// In this respect, the user's attention is drawn to the risks associated -// with loading, using, modifying and/or developing or reproducing the -// software by the user in light of its specific status of free software, -// that may mean that it is complicated to manipulate, and that also -// therefore means that it is reserved for developers and experienced -// professionals having in-depth computer knowledge. Users are therefore -// encouraged to load and test the software's suitability as regards their -// requirements in conditions enabling the security of their systems and/or -// data to be ensured and, more generally, to use and operate it in the -// same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package types - -import ( - "go.nc0.fr/svgu/pkg/templates" - "os" - "path" - "sync" -) - -// Index is the global object representing the Starlark configuration. -type Index struct { - Domain string - Modules map[string]*Module - // internal - lock sync.Mutex -} - -// SetDomain sets the domain of the index. -func (i *Index) SetDomain(d string) { - i.lock.Lock() - defer i.lock.Unlock() - i.Domain = d -} - -// AddModule adds a module to the index. -func (i *Index) AddModule(n string, m *Module) { - i.lock.Lock() - defer i.lock.Unlock() - i.Modules[n] = m -} - -// GetModule returns a module from the index. -func (i *Index) GetModule(n string) *Module { - i.lock.Lock() - defer i.lock.Unlock() - return i.Modules[n] -} - -// RemoveModule removes a module from the index. -func (i *Index) RemoveModule(n string) { - i.lock.Lock() - defer i.lock.Unlock() - delete(i.Modules, n) -} - -// CheckModule checks if a module is in the index. -func (i *Index) CheckModule(n string) bool { - i.lock.Lock() - defer i.lock.Unlock() - _, ok := i.Modules[n] - return ok -} - -// GenerateFile generates the index file. -func (i *Index) GenerateFile(out string) error { - i.lock.Lock() - defer i.lock.Unlock() - - f := path.Join(out, "index.html") - - // Create the file. - fd, err := os.Create(f) - if err != nil { - return err - } - defer func(fd *os.File) { - err := fd.Close() - if err != nil { - panic(err) - } - }(fd) - - // Execute the template and write the output to the file. - if err := templates.ExecIndex(fd, - "https://pkg.go.dev", 2); err != nil { - return err - } - - return nil -} diff --git a/pkg/types/module.go b/pkg/types/module.go deleted file mode 100644 index 2206659..0000000 --- a/pkg/types/module.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright Nicolas Paul (2023) -// -// * Nicolas Paul -// -// This software is a computer program whose purpose is to allow the hosting -// and sharing of Go modules using a personal domain. -// -// This software is governed by the CeCILL license under French law and -// abiding by the rules of distribution of free software. You can use, -// modify and/ or redistribute the software under the terms of the CeCILL -// license as circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, -// modify and redistribute granted by the license, users are provided only -// with a limited warranty and the software's author, the holder of the -// economic rights, and the successive licensors have only limited -// liability. -// -// In this respect, the user's attention is drawn to the risks associated -// with loading, using, modifying and/or developing or reproducing the -// software by the user in light of its specific status of free software, -// that may mean that it is complicated to manipulate, and that also -// therefore means that it is reserved for developers and experienced -// professionals having in-depth computer knowledge. Users are therefore -// encouraged to load and test the software's suitability as regards their -// requirements in conditions enabling the security of their systems and/or -// data to be ensured and, more generally, to use and operate it in the -// same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package types - -import ( - "fmt" - "go.nc0.fr/svgu/pkg/templates" - "os" - "path" - "strings" - "sync" -) - -// Vcs is an enum for version control systems supported by the standard Go -// toolchain. -// -// See https://pkg.go.dev/cmd/go#hdr-Module_configuration_for_non_public_modules -type Vcs string - -// Vcs enum. -const ( - VcsBazaar Vcs = "bzr" - VcsFossil Vcs = "fossil" - VcsGit Vcs = "git" - VcsMercurial Vcs = "hg" - VcsSubversion Vcs = "svn" -) - -// Module represents a Go module to index. -type Module struct { - Path string // module path (without domain) - Vcs Vcs // vcs system - Repo string // repository's home - Dir string // url template - File string // url template - - // internal - mu sync.Mutex -} - -// GenerateFile generates the index file. -func (m *Module) GenerateFile(out string, domain string) error { - m.mu.Lock() - p := m.Path - v := m.Vcs - r := m.Repo - d := m.Dir - f := m.File - m.mu.Unlock() - - outf := path.Join(out, p+".html") - - // Create the file. - if strings.Contains(p, "/") { - if err := os.MkdirAll(path.Dir(outf), 0755); err != nil { - return err - } - } - - fd, err := os.Create(outf) - if err != nil { - return err - } - defer func(fd *os.File) { - err := fd.Close() - if err != nil { - panic(err) - } - }(fd) - - // Execute the template and write the output to the file. - if err := templates.ExecModule(fd, - fmt.Sprintf("%s/%s", domain, p), string(v), r, - d, f); err != nil { - return err - } - - return nil -} diff --git a/prelude.go b/prelude.go new file mode 100644 index 0000000..d109a82 --- /dev/null +++ b/prelude.go @@ -0,0 +1,127 @@ +// Copyright Nicolas Paul (2023) +// +// * Nicolas Paul +// +// This software is a computer program whose purpose is to allow the hosting +// and sharing of Go modules using a personal domain. +// +// This software is governed by the CeCILL license under French law and +// abiding by the rules of distribution of free software. You can use, +// modify and/ or redistribute the software under the terms of the CeCILL +// license as circulated by CEA, CNRS and INRIA at the following URL +// "http://www.cecill.info". +// +// As a counterpart to the access to the source code and rights to copy, +// modify and redistribute granted by the license, users are provided only +// with a limited warranty and the software's author, the holder of the +// economic rights, and the successive licensors have only limited +// liability. +// +// In this respect, the user's attention is drawn to the risks associated +// with loading, using, modifying and/or developing or reproducing the +// software by the user in light of its specific status of free software, +// that may mean that it is complicated to manipulate, and that also +// therefore means that it is reserved for developers and experienced +// professionals having in-depth computer knowledge. Users are therefore +// encouraged to load and test the software's suitability as regards their +// requirements in conditions enabling the security of their systems and/or +// data to be ensured and, more generally, to use and operate it in the +// same conditions as regards security. +// +// The fact that you are presently reading this means that you have had +// knowledge of the CeCILL license and that you accept its terms. + +// Prelude for the Starlark environment. + +package main + +import ( + "fmt" + "go.starlark.net/starlark" + "strings" +) + +// https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names +const invalidName string = "..\\/<>:\"|?* \t\n\r\b\findex" + +// Registered represents the index of registered modules. +var Registered Index + +// InternIndex represents the built-in function "index". +// index(domain) initializes a new index with the given domain. +func InternIndex(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, + kwargs []starlark.Tuple) (starlark.Value, error) { + var domain string + if err := starlark.UnpackArgs("index", args, kwargs, + "domain", &domain); err != nil { + return nil, err + } + + Registered.Domain = domain + + return starlark.None, nil +} + +// InternModule represents the built-in function "module". +// module(name, vcs, repo, dir, file) registers a new module into the +// index. +func InternModule(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, + kwargs []starlark.Tuple) (starlark.Value, error) { + + var name, vcs, repo, dir, file string + if err := starlark.UnpackArgs("module", args, kwargs, "name", + &name, "vcs", &vcs, "repo", &repo, "dir", &dir, "file", &file); err != nil { + return nil, err + } + + if Registered.Domain == "" { + return nil, fmt.Errorf("index not initialized") + } + + if name == "" { + return nil, fmt.Errorf("module name cannot be empty") + } + + if vcs == "" { + return nil, fmt.Errorf("module %q vcs cannot be empty", name) + } + + if repo == "" { + return nil, fmt.Errorf("module %q repo cannot be empty", name) + } + + // Check for name conditions. + if strings.Contains(invalidName, name) { + return nil, fmt.Errorf("module %q name is invalid", name) + } + + if Registered.CheckModule(name) { + return nil, fmt.Errorf("module %q already exists", name) + } + + var v Vcs + switch vcs { + case "git": + v = VcsGit + case "hg": + v = VcsMercurial + case "svn": + v = VcsSubversion + case "fossil": + v = VcsFossil + case "bzr": + v = VcsBazaar + default: + return nil, fmt.Errorf("unknown vcs %q", vcs) + } + + Registered.AddModule(name, &Module{ + Path: name, + Vcs: v, + Repo: repo, + Dir: dir, + File: file, + }) + + return starlark.None, nil +} diff --git a/starlark.go b/starlark.go new file mode 100644 index 0000000..9f79a69 --- /dev/null +++ b/starlark.go @@ -0,0 +1,65 @@ +// Copyright Nicolas Paul (2023) +// +// * Nicolas Paul +// +// This software is a computer program whose purpose is to allow the hosting +// and sharing of Go modules using a personal domain. +// +// This software is governed by the CeCILL license under French law and +// abiding by the rules of distribution of free software. You can use, +// modify and/ or redistribute the software under the terms of the CeCILL +// license as circulated by CEA, CNRS and INRIA at the following URL +// "http://www.cecill.info". +// +// As a counterpart to the access to the source code and rights to copy, +// modify and redistribute granted by the license, users are provided only +// with a limited warranty and the software's author, the holder of the +// economic rights, and the successive licensors have only limited +// liability. +// +// In this respect, the user's attention is drawn to the risks associated +// with loading, using, modifying and/or developing or reproducing the +// software by the user in light of its specific status of free software, +// that may mean that it is complicated to manipulate, and that also +// therefore means that it is reserved for developers and experienced +// professionals having in-depth computer knowledge. Users are therefore +// encouraged to load and test the software's suitability as regards their +// requirements in conditions enabling the security of their systems and/or +// data to be ensured and, more generally, to use and operate it in the +// same conditions as regards security. +// +// The fact that you are presently reading this means that you have had +// knowledge of the CeCILL license and that you accept its terms. + +// Loader for the Starlark execution system with a pre-defined environment. + +package main + +import ( + "go.starlark.net/starlark" +) + +// ExecConfig configures the Starlark environment and executes the given +// configuration file "fl". +// The function returns a list of registered modules, or an error if something +// went wrong. +func ExecConfig(fl string) (*Index, error) { + th := &starlark.Thread{ + Name: "exec " + fl, + } + + env := starlark.StringDict{ + "index": starlark.NewBuiltin("index", InternIndex), + "module": starlark.NewBuiltin("module", InternModule), + } + + Registered = Index{ + Domain: "", + Modules: make(map[string]*Module), + } + if _, err := starlark.ExecFile(th, fl, nil, env); err != nil { + return &Index{}, err + } + + return &Registered, nil +} diff --git a/svgu.go b/svgu.go new file mode 100644 index 0000000..3a2e775 --- /dev/null +++ b/svgu.go @@ -0,0 +1,138 @@ +// Copyright Nicolas Paul (2023) +// +// * Nicolas Paul +// +// This software is a computer program whose purpose is to allow the hosting +// and sharing of Go modules using a personal domain. +// +// This software is governed by the CeCILL license under French law and +// abiding by the rules of distribution of free software. You can use, +// modify and/ or redistribute the software under the terms of the CeCILL +// license as circulated by CEA, CNRS and INRIA at the following URL +// "http://www.cecill.info". +// +// As a counterpart to the access to the source code and rights to copy, +// modify and redistribute granted by the license, users are provided only +// with a limited warranty and the software's author, the holder of the +// economic rights, and the successive licensors have only limited +// liability. +// +// In this respect, the user's attention is drawn to the risks associated +// with loading, using, modifying and/or developing or reproducing the +// software by the user in light of its specific status of free software, +// that may mean that it is complicated to manipulate, and that also +// therefore means that it is reserved for developers and experienced +// professionals having in-depth computer knowledge. Users are therefore +// encouraged to load and test the software's suitability as regards their +// requirements in conditions enabling the security of their systems and/or +// data to be ensured and, more generally, to use and operate it in the +// same conditions as regards security. +// +// The fact that you are presently reading this means that you have had +// knowledge of the CeCILL license and that you accept its terms. + +// Entry-point of the CLI program. + +// Package main implements the SVGU utility. +package main // import "go.nc0.fr/svgu" + +import ( + "flag" + "log" + "os" + "path/filepath" + "sync" +) + +var ( + cfg = flag.String("c", "DOMAINS.star", "the configuration file to use.") + out = flag.String("o", "dst", "output directory") + verbose = flag.Bool("v", false, "prints additional information logs") +) + +func main() { + log.SetFlags(0) + log.SetPrefix("svgu: ") + flag.Parse() + + // Check if the configuration file exists. + if *verbose { + log.Printf("checking if configuration file %q exists", *cfg) + } + + if cfg, err := filepath.Abs(*cfg); err != nil { + log.Fatalf("could not get absolute path of %s: %v", cfg, err) + } + + if cfgfd, err := os.Stat(*cfg); os.IsNotExist(err) || cfgfd.IsDir() { + log.Fatalf("configuration file %q does not exist", *cfg) + } else if err != nil { + log.Fatalf("could not stat %q: %v", *cfg, err) + } + + // Check if the output directory exists. + if *verbose { + log.Printf("checking if output directory %q exists", *out) + } + + if out, err := filepath.Abs(*out); err != nil { + log.Fatalf("could not get absolute path of %s: %v", out, err) + } + + if outfd, err := os.Stat(*out); outfd != nil && outfd.IsDir() { + log.Fatalf("output directory %q already exists", *out) + } else if err != nil && !os.IsNotExist(err) { + log.Fatalf("could not stat %q: %v", *out, err) + } + + // Execute the configuration file and get the registered modules. + if *verbose { + log.Printf("executing configuration file %q", *cfg) + } + + idx, err := ExecConfig(*cfg) + if err != nil { + log.Fatalf("could not execute configuration file %q: %v", *cfg, err) + } + + // Create the output directory. + if *verbose { + log.Printf("creating output directory %q", *out) + } + + if err := os.MkdirAll(*out, 0755); err != nil { + log.Fatalf("could not create output directory %q: %v", *out, err) + } + + // Generate the index file. + if *verbose { + log.Printf("generating index file") + } + + if err := idx.GenerateFile(*out); err != nil { + log.Fatalf("could not generate index file: %v", err) + } + + // Generate the modules. + if *verbose { + log.Printf("generating modules") + } + + var wg sync.WaitGroup + var mu sync.Mutex + for _, mod := range idx.Modules { + wg.Add(1) + go func(m *Module) { + defer wg.Done() + defer mu.Unlock() + + mu.Lock() + if err := m.GenerateFile(*out, idx.Domain); err != nil { + log.Fatalf("could not generate module %q: %v", m.Path, err) + } + }(mod) + } + + wg.Wait() + log.Println("done") +} diff --git a/templates.go b/templates.go new file mode 100644 index 0000000..e12413e --- /dev/null +++ b/templates.go @@ -0,0 +1,117 @@ +// Copyright Nicolas Paul (2023) +// +// * Nicolas Paul +// +// This software is a computer program whose purpose is to allow the hosting +// and sharing of Go modules using a personal domain. +// +// This software is governed by the CeCILL license under French law and +// abiding by the rules of distribution of free software. You can use, +// modify and/ or redistribute the software under the terms of the CeCILL +// license as circulated by CEA, CNRS and INRIA at the following URL +// "http://www.cecill.info". +// +// As a counterpart to the access to the source code and rights to copy, +// modify and redistribute granted by the license, users are provided only +// with a limited warranty and the software's author, the holder of the +// economic rights, and the successive licensors have only limited +// liability. +// +// In this respect, the user's attention is drawn to the risks associated +// with loading, using, modifying and/or developing or reproducing the +// software by the user in light of its specific status of free software, +// that may mean that it is complicated to manipulate, and that also +// therefore means that it is reserved for developers and experienced +// professionals having in-depth computer knowledge. Users are therefore +// encouraged to load and test the software's suitability as regards their +// requirements in conditions enabling the security of their systems and/or +// data to be ensured and, more generally, to use and operate it in the +// same conditions as regards security. +// +// The fact that you are presently reading this means that you have had +// knowledge of the CeCILL license and that you accept its terms. + +// HTML templates to generate for each module registered in the index. + +package main + +import ( + "fmt" + "html/template" + "io" +) + +// HTML document templates +var ( + // indexTmpl is the HTML template for the root of the static site. + // It redirects to a site configured by the user. + // + // {{.Count}} corresponds to the timer—in seconds— before redirecting. + // {{.Redirect}} is the URL to redirect visitors to. + indexTmpl = template.Must( + template.New("index").Parse(` + + + +

Nothing to see, redirecting here.`)) + + // moduleTmpl is the HTML template for module-specific documents. + // They include information for the Go toolchain to find and download + // the module source code. It also redirects users to the module's + // documentation on pkg.go.dev. + // + // {{.Prefix}} is the module's prefix, a.k.a. import path, + // e.g. example.com/foo + // {{.Vcs}} is the version control system used in the codebase + // {{.Home}} is the repository's home. + // {{.Dir}} is a URL template to a page listing the files inside a package + // {{.File}} is a URL template listing the lines of a file + // {{.Doc}} is the URL of the module's documentation on pkg.go.dev + // + // Templates support a specific set of substitutions which are documented + // here: https://github.com/golang/gddo/wiki/Source-Code-Links + moduleTmpl = template.Must( + template.New("module").Parse(` + + + + +

There is nothing to see, redirecting here.`)) +) + +// ExecIndex constructs an HTML document for the index of the generated site +// in the given writer "w." +// The document redirects visitor to the specified "url" after "count" seconds. +func ExecIndex(w io.Writer, url string, count uint8) error { + return indexTmpl.Execute(w, struct { + Redirect string + Count uint8 + }{ + Redirect: url, + Count: count, + }) +} + +// ExecModule constructs an HTML document for a module indexed on the domain +// in the given writer "w." The "prefix" corresponds to the import path of the +// module, "vcs" to the version control system used — git, bazaar..., "home" +// is the repository's home and "dir"/"file" are URL templates as documented +// by GoDoc: https://github.com/golang/gddo/wiki/Source-Code-Links. +func ExecModule(w io.Writer, prefix, vcs, + home, dir, file string) error { + return moduleTmpl.Execute(w, struct { + Prefix string + Vcs string + Home string + Dir string + File string + Doc string + }{ + Prefix: prefix, + Vcs: vcs, + Home: home, + Dir: dir, + File: file, + Doc: fmt.Sprintf("https://pkg.go.dev/%s", prefix), + }) +} diff --git a/templates_test.go b/templates_test.go new file mode 100644 index 0000000..ac386e1 --- /dev/null +++ b/templates_test.go @@ -0,0 +1,191 @@ +// Copyright Nicolas Paul (2023) +// +// * Nicolas Paul +// +// This software is a computer program whose purpose is to allow the hosting +// and sharing of Go modules using a personal domain. +// +// This software is governed by the CeCILL license under French law and +// abiding by the rules of distribution of free software. You can use, +// modify and/ or redistribute the software under the terms of the CeCILL +// license as circulated by CEA, CNRS and INRIA at the following URL +// "http://www.cecill.info". +// +// As a counterpart to the access to the source code and rights to copy, +// modify and redistribute granted by the license, users are provided only +// with a limited warranty and the software's author, the holder of the +// economic rights, and the successive licensors have only limited +// liability. +// +// In this respect, the user's attention is drawn to the risks associated +// with loading, using, modifying and/or developing or reproducing the +// software by the user in light of its specific status of free software, +// that may mean that it is complicated to manipulate, and that also +// therefore means that it is reserved for developers and experienced +// professionals having in-depth computer knowledge. Users are therefore +// encouraged to load and test the software's suitability as regards their +// requirements in conditions enabling the security of their systems and/or +// data to be ensured and, more generally, to use and operate it in the +// same conditions as regards security. +// +// The fact that you are presently reading this means that you have had +// knowledge of the CeCILL license and that you accept its terms. + +package main + +import ( + "bytes" + "io" + "testing" +) + +func TestExecIndex(t *testing.T) { + type args struct { + url string + count uint8 + } + tests := []struct { + name string + args args + want *bytes.Buffer + wantErr bool + }{ + { + name: "valid url waiting 5 seconds", + args: args{ + url: "https://example.com", + count: 5, + }, + want: bytes.NewBufferString(` + + + +

Nothing to see, redirecting here.`), + wantErr: false, + }, + { + name: "valid url waiting 0 second", + args: args{ + url: "https://example.com", + count: 0, + }, + want: bytes.NewBufferString(` + + + +

Nothing to see, redirecting here.`), + wantErr: false, + }, + { + name: "not the same url", + args: args{ + url: "https://example.com", + count: 0, + }, + want: bytes.NewBufferString(` + + + +

Nothing to see, redirecting here.`), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + err := ExecIndex(&buf, tt.args.url, tt.args.count) + + if !tt.wantErr && err != nil { + t.Errorf("ExecIndex() error = %v", err) + } + + if !tt.wantErr && !bytes.Equal(buf.Bytes(), tt.want.Bytes()) { + t.Errorf("ExecIndex() = %v, want %v", buf, tt.want) + } + }) + } +} + +func TestExecModule(t *testing.T) { + type args struct { + w *io.Writer + prefix string + vcs string + home string + dir string + file string + } + tests := []struct { + name string + args args + want *bytes.Buffer + wantErr bool + }{ + { + name: "module example.com/foo hosted on github", + args: args{ + prefix: "example.com/foo", + vcs: "git", + home: "https://github.com/example/foo", + dir: "https://github.com/example/foo/tree/master{/dir}", + file: "https://github.com/example/foo/blob/master{/dir}/{file}#L{line}", + }, + want: bytes.NewBufferString(` + + + + +

There is nothing to see, redirecting here.`), + wantErr: false, + }, + { + name: "module example.com/foo hosted on gitlab", + args: args{ + prefix: "example.com/foo", + vcs: "git", + home: "https://gitlab.com/example/foo", + dir: "https://gitlab.com/example/foo/-/tree/master{/dir}", + file: "https://gitlab.com/example/foo/-/blob/master{/dir}/{file}#L{line}", + }, + want: bytes.NewBufferString(` + + + + +

There is nothing to see, redirecting here.`), + wantErr: false, + }, + { + name: "module example.com/foo hosted on Source Hut with Mercurial", + args: args{ + prefix: "example.com/foo", + vcs: "hg", + home: "https://hg.sr.ht/~example/foo", + dir: "https://hg.sr.ht/~example/foo/tree/master{/dir}", + file: "https://hg.sr.ht/~example/foo/browse/master{/dir}/{file}#{line}", + }, + want: bytes.NewBufferString(` + + + + +

There is nothing to see, redirecting here.`), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + err := ExecModule(&buf, tt.args.prefix, tt.args.vcs, tt.args.home, + tt.args.dir, tt.args.file) + + if !tt.wantErr && err != nil { + t.Errorf("ExecIndex() error = %v", err) + } + + if !tt.wantErr && !bytes.Equal(buf.Bytes(), tt.want.Bytes()) { + t.Errorf("ExecIndex() = %v, want %v", buf, tt.want) + } + }) + } +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..28651b3 --- /dev/null +++ b/types.go @@ -0,0 +1,176 @@ +// Copyright Nicolas Paul (2023) +// +// * Nicolas Paul +// +// This software is a computer program whose purpose is to allow the hosting +// and sharing of Go modules using a personal domain. +// +// This software is governed by the CeCILL license under French law and +// abiding by the rules of distribution of free software. You can use, +// modify and/ or redistribute the software under the terms of the CeCILL +// license as circulated by CEA, CNRS and INRIA at the following URL +// "http://www.cecill.info". +// +// As a counterpart to the access to the source code and rights to copy, +// modify and redistribute granted by the license, users are provided only +// with a limited warranty and the software's author, the holder of the +// economic rights, and the successive licensors have only limited +// liability. +// +// In this respect, the user's attention is drawn to the risks associated +// with loading, using, modifying and/or developing or reproducing the +// software by the user in light of its specific status of free software, +// that may mean that it is complicated to manipulate, and that also +// therefore means that it is reserved for developers and experienced +// professionals having in-depth computer knowledge. Users are therefore +// encouraged to load and test the software's suitability as regards their +// requirements in conditions enabling the security of their systems and/or +// data to be ensured and, more generally, to use and operate it in the +// same conditions as regards security. +// +// The fact that you are presently reading this means that you have had +// knowledge of the CeCILL license and that you accept its terms. + +// Data structure representaton of the configuration + +package main + +import ( + "fmt" + "os" + "path" + "strings" + "sync" +) + +// Index is the global registry of Go modules. +type Index struct { + Domain string + Modules map[string]*Module + // internal + lock sync.Mutex +} + +// AddModule adds a module to the index. +func (i *Index) AddModule(n string, m *Module) { + i.lock.Lock() + defer i.lock.Unlock() + i.Modules[n] = m +} + +// GetModule returns a module from the index. +func (i *Index) GetModule(n string) *Module { + i.lock.Lock() + defer i.lock.Unlock() + return i.Modules[n] +} + +// RemoveModule removes a module from the index. +func (i *Index) RemoveModule(n string) { + i.lock.Lock() + defer i.lock.Unlock() + delete(i.Modules, n) +} + +// CheckModule checks if a module is in the index. +func (i *Index) CheckModule(n string) bool { + i.lock.Lock() + defer i.lock.Unlock() + _, ok := i.Modules[n] + return ok +} + +// GenerateFile generates the index file. +func (i *Index) GenerateFile(out string) error { + i.lock.Lock() + defer i.lock.Unlock() + + f := path.Join(out, "index.html") + + // Create the file. + fd, err := os.Create(f) + if err != nil { + return err + } + defer func(fd *os.File) { + err := fd.Close() + if err != nil { + panic(err) + } + }(fd) + + // Execute the template and write the output to the file. + if err := ExecIndex(fd, + "https://pkg.go.dev", 2); err != nil { + return err + } + + return nil +} + +// Vcs is an enum for version control systems supported by the standard Go +// toolchain. +// +// See https://pkg.go.dev/cmd/go#hdr-Module_configuration_for_non_public_modules +type Vcs string + +// Vcs enum. +const ( + VcsBazaar Vcs = "bzr" + VcsFossil Vcs = "fossil" + VcsGit Vcs = "git" + VcsMercurial Vcs = "hg" + VcsSubversion Vcs = "svn" +) + +// Module represents a Go module to index. +type Module struct { + Path string // module path (without domain) + Vcs Vcs // vcs system + Repo string // repository's home + Dir string // url template + File string // url template + + // internal + mu sync.Mutex +} + +// GenerateFile generates the index file. +func (m *Module) GenerateFile(out string, domain string) error { + m.mu.Lock() + p := m.Path + v := m.Vcs + r := m.Repo + d := m.Dir + f := m.File + m.mu.Unlock() + + outf := path.Join(out, p+".html") + + // Create the file. + if strings.Contains(p, "/") { + if err := os.MkdirAll(path.Dir(outf), 0755); err != nil { + return err + } + } + + fd, err := os.Create(outf) + if err != nil { + return err + } + defer func(fd *os.File) { + err := fd.Close() + if err != nil { + panic(err) + } + }(fd) + + // Execute the template and write the output to the file. + if err := ExecModule(fd, + fmt.Sprintf("%s/%s", domain, p), string(v), r, + d, f); err != nil { + return err + } + + return nil +} -- cgit v1.2.3