Skip to content

Commit 7c98beb

Browse files
committed
initial commit
0 parents  commit 7c98beb

File tree

9 files changed

+300
-0
lines changed

9 files changed

+300
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
gen/
2+
modgen.yaml

LICENSE

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ISC License
2+
3+
Copyright (c) 2023 Antoine Coulon
4+
5+
Permission to use, copy, modify, and/or distribute this software for any
6+
purpose with or without fee is hereby granted, provided that the above
7+
copyright notice and this permission notice appear in all copies.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15+
PERFORMANCE OF THIS SOFTWARE.

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# `modgen`
2+
Static generator for Go module import URLs.
3+
4+
## Description
5+
`modgen` generates webpages to serve an Go module import URLs (also known as Vanity URLs).
6+
Unlike similar projects (e.g. [govanityurls](https://github.com/GoogleCloudPlatform/govanityurls/tree/master)), `modgen` was designed to be static, which makes it possible to use static hosting providers — like Github Pages for example.
7+
8+
For more informations on Go module imports, please refer to the [documentation](https://go.dev/ref/mod#serving-from-proxy).
9+
10+
## Installation
11+
```
12+
go install go.essaim.dev/modgen/cmd/modgen@latest
13+
```
14+
15+
## Usage
16+
17+
```
18+
Usage of modgen:
19+
-config string
20+
path of the configuration file (default "modgen.yaml")
21+
-index-tmpl string
22+
path of an optional custom index template
23+
-module-tmpl string
24+
path of an optional custom module template
25+
-target string
26+
path where the site should be generated (default "gen/")
27+
```
28+
### Config file example
29+
```yaml
30+
host: go.essaim.dev
31+
32+
modules:
33+
- path: /modgen
34+
vcs: git
35+
repo-url: https://github.com/essaim-dev/modgen
36+
```
37+
**Note:** All fields are mandatory.
38+
39+
### Index template example
40+
```html
41+
<!DOCTYPE html>
42+
<html>
43+
<head>
44+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
45+
<title>{{.Host}}</title>
46+
</head>
47+
<body>
48+
<h1>{{.Host}}</h1>
49+
<ul>
50+
{{range .Modules}}<li><a href="{{.Path}}">{{$.Host}}{{.Path}}</a></li>{{end}}
51+
</ul>
52+
</body>
53+
</html>
54+
```
55+
56+
### Module template example
57+
```html
58+
<!DOCTYPE html>
59+
<html>
60+
<head>
61+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
62+
<meta name="go-import" content="{{.Path}} {{.VCS}} {{.RepoURL}}">
63+
<meta http-equiv="refresh" content="1; url=https://pkg.go.dev/{{.Path}}">
64+
</head>
65+
<body>
66+
<h1>{{.Path}}</h1>
67+
<p><a href="https://pkg.go.dev/{{.Path}}">See the package on pkg.go.dev</a>.</p>
68+
</body>
69+
</html>
70+
```

cmd/modgen/main.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"log"
6+
7+
"go.essaim.dev/modgen"
8+
)
9+
10+
var (
11+
configPath = flag.String("config", "modgen.yaml", "path of the configuration file")
12+
targetPath = flag.String("target", "gen/", "path where the site should be generated")
13+
indexTmplPath = flag.String("index-tmpl", "", "path of an optional custom index template")
14+
moduleTmplPath = flag.String("module-tmpl", "", "path of an optional custom module template")
15+
)
16+
17+
func main() {
18+
flag.Parse()
19+
20+
config, err := modgen.LoadConfig(*configPath)
21+
if err != nil {
22+
log.Fatalf("could not parse config file: %s\n", err)
23+
}
24+
25+
g := modgen.NewGenerator(config)
26+
27+
if *indexTmplPath != "" {
28+
if err := g.WithIndexTemplate(*indexTmplPath); err != nil {
29+
log.Fatalf("could not parse custom index template file: %s\n", err)
30+
}
31+
}
32+
33+
if *moduleTmplPath != "" {
34+
if err := g.WithIndexTemplate(*moduleTmplPath); err != nil {
35+
log.Fatalf("could not parse custom module template file: %s\n", err)
36+
}
37+
}
38+
39+
if err := g.Generate(*targetPath); err != nil {
40+
log.Fatalf("could not generate the site: %s\n", err)
41+
}
42+
}

config.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package modgen
2+
3+
import (
4+
"os"
5+
"path"
6+
7+
"gopkg.in/yaml.v3"
8+
)
9+
10+
// Config defines the format of a modgen configuration file.
11+
type Config struct {
12+
Host string `yaml:"host"`
13+
Modules []ModuleConfig `yaml:"modules"`
14+
}
15+
16+
// Config defines an individual module from a configuration file.
17+
type ModuleConfig struct {
18+
Path string `yaml:"path"`
19+
VCS string `yaml:"vcs"`
20+
RepoURL string `yaml:"repo-url"`
21+
}
22+
23+
// LoadConfig tries to load and parse a modgen configuration file at the given path.
24+
func LoadConfig(path string) (Config, error) {
25+
raw, err := os.ReadFile(path)
26+
if err != nil {
27+
return Config{}, err
28+
}
29+
30+
var config Config
31+
err = yaml.Unmarshal(raw, &config)
32+
33+
return config, err
34+
}
35+
36+
// withHost returns a copy of the ModuleConfig with the given host prepended to the Path field.
37+
func (mc ModuleConfig) withHost(host string) ModuleConfig {
38+
mc.Path = path.Join(host, mc.Path)
39+
40+
return mc
41+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module go.essaim.dev/modgen
2+
3+
go 1.20
4+
5+
require gopkg.in/yaml.v3 v3.0.1

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

modgen.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package modgen
2+
3+
import (
4+
"bytes"
5+
"html/template"
6+
"os"
7+
"path"
8+
)
9+
10+
// Generator contains the context required to generate a Go module import URL static site.
11+
type Generator struct {
12+
config Config
13+
indexTmpl *template.Template
14+
moduleTmpl *template.Template
15+
}
16+
17+
func NewGenerator(config Config) *Generator {
18+
return &Generator{
19+
config: config,
20+
indexTmpl: indexTmpl,
21+
moduleTmpl: moduleTmpl,
22+
}
23+
}
24+
25+
func (g *Generator) WithIndexTemplate(path string) error {
26+
raw, err := os.ReadFile(path)
27+
if err != nil {
28+
return err
29+
}
30+
31+
tmpl, err := template.New("index-custom").Parse(string(raw))
32+
if err != nil {
33+
return err
34+
}
35+
g.indexTmpl = tmpl
36+
37+
return nil
38+
}
39+
40+
func (g *Generator) WithModuleTemplate(path string) error {
41+
raw, err := os.ReadFile(path)
42+
if err != nil {
43+
return err
44+
}
45+
46+
tmpl, err := template.New("module-custom").Parse(string(raw))
47+
if err != nil {
48+
return err
49+
}
50+
g.moduleTmpl = tmpl
51+
52+
return nil
53+
}
54+
55+
func (g *Generator) Generate(target string) error {
56+
if err := os.MkdirAll(target, 0755); err != nil {
57+
return err
58+
}
59+
60+
if err := g.generateIndex(path.Join(target, "index.html")); err != nil {
61+
return err
62+
}
63+
64+
for _, module := range g.config.Modules {
65+
modulePath := path.Join(target, module.Path+".html")
66+
if err := g.generateModule(modulePath, module); err != nil {
67+
return err
68+
}
69+
}
70+
71+
return nil
72+
}
73+
74+
func (g *Generator) generateIndex(target string) error {
75+
b := bytes.Buffer{}
76+
if err := g.indexTmpl.Execute(&b, g.config); err != nil {
77+
return err
78+
}
79+
80+
return os.WriteFile(target, b.Bytes(), 0644)
81+
}
82+
83+
func (g *Generator) generateModule(target string, module ModuleConfig) error {
84+
b := bytes.Buffer{}
85+
if err := g.moduleTmpl.Execute(&b, module.withHost(g.config.Host)); err != nil {
86+
return err
87+
}
88+
89+
return os.WriteFile(target, b.Bytes(), 0644)
90+
}

template.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package modgen
2+
3+
import "html/template"
4+
5+
var indexTmpl = template.Must(template.New("index").Parse(`<!DOCTYPE html>
6+
<html>
7+
<head>
8+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
9+
<title>{{.Host}}</title>
10+
</head>
11+
<body>
12+
<h1>{{.Host}}</h1>
13+
<ul>
14+
{{range .Modules}}<li><a href="{{.Path}}">{{$.Host}}{{.Path}}</a></li>{{end}}
15+
</ul>
16+
</body>
17+
</html>
18+
`))
19+
20+
var moduleTmpl = template.Must(template.New("module").Parse(`<!DOCTYPE html>
21+
<html>
22+
<head>
23+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
24+
<meta name="go-import" content="{{.Path}} {{.VCS}} {{.RepoURL}}">
25+
<meta http-equiv="refresh" content="1; url=https://pkg.go.dev/{{.Path}}">
26+
</head>
27+
<body>
28+
<h1>{{.Path}}</h1>
29+
<p><a href="https://pkg.go.dev/{{.Path}}">See the package on pkg.go.dev</a>.</p>
30+
</body>
31+
</html>`))

0 commit comments

Comments
 (0)