summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Paul <n@nc0.fr>2023-05-26 14:13:11 +0200
committerNicolas Paul <n@nc0.fr>2023-05-26 14:13:11 +0200
commite00e455cafb72edb4bddd7bc087e1fb0ff1e4e45 (patch)
tree7f3d4782862595c3da5a4d5c6c9a73b21a017bae
Migrating from GitHub
-rw-r--r--.github/workflows/ci.yml41
-rw-r--r--.gitignore24
-rw-r--r--LICENSE28
-rw-r--r--README.md76
-rw-r--r--config.go51
-rw-r--r--doc/example-files.pngbin0 -> 216074 bytes
-rw-r--r--examples/vanity.yaml16
-rw-r--r--go.mod5
-rw-r--r--go.sum4
-rw-r--r--main.go98
-rw-r--r--templates.go95
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cc1674c
--- /dev/null
+++ b/LICENSE
@@ -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:
+
+![Directory listing](doc/example-files.png)
+
+## 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
new file mode 100644
index 0000000..2844df7
--- /dev/null
+++ b/doc/example-files.png
Binary files differ
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}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..dc8b415
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module go.nc0.fr/staticgovanityurls
+
+go 1.19
+
+require gopkg.in/yaml.v3 v3.0.1
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..a62c313
--- /dev/null
+++ b/go.sum
@@ -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=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..723cce7
--- /dev/null
+++ b/main.go
@@ -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,
+ })
+}