├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile.vangen ├── LICENSE ├── Makefile ├── README.md ├── cloudbuild.yaml ├── config.go ├── config_test.go ├── example ├── vangen.json └── vangen │ ├── index.html │ ├── pkg1 │ └── index.html │ ├── pkg2 │ ├── index.html │ ├── subpkg1 │ │ └── subsubpkg1 │ │ │ └── index.html │ └── subpkg2 │ │ ├── subsubpkg1 │ │ └── index.html │ │ ├── subsubpkg2 │ │ └── index.html │ │ └── subsubpkg3 │ │ └── index.html │ └── pkg3 │ ├── index.html │ └── subpkg1 │ └── index.html ├── generate_index.go ├── generate_index_test.go ├── generate_package.go ├── generate_package_test.go ├── go.mod ├── go.sum └── main.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*' 5 | jobs: 6 | goreleaser: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-go@v2 11 | - uses: goreleaser/goreleaser-action@v2 12 | with: 13 | version: latest 14 | args: release --rm-dist 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | jobs: 3 | test: 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v2 7 | - uses: actions/setup-go@v2 8 | - run: go test -cover ./... 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vangen 2 | dist/ 3 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - binary: vangen 3 | goos: 4 | - linux 5 | - darwin 6 | - windows 7 | goarch: 8 | - amd64 9 | 10 | archives: 11 | - format: tar.gz 12 | files: 13 | - LICENSE 14 | format_overrides: 15 | - goos: windows 16 | format: zip 17 | -------------------------------------------------------------------------------- /Dockerfile.vangen: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY vangen vangen 3 | ENTRYPOINT ["/vangen"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Leigh McCulloch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test build release 2 | 3 | test: 4 | go test -cover 5 | go vet 6 | ./vangen -config=example/vangen.json -out=example/vangen 7 | 8 | build: 9 | go build 10 | 11 | release: 12 | goreleaser 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vangen 2 | ![.github/workflows/test.yml](https://github.com/leighmcculloch/vangen/workflows/.github/workflows/test.yml/badge.svg) 3 | ![.github/workflows/release.yml](https://github.com/leighmcculloch/vangen/workflows/.github/workflows/release.yml/badge.svg) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/leighmcculloch/vangen)](https://goreportcard.com/report/github.com/leighmcculloch/vangen) 5 | [![Go docs](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/leighmcculloch/vangen) 6 | 7 | Vangen is a tool for generating static HTML for Go vanity import paths. 8 | 9 | Go vanity import paths work by serving a HTML file that tells the `go get` tool where to download the source from. You can still host the source code at Github, BitBucket, but the vanity URL gives you portability and other benefits. 10 | 11 | ## Why 12 | * Maintain Go vanity import paths with a simple definition file `vangen.json`. 13 | * Host Go vanity import paths using static hosting. No need for Google AppEngine, Heroku, etc. Host the files on Github Pages, AWS S3, Google Cloud Storage, etc. 14 | 15 | ## Install 16 | 17 | ### Source 18 | 19 | ``` 20 | go get 4d63.com/vangen 21 | ``` 22 | 23 | ### Linux 24 | 25 | ``` 26 | curl -sSL https://github.com/leighmcculloch/vangen/releases/download/v1.1.3/vangen_1.1.3_linux_amd64.tar.gz | tar xz -C /usr/local/bin vangen 27 | ``` 28 | 29 | ### Mac 30 | 31 | ``` 32 | curl -sSL https://github.com/leighmcculloch/vangen/releases/download/v1.1.3/vangen_1.1.3_darwin_amd64.tar.gz | tar xz -C /usr/local/bin vangen 33 | ``` 34 | 35 | ### Windows 36 | 37 | [Download the executable](https://github.com/leighmcculloch/vangen/releases/download/v1.1.3/vangen_1.1.3_windows_amd64.zip), and save it to your path. 38 | 39 | ## Usage 40 | 41 | 1. Create a `vangen.json` (see examples below) 42 | 2. Run `vangen` 43 | 3. Host the files outputted in `vangen/` at your domain 44 | 4. Try it out with `go get [domain]/[package]` 45 | 46 | ``` 47 | $ vangen -help 48 | Vangen is a tool for generating static HTML for hosting Go repositories at a vanity import path. 49 | 50 | Usage: 51 | 52 | vangen [-config=vangen.json] [-out=vangen/] 53 | 54 | Flags: 55 | 56 | -config filename 57 | vangen json configuration filename (default "vangen.json") 58 | -help 59 | print this help list 60 | -out directory 61 | output directory that static files will be written to (default "vangen/") 62 | -verbose 63 | print verbose output when run 64 | -version 65 | print program version 66 | ``` 67 | 68 | ## Examples 69 | 70 | ### Minimal 71 | 72 | The repository `type` and `source` properties will be set automatically when `url` begins with `https://github.com` or `https://gitlab.com`. Below is a minimal config for a project hosted on GitHub. 73 | 74 | ```json 75 | { 76 | "domain": "4d63.com", 77 | "repositories": [ 78 | { 79 | "prefix": "optional", 80 | "subs": [ 81 | "template" 82 | ], 83 | "url": "https://github.com/leighmcculloch/go-optional" 84 | } 85 | ] 86 | } 87 | ``` 88 | 89 | ### All fields 90 | 91 | ```json 92 | { 93 | "domain": "4d63.com", 94 | "docsDomain": "pkg.go.dev", 95 | "repositories": [ 96 | { 97 | "prefix": "optional", 98 | "subs": [ 99 | "template" 100 | ], 101 | "type": "git", 102 | "hidden": false, 103 | "url": "https://github.com/leighmcculloch/go-optional", 104 | "source": { 105 | "home": "https://github.com/leighmcculloch/go-optional", 106 | "dir": "https://github.com/leighmcculloch/go-optional/tree/master{/dir}", 107 | "file": "https://github.com/leighmcculloch/go-optional/blob/master{/dir}/{file}#L{line}" 108 | }, 109 | "website": { 110 | "url": "https://github.com/leighmcculoch/go-optional" 111 | } 112 | } 113 | ] 114 | } 115 | ``` 116 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: 'golang' 3 | args: ['go', 'build'] 4 | - name: 'gcr.io/cloud-builders/docker' 5 | args: ['build', '-t', 'gcr.io/$PROJECT_ID/vangen', '-f', 'Dockerfile.vangen', '.'] 6 | images: 7 | - 'gcr.io/$PROJECT_ID/vangen' 8 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "io/ioutil" 7 | "path" 8 | "sort" 9 | ) 10 | 11 | type config struct { 12 | Domain string `json:"domain"` 13 | DocsDomain string `json:"docsDomain"` 14 | Index bool `json:"index"` 15 | Repositories []repository `json:"repositories"` 16 | } 17 | 18 | type repository struct { 19 | Prefix string `json:"prefix"` 20 | Subs []sub `json:"subs"` 21 | Type string `json:"type"` 22 | URL string `json:"url"` 23 | Main bool `json:"main"` 24 | Hidden bool `json:"hidden"` 25 | SourceURLs sourceURLs `json:"source"` 26 | Website website `json:"website"` 27 | } 28 | 29 | func (r repository) PrefixPath() string { 30 | if r.Prefix == "" { 31 | return "" 32 | } else { 33 | return "/" + r.Prefix 34 | } 35 | } 36 | 37 | func (r repository) Packages() []string { 38 | pkgs := []string{r.Prefix} 39 | for i := range r.Subs { 40 | pkgs = append(pkgs, r.SubPath(i)) 41 | } 42 | return pkgs 43 | } 44 | 45 | func (r repository) SubPath(i int) string { 46 | return path.Join(r.Prefix, r.Subs[i].Name) 47 | } 48 | 49 | type sub struct { 50 | Name string 51 | Hidden bool 52 | } 53 | 54 | func (s *sub) UnmarshalJSON(raw []byte) error { 55 | *s = sub{} 56 | 57 | err := json.Unmarshal(raw, &s.Name) 58 | if err == nil { 59 | return nil 60 | } 61 | 62 | subWithTags := struct { 63 | Name string `json:"name"` 64 | Hidden bool `json:"hidden"` 65 | }{} 66 | err = json.Unmarshal(raw, &subWithTags) 67 | if err != nil { 68 | return err 69 | } 70 | *s = sub(subWithTags) 71 | return nil 72 | } 73 | 74 | type sourceURLs struct { 75 | Home string `json:"home"` 76 | Dir string `json:"dir"` 77 | File string `json:"file"` 78 | } 79 | 80 | type website struct { 81 | URL string `json:"url"` 82 | } 83 | 84 | func parseConfig(r io.Reader) (config, error) { 85 | bytes, err := ioutil.ReadAll(r) 86 | if err != nil { 87 | return config{}, err 88 | } 89 | 90 | var c config 91 | err = json.Unmarshal(bytes, &c) 92 | if err != nil { 93 | return config{}, err 94 | } 95 | 96 | sort.Slice(c.Repositories, func(i, j int) bool { 97 | return c.Repositories[i].Prefix < c.Repositories[j].Prefix 98 | }) 99 | 100 | return c, nil 101 | } 102 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestParseConfigNoIndex(t *testing.T) { 10 | r := strings.NewReader(`{}`) 11 | 12 | c, err := parseConfig(r) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | if g, w := c.Index, false; g != w { 18 | t.Errorf("Got index %#v, want %#v", g, w) 19 | } 20 | } 21 | 22 | func TestParseConfigIndex(t *testing.T) { 23 | r := strings.NewReader(`{ 24 | "index": true 25 | }`) 26 | 27 | c, err := parseConfig(r) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | if g, w := c.Index, true; g != w { 33 | t.Errorf("Got index %#v, want %#v", g, w) 34 | } 35 | } 36 | 37 | func TestParseConfigPackages(t *testing.T) { 38 | r := strings.NewReader(`{ 39 | "repositories": [ 40 | { 41 | "prefix": "foo", 42 | "subs": [ 43 | "bar", 44 | "car", 45 | "car/dar", 46 | "ear/far" 47 | ] 48 | } 49 | ] 50 | }`) 51 | 52 | c, err := parseConfig(r) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | p := c.Repositories[0].Packages() 57 | 58 | e := []string{ 59 | "foo", 60 | "foo/bar", 61 | "foo/car", 62 | "foo/car/dar", 63 | "foo/ear/far", 64 | } 65 | 66 | if !reflect.DeepEqual(p, e) { 67 | t.Errorf("Got packages %#v, want %#v", p, e) 68 | } 69 | } 70 | 71 | func TestParseConfigHiddenPackages(t *testing.T) { 72 | r := strings.NewReader(`{ 73 | "repositories": [ 74 | { 75 | "prefix": "foo", 76 | "subs": [ 77 | "bar", 78 | "car", 79 | { "name": "car/dar", "hidden": true }, 80 | { "name": "ear/far", "hidden": false } 81 | ] 82 | } 83 | ] 84 | }`) 85 | 86 | c, err := parseConfig(r) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | s := c.Repositories[0].Subs 91 | 92 | e := []sub{ 93 | {Name: "bar"}, 94 | {Name: "car"}, 95 | {Name: "car/dar", Hidden: true}, 96 | {Name: "ear/far", Hidden: false}, 97 | } 98 | 99 | if !reflect.DeepEqual(s, e) { 100 | t.Errorf("Got packages %#v, want %#v", s, e) 101 | } 102 | } 103 | 104 | func TestParseConfigGithubMinimal(t *testing.T) { 105 | r := strings.NewReader(`{ 106 | "domain": "4d63.com", 107 | "repositories": [ 108 | { 109 | "prefix": "optional", 110 | "subs": [ 111 | "template" 112 | ], 113 | "url": "https://github.com/leighmcculloch/go-optional" 114 | } 115 | ] 116 | }`) 117 | 118 | e := config{ 119 | Domain: "4d63.com", 120 | Repositories: []repository{ 121 | { 122 | Prefix: "optional", 123 | Subs: []sub{ 124 | {Name: "template"}, 125 | }, 126 | URL: "https://github.com/leighmcculloch/go-optional", 127 | }, 128 | }, 129 | } 130 | 131 | c, err := parseConfig(r) 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | 136 | if !reflect.DeepEqual(c, e) { 137 | t.Errorf("Got config %#v, want %#v", c, e) 138 | } 139 | } 140 | 141 | func TestParseConfigGithubComplete(t *testing.T) { 142 | r := strings.NewReader(`{ 143 | "domain": "4d63.com", 144 | "repositories": [ 145 | { 146 | "prefix": "optional", 147 | "subs": [ 148 | "template" 149 | ], 150 | "type": "git", 151 | "url": "https://github.com/leighmcculloch/go-optional", 152 | "source": { 153 | "home": "https://github.com/leighmcculloch/go-optional", 154 | "dir": "https://github.com/leighmcculloch/go-optional/tree/master{/dir}", 155 | "file": "https://github.com/leighmcculloch/go-optional/blob/master{/dir}/{file}#L{line}" 156 | }, 157 | "website": { 158 | "url": "https://github.com/leighmcculloch/go-optional" 159 | } 160 | } 161 | ] 162 | }`) 163 | 164 | e := config{ 165 | Domain: "4d63.com", 166 | Repositories: []repository{ 167 | { 168 | Prefix: "optional", 169 | Subs: []sub{ 170 | {Name: "template"}, 171 | }, 172 | Type: "git", 173 | URL: "https://github.com/leighmcculloch/go-optional", 174 | SourceURLs: sourceURLs{ 175 | Home: "https://github.com/leighmcculloch/go-optional", 176 | Dir: "https://github.com/leighmcculloch/go-optional/tree/master{/dir}", 177 | File: "https://github.com/leighmcculloch/go-optional/blob/master{/dir}/{file}#L{line}", 178 | }, 179 | Website: website{ 180 | URL: "https://github.com/leighmcculloch/go-optional", 181 | }, 182 | }, 183 | }, 184 | } 185 | 186 | c, err := parseConfig(r) 187 | if err != nil { 188 | t.Fatal(err) 189 | } 190 | 191 | if !reflect.DeepEqual(c, e) { 192 | t.Errorf("Got config %#v, want %#v", c, e) 193 | } 194 | } 195 | 196 | func TestParseConfigGitlabMinimal(t *testing.T) { 197 | r := strings.NewReader(`{ 198 | "domain": "4d63.com", 199 | "repositories": [ 200 | { 201 | "prefix": "optional", 202 | "subs": [ 203 | "template" 204 | ], 205 | "url": "https://gitlab.com/leighmcculloch/go-optional" 206 | } 207 | ] 208 | }`) 209 | 210 | e := config{ 211 | Domain: "4d63.com", 212 | Repositories: []repository{ 213 | { 214 | Prefix: "optional", 215 | Subs: []sub{ 216 | {Name: "template"}, 217 | }, 218 | URL: "https://gitlab.com/leighmcculloch/go-optional", 219 | }, 220 | }, 221 | } 222 | 223 | c, err := parseConfig(r) 224 | if err != nil { 225 | t.Fatal(err) 226 | } 227 | 228 | if !reflect.DeepEqual(c, e) { 229 | t.Errorf("Got config %#v, want %#v", c, e) 230 | } 231 | } 232 | 233 | func TestParseConfigGitlabComplete(t *testing.T) { 234 | r := strings.NewReader(`{ 235 | "domain": "4d63.com", 236 | "repositories": [ 237 | { 238 | "prefix": "optional", 239 | "subs": [ 240 | "template" 241 | ], 242 | "type": "git", 243 | "url": "https://gitlab.com/leighmcculloch/go-optional", 244 | "source": { 245 | "home": "https://gitlab.com/leighmcculloch/go-optional", 246 | "dir": "https://gitlab.com/leighmcculloch/go-optional/tree/master{/dir}", 247 | "file": "https://gitlab.com/leighmcculloch/go-optional/blob/master{/dir}/{file}#L{line}" 248 | }, 249 | "website": { 250 | "url": "https://gitlab.com/leighmcculloch/go-optional" 251 | } 252 | } 253 | ] 254 | }`) 255 | 256 | e := config{ 257 | Domain: "4d63.com", 258 | Repositories: []repository{ 259 | { 260 | Prefix: "optional", 261 | Subs: []sub{ 262 | {Name: "template"}, 263 | }, 264 | Type: "git", 265 | URL: "https://gitlab.com/leighmcculloch/go-optional", 266 | SourceURLs: sourceURLs{ 267 | Home: "https://gitlab.com/leighmcculloch/go-optional", 268 | Dir: "https://gitlab.com/leighmcculloch/go-optional/tree/master{/dir}", 269 | File: "https://gitlab.com/leighmcculloch/go-optional/blob/master{/dir}/{file}#L{line}", 270 | }, 271 | Website: website{ 272 | URL: "https://gitlab.com/leighmcculloch/go-optional", 273 | }, 274 | }, 275 | }, 276 | } 277 | 278 | c, err := parseConfig(r) 279 | if err != nil { 280 | t.Fatal(err) 281 | } 282 | 283 | if !reflect.DeepEqual(c, e) { 284 | t.Errorf("Got config %#v, want %#v", c, e) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /example/vangen.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "example.com", 3 | "docsDomain": "pkg.go.dev", 4 | "index": true, 5 | "repositories": [ 6 | { 7 | "prefix": "pkg1", 8 | "url": "https://github.com/leighmcculloch/go-pkg1" 9 | }, 10 | { 11 | "prefix": "pkg2", 12 | "subs": [ 13 | "subpkg1/subsubpkg1", 14 | "subpkg2/subsubpkg1", 15 | "subpkg2/subsubpkg2", 16 | "subpkg2/subsubpkg3" 17 | ], 18 | "url": "https://github.com/leighmcculloch/go-pkg2", 19 | "main": true 20 | }, 21 | { 22 | "prefix": "pkg3", 23 | "subs": [ 24 | "subpkg1" 25 | ], 26 | "type": "git", 27 | "url": "https://example.com/repositories/go-pkg3", 28 | "source": { 29 | "home": "https://example.com/repositories/go-pkg3", 30 | "dir": "https://example.com/repositories/go-pkg3/browse{/dir}", 31 | "file": "https://example.com/repositories/go-pkg3/show{/dir}/{file}#L{line}" 32 | }, 33 | "website": { 34 | "url": "https://example.com/readme/go-pkg3" 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /example/vangen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com Go Modules 6 | 11 | 12 | 13 |
14 | 15 |

example.com Go Modules

16 | 17 |

Tools:

18 | 19 | 24 | 25 |

Libraries:

26 | 27 | 35 | 36 |
37 | 38 | Generated by vangen. 39 | 40 |
41 | 42 | -------------------------------------------------------------------------------- /example/vangen/pkg1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg1 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg1

19 | go get example.com/pkg1 20 | import "example.com/pkg1" 21 | Home: https://pkg.go.dev/example.com/pkg1
22 | Source: https://github.com/leighmcculloch/go-pkg1
23 |
24 | 25 | -------------------------------------------------------------------------------- /example/vangen/pkg2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg2 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg2

19 | go get example.com/pkg2 20 | import "example.com/pkg2" 21 | Home: https://pkg.go.dev/example.com/pkg2
22 | Source: https://github.com/leighmcculloch/go-pkg2
23 | Sub-packages:
24 | 25 | -------------------------------------------------------------------------------- /example/vangen/pkg2/subpkg1/subsubpkg1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg2/subpkg1/subsubpkg1 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg2/subpkg1/subsubpkg1

19 | go get example.com/pkg2/subpkg1/subsubpkg1 20 | import "example.com/pkg2/subpkg1/subsubpkg1" 21 | Home: https://pkg.go.dev/example.com/pkg2/subpkg1/subsubpkg1
22 | Source: https://github.com/leighmcculloch/go-pkg2
23 | Sub-packages:
24 | 25 | -------------------------------------------------------------------------------- /example/vangen/pkg2/subpkg2/subsubpkg1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg2/subpkg2/subsubpkg1 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg2/subpkg2/subsubpkg1

19 | go get example.com/pkg2/subpkg2/subsubpkg1 20 | import "example.com/pkg2/subpkg2/subsubpkg1" 21 | Home: https://pkg.go.dev/example.com/pkg2/subpkg2/subsubpkg1
22 | Source: https://github.com/leighmcculloch/go-pkg2
23 | Sub-packages:
24 | 25 | -------------------------------------------------------------------------------- /example/vangen/pkg2/subpkg2/subsubpkg2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg2/subpkg2/subsubpkg2 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg2/subpkg2/subsubpkg2

19 | go get example.com/pkg2/subpkg2/subsubpkg2 20 | import "example.com/pkg2/subpkg2/subsubpkg2" 21 | Home: https://pkg.go.dev/example.com/pkg2/subpkg2/subsubpkg2
22 | Source: https://github.com/leighmcculloch/go-pkg2
23 | Sub-packages:
24 | 25 | -------------------------------------------------------------------------------- /example/vangen/pkg2/subpkg2/subsubpkg3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg2/subpkg2/subsubpkg3 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg2/subpkg2/subsubpkg3

19 | go get example.com/pkg2/subpkg2/subsubpkg3 20 | import "example.com/pkg2/subpkg2/subsubpkg3" 21 | Home: https://pkg.go.dev/example.com/pkg2/subpkg2/subsubpkg3
22 | Source: https://github.com/leighmcculloch/go-pkg2
23 | Sub-packages:
24 | 25 | -------------------------------------------------------------------------------- /example/vangen/pkg3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg3 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg3

19 | go get example.com/pkg3 20 | import "example.com/pkg3" 21 | Home: https://example.com/readme/go-pkg3
22 | Source: https://example.com/repositories/go-pkg3
23 | Sub-packages:
24 | 25 | -------------------------------------------------------------------------------- /example/vangen/pkg3/subpkg1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com/pkg3/subpkg1 6 | 7 | 8 | 15 | 16 | 17 |
18 |

example.com/pkg3/subpkg1

19 | go get example.com/pkg3/subpkg1 20 | import "example.com/pkg3/subpkg1" 21 | Home: https://example.com/readme/go-pkg3
22 | Source: https://example.com/repositories/go-pkg3
23 | Sub-packages:
24 | 25 | -------------------------------------------------------------------------------- /generate_index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io" 7 | ) 8 | 9 | func generate_index(w io.Writer, domain string, r []repository) error { 10 | const html = ` 11 | 12 | 13 | 14 | {{.Domain}} Go Modules 15 | 20 | 21 | 22 |
23 | 24 |

{{.Domain}} Go Modules

25 | 26 |

Tools:

27 | 28 | 38 | 39 |

Libraries:

40 | 41 | 51 | 52 |
53 | 54 | Generated by vangen. 55 | 56 |
57 | 58 | ` 59 | 60 | tmpl, err := template.New("").Parse(html) 61 | if err != nil { 62 | return fmt.Errorf("error loading template: %v", err) 63 | } 64 | 65 | mainRepositories := []repository{} 66 | packageRepositories := []repository{} 67 | for _, r := range r { 68 | if r.Main { 69 | mainRepositories = append(mainRepositories, r) 70 | } else { 71 | packageRepositories = append(packageRepositories, r) 72 | } 73 | } 74 | 75 | data := struct { 76 | Domain string 77 | MainRepositories []repository 78 | PackageRepositories []repository 79 | }{ 80 | Domain: domain, 81 | MainRepositories: mainRepositories, 82 | PackageRepositories: packageRepositories, 83 | } 84 | 85 | err = tmpl.ExecuteTemplate(w, "", data) 86 | if err != nil { 87 | return fmt.Errorf("generating template: %v", err) 88 | } 89 | 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /generate_index_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/sergi/go-diff/diffmatchpatch" 8 | ) 9 | 10 | func TestGenerateIndex(t *testing.T) { 11 | testCases := []struct { 12 | description string 13 | domain string 14 | r []repository 15 | expectedOut string 16 | expectedErr error 17 | }{ 18 | { 19 | description: "basic", 20 | domain: "example.com", 21 | r: []repository{ 22 | { 23 | Prefix: "pkg1", 24 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 25 | Main: true, 26 | }, 27 | { 28 | Prefix: "pkg2", 29 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2/subsubpkg1"}}, 30 | }, 31 | { 32 | Prefix: "pkg3", 33 | Hidden: true, 34 | }, 35 | }, 36 | expectedOut: ` 37 | 38 | 39 | 40 | example.com Go Modules 41 | 46 | 47 | 48 |
49 | 50 |

