Permalink
Browse files

Firt pass at supporting presentations.

  • Loading branch information...
garyburd committed Feb 7, 2013
1 parent 5b70929 commit d2364fcc0b1fa74a3786074232e377133f483989
View
@@ -23,8 +23,9 @@ Development Environment Setup
- Install [Go](http://golang.org/doc/install) and configure [GOPATH](http://golang.org/doc/code.html).
- Install and run the server:
- go get github.com/garyburd/gopkgdoc/gddo-server
- gddo-server
+ go get github.com/garyburd/gopkgdoc/gddo-server
+ cd `go list -f '{{.Dir}}' github.com/garyburd/gopkgdoc/gddo-server`
+ gddo-server
License
-------
View
@@ -53,18 +53,19 @@ var (
// service represents a source code control service.
type service struct {
- pattern *regexp.Regexp
- getDoc func(*http.Client, map[string]string, string) (*Package, error)
- prefix string
+ pattern *regexp.Regexp
+ prefix string
+ get func(*http.Client, map[string]string, string) (*Package, error)
+ getPresentation func(*http.Client, map[string]string) (*Presentation, error)
}
// services is the list of source code control services handled by gopkgdoc.
var services = []*service{
- {githubPattern, getGithubDoc, "github.com/"},
- {googlePattern, getGoogleDoc, "code.google.com/"},
- {bitbucketPattern, getBitbucketDoc, "bitbucket.org/"},
- {launchpadPattern, getLaunchpadDoc, "launchpad.net/"},
- {generalPattern, getGeneralDoc, ""},
+ {githubPattern, "github.com/", getGithubDoc, getGithubPresentation},
+ {googlePattern, "code.google.com/", getGoogleDoc, getGooglePresentation},
+ {bitbucketPattern, "bitbucket.org/", getBitbucketDoc, nil},
+ {launchpadPattern, "launchpad.net/", getLaunchpadDoc, nil},
+ {generalPattern, "", getGeneralDoc, nil},
}
func attrValue(attrs []xml.Attr, name string) string {
@@ -199,7 +200,7 @@ func getGeneralDoc(client *http.Client, match map[string]string, etag string) (*
// returns errNoMatch if the import path is not recognized.
func getStatic(client *http.Client, importPath string, etag string) (*Package, error) {
for _, s := range services {
- if !strings.HasPrefix(importPath, s.prefix) {
+ if s.get == nil || !strings.HasPrefix(importPath, s.prefix) {
continue
}
m := s.pattern.FindStringSubmatch(importPath)
@@ -215,7 +216,7 @@ func getStatic(client *http.Client, importPath string, etag string) (*Package, e
match[n] = m[i]
}
}
- return s.getDoc(client, match, etag)
+ return s.get(client, match, etag)
}
return nil, errNoMatch
}
@@ -252,3 +253,34 @@ func Get(client *http.Client, importPath string, etag string) (pdoc *Package, er
return pdoc, err
}
+
+// GetPresentation gets a presentation from the the given path.
+func GetPresentation(client *http.Client, importPath string) (*Presentation, error) {
+ ext := path.Ext(importPath)
+ if ext != ".slide" && ext != ".article" {
+ return nil, NotFoundError{"unknown file extension."}
+ }
+
+ importPath, file := path.Split(importPath)
+ importPath = strings.TrimSuffix(importPath, "/")
+ for _, s := range services {
+ if s.getPresentation == nil || !strings.HasPrefix(importPath, s.prefix) {
+ continue
+ }
+ m := s.pattern.FindStringSubmatch(importPath)
+ if m == nil {
+ if s.prefix != "" {
+ return nil, NotFoundError{"path prefix matches known service, but regexp does not."}
+ }
+ continue
+ }
+ match := map[string]string{"importPath": importPath, "file": file}
+ for i, n := range s.pattern.SubexpNames() {
+ if n != "" {
+ match[n] = m[i]
+ }
+ }
+ return s.getPresentation(client, match)
+ }
+ return nil, errNoMatch
+}
View
@@ -15,7 +15,9 @@
package doc
import (
+ "io"
"net/http"
+ "net/url"
"path"
"regexp"
"strings"
@@ -136,3 +138,45 @@ func getGithubDoc(client *http.Client, match map[string]string, savedEtag string
return b.build(files)
}
+
+func getGithubPresentation(client *http.Client, match map[string]string) (*Presentation, error) {
+
+ match["cred"] = githubCred
+
+ p, err := httpGet(client, expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/{file}?{cred}", match), githubRawHeader)
+ if err != nil {
+ return nil, err
+ }
+ defer p.Close()
+
+ apiBase, err := url.Parse(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/?{cred}", match))
+ if err != nil {
+ return nil, err
+ }
+ rawBase, err := url.Parse(expand("https://raw.github.com/{owner}/{repo}/master{dir}/", match))
+ if err != nil {
+ return nil, err
+ }
+
+ b := &presBuilder{
+ pres: &Presentation{},
+ content: p,
+ openFile: func(fname string) (io.ReadCloser, error) {
+ u, err := apiBase.Parse(fname)
+ if err != nil {
+ return nil, err
+ }
+ u.RawQuery = match["cred"]
+ return httpGet(client, u.String(), githubRawHeader)
+ },
+ resolveURL: func(fname string) string {
+ u, err := rawBase.Parse(fname)
+ if err != nil {
+ return "/-/notfound"
+ }
+ return u.String()
+ },
+ }
+
+ return b.build()
+}
View
@@ -16,7 +16,9 @@ package doc
import (
"errors"
+ "io"
"net/http"
+ "net/url"
"regexp"
"strings"
)
@@ -30,28 +32,11 @@ var (
)
func getGoogleDoc(client *http.Client, match map[string]string, savedEtag string) (*Package, error) {
-
- if s := match["subrepo"]; s != "" {
- match["dot"] = "."
- match["query"] = "?repo=" + s
- } else {
- match["dot"] = ""
- match["query"] = ""
- }
-
+ setupGoogleMatch(match)
if m := googleEtagRe.FindStringSubmatch(savedEtag); m != nil {
match["vcs"] = m[1]
- } else {
- // Scrape the HTML project page to find the VCS.
- p, err := httpGetBytes(client, expand("http://code.google.com/p/{repo}/source/checkout", match))
- if err != nil {
- return nil, err
- }
- if m := googleRepoRe.FindSubmatch(p); m != nil {
- match["vcs"] = string(m[1])
- } else {
- return nil, NotFoundError{"Could not VCS on Google Code project page."}
- }
+ } else if err := getGoogleVCS(client, match); err != nil {
+ return nil, err
}
// Scrape the repo browser to find the project revision and individual Go files.
@@ -102,6 +87,30 @@ func getGoogleDoc(client *http.Client, match map[string]string, savedEtag string
return b.build(files)
}
+func setupGoogleMatch(match map[string]string) {
+ if s := match["subrepo"]; s != "" {
+ match["dot"] = "."
+ match["query"] = "?repo=" + s
+ } else {
+ match["dot"] = ""
+ match["query"] = ""
+ }
+}
+
+func getGoogleVCS(client *http.Client, match map[string]string) error {
+ // Scrape the HTML project page to find the VCS.
+ p, err := httpGetBytes(client, expand("http://code.google.com/p/{repo}/source/checkout", match))
+ if err != nil {
+ return err
+ }
+ m := googleRepoRe.FindSubmatch(p)
+ if m == nil {
+ return NotFoundError{"Could not VCS on Google Code project page."}
+ }
+ match["vcs"] = string(m[1])
+ return nil
+}
+
func getStandardDoc(client *http.Client, importPath string, savedEtag string) (*Package, error) {
p, err := httpGetBytes(client, "http://go.googlecode.com/hg-history/release/src/pkg/"+importPath+"/")
@@ -150,3 +159,42 @@ func getStandardDoc(client *http.Client, importPath string, savedEtag string) (*
return b.build(files)
}
+
+func getGooglePresentation(client *http.Client, match map[string]string) (*Presentation, error) {
+ setupGoogleMatch(match)
+ if err := getGoogleVCS(client, match); err != nil {
+ return nil, err
+ }
+
+ rawBase, err := url.Parse(expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}{dir}/", match))
+ if err != nil {
+ return nil, err
+ }
+
+ p, err := httpGet(client, expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}{dir}/{file}", match), nil)
+ if err != nil {
+ return nil, err
+ }
+ defer p.Close()
+
+ b := &presBuilder{
+ pres: &Presentation{},
+ content: p,
+ openFile: func(fname string) (io.ReadCloser, error) {
+ u, err := rawBase.Parse(fname)
+ if err != nil {
+ return nil, err
+ }
+ return httpGet(client, u.String(), nil)
+ },
+ resolveURL: func(fname string) string {
+ u, err := rawBase.Parse(fname)
+ if err != nil {
+ return "/-/notfound"
+ }
+ return u.String()
+ },
+ }
+
+ return b.build()
+}
View
@@ -31,7 +31,7 @@ var launchpadPattern = regexp.MustCompile(`^launchpad\.net/(?P<repo>(?P<project>
func getLaunchpadDoc(client *http.Client, match map[string]string, savedEtag string) (*Package, error) {
if match["project"] != "" && match["series"] != "" {
- rc, err := httpGet(client, expand("https://code.launchpad.net/{project}{series}/.bzr/branch-format", match))
+ rc, err := httpGet(client, expand("https://code.launchpad.net/{project}{series}/.bzr/branch-format", match), nil)
switch {
case err == nil:
rc.Close()
View
@@ -0,0 +1,72 @@
+// Copyright 2013 Gary Burd
+//
+// 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 doc
+
+import (
+ "io"
+ "time"
+
+ "code.google.com/p/go.talks/pkg/present"
+)
+
+type Presentation struct {
+ Doc *present.Doc
+ Updated time.Time
+}
+
+type presBuilder struct {
+ pres *Presentation
+ filename string
+ content io.Reader
+ openFile func(fname string) (io.ReadCloser, error)
+ resolveURL func(fname string) string
+}
+
+func (b *presBuilder) resolveElem(e present.Elem) present.Elem {
+ switch e := e.(type) {
+ case present.Section:
+ for i := range e.Elem {
+ e.Elem[i] = b.resolveElem(e.Elem[i])
+ }
+ return e
+ case present.Image:
+ e.URL = b.resolveURL(e.URL)
+ return e
+ case present.Iframe:
+ e.URL = b.resolveURL(e.URL)
+ return e
+ }
+ return e
+}
+
+func (b *presBuilder) build() (*Presentation, error) {
+ ctxt := &present.Context{
+ OpenFile: b.openFile,
+ }
+
+ var err error
+ b.pres.Doc, err = present.Parse(ctxt, b.content, b.filename, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := range b.pres.Doc.Sections {
+ b.pres.Doc.Sections[i] = b.resolveElem(b.pres.Doc.Sections[i]).(present.Section)
+ }
+
+ b.pres.Updated = time.Now().UTC()
+
+ return b.pres, nil
+}
View
@@ -34,23 +34,32 @@ func indent(s string, n int) string {
}
var (
- etag = flag.String("etag", "", "Etag")
- local = flag.Bool("local", false, "Get package from local directory.")
+ etag = flag.String("etag", "", "Etag")
+ local = flag.Bool("local", false, "Get package from local directory.")
+ present = flag.Bool("present", false, "Get presentation.")
)
func main() {
flag.Parse()
if len(flag.Args()) != 1 {
log.Fatal("Usage: go run print.go importPath")
}
+ if *present {
+ printPresentation(flag.Args()[0])
+ } else {
+ printPackage(flag.Args()[0])
+ }
+}
+
+func printPackage(path string) {
var (
pdoc *doc.Package
err error
)
if *local {
- pdoc, err = doc.GetDir(flag.Args()[0])
+ pdoc, err = doc.GetDir(path)
} else {
- pdoc, err = doc.Get(http.DefaultClient, flag.Args()[0], *etag)
+ pdoc, err = doc.Get(http.DefaultClient, path, *etag)
}
if err != nil {
log.Fatal(err)
@@ -132,3 +141,14 @@ func main() {
}
}
}
+
+func printPresentation(path string) {
+ pres, err := doc.GetPresentation(http.DefaultClient, path)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("Doc: ", pres.Doc)
+ fmt.Println("Kind: ", pres.Kind)
+ fmt.Println("Name: ", pres.Name)
+ fmt.Println("URL: ", pres.URL)
+}
Oops, something went wrong.

3 comments on commit d2364fc

Contributor

AlekSi replied Feb 7, 2013

It's awesome!

Contributor

AlekSi replied Feb 7, 2013

But it doesn't complies. :)

# github.com/garyburd/gopkgdoc/doc
../doc/get.go:267: undefined: strings.TrimSuffix
../doc/present.go:55: undefined: present.Context
../doc/present.go:60: cannot use b.content (type io.Reader) as type string in function argument
../doc/present.go:60: cannot use b.filename (type string) as type present.ParseMode in function argument
../doc/present.go:60: too many arguments in call to present.Parse
Contributor

garyburd replied Feb 7, 2013

Oops, I used a new feature in tip (strings.TrimSuffix). The error on present.go:60 will be taken care of when I push my changes to go.talks upstream.

Please sign in to comment.