mirror of https://github.com/misterzym/jdocset.git
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
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] |
|
}()) |
|
}
|
|
|