mirror of
https://github.com/golang/go
synced 2024-11-25 08:47:56 -07:00
godoc: support for file systems stored in .zip files
Instead of serving files of the underlying OS file system, a .zip file may be provided to godoc containing the files to serve; for instance: godoc -http=:6060 -zip=go.zip using a .zip file created from a clean tree as follows: zip -r go.zip $GOROOT R=rsc CC=golang-dev https://golang.org/cl/4670053
This commit is contained in:
parent
e8ff9a624f
commit
df68a61c9e
@ -73,6 +73,8 @@ The flags are:
|
|||||||
filter file containing permitted package directory paths
|
filter file containing permitted package directory paths
|
||||||
-filter_minutes=0
|
-filter_minutes=0
|
||||||
filter file update interval in minutes; update is disabled if <= 0
|
filter file update interval in minutes; update is disabled if <= 0
|
||||||
|
-zip=""
|
||||||
|
zip file providing the file system to serve; disabled if empty
|
||||||
|
|
||||||
The -path flag accepts a list of colon-separated paths; unrooted paths are relative
|
The -path flag accepts a list of colon-separated paths; unrooted paths are relative
|
||||||
to the current working directory. Each path is considered as an additional root for
|
to the current working directory. Each path is considered as an additional root for
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// This file defines abstract file system access.
|
// This file defines types for abstract file system access and
|
||||||
|
// provides an implementation accessing the file system of the
|
||||||
|
// underlying OS.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -62,7 +65,18 @@ func (fi osFI) Size() int64 {
|
|||||||
type osFS struct{}
|
type osFS struct{}
|
||||||
|
|
||||||
func (osFS) Open(path string) (io.ReadCloser, os.Error) {
|
func (osFS) Open(path string) (io.ReadCloser, os.Error) {
|
||||||
return os.Open(path)
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fi.IsDirectory() {
|
||||||
|
return nil, fmt.Errorf("Open: %s is a directory", path)
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -79,7 +93,7 @@ func (osFS) Stat(path string) (FileInfo, os.Error) {
|
|||||||
|
|
||||||
|
|
||||||
func (osFS) ReadDir(path string) ([]FileInfo, os.Error) {
|
func (osFS) ReadDir(path string) ([]FileInfo, os.Error) {
|
||||||
l0, err := ioutil.ReadDir(path)
|
l0, err := ioutil.ReadDir(path) // l0 is sorted
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "expvar" // to serve /debug/vars
|
_ "expvar" // to serve /debug/vars
|
||||||
"flag"
|
"flag"
|
||||||
@ -47,6 +48,10 @@ import (
|
|||||||
const defaultAddr = ":6060" // default webserver address
|
const defaultAddr = ":6060" // default webserver address
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// file system to serve
|
||||||
|
// (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh)
|
||||||
|
zipfile = flag.String("zip", "", "zip file providing the file system to serve; disabled if empty")
|
||||||
|
|
||||||
// periodic sync
|
// periodic sync
|
||||||
syncCmd = flag.String("sync", "", "sync command; disabled if empty")
|
syncCmd = flag.String("sync", "", "sync command; disabled if empty")
|
||||||
syncMin = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0")
|
syncMin = flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0")
|
||||||
@ -223,13 +228,19 @@ func main() {
|
|||||||
flag.Usage = usage
|
flag.Usage = usage
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Determine file system to use.
|
|
||||||
// TODO(gri) Complete this - for now we only have one.
|
|
||||||
fs = OS
|
|
||||||
|
|
||||||
// Clean goroot: normalize path separator.
|
// Clean goroot: normalize path separator.
|
||||||
*goroot = filepath.Clean(*goroot)
|
*goroot = filepath.Clean(*goroot)
|
||||||
|
|
||||||
|
// Determine file system to use.
|
||||||
|
fs = OS
|
||||||
|
if *zipfile != "" {
|
||||||
|
rc, err := zip.OpenReader(*zipfile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%s: %s\n", *zipfile, err)
|
||||||
|
}
|
||||||
|
fs = NewZipFS(rc)
|
||||||
|
}
|
||||||
|
|
||||||
// Check usage: either server and no args, or command line and args
|
// Check usage: either server and no args, or command line and args
|
||||||
if (*httpAddr != "") != (flag.NArg() == 0) {
|
if (*httpAddr != "") != (flag.NArg() == 0) {
|
||||||
usage()
|
usage()
|
||||||
|
199
src/cmd/godoc/zip.go
Normal file
199
src/cmd/godoc/zip.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file provides an implementation of the FileSystem
|
||||||
|
// interface based on the contents of a .zip file.
|
||||||
|
//
|
||||||
|
// Assumptions:
|
||||||
|
//
|
||||||
|
// - The file paths stored in the zip file must use a slash ('/') as path
|
||||||
|
// separator; and they must be relative (i.e., they must not start with
|
||||||
|
// a '/' - this is usually the case if the file was created w/o special
|
||||||
|
// options).
|
||||||
|
// - The zip file system treats the file paths found in the zip internally
|
||||||
|
// like absolute paths w/o a leading '/'; i.e., the paths are considered
|
||||||
|
// relative to the root of the file system.
|
||||||
|
// - All path arguments to file system methods must be absolute paths.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// zipFI is the zip-file based implementation of FileInfo
|
||||||
|
type zipFI struct {
|
||||||
|
name string // directory-local name
|
||||||
|
file *zip.File // nil for a directory
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fi zipFI) Name() string {
|
||||||
|
return fi.name
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fi zipFI) Size() int64 {
|
||||||
|
if fi.file != nil {
|
||||||
|
return int64(fi.file.UncompressedSize)
|
||||||
|
}
|
||||||
|
return 0 // directory
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fi zipFI) IsDirectory() bool {
|
||||||
|
return fi.file == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fi zipFI) IsRegular() bool {
|
||||||
|
return fi.file != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// zipFS is the zip-file based implementation of FileSystem
|
||||||
|
type zipFS struct {
|
||||||
|
*zip.ReadCloser
|
||||||
|
list zipList
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fs *zipFS) Close() os.Error {
|
||||||
|
fs.list = nil
|
||||||
|
return fs.ReadCloser.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func zipPath(name string) string {
|
||||||
|
if !path.IsAbs(name) {
|
||||||
|
panic(fmt.Sprintf("stat: not an absolute path: %s", name))
|
||||||
|
}
|
||||||
|
return name[1:] // strip '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fs *zipFS) stat(abspath string) (int, zipFI, os.Error) {
|
||||||
|
i := fs.list.lookup(abspath)
|
||||||
|
if i < 0 {
|
||||||
|
return -1, zipFI{}, fmt.Errorf("file not found: %s", abspath)
|
||||||
|
}
|
||||||
|
var file *zip.File
|
||||||
|
if abspath == fs.list[i].Name {
|
||||||
|
file = fs.list[i] // exact match found - must be a file
|
||||||
|
}
|
||||||
|
_, name := path.Split(abspath)
|
||||||
|
return i, zipFI{name, file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fs *zipFS) Open(abspath string) (io.ReadCloser, os.Error) {
|
||||||
|
_, fi, err := fs.stat(zipPath(abspath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fi.IsDirectory() {
|
||||||
|
return nil, fmt.Errorf("Open: %s is a directory", abspath)
|
||||||
|
}
|
||||||
|
return fi.file.Open()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fs *zipFS) Lstat(abspath string) (FileInfo, os.Error) {
|
||||||
|
_, fi, err := fs.stat(zipPath(abspath))
|
||||||
|
return fi, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fs *zipFS) Stat(abspath string) (FileInfo, os.Error) {
|
||||||
|
_, fi, err := fs.stat(zipPath(abspath))
|
||||||
|
return fi, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fs *zipFS) ReadDir(abspath string) ([]FileInfo, os.Error) {
|
||||||
|
path := zipPath(abspath)
|
||||||
|
i, fi, err := fs.stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !fi.IsDirectory() {
|
||||||
|
return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var list []FileInfo
|
||||||
|
dirname := path + "/"
|
||||||
|
prevname := ""
|
||||||
|
for _, e := range fs.list[i:] {
|
||||||
|
if !strings.HasPrefix(e.Name, dirname) {
|
||||||
|
break // not in the same directory anymore
|
||||||
|
}
|
||||||
|
name := e.Name[len(dirname):] // local name
|
||||||
|
file := e
|
||||||
|
if i := strings.IndexRune(name, '/'); i >= 0 {
|
||||||
|
// We infer directories from files in subdirectories.
|
||||||
|
// If we have x/y, return a directory entry for x.
|
||||||
|
name = name[0:i] // keep local directory name only
|
||||||
|
file = nil
|
||||||
|
}
|
||||||
|
// If we have x/y and x/z, don't return two directory entries for x.
|
||||||
|
// TODO(gri): It should be possible to do this more efficiently
|
||||||
|
// by determining the (fs.list) range of local directory entries
|
||||||
|
// (via two binary searches).
|
||||||
|
if name != prevname {
|
||||||
|
list = append(list, zipFI{name, file})
|
||||||
|
prevname = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (fs *zipFS) ReadFile(abspath string) ([]byte, os.Error) {
|
||||||
|
rc, err := fs.Open(abspath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.ReadAll(rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func NewZipFS(rc *zip.ReadCloser) FileSystem {
|
||||||
|
list := make(zipList, len(rc.File))
|
||||||
|
copy(list, rc.File) // sort a copy of rc.File
|
||||||
|
sort.Sort(list)
|
||||||
|
return &zipFS{rc, list}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type zipList []*zip.File
|
||||||
|
|
||||||
|
// zipList implements sort.Interface
|
||||||
|
func (z zipList) Len() int { return len(z) }
|
||||||
|
func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
|
||||||
|
func (z zipList) Swap(i, j int) { z[i], z[j] = z[j], z[i] }
|
||||||
|
|
||||||
|
|
||||||
|
// lookup returns the first index in the zipList
|
||||||
|
// of a path equal to name or beginning with name/.
|
||||||
|
func (z zipList) lookup(name string) int {
|
||||||
|
i := sort.Search(len(z), func(i int) bool {
|
||||||
|
return name <= z[i].Name
|
||||||
|
})
|
||||||
|
if i >= 0 {
|
||||||
|
iname := z[i].Name
|
||||||
|
if strings.HasPrefix(iname, name) && (len(name) == len(iname) || iname[len(name)] == '/') {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1 // no match
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user