diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5e663d4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +sudo: false +language: go +go: +- 1.6 +- 1.x diff --git a/appengine.go b/appengine.go new file mode 100644 index 0000000..2d2c159 --- /dev/null +++ b/appengine.go @@ -0,0 +1,42 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//+build appengine + +package main + +import ( + "io/ioutil" + "log" + "net/http" + + "google.golang.org/appengine" +) + +func main() { + vanity, err := ioutil.ReadFile("./vanity.yaml") + if err != nil { + log.Fatal(err) + } + h, err := newHandler(vanity) + if err != nil { + log.Fatal(err) + } + http.Handle("/", h) + appengine.Main() +} + +func requestHost(r *http.Request) string { + return appengine.DefaultVersionHostname(appengine.NewContext(r)) +} diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..92264e3 --- /dev/null +++ b/handler.go @@ -0,0 +1,87 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// govanityurls serves Go vanity URLs. +package main + +import ( + "fmt" + "html/template" + "net/http" + "strings" + + "gopkg.in/yaml.v2" +) + +type handler struct { + host string + m map[string]*struct { + Repo string `yaml:"repo,omitempty"` + Display string `yaml:"display,omitempty"` + } +} + +func newHandler(config []byte) (*handler, error) { + h := new(handler) + if err := yaml.Unmarshal(config, &h.m); err != nil { + return nil, err + } + for _, e := range h.m { + if e.Display != "" { + continue + } + if strings.Contains(e.Repo, "github.com") { + e.Display = fmt.Sprintf("%v %v/tree/master{/dir} %v/blob/master{/dir}/{file}#L{line}", e.Repo, e.Repo, e.Repo) + } + } + return h, nil +} + +func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + current := r.URL.Path + p, ok := h.m[current] + if !ok { + http.NotFound(w, r) + return + } + + host := h.host + if host == "" { + host = requestHost(r) + } + if err := vanityTmpl.Execute(w, struct { + Import string + Repo string + Display string + }{ + Import: host + current, + Repo: p.Repo, + Display: p.Display, + }); err != nil { + http.Error(w, "cannot render the page", http.StatusInternalServerError) + } +} + +var vanityTmpl = template.Must(template.New("vanity").Parse(` + + + + + + + + +Nothing to see here; see the package on godoc. + +`)) diff --git a/handler_test.go b/handler_test.go new file mode 100644 index 0000000..291966b --- /dev/null +++ b/handler_test.go @@ -0,0 +1,100 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" +) + +func TestHandler(t *testing.T) { + tests := []struct { + name string + config string + path string + + goImport string + goSource string + }{ + { + name: "explicit display", + config: "/portmidi:\n" + + " repo: https://github.com/rakyll/portmidi\n" + + " display: https://github.com/rakyll/portmidi _ _\n", + path: "/portmidi", + goImport: "example.com/portmidi git https://github.com/rakyll/portmidi", + goSource: "example.com/portmidi https://github.com/rakyll/portmidi _ _", + }, + { + name: "display GitHub inference", + config: "/portmidi:\n" + + " repo: https://github.com/rakyll/portmidi\n", + path: "/portmidi", + goImport: "example.com/portmidi git https://github.com/rakyll/portmidi", + goSource: "example.com/portmidi https://github.com/rakyll/portmidi https://github.com/rakyll/portmidi/tree/master{/dir} https://github.com/rakyll/portmidi/blob/master{/dir}/{file}#L{line}", + }, + } + for _, test := range tests { + h, err := newHandler([]byte(test.config)) + if err != nil { + t.Errorf("%s: newHandler: %v", test.name, err) + continue + } + h.host = "example.com" + s := httptest.NewServer(h) + resp, err := http.Get(s.URL + test.path) + if err != nil { + s.Close() + t.Errorf("%s: http.Get: %v", test.name, err) + continue + } + data, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + s.Close() + if resp.StatusCode != http.StatusOK { + t.Errorf("%s: status code = %s; want 200 OK", test.name, resp.Status) + } + if err != nil { + t.Errorf("%s: ioutil.ReadAll: %v", test.name, err) + continue + } + if got := findMeta(data, "go-import"); got != test.goImport { + t.Errorf("%s: meta go-import = %q; want %q", test.name, got, test.goImport) + } + if got := findMeta(data, "go-source"); got != test.goSource { + t.Errorf("%s: meta go-source = %q; want %q", test.name, got, test.goSource) + } + } +} + +func findMeta(data []byte, name string) string { + var sep []byte + sep = append(sep, ` - - - - - - - - -Nothing to see here; see the package on godoc. - -`) +func requestHost(r *http.Request) string { + return r.Host +}