You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

358 lines
8.4 KiB

package main
import (
"os"
"path"
"github.com/inconshreveable/log15"
"path/filepath"
"errors"
"strings"
"io"
"database/sql"
_ "github.com/mattn/go-sqlite3"
"fmt"
)
const OVERVIEW_SUMMARY = "overview-summary.html"
var log = log15.New()
var toIndex []string
func main() {
arguments := os.Args
argLength := len(arguments)
if (argLength == 2 && arguments[1] == "--help") {
printUsage()
return
} else if (argLength != 3) {
log.Error("Invalid argument(s) provided")
printUsage()
os.Exit(1)
}
docsetName := path.Clean(arguments[1])
var javadocPath = path.Clean(arguments[2])
log.Info("Running with arguments", "docsetName", docsetName, "javadocPath", javadocPath)
docsetDirectoryPath := docsetName + ".docset"
if exists, _ := pathExists(docsetDirectoryPath); exists {
log.Info("Removing existing docset directory", "Docset directory path", docsetDirectoryPath)
if err := os.RemoveAll(docsetDirectoryPath); err != nil {
log.Error(
"Unable to remove existing docset directory",
"Docset directory path", docsetDirectoryPath,
"error", err,
)
os.Exit(1)
}
}
contentsDirectoryPath := docsetDirectoryPath + "/Contents"
resourcesDirectoryPath := contentsDirectoryPath + "/Resources"
documentsDirectoryPath := resourcesDirectoryPath + "/Documents"
log.Info("Creating docset folder structure...")
if err := os.MkdirAll(documentsDirectoryPath, os.ModePerm); err != nil {
log.Error("Unable to create docset folder structure", "Docset directory", docsetDirectoryPath)
os.Exit(1)
}
var docsetIndexFile string
overviewSummaryPath := javadocPath + OVERVIEW_SUMMARY
var summaryFound = false
if exists, _ := pathExists(overviewSummaryPath); !exists {
walkCount := 0
filepath.Walk(javadocPath, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
log.Error("Failed to walk path", "path", filePath, "err", err)
os.Exit(1)
}
walkCount++
if walkCount < 10000 {
if info.Name() == OVERVIEW_SUMMARY {
javadocPath = path.Dir(filePath)
summaryFound = true
}
return nil
} else {
return errors.New("Hit file enumeration limit")
}
})
} else {
summaryFound = true
}
if summaryFound {
docsetIndexFile = OVERVIEW_SUMMARY
}
hasMultipleIndices := false
indexFilesPath := javadocPath + "index-files"
if exists, _ := pathExists(indexFilesPath); exists {
if docsetIndexFile == "" {
docsetIndexFile = "index-files/index-1.html"
}
hasMultipleIndices = true
}
log.Info("Done!")
copyFiles(documentsDirectoryPath, javadocPath)
documentsDirectoryIndex := documentsDirectoryPath + "/index-all.html"
if exists, _ := pathExists(documentsDirectoryIndex); !hasMultipleIndices && exists {
toIndex = append(toIndex, documentsDirectoryIndex)
if docsetIndexFile == "" {
docsetIndexFile = "index-all.html"
}
} else {
indexFilesPath := documentsDirectoryPath + "/index-files"
filepath.Walk(indexFilesPath, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
log.Error("Failed to walk path", "filePath", filePath, "err", err)
os.Exit(1)
}
filename := info.Name()
if strings.HasPrefix(filename, "index-") && strings.HasSuffix(filename, ".html") {
toIndex = append(toIndex, filePath)
}
return err
})
}
if len(toIndex) == 0 {
log.Error("API folder specified does not contain any index files (either an 'index-all.html' file or an 'index-files' folder and is not valid")
printUsage()
return
}
writeInfoPlist(docsetName, docsetIndexFile, contentsDirectoryPath)
initDB(resourcesDirectoryPath, index(toIndex))
}
func printUsage() {
log.Info("Usage: javadocset <docset name> <javadoc API folder>")
log.Info("<docset name> - anything you want")
log.Info("<javadoc API folder> - the path of the javadoc API folder you want to index")
}
func copyFiles(documentsDirectoryPath, javadocPath string) {
log.Info("Copying files...", "source", javadocPath, "destination", documentsDirectoryPath)
src := path.Clean(javadocPath)
dst := path.Clean(documentsDirectoryPath)
srcBase := path.Base(src)
filepath.Walk(src, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
log.Error("Error walking path", "filePath", filePath)
os.Exit(1)
}
if info.IsDir() {
if path.Base(filePath) != srcBase {
// We only want to copy the directories within the source directory
// to the destination directory
directoryName := strings.Split(filePath, srcBase)[1]
err := os.MkdirAll(dst + directoryName, os.ModePerm)
if err != nil {
log.Error("Unable to create directory", "directory", directoryName)
os.Exit(1)
}
}
} else {
// Copy file
fileName := filepath.Base(filePath)
directoryName := strings.Split(filepath.Dir(filePath), srcBase)[1]
dstPath := filepath.Clean(dst + directoryName + "/" + fileName)
err = copyFileContents(filePath, dstPath)
if err != nil {
log.Error("Unable to copy file", "src", filePath, "dst", dstPath)
os.Exit(1)
}
}
return err
})
log.Info("Done!")
}
func writeInfoPlist(docsetName, docsetIndexFile, contentsDirectoryPath string) {
plistContentTemplate := "<?xml version=\"1.0\" encoding=\"UTF-8\"?><plist version=\"1.0\"><dict><key>CFBundleIdentifier</key> <string>%v</string><key>CFBundleName</key> <string>%v</string> <key>DocSetPlatformFamily</key> <string>%v</string> <key>dashIndexFilePath</key><string>%v</string><key>DashDocSetFamily</key><string>java</string><key>isDashDocset</key><true/></dict></plist>"
docsetIdentifier := firstPhraseLowerCased(docsetName)
plistContent := fmt.Sprintf(
plistContentTemplate,
docsetIdentifier,
docsetName,
docsetIdentifier,
docsetIndexFile,
)
infoPlistPath := contentsDirectoryPath + "/Info.plist"
err := writeStringToFile(plistContent, infoPlistPath)
if err != nil {
log.Error("Unable to write to plist file", "plistPath", infoPlistPath)
}
}
func initDB(resourcesDirectoryPath string, dbFunc func(*sql.DB)) {
dbPath := filepath.Clean(resourcesDirectoryPath + "/docSet.dsidx")
// We don't care, we just want to remove the index
os.Remove(dbPath)
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
log.Error("Unable to create sqlite database", "destination", dbPath, "error", err)
os.Exit(1)
}
defer db.Close()
_, err = db.Exec("CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)")
if err != nil {
log.Error("Unable to create table", "error", err)
os.Exit(1)
}
if dbFunc != nil {
dbFunc(db)
}
db.Exec("UPDATE searchIndex SET path = substr(path, 2)");
}
func index(indicesToIndex []string) func(db *sql.DB) {
return func(db *sql.DB) {
tx, err := db.Begin()
if err != nil {
log.Error("Unable to begin transactions for database", "error", err)
os.Exit(1)
}
stmt, err := tx.Prepare("INSERT INTO searchIndex(name, type, path) VALUES (?, ?, ?)")
if err != nil {
log.Error("Unable to create statement to insert into database", "error", err)
os.Exit(1)
}
defer stmt.Close()
added := make(map[string]bool)
for _, toIndex := range indicesToIndex {
parseIndex(toIndex, func(entry IndexEntry) {
name, elementType, path := entry.name, entry.elementType.value(), entry.path
uniqueKey := name + elementType + path
if !added[uniqueKey] {
_, err := stmt.Exec(name, elementType, path)
if err != nil {
log.Error(
"Unable to insert entry",
"name", name,
"elementType", elementType,
"path", path,
)
os.Exit(1)
}
added[uniqueKey] = true
}
})
}
tx.Commit()
}
}
/**
Utility functions
*/
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
func writeStringToFile(content, dst string) error {
file, err := os.Create(dst)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write([]byte(content))
return err
}
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
log.Error("Error copying", "error", err)
return
}
err = out.Sync()
return
}
func firstPhraseLowerCased(s string) string {
return strings.ToLower(func() string {
return strings.Split(s, " ")[0]
}())
}