mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-06 15:47:19 +02:00
275 lines
7.0 KiB
Go
275 lines
7.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gomarkdown/markdown"
|
|
"github.com/gomarkdown/markdown/html"
|
|
"github.com/gomarkdown/markdown/parser"
|
|
"github.com/yosssi/gohtml"
|
|
)
|
|
|
|
func build() {
|
|
rootDir := "./docs"
|
|
outputFile := "./index.json"
|
|
|
|
type Folder struct {
|
|
Title string `json:"title"`
|
|
Path string `json:"path"`
|
|
Type string `json:"type"`
|
|
Files []interface{} `json:"files,omitempty"`
|
|
}
|
|
|
|
var buildTree func(path string, d fs.DirEntry) interface{}
|
|
buildTree = func(path string, d fs.DirEntry) interface{} {
|
|
relativePath, _ := filepath.Rel(rootDir, path)
|
|
relativePath = filepath.ToSlash(relativePath)
|
|
var title string
|
|
if d.IsDir() {
|
|
title = filepath.Base(relativePath)
|
|
} else {
|
|
title = strings.TrimSuffix(filepath.Base(relativePath), filepath.Ext(relativePath))
|
|
}
|
|
|
|
//Strip the leader numbers from the title, e.g. 1. Introduction -> Introduction
|
|
if strings.Contains(title, ".") {
|
|
parts := strings.SplitN(title, ".", 2)
|
|
if len(parts) > 1 {
|
|
title = strings.TrimSpace(parts[1])
|
|
}
|
|
}
|
|
// Remove leading numbers and dots
|
|
title = strings.TrimLeft(title, "0123456789. ")
|
|
// Remove leading spaces
|
|
title = strings.TrimLeft(title, " ")
|
|
|
|
if d.IsDir() {
|
|
folder := Folder{
|
|
Title: title,
|
|
Path: relativePath,
|
|
Type: "folder",
|
|
}
|
|
|
|
entries, err := os.ReadDir(path)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.Name() == "img" || entry.Name() == "assets" {
|
|
continue
|
|
}
|
|
if strings.Contains(filepath.ToSlash(filepath.Join(relativePath, entry.Name())), "/img/") || strings.Contains(filepath.ToSlash(filepath.Join(relativePath, entry.Name())), "/assets/") {
|
|
continue
|
|
}
|
|
child := buildTree(filepath.Join(path, entry.Name()), entry)
|
|
if child != nil {
|
|
folder.Files = append(folder.Files, child)
|
|
}
|
|
}
|
|
return folder
|
|
} else {
|
|
ext := filepath.Ext(relativePath)
|
|
if ext != ".md" && ext != ".html" && ext != ".txt" {
|
|
return nil
|
|
}
|
|
return FileInfo{
|
|
Filename: relativePath,
|
|
Title: title,
|
|
Type: "file",
|
|
}
|
|
}
|
|
}
|
|
|
|
rootInfo, err := os.Stat(rootDir)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
rootFolder := buildTree(rootDir, fs.FileInfoToDirEntry(rootInfo))
|
|
jsonData, err := json.MarshalIndent(rootFolder, "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
/* For debug purposes, print the JSON structure */
|
|
err = os.WriteFile(outputFile, jsonData, 0644)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
/* For each file in the folder structure, convert markdown to HTML */
|
|
htmlOutputDir := "./html"
|
|
os.RemoveAll(htmlOutputDir) // Clear previous HTML output
|
|
err = os.MkdirAll(htmlOutputDir, os.ModePerm)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var processFiles func(interface{})
|
|
processFiles = func(node interface{}) {
|
|
switch n := node.(type) {
|
|
case FileInfo:
|
|
if filepath.Ext(n.Filename) == ".md" {
|
|
inputPath := filepath.Join(rootDir, n.Filename)
|
|
outputPath := filepath.Join(htmlOutputDir, strings.TrimSuffix(n.Filename, ".md")+".html")
|
|
|
|
// Ensure the output directory exists
|
|
err := os.MkdirAll(filepath.Dir(outputPath), os.ModePerm)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Read the markdown file
|
|
mdContent, err := os.ReadFile(inputPath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Convert markdown to HTML
|
|
docContent := mdToHTML(mdContent)
|
|
docContent, err = optimizeCss(docContent)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Load the HTML template
|
|
templateBytes, err := os.ReadFile("template/documents.html")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Generate the side menu HTML
|
|
sideMenuHTML, err := generateSideMenu(string(jsonData), n.Title)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
templateBody := string(templateBytes)
|
|
// Replace placeholders in the template
|
|
htmlContent := strings.ReplaceAll(templateBody, "{{title}}", n.Title+" | Zoraxy Documentation")
|
|
htmlContent = strings.ReplaceAll(htmlContent, "{{content}}", string(docContent))
|
|
htmlContent = strings.ReplaceAll(htmlContent, "{{sideMenu}}", sideMenuHTML)
|
|
htmlContent = strings.ReplaceAll(htmlContent, "{{root_url}}", *root_url)
|
|
//Add more if needed
|
|
|
|
//Beautify the HTML content
|
|
htmlContent = gohtml.Format(htmlContent)
|
|
|
|
// Write the HTML file
|
|
err = os.WriteFile(outputPath, []byte(htmlContent), 0644)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
//Check if the .md file directory have an ./img or ./assets folder. If yes, copy the contents to the output directory
|
|
imgDir := filepath.Join(rootDir, filepath.Dir(n.Filename), "img")
|
|
assetsDir := filepath.Join(rootDir, filepath.Dir(n.Filename), "assets")
|
|
if _, err := os.Stat(imgDir); !os.IsNotExist(err) {
|
|
err = filepath.Walk(imgDir, func(srcPath string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
relPath, err := filepath.Rel(imgDir, srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destPath := filepath.Join(filepath.Dir(outputPath), "img", relPath)
|
|
if info.IsDir() {
|
|
return os.MkdirAll(destPath, os.ModePerm)
|
|
}
|
|
data, err := os.ReadFile(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(destPath, data, 0644)
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
if _, err := os.Stat(assetsDir); !os.IsNotExist(err) {
|
|
err = filepath.Walk(assetsDir, func(srcPath string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
relPath, err := filepath.Rel(assetsDir, srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destPath := filepath.Join(filepath.Dir(outputPath), "assets", relPath)
|
|
if info.IsDir() {
|
|
return os.MkdirAll(destPath, os.ModePerm)
|
|
}
|
|
data, err := os.ReadFile(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(destPath, data, 0644)
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
fmt.Println("Generated HTML:", outputPath)
|
|
}
|
|
case Folder:
|
|
for _, child := range n.Files {
|
|
processFiles(child)
|
|
}
|
|
}
|
|
}
|
|
|
|
processFiles(rootFolder)
|
|
copyOtherRes()
|
|
}
|
|
|
|
func copyOtherRes() {
|
|
srcDir := "./assets"
|
|
destDir := "./html/assets"
|
|
|
|
err := filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
relPath, err := filepath.Rel(srcDir, srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destPath := filepath.Join(destDir, relPath)
|
|
if info.IsDir() {
|
|
return os.MkdirAll(destPath, os.ModePerm)
|
|
}
|
|
data, err := os.ReadFile(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(destPath, data, 0644)
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
}
|
|
|
|
func mdToHTML(md []byte) []byte {
|
|
// create markdown parser with extensions
|
|
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
|
|
p := parser.NewWithExtensions(extensions)
|
|
doc := p.Parse(md)
|
|
|
|
// create HTML renderer with extensions
|
|
htmlFlags := html.CommonFlags | html.HrefTargetBlank
|
|
opts := html.RendererOptions{
|
|
Flags: htmlFlags,
|
|
}
|
|
renderer := html.NewRenderer(opts)
|
|
|
|
return markdown.Render(doc, renderer)
|
|
}
|