func openDocsiteFromConfig(configData []byte, baseDir string) (*docsite.Site, *docsiteConfig, error) {
	var config docsiteConfig
	if err := json.Unmarshal(configData, &config); err != nil {
		return nil, nil, errors.WithMessage(err, "reading docsite configuration")
	}
	site, err := partialSiteFromConfig(config)
	if err != nil {
		return nil, nil, err
	}
	httpDirOrNil := func(dir string) http.FileSystem {
		if dir == "" {
			return nil
		}
		return http.Dir(filepath.Join(baseDir, dir))
	}
	site.Templates = httpDirOrNil(config.Templates)
	site.Content = nonVersionedFileSystem{httpDirOrNil(config.Content)}
	site.Assets = httpDirOrNil(config.Assets)
	if err := addRedirectsFromAssets(site); err != nil {
		return nil, nil, err
	}
	return site, &config, nil
}
openDocsiteFromConfig reads the documentation site data from a docsite.json file. All file system
paths in docsite.json are resolved relative to baseDir.
package main
import (
	"archive/zip"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode"
	"github.com/pkg/errors"
	"github.com/sourcegraph/docsite"
	"golang.org/x/tools/godoc/vfs/httpfs"
	"golang.org/x/tools/godoc/vfs/mapfs"
)
func siteFromFlags() (*docsite.Site, *docsiteConfig, error) {
	site, config, err := openDocsiteFromEnv()
	if site != nil || err != nil {
		return site, config, err
	}
	paths := filepath.SplitList(*configPath)
	for _, path := range paths {
		data, err := ioutil.ReadFile(path)
		if os.IsNotExist(err) {
			continue
		} else if err != nil {
			return nil, nil, errors.WithMessage(err, "reading docsite config file (from -config flag)")
		}
		return openDocsiteFromConfig(data, filepath.Dir(path))
	}
	return nil, nil, fmt.Errorf("no docsite.json config file found (search paths: %s)", *configPath)
}
type docsiteConfig struct {
	Content               string
	ContentExcludePattern string
	DefaultContentBranch  string
	BaseURLPath           string
	RootURL               string
	Templates             string
	Assets                string
	AssetsBaseURLPath     string
	Redirects             map[string]string
	Check                 struct {
		IgnoreURLPattern string
	}
}
func partialSiteFromConfig(config docsiteConfig) (*docsite.Site, error) {
	var site docsite.Site
	if config.ContentExcludePattern != "" {
		var err error
		site.ContentExcludePattern, err = regexp.Compile(config.ContentExcludePattern)
		if err != nil {
			return nil, err
		}
	}
	if config.Check.IgnoreURLPattern != "" {
		var err error
		site.CheckIgnoreURLPattern, err = regexp.Compile(config.Check.IgnoreURLPattern)
		if err != nil {
			return nil, err
		}
	}
	if config.BaseURLPath != "" {
		site.Base = &url.URL{Path: config.BaseURLPath}
	}
	if config.RootURL != "" {
		var err error
		site.Root, err = url.Parse(config.RootURL)
		if err != nil {
			return nil, err
		}
		if site.Root.Scheme == "" || site.Root.Host == "" {
			return nil, fmt.Errorf(
				"invalid RootURL, should either be blank or must include scheme and host, got %s instead",
				config.RootURL,
			)
		}
	}
	if config.AssetsBaseURLPath != "" {
		site.AssetsBase = &url.URL{Path: config.AssetsBaseURLPath}
	}
	for fromPath, toURLStr := range config.Redirects {
		if err := addSiteRedirect(&site, fromPath, toURLStr); err != nil {
			return nil, err
		}
	}
	return &site, nil
}
func addSiteRedirect(site *docsite.Site, fromPath, toURLStr string) error {
	if !strings.HasPrefix(fromPath, "/") || path.Clean(fromPath) != fromPath {
		return fmt.Errorf("invalid redirect from-path %q (must start with '/' and be clean)", fromPath)
	}
	toURL, err := url.Parse(toURLStr)
	if err != nil {
		return errors.WithMessage(err, "invalid redirect destination URL")
	}
	if site.Redirects == nil {
		site.Redirects = map[string]*url.URL{}
	}
	site.Redirects[fromPath] = toURL
	return nil
}
func addRedirectsFromAssets(site *docsite.Site) error {
	raw, err := docsite.ReadFile(site.Assets, "redirects")
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	for _, line := range bytes.Split(raw, []byte("\n")) {
		line = bytes.TrimSpace(line)
		if len(line) == 0 || line[0] == '#' {
			continue
		}
		parts := strings.Fields(string(line))
		if len(parts) != 3 {
			return fmt.Errorf("invalid redirects line: %q", line)
		}
		fromPath, toURLStr, statusCode := parts[0], parts[1], parts[2]
		if want := http.StatusPermanentRedirect; statusCode != strconv.Itoa(want) {
			return fmt.Errorf("invalid redirect from path %q with HTTP status code %s (only HTTP %d %s is supported)", fromPath, statusCode, want, http.StatusText(want))
		}
		if err := addSiteRedirect(site, fromPath, toURLStr); err != nil {
			return err
		}
	}
	return nil
}