diff options
| author | Nicolas Paul <n@nc0.fr> | 2023-05-26 14:13:11 +0200 |
|---|---|---|
| committer | Nicolas Paul <n@nc0.fr> | 2023-05-26 14:13:11 +0200 |
| commit | e00e455cafb72edb4bddd7bc087e1fb0ff1e4e45 (patch) | |
| tree | 7f3d4782862595c3da5a4d5c6c9a73b21a017bae | |
Migrating from GitHub
| -rw-r--r-- | .github/workflows/ci.yml | 41 | ||||
| -rw-r--r-- | .gitignore | 24 | ||||
| -rw-r--r-- | LICENSE | 28 | ||||
| -rw-r--r-- | README.md | 76 | ||||
| -rw-r--r-- | config.go | 51 | ||||
| -rw-r--r-- | doc/example-files.png | bin | 0 -> 216074 bytes | |||
| -rw-r--r-- | examples/vanity.yaml | 16 | ||||
| -rw-r--r-- | go.mod | 5 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | main.go | 98 | ||||
| -rw-r--r-- | templates.go | 95 |
11 files changed, 438 insertions, 0 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0e8a5a6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +# Copyright (c) 2023 Nicolas Paul All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +name: CI +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + go: + name: Golang CI + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + strategy: + fail-fast: true + matrix: + os: + - ubuntu-22.04 + - macos-12 + go_version: + - "1.19" + steps: + - uses: actions/checkout@v3 + name: Cloning repository + - uses: actions/setup-go@v4 + name: Installing Go ${{ matrix.go_version }} + with: + go-version: ${{ matrix.go_version }} + - name: Building project + run: go build -race -v -o=staticgovanityurls . + - name: Testing project + run: go test + - name: Downloading goimports + run: go install golang.org/x/tools/cmd/goimports@latest + - name: Formatting project + run: goimports -e -d -l . + # TODO: doc: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1e61c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +/.vscode/ +/.idea/ + +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Nicolas Paul + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1641632 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# staticgovanityurls + +`staticgovanityurls` (Static Go Vanity URLs) is a simple script that generates +documents to index Go modules on custom domain names. + +## Usage + +Using the script is dead-simple! All you need is a valid configuration file and +a copy of the compiled executable. If you have Go installed on the host, you +can install the script by running: + +```bash +$ go install go.nc0.fr/staticgovanityurls@latest +``` + +Once the binary is installed – and available in $PATH, you will need to write a +configuration file. +Here is a sample one: + +```yaml +hostname: "go.example.com" +paths: + - prefix: "foo" + repository: "https://github.com/example/foo.git" + vcs: "git" + dir: "https://github.com/example/foo/tree/master{/dir}" + file: "https://github.com/example/foo/blob/master{/dir}/{file}#L{line}" + - prefix: "bar" + repository: "https://svn.example.com/~baz/bar.svn" + vcs: "svn" + dir: "https://svn.example.com/~baz/bar.svn{/dir}" + file: "https://svn.example.com/~baz/bar.svn{/dir}/{file}#{line}" +``` + +When you are ready, you can generate HTML documents by executing: + +```bash +$ staticgovanityurls -i=vanity.yaml -o=dist +``` + +> The `-i` flag is used to specify the input file, while `-o` is used to +> define the output directory. + +Inside the `dist` directory, you should find a set of files as follows: + + + +## Configuration + +The configuration file is a YAML document that contains the following fields: + +| Field | Type | Description | +| ---------- | ------ | ------------------------------- | +| `hostname` | string | The hostname of the vanity URL. | +| `paths` | array | A list of paths to index. | + +Each path is a map that contains the following fields: + +| Field | Type | Description | +| ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `prefix` | string | The prefix of the vanity URL. | +| `repository` | string | The URL of the repository. | +| `vcs` | string | The version control system used: `git`, `svn`, `bzr`, `hg` or `fossil`. | +| `dir` | string | URL to a document listing the files inside a directory of the module. It supports substitutions from the `go-source` meta tag.[^go-source] | +| `file` | string | URL to a document listing the content – lines – inside a file of the module. It supports substitutions from the `go-source` meta tag[^go-source]. | + +[^go-source]: https://github.com/golang/gddo/wiki/Source-Code-Links + +## License + +The project is governed by a BSD-style license that can be found in the +[LICENSE](LICENSE) file. + +The Gopher illustrations used are under the [CC0](https://github.com/egonelbre/gophers/blob/master/LICENSE-CC0) +license. + diff --git a/config.go b/config.go new file mode 100644 index 0000000..505c5c3 --- /dev/null +++ b/config.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 Nicolas Paul All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Config represents the content of the `vanity.yaml` configuration file. +// +// Hostname is the used domain, e.g. `go.example.com`. +// +// Paths describes the various import paths to generate. +type Config struct { + Hostname string `yaml:"hostname"` + Paths []Path `yaml:"paths"` +} + +// Path represents an import path. +// +// Repository is the repository URL as it would appear in `go-import` meta tags +// (https://golang.org/cmd/go/#hdr-Remote_import_paths). +// +// Vcs marks the version control system used on the defined repository. +// +// Dir is the URL template for a page listing the files in the package. +// Available substitutions: `{dir}`, `{/dir}`. +// +// File is the URL template for a link to a line in a source file. +// Available substitutions: `{file}`, `{line}`. +// +// Prefix represents the URL path in the total import path, without a +// leading `/`, e.g. `foo`. +// +// Read more about substitutions: https://github.com/golang/gddo/wiki/Source-Code-Links +type Path struct { + Dir string `yaml:"dir"` + File string `yaml:"file"` + Prefix string `yaml:"prefix"` + Repository string `yaml:"repository"` + Vcs VCS `yaml:"vcs"` +} + +// VCS represents a version control system for a Go repository. +type VCS string + +const ( + VcsBazaar VCS = "bzr" + VcsFossil VCS = "fossil" + VcsGit VCS = "git" + VcsMercurial VCS = "hg" + VcsSubversion VCS = "svn" +) diff --git a/doc/example-files.png b/doc/example-files.png Binary files differnew file mode 100644 index 0000000..2844df7 --- /dev/null +++ b/doc/example-files.png diff --git a/examples/vanity.yaml b/examples/vanity.yaml new file mode 100644 index 0000000..78d9dca --- /dev/null +++ b/examples/vanity.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2023 Nicolas Paul All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +hostname: example.com +paths: + - prefix: foo + repository: https://github.com/example/foo.git + vcs: git + dir: https://github.com/example/foo/tree/master{/dir} + file: https://github.com/example/foo/blob/master{/dir}/{file}#L{line} + - prefix: bar + repository: https://svn.example.com/~baz/bar.svn + vcs: svn + dir: https://svn.example.com/~baz/bar.svn{/dir} + file: https://svn.example.com/~baz/bar.svn{/dir}/{file}#{line} @@ -0,0 +1,5 @@ +module go.nc0.fr/staticgovanityurls + +go 1.19 + +require gopkg.in/yaml.v3 v3.0.1 @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -0,0 +1,98 @@ +// Copyright (c) 2023 Nicolas Paul All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "gopkg.in/yaml.v3" +) + +var ( + inputfile = flag.String("i", "", "Input configuration file.") + outputdir = flag.String("o", "dist", + "Place output files in the specified directory, by default it is `dist`.") +) + +func main() { + flag.Parse() + log.SetFlags(0) + log.SetPrefix("staticgovanityurls: ") + + if *inputfile == "" { + log.Fatalln("Missing input configuration file.") + } + + if *outputdir == "" { + log.Fatalln("Missing output directory.") + } + + // Read the input configuration file and parse it. + b, err := os.ReadFile(*inputfile) + if err != nil { + log.Fatalf("Failed to read file %s: %v\n", *inputfile, err) + } + + cfg := new(Config) + if err := yaml.Unmarshal(b, cfg); err != nil { + log.Fatalf("Failed to parse configuration file: %v\n", err) + } + + // Create output directory and files + // The index file ($OUTPUT/index.html) should contain an index of the + // modules listed in cfg.paths. + if err := os.Mkdir(*outputdir, 0777); err != nil { + log.Fatalf("Unable to create output directory %s: %v\n", *outputdir, err) + } + + // Create a list of all paths' prefixes with the hostname. + var pfs []string + for _, p := range cfg.Paths { + pfs = append(pfs, fmt.Sprintf("%s/%s", cfg.Hostname, p.Prefix)) + } + + // Create index file + idxpath := fmt.Sprintf("%s/index.html", *outputdir) + indexfl, err := os.Create(idxpath) + if err != nil { + log.Fatalf("Unable to create file %s: %v\n", idxpath, err) + } + defer func(fl *os.File) { + err := fl.Close() + if err != nil { + log.Fatalf("Cannot close file %s: %v\n", idxpath, err) + } + }(indexfl) + + if err := executeIndex(indexfl, cfg.Hostname, pfs); err != nil { + log.Fatalf("Cannot execute template: %v\n", err) + } + log.Printf("Generated %s\n", idxpath) + + // Create path files + for _, p := range cfg.Paths { + if err := os.Mkdir(fmt.Sprintf("%s/%s", *outputdir, p.Prefix), 0777); err != nil { + log.Fatalf("Unable to create directory %s/%s: %v\n", *outputdir, p.Prefix, err) + } + + ppath := fmt.Sprintf("%s/%s/index.html", *outputdir, p.Prefix) + pathfl, err := os.Create(ppath) + if err != nil { + log.Fatalf("Unable to create file %s: %v\n", ppath, err) + } + defer func(fl *os.File) { + err := fl.Close() + if err != nil { + log.Fatalf("Cannot close file %s: %v\n", ppath, err) + } + }(pathfl) + + executePath(pathfl, cfg.Hostname, p.Prefix, p.Vcs, p.Repository, p.Dir, p.File) + log.Printf("Generated %s\n", ppath) + } +} diff --git a/templates.go b/templates.go new file mode 100644 index 0000000..b6b274b --- /dev/null +++ b/templates.go @@ -0,0 +1,95 @@ +// Copyright (c) 2023 Nicolas Paul All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "html/template" + "io" +) + +// indextmpl is the HTML template to generate for the index page of the static +// site (route "/"). +var indextmpl = template.Must( + template.New("index").Parse(`<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta http-equiv="content-type" content="text/html" charset="UTF-8"> + <title>{{.Hostname}}</title> + <meta name="generator" content="staticgovanityurls (https://staticgovanityurls.nc0.fr)"> + <meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0,user-scalable=yes"> + </head> + <body> + <h1>{{.Hostname}}</h1> + <ul> + {{range .Paths}}<li><a href="https://{{.}}">{{.}}</a></li> + {{end}} + </ul> + </body> +</html>`), +) + +// executeIndex generates the Index template using the given variables. +// paths is a list of import path (containing both hostname and prefix). +func executeIndex(o io.Writer, hostname string, paths []string) error { + return indextmpl.Execute(o, struct { + Hostname string + Paths []string + }{ + Hostname: hostname, + Paths: paths, + }) +} + +// pathtmpl is the HTML template to generate for the page of a module. +var pathtmpl = template.Must( + template.New("path").Parse(`<!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta http-equiv="content-type" content="text/html" charset="UTF-8"> + <meta name="generator" content="staticgovanityurls (https://staticgovanityurls.nc0.fr)"> + <meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0,user-scalable=yes"> + <meta name="go-import" content="{{.Prefix}} {{.Vcs}} {{.Repo}}"> + <meta name="go-source" content="{{.Prefix}} {{.Repo}} {{.Dir}} {{.File}}"> + <title>{{.Prefix}}</title> + </head> + <body> + <h1>{{.Prefix}}</h1> + <ul> + <li><a href="https://pkg.go.dev/{{.Prefix}}">Documentation</a></li> + <li><a href="{{.Repo}}">Source ({{.Vcs}})</a></li> + </ul> + </body> + </html>`), +) + +// executePath generates the path template using the given variables. +func executePath( + o io.Writer, + hostname string, + prefix string, + vcs VCS, + repo string, + dir string, + file string, +) error { + return pathtmpl.Execute(o, struct { + Prefix string + Repo string + Dir string + File string + Vcs VCS + }{ + Prefix: fmt.Sprintf("%s/%s", hostname, prefix), + Repo: repo, + Vcs: vcs, + Dir: dir, + File: file, + }) +} |