example.com Go Modules

51 | 52 |

Tools:

53 | 54 | 59 | 60 |

Libraries:

61 | 62 | 67 | 68 |
69 | 70 | Generated by vangen. 71 | 72 |
73 | 74 | `, 75 | expectedErr: nil, 76 | }, 77 | { 78 | description: "hidden sub-package", 79 | domain: "example.com", 80 | r: []repository{ 81 | { 82 | Prefix: "pkg1", 83 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}, {Name: "subpkg3", Hidden: true}}, 84 | Main: true, 85 | }, 86 | { 87 | Prefix: "pkg2", 88 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2/subsubpkg1"}, {Name: "subpkg2/subsubpkg2", Hidden: true}}, 89 | }, 90 | }, 91 | expectedOut: ` 92 | 93 | 94 | 95 | example.com Go Modules 96 | 101 | 102 | 103 |
104 | 105 |

example.com Go Modules

106 | 107 |

Tools:

108 | 109 | 114 | 115 |

Libraries:

116 | 117 | 122 | 123 |
124 | 125 | Generated by vangen. 126 | 127 |
128 | 129 | `, 130 | expectedErr: nil, 131 | }, 132 | } 133 | 134 | for _, tc := range testCases { 135 | var out bytes.Buffer 136 | err := generate_index(&out, tc.domain, tc.r) 137 | if err != tc.expectedErr { 138 | t.Errorf("Test case %#v got err %#v, want %#v", tc, err, tc.expectedErr) 139 | } else if out.String() != tc.expectedOut { 140 | dmp := diffmatchpatch.New() 141 | diffs := dmp.DiffMain(tc.expectedOut, out.String(), false) 142 | t.Errorf("Test case %q got: \n%s\nAs diff:\n%s", tc.description, out.String(), dmp.DiffPrettyText(diffs)) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /generate_package.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | func generate_package(w io.Writer, domain, docsDomain, pkg string, r repository) error { 11 | const html = ` 12 | 13 | 14 | 15 | {{.Domain}}/{{.Package}} 16 | 17 | 18 | 25 | 26 | 27 |
28 |

{{.Domain}}/{{.Package}}

29 | go get {{.Domain}}/{{.Package}} 30 | import "{{.Domain}}/{{.Package}}" 31 | Home: {{.HomeURL}}
32 | Source: {{.Repository.URL}}
33 | {{if .Repository.Subs -}}Sub-packages:{{end -}} 36 |
37 | 38 | ` 39 | 40 | tmpl, err := template.New("").Parse(html) 41 | if err != nil { 42 | return fmt.Errorf("error loading template: %v", err) 43 | } 44 | 45 | var homeURL string 46 | if r.Website.URL != "" { 47 | homeURL = r.Website.URL 48 | } else { 49 | if docsDomain == "" { 50 | docsDomain = "pkg.go.dev" 51 | } 52 | homeURL = fmt.Sprintf("https://%s/%s/%s", docsDomain, domain, pkg) 53 | } 54 | 55 | if strings.HasPrefix(r.URL, "https://github.com") || strings.HasPrefix(r.URL, "https://gitlab.com") { 56 | if r.Type == "" { 57 | r.Type = "git" 58 | } 59 | if r.SourceURLs.Home == "" { 60 | r.SourceURLs.Home = r.URL 61 | } 62 | if r.SourceURLs.Dir == "" { 63 | r.SourceURLs.Dir = r.URL + "/tree/master{/dir}" 64 | } 65 | if r.SourceURLs.File == "" { 66 | r.SourceURLs.File = r.URL + "/blob/master{/dir}/{file}#L{line}" 67 | } 68 | } 69 | 70 | if r.SourceURLs.Home == "" { 71 | r.SourceURLs.Home = "_" 72 | } 73 | if r.SourceURLs.Dir == "" { 74 | r.SourceURLs.Dir = "_" 75 | } 76 | if r.SourceURLs.File == "" { 77 | r.SourceURLs.File = "_" 78 | } 79 | 80 | data := struct { 81 | Domain string 82 | Package string 83 | Repository repository 84 | HomeURL string 85 | }{ 86 | Domain: domain, 87 | Package: pkg, 88 | Repository: r, 89 | HomeURL: homeURL, 90 | } 91 | 92 | err = tmpl.ExecuteTemplate(w, "", data) 93 | if err != nil { 94 | return fmt.Errorf("generating template: %v", err) 95 | } 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /generate_package_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/sergi/go-diff/diffmatchpatch" 8 | ) 9 | 10 | func TestGenerate(t *testing.T) { 11 | testCases := []struct { 12 | description string 13 | domain string 14 | docsDomain string 15 | pkg string 16 | r repository 17 | expectedOut string 18 | expectedErr error 19 | }{ 20 | { 21 | description: "simple", 22 | domain: "example.com", 23 | docsDomain: "godoc.org", 24 | pkg: "pkg1", 25 | r: repository{ 26 | Prefix: "pkg1", 27 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 28 | Type: "git", 29 | URL: "https://repositoryhost.com/example/go-pkg1", 30 | }, 31 | expectedOut: ` 32 | 33 | 34 | 35 | example.com/pkg1 36 | 37 | 38 | 45 | 46 | 47 |
48 |

example.com/pkg1

49 | go get example.com/pkg1 50 | import "example.com/pkg1" 51 | Home: https://godoc.org/example.com/pkg1
52 | Source: https://repositoryhost.com/example/go-pkg1
53 | Sub-packages:
54 | 55 | `, 56 | expectedErr: nil, 57 | }, 58 | { 59 | description: "hidden", 60 | domain: "example.com", 61 | docsDomain: "godoc.org", 62 | pkg: "pkg1", 63 | r: repository{ 64 | Prefix: "pkg1", 65 | Hidden: true, 66 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 67 | Type: "git", 68 | URL: "https://repositoryhost.com/example/go-pkg1", 69 | }, 70 | expectedOut: ` 71 | 72 | 73 | 74 | example.com/pkg1 75 | 76 | 77 | 84 | 85 | 86 |
87 |

example.com/pkg1

88 | go get example.com/pkg1 89 | import "example.com/pkg1" 90 | Home: https://godoc.org/example.com/pkg1
91 | Source: https://repositoryhost.com/example/go-pkg1
92 | Sub-packages:
93 | 94 | `, 95 | expectedErr: nil, 96 | }, 97 | { 98 | description: "custom source urls", 99 | domain: "example.com", 100 | docsDomain: "pkg.go.dev", 101 | pkg: "pkg1", 102 | r: repository{ 103 | Prefix: "pkg1", 104 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 105 | Type: "git", 106 | URL: "https://repositoryhost.com/example/go-pkg1", 107 | SourceURLs: sourceURLs{ 108 | Home: "https://repositoryhost.com/example/go-pkg1/home", 109 | Dir: "https://repositoryhost.com/example/go-pkg1/browser{/dir}", 110 | File: "https://repositoryhost.com/example/go-pkg1/view{/dir}{/file}", 111 | }, 112 | Website: website{ 113 | URL: "https://www.example.com", 114 | }, 115 | }, 116 | expectedOut: ` 117 | 118 | 119 | 120 | example.com/pkg1 121 | 122 | 123 | 130 | 131 | 132 |
133 |

example.com/pkg1

134 | go get example.com/pkg1 135 | import "example.com/pkg1" 136 | Home: https://www.example.com
137 | Source: https://repositoryhost.com/example/go-pkg1
138 | Sub-packages:
139 | 140 | `, 141 | expectedErr: nil, 142 | }, 143 | { 144 | description: "sub-package", 145 | domain: "example.com", 146 | pkg: "pkg1/subpkg1", 147 | r: repository{ 148 | Prefix: "pkg1", 149 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 150 | Type: "git", 151 | URL: "https://repositoryhost.com/example/go-pkg1", 152 | SourceURLs: sourceURLs{ 153 | Home: "https://repositoryhost.com/example/go-pkg1/home", 154 | Dir: "https://repositoryhost.com/example/go-pkg1/browser{/dir}", 155 | File: "https://repositoryhost.com/example/go-pkg1/view{/dir}{/file}", 156 | }, 157 | Website: website{ 158 | URL: "https://www.example.com", 159 | }, 160 | }, 161 | expectedOut: ` 162 | 163 | 164 | 165 | example.com/pkg1/subpkg1 166 | 167 | 168 | 175 | 176 | 177 |
178 |

example.com/pkg1/subpkg1

179 | go get example.com/pkg1/subpkg1 180 | import "example.com/pkg1/subpkg1" 181 | Home: https://www.example.com
182 | Source: https://repositoryhost.com/example/go-pkg1
183 | Sub-packages:
184 | 185 | `, 186 | expectedErr: nil, 187 | }, 188 | { 189 | description: "sub-package hidden", 190 | domain: "example.com", 191 | pkg: "pkg1/subpkg1", 192 | r: repository{ 193 | Prefix: "pkg1", 194 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}, {Name: "subpkg3", Hidden: true}}, 195 | Type: "git", 196 | URL: "https://repositoryhost.com/example/go-pkg1", 197 | SourceURLs: sourceURLs{ 198 | Home: "https://repositoryhost.com/example/go-pkg1/home", 199 | Dir: "https://repositoryhost.com/example/go-pkg1/browser{/dir}", 200 | File: "https://repositoryhost.com/example/go-pkg1/view{/dir}{/file}", 201 | }, 202 | Website: website{ 203 | URL: "https://www.example.com", 204 | }, 205 | }, 206 | expectedOut: ` 207 | 208 | 209 | 210 | example.com/pkg1/subpkg1 211 | 212 | 213 | 220 | 221 | 222 |
223 |

example.com/pkg1/subpkg1

224 | go get example.com/pkg1/subpkg1 225 | import "example.com/pkg1/subpkg1" 226 | Home: https://www.example.com
227 | Source: https://repositoryhost.com/example/go-pkg1
228 | Sub-packages:
229 | 230 | `, 231 | expectedErr: nil, 232 | }, 233 | { 234 | description: "github defaults", 235 | domain: "example.com", 236 | docsDomain: "pkg.go.dev", 237 | pkg: "pkg1", 238 | r: repository{ 239 | Prefix: "pkg1", 240 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 241 | URL: "https://github.com/example/go-pkg1", 242 | }, 243 | expectedOut: ` 244 | 245 | 246 | 247 | example.com/pkg1 248 | 249 | 250 | 257 | 258 | 259 |
260 |

example.com/pkg1

261 | go get example.com/pkg1 262 | import "example.com/pkg1" 263 | Home: https://pkg.go.dev/example.com/pkg1
264 | Source: https://github.com/example/go-pkg1
265 | Sub-packages:
266 | 267 | `, 268 | expectedErr: nil, 269 | }, 270 | { 271 | description: "sub-package github defaults", 272 | domain: "example.com", 273 | docsDomain: "pkg.go.dev", 274 | pkg: "pkg1/subpkg1", 275 | r: repository{ 276 | Prefix: "pkg1", 277 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 278 | URL: "https://github.com/example/go-pkg1", 279 | }, 280 | expectedOut: ` 281 | 282 | 283 | 284 | example.com/pkg1/subpkg1 285 | 286 | 287 | 294 | 295 | 296 |
297 |

example.com/pkg1/subpkg1

298 | go get example.com/pkg1/subpkg1 299 | import "example.com/pkg1/subpkg1" 300 | Home: https://pkg.go.dev/example.com/pkg1/subpkg1
301 | Source: https://github.com/example/go-pkg1
302 | Sub-packages:
303 | 304 | `, 305 | expectedErr: nil, 306 | }, 307 | { 308 | description: "gitlab defaults", 309 | domain: "example.com", 310 | docsDomain: "", 311 | pkg: "pkg1", 312 | r: repository{ 313 | Prefix: "pkg1", 314 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 315 | URL: "https://gitlab.com/example/go-pkg1", 316 | }, 317 | expectedOut: ` 318 | 319 | 320 | 321 | example.com/pkg1 322 | 323 | 324 | 331 | 332 | 333 |
334 |

example.com/pkg1

335 | go get example.com/pkg1 336 | import "example.com/pkg1" 337 | Home: https://pkg.go.dev/example.com/pkg1
338 | Source: https://gitlab.com/example/go-pkg1
339 | Sub-packages:
340 | 341 | `, 342 | expectedErr: nil, 343 | }, 344 | { 345 | description: "sub-package gitlab defaults", 346 | domain: "example.com", 347 | docsDomain: "pkg.go.dev", 348 | pkg: "pkg1/subpkg1", 349 | r: repository{ 350 | Prefix: "pkg1", 351 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 352 | URL: "https://gitlab.com/example/go-pkg1", 353 | }, 354 | expectedOut: ` 355 | 356 | 357 | 358 | example.com/pkg1/subpkg1 359 | 360 | 361 | 368 | 369 | 370 |
371 |

example.com/pkg1/subpkg1

372 | go get example.com/pkg1/subpkg1 373 | import "example.com/pkg1/subpkg1" 374 | Home: https://pkg.go.dev/example.com/pkg1/subpkg1
375 | Source: https://gitlab.com/example/go-pkg1
376 | Sub-packages:
377 | 378 | `, 379 | expectedErr: nil, 380 | }, 381 | { 382 | description: "github defaults with custom source", 383 | domain: "example.com", 384 | docsDomain: "", 385 | pkg: "pkg1", 386 | r: repository{ 387 | Prefix: "pkg1", 388 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 389 | Type: "git", 390 | URL: "https://github.com/example/go-pkg1", 391 | SourceURLs: sourceURLs{ 392 | Home: "https://github.com/example/go-pkg1", 393 | Dir: "https://github.com/example/go-pkg1/tree/branch{/dir}", 394 | File: "https://github.com/example/go-pkg1/blob/branch{/dir}/{file}#L{line}", 395 | }, 396 | Website: website{ 397 | URL: "https://www.example.com", 398 | }, 399 | }, 400 | expectedOut: ` 401 | 402 | 403 | 404 | example.com/pkg1 405 | 406 | 407 | 414 | 415 | 416 |
417 |

example.com/pkg1

418 | go get example.com/pkg1 419 | import "example.com/pkg1" 420 | Home: https://www.example.com
421 | Source: https://github.com/example/go-pkg1
422 | Sub-packages:
423 | 424 | `, 425 | expectedErr: nil, 426 | }, 427 | { 428 | description: "single module deployment that has no 'prefix'", 429 | domain: "example.com", 430 | docsDomain: "", 431 | pkg: "", 432 | r: repository{ 433 | Prefix: "", 434 | Subs: []sub{{Name: "subpkg1"}, {Name: "subpkg2"}}, 435 | Type: "git", 436 | URL: "https://github.com/example/go-pkg1", 437 | SourceURLs: sourceURLs{ 438 | Home: "https://github.com/example/go-pkg1", 439 | Dir: "https://github.com/example/go-pkg1/tree/branch{/dir}", 440 | File: "https://github.com/example/go-pkg1/blob/branch{/dir}/{file}#L{line}", 441 | }, 442 | Website: website{ 443 | URL: "https://www.example.com", 444 | }, 445 | }, 446 | expectedOut: ` 447 | 448 | 449 | 450 | example.com/ 451 | 452 | 453 | 460 | 461 | 462 |
463 |

example.com/

464 | go get example.com/ 465 | import "example.com/" 466 | Home: https://www.example.com
467 | Source: https://github.com/example/go-pkg1
468 | Sub-packages:
469 | 470 | `, 471 | expectedErr: nil, 472 | }, 473 | } 474 | 475 | for _, tc := range testCases { 476 | var out bytes.Buffer 477 | err := generate_package(&out, tc.domain, tc.docsDomain, tc.pkg, tc.r) 478 | if err != tc.expectedErr { 479 | t.Errorf("Test case %q got err %#v, want %#v", tc.description, err, tc.expectedErr) 480 | } else if out.String() != tc.expectedOut { 481 | dmp := diffmatchpatch.New() 482 | diffs := dmp.DiffMain(tc.expectedOut, out.String(), false) 483 | t.Errorf("Test case %q got: \n%s\nAs diff:\n%s", tc.description, out.String(), dmp.DiffPrettyText(diffs)) 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module 4d63.com/vangen 2 | 3 | go 1.21.0 4 | 5 | require github.com/sergi/go-diff v1.0.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.0 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | github.com/stretchr/objx v0.1.0 // indirect 11 | github.com/stretchr/testify v1.3.0 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 6 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 9 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | var version = "" 11 | 12 | func main() { 13 | err := run() 14 | if err != nil { 15 | fmt.Fprintf(os.Stderr, "%v\n", err) 16 | os.Exit(1) 17 | } 18 | } 19 | 20 | func run() error { 21 | printHelp := flag.Bool("help", false, "print this help list") 22 | printVersion := flag.Bool("version", false, "print program version") 23 | verbose := flag.Bool("verbose", false, "print verbose output when run") 24 | filename := flag.String("config", "vangen.json", "vangen json configuration `filename`") 25 | outputDir := flag.String("out", "vangen/", "output `directory` that static files will be written to") 26 | noOverwrite := flag.Bool("no-overwrite", false, "If an output file already exists, stops with a non-zero return code") 27 | flag.Usage = func() { 28 | fmt.Fprintf(os.Stderr, "Vangen is a tool for generating static HTML for hosting Go repositories at a vanity import path.\n\n") 29 | fmt.Fprintf(os.Stderr, "Usage:\n\n") 30 | fmt.Fprintf(os.Stderr, " vangen [-config=vangen.json] [-out=vangen/]\n\n") 31 | fmt.Fprintf(os.Stderr, "Flags:\n\n") 32 | flag.PrintDefaults() 33 | } 34 | flag.Parse() 35 | 36 | if *printHelp { 37 | flag.Usage() 38 | return nil 39 | } 40 | 41 | if *printVersion { 42 | fmt.Fprintln(os.Stderr, version) 43 | return nil 44 | } 45 | 46 | cf, err := os.Open(*filename) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | c, err := parseConfig(cf) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | err = os.MkdirAll(*outputDir, os.ModePerm) 57 | if err != nil { 58 | return fmt.Errorf("making dir %s: %w", *outputDir, err) 59 | } 60 | 61 | if c.Index { 62 | pathOut := filepath.Join(*outputDir, "index.html") 63 | f, err := os.Create(pathOut) 64 | if err != nil { 65 | return fmt.Errorf("writing file %s: %w", pathOut, err) 66 | } 67 | defer func() { 68 | err := f.Close() 69 | if err != nil { 70 | fmt.Fprintf(os.Stderr, "closing file %s: %v", pathOut, err) 71 | } 72 | }() 73 | 74 | if *verbose { 75 | fmt.Fprintf(os.Stderr, "Writing %s\n", pathOut) 76 | } 77 | err = generate_index(f, c.Domain, c.Repositories) 78 | if err != nil { 79 | return fmt.Errorf("generating index: %w", err) 80 | } 81 | 82 | err = f.Sync() 83 | if err != nil { 84 | return fmt.Errorf("flushing file %s: %w", pathOut, err) 85 | } 86 | } 87 | 88 | for _, r := range c.Repositories { 89 | for _, p := range r.Packages() { 90 | dirOut := filepath.Join(*outputDir, p) 91 | err = os.MkdirAll(dirOut, os.ModePerm) 92 | if err != nil { 93 | return fmt.Errorf("making dir %s: %v", dirOut, err) 94 | } 95 | 96 | pathOut := filepath.Join(dirOut, "index.html") 97 | if *noOverwrite { 98 | if _, err := os.Stat(pathOut); !os.IsNotExist(err) { 99 | if err == nil { 100 | return fmt.Errorf("cannot overwrite output file %s: %w", pathOut, err) 101 | } 102 | 103 | return fmt.Errorf("checking file %s: %w", pathOut, err) 104 | } 105 | } 106 | f, err := os.Create(pathOut) 107 | if err != nil { 108 | return fmt.Errorf("writing file %s: %v", pathOut, err) 109 | } 110 | defer func() { 111 | err := f.Close() 112 | if err != nil { 113 | fmt.Fprintf(os.Stderr, "closing file %s: %v", pathOut, err) 114 | } 115 | }() 116 | 117 | if *verbose { 118 | fmt.Fprintf(os.Stderr, "Writing %s\n", pathOut) 119 | } 120 | err = generate_package(f, c.Domain, c.DocsDomain, p, r) 121 | if err != nil { 122 | return fmt.Errorf("generating package %s: %w", p, err) 123 | } 124 | 125 | err = f.Sync() 126 | if err != nil { 127 | return fmt.Errorf("flushing file %s: %w", pathOut, err) 128 | } 129 | } 130 | } 131 | 132 | return nil 133 | } 134 | --------------------------------------------------------------------------------