docset-sfdc/SFDashC/main.go

323 lines
8.3 KiB
Go
Raw Normal View History

2016-02-10 20:08:26 +00:00
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
2016-02-10 20:08:26 +00:00
"io/ioutil"
"net/http"
"os"
"path/filepath"
"sync"
)
// CSS Paths
2017-07-15 21:24:10 +00:00
var cssBaseURL = "https://developer.salesforce.com/resource/stylesheets"
var cssFiles = []string{"holygrail.min.css", "docs.min.css", "syntax-highlighter.min.css"}
2017-07-15 21:24:10 +00:00
var buildDir = "build"
2016-02-10 20:08:26 +00:00
var wg sync.WaitGroup
2016-07-27 01:26:47 +00:00
var throttle = make(chan int, maxConcurrency)
2016-02-10 20:08:26 +00:00
const maxConcurrency = 16
func parseFlags() (locale string, deliverables []string, debug bool) {
2016-02-10 20:08:26 +00:00
flag.StringVar(
&locale, "locale", "en-us",
"locale to use for documentation (default: en-us)",
)
flag.BoolVar(
&debug, "debug", false, "this flag supresses warning messages",
2016-02-10 20:08:26 +00:00
)
flag.Parse()
// All other args are for deliverables
// apexcode, pages, or lightening
2016-02-10 20:08:26 +00:00
deliverables = flag.Args()
return
}
// getTOC Retrieves the TOC JSON and Unmarshals it
func getTOC(locale string, deliverable string) (toc *AtlasTOC, err error) {
var tocURL = fmt.Sprintf("https://developer.salesforce.com/docs/get_document/atlas.%s.%s.meta", locale, deliverable)
LogDebug("TOC URL: %s", tocURL)
2016-02-10 20:08:26 +00:00
resp, err := http.Get(tocURL)
2017-07-12 17:35:51 +00:00
ExitIfError(err)
2016-02-10 20:08:26 +00:00
// Read the downloaded JSON
2018-01-06 06:05:24 +00:00
defer func() {
ExitIfError(resp.Body.Close())
}()
2016-02-10 20:08:26 +00:00
contents, err := ioutil.ReadAll(resp.Body)
2017-07-12 17:35:51 +00:00
ExitIfError(err)
2016-02-10 20:08:26 +00:00
// Load into Struct
toc = new(AtlasTOC)
LogDebug("TOC JSON: %s", string(contents))
2016-02-10 20:08:26 +00:00
err = json.Unmarshal([]byte(contents), toc)
return
}
// verifyVersion ensures that the version retrieved is the latest
func verifyVersion(toc *AtlasTOC) error {
// jsonVersion, _ := json.Marshal(toc.Version)
// LogDebug("toc.Version" + string(jsonVersion))
2016-02-10 20:08:26 +00:00
currentVersion := toc.Version.DocVersion
// jsonAvailVersions, _ := json.Marshal(toc.AvailableVersions)
// LogDebug("toc.AvailableVersions" + string(jsonAvailVersions))
2016-02-10 20:08:26 +00:00
topVersion := toc.AvailableVersions[0].DocVersion
if currentVersion != topVersion {
2017-07-12 17:35:51 +00:00
return NewFormatedError("verifyVersion: retrieved version is not the latest. Found %s, latest is %s", currentVersion, topVersion)
2016-02-10 20:08:26 +00:00
}
return nil
}
func printSuccess(toc *AtlasTOC) {
LogInfo("Success: %s - %s - %s", toc.DocTitle, toc.Version.VersionText, toc.Version.DocVersion)
2016-02-10 20:08:26 +00:00
}
func saveMainContent(toc *AtlasTOC) {
filePath := fmt.Sprintf("%s.html", toc.Deliverable)
2017-07-15 21:24:10 +00:00
// Prepend build dir
filePath = filepath.Join(buildDir, filePath)
2016-02-10 20:08:26 +00:00
// Make sure file doesn't exist first
if _, err := os.Stat(filePath); os.IsNotExist(err) {
content := toc.Content
err = os.MkdirAll(filepath.Dir(filePath), 0755)
ExitIfError(err)
// TODO: Do something to format full page here
ofile, err := os.Create(filePath)
ExitIfError(err)
2018-01-06 06:05:24 +00:00
defer func() {
ExitIfError(ofile.Close())
}()
_, err = ofile.WriteString(
"<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />" +
content,
)
2016-02-10 20:08:26 +00:00
ExitIfError(err)
}
}
2018-01-06 06:05:24 +00:00
// saveContentVersion will retrieve the version number from the TOC and save that to a text file
2016-07-27 01:26:47 +00:00
func saveContentVersion(toc *AtlasTOC) {
filePath := fmt.Sprintf("%s-version.txt", toc.Deliverable)
2017-07-15 21:24:10 +00:00
// Prepend build dir
filePath = filepath.Join(buildDir, filePath)
2016-07-27 01:26:47 +00:00
err := os.MkdirAll(filepath.Dir(filePath), 0755)
ExitIfError(err)
ofile, err := os.Create(filePath)
ExitIfError(err)
2018-01-06 06:05:24 +00:00
defer func() {
ExitIfError(ofile.Close())
}()
2016-07-27 01:26:47 +00:00
_, err = ofile.WriteString(toc.Version.DocVersion)
ExitIfError(err)
}
2018-01-06 06:05:24 +00:00
// downloadCSS will download a CSS file using the CSS base URL
func downloadCSS(fileName string, wg *sync.WaitGroup) {
downloadFile(cssBaseURL+"/"+fileName, fileName, wg)
}
2016-02-10 20:08:26 +00:00
2018-01-06 06:05:24 +00:00
// downloadFile will download n aribtrary file to a given file path
// It will also handle throttling if a WaitGroup is provided
func downloadFile(url string, fileName string, wg *sync.WaitGroup) {
if wg != nil {
defer wg.Done()
}
filePath := filepath.Join(buildDir, fileName)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
err = os.MkdirAll(filepath.Dir(filePath), 0755)
ExitIfError(err)
2016-02-10 20:08:26 +00:00
ofile, err := os.Create(filePath)
ExitIfError(err)
2018-01-06 06:05:24 +00:00
defer func() {
ExitIfError(ofile.Close())
}()
2016-07-27 01:26:47 +00:00
response, err := http.Get(url)
ExitIfError(err)
2018-01-06 06:05:24 +00:00
defer func() {
ExitIfError(response.Body.Close())
}()
2016-02-10 20:08:26 +00:00
_, err = io.Copy(ofile, response.Body)
ExitIfError(err)
2016-02-10 20:08:26 +00:00
}
if wg != nil {
<-throttle
}
2016-02-10 20:08:26 +00:00
}
2018-01-06 06:05:24 +00:00
// getEntryType will return an entry type that should be used for a given entry and it's parent's type
func getEntryType(entry TOCEntry, parentType SupportedType) (SupportedType, error) {
if parentType.ForceCascadeType {
return parentType.CreateChildType(), nil
}
childType, err := lookupEntryType(entry)
if err != nil && parentType.ShouldCascade() {
2018-01-06 06:05:24 +00:00
childType = parentType.CreateChildType()
err = nil
}
return childType, err
}
// lookupEntryType returns the matching SupportedType for a given entry or returns an error
func lookupEntryType(entry TOCEntry) (SupportedType, error) {
for _, t := range SupportedTypes {
2016-02-10 20:08:26 +00:00
if entry.IsType(t) {
2018-01-06 06:05:24 +00:00
return t, nil
2016-02-10 20:08:26 +00:00
}
}
2018-01-06 06:05:24 +00:00
return SupportedType{}, NewTypeNotFoundError(entry)
2016-02-10 20:08:26 +00:00
}
// processEntryReference downloads html and indexes a toc item
2018-01-06 06:05:24 +00:00
func processEntryReference(entry TOCEntry, entryType SupportedType, toc *AtlasTOC) {
LogDebug("Processing: %s", entry.Text)
throttle <- 1
wg.Add(1)
go downloadContent(entry, toc, &wg)
2018-01-06 06:05:24 +00:00
if entryType.ShouldSkipIndex() {
LogDebug("%s is a container or is hidden. Do not index", entry.Text)
2018-01-06 06:05:24 +00:00
} else if !entryType.IsValidType() {
LogDebug("No entry type for %s. Cannot index", entry.Text)
} else {
SaveSearchIndex(dbmap, entry, entryType, toc)
}
}
// entryHierarchy allows breadcrumb naming
2016-02-10 20:08:26 +00:00
var entryHierarchy []string
// processChildReferences iterates through all child toc items, cascading types, and indexes them
2018-01-06 06:05:24 +00:00
func processChildReferences(entry TOCEntry, entryType SupportedType, toc *AtlasTOC) {
if entryType.PushName {
entryHierarchy = append(entryHierarchy, entry.CleanTitle(entryType))
2016-02-10 20:08:26 +00:00
}
for _, child := range entry.Children {
LogDebug("Reading child: %s", child.Text)
2016-02-10 20:08:26 +00:00
var err error
2018-01-06 06:05:24 +00:00
var childType SupportedType
// Skip anything without an HTML page
2016-02-10 20:08:26 +00:00
if child.LinkAttr.Href != "" {
2018-01-06 06:05:24 +00:00
childType, err = getEntryType(child, entryType)
if err == nil {
processEntryReference(child, childType, toc)
} else {
WarnIfError(err)
2016-02-10 20:08:26 +00:00
}
} else {
LogDebug("%s has no link. Skipping", child.Text)
2016-02-10 20:08:26 +00:00
}
if len(child.Children) > 0 {
processChildReferences(child, childType, toc)
}
}
LogDebug("Done processing children for %s", entry.Text)
2016-02-10 20:08:26 +00:00
2018-01-06 06:05:24 +00:00
if entryType.PushName {
2016-02-10 20:08:26 +00:00
entryHierarchy = entryHierarchy[:len(entryHierarchy)-1]
}
}
// downloadContent will download the html file for a given entry
2016-02-10 20:08:26 +00:00
func downloadContent(entry TOCEntry, toc *AtlasTOC, wg *sync.WaitGroup) {
defer wg.Done()
filePath := entry.GetContentFilepath(toc, true)
2017-07-15 21:24:10 +00:00
// Prepend build dir
filePath = filepath.Join(buildDir, filePath)
2016-02-10 20:08:26 +00:00
// Make sure file doesn't exist first
if _, err := os.Stat(filePath); os.IsNotExist(err) {
content, err := entry.GetContent(toc)
ExitIfError(err)
err = os.MkdirAll(filepath.Dir(filePath), 0755)
ExitIfError(err)
// TODO: Do something to format full page here
ofile, err := os.Create(filePath)
ExitIfError(err)
header := "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />" +
"<base href=\"../../\"/>\n"
for _, cssFile := range cssFiles {
header += fmt.Sprintf("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">", cssFile)
}
header += "<style>body { padding: 15px; }</style>"
2018-01-06 06:05:24 +00:00
defer func() {
ExitIfError(ofile.Close())
}()
_, err = ofile.WriteString(
header + content.Content,
)
2016-02-10 20:08:26 +00:00
ExitIfError(err)
}
<-throttle
}
func main() {
LogInfo("Starting...")
locale, deliverables, debug := parseFlags()
if debug {
SetLogLevel(DEBUG)
}
// Download CSS
for _, cssFile := range cssFiles {
throttle <- 1
wg.Add(1)
go downloadCSS(cssFile, &wg)
}
// Download icon
go downloadFile("https://developer.salesforce.com/resources2/favicon.ico", "icon.ico", nil)
// Init the Sqlite db
dbmap = InitDb(buildDir)
err := dbmap.TruncateTables()
ExitIfError(err)
for _, deliverable := range deliverables {
toc, err := getTOC(locale, deliverable)
err = verifyVersion(toc)
WarnIfError(err)
saveMainContent(toc)
saveContentVersion(toc)
// Download each entry
for _, entry := range toc.TOCEntries {
2018-01-06 06:05:24 +00:00
entryType, err := lookupEntryType(entry)
if err == nil {
processEntryReference(entry, entryType, toc)
}
processChildReferences(entry, entryType, toc)
}
printSuccess(toc)
}
wg.Wait()
}