nirvash/archetype/eureka.go

290 lines
6.7 KiB
Go

package archetype
import (
"errors"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
type EurekaAdapter struct {
Root string
Config map[ConfigOption]string
}
func (self *EurekaAdapter) Init(cfg *Config) {
fileInfo, err := os.Stat(cfg.Root)
if os.IsNotExist(err) {
panic("SSG content root does not exist! Ensure your configs are correct or create it!")
} else if !fileInfo.IsDir() {
panic("SSG content root is not a directory!")
}
self.Root = cfg.Root
self.Config = make(map[ConfigOption]string)
// TODO: read config.h and build self.Config
err = self.readCfg()
if err != nil {
panic("config.h is malformed!")
}
}
func (self *EurekaAdapter) Name() string {
return "eureka"
}
func (self *EurekaAdapter) EditableSlugs() bool {
return false
}
func (self *EurekaAdapter) BuildOptions() []string {
return []string{"twtxt"}
}
func (self *EurekaAdapter) GetConfig() map[ConfigOption]string {
return self.Config
}
func (self *EurekaAdapter) SetConfig(cfg map[ConfigOption]string) error {
self.Config = cfg
return self.writeCfg()
}
func (self *EurekaAdapter) ListPages() map[string]string {
files, err := ioutil.ReadDir(
filepath.Join(self.Root, "inc"))
if err != nil {
panic(err.Error())
}
pages := map[string]string{}
for _, file := range files {
filename := file.Name()
if strings.HasSuffix(filename, ".htm") {
pages[filename] = strings.Replace(
strings.TrimSuffix(filename, ".htm"), "_", " ", -1)
}
}
return pages
}
func (self *EurekaAdapter) GetPage(filename string) (Page, error) {
fullPath := filepath.Join(self.Root, "inc", filename)
f, err := os.ReadFile(fullPath)
if err != nil {
return Page{}, err
}
if !strings.HasSuffix(filename, ".htm") {
return Page{}, errors.New("Page file extension is not '.htm'")
}
title := strings.Replace(
strings.TrimSuffix(filename, ".htm"), "_", " ", -1)
fileInfo, _ := os.Stat(fullPath)
content := string(f[:])
return Page{
Title: title,
Content: content,
Edited: fileInfo.ModTime(),
}, nil
}
func (self *EurekaAdapter) FormatPage(raw string) string {
// TODO: implement Eureka formatter to show preview
return raw
}
func (self *EurekaAdapter) FormattingHelp() string {
// TODO: show Eureka formatting guide
return "help!"
}
func (self *EurekaAdapter) CreatePage(slug, title, content string) error {
// eureka creates titles from slugs, so we transform the title into the slug
slug = strings.ReplaceAll(title, " ", "_") + ".htm"
path := filepath.Join(self.Root, "inc", slug)
_, err := os.Stat(path)
if err == nil || !os.IsNotExist(err) {
return errors.New("File already exists")
}
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
f.WriteString(content)
return nil
}
func (self *EurekaAdapter) SavePage(oldSlug, newSlug, title, content string) error {
// eureka creates titles from slugs, so we transform the title into the slug
newSlug = strings.ReplaceAll(title, " ", "_") + ".htm"
f, err := os.Create(filepath.Join(self.Root, "inc", newSlug))
if err != nil {
return err
}
defer f.Close()
if oldSlug != newSlug {
siteRoot := self.Config[ConfigOption{
Name: "SITEROOT",
Type: "string",
}]
htmlFile := filepath.Join(self.Root, siteRoot, oldSlug+"l")
_, err := os.Stat(htmlFile)
if !os.IsNotExist(err) {
os.Remove(htmlFile)
}
os.Remove(filepath.Join(self.Root, "inc", oldSlug))
}
f.WriteString(content)
return nil
}
func (self *EurekaAdapter) DeletePage(slug string) error {
siteRoot := self.Config[ConfigOption{
Name: "SITEROOT",
Type: "string",
}]
htmlFile := filepath.Join(self.Root, siteRoot, slug+"l")
_, err := os.Stat(htmlFile)
if !os.IsNotExist(err) {
os.Remove(htmlFile)
}
return os.Remove(filepath.Join(self.Root, "inc", slug))
}
func (self *EurekaAdapter) Build(buildOptions map[string][]string) BuildStatus {
twtxt := buildOptions["twtxt"][0]
cmdArgs := ""
if twtxt != "" {
cmdArgs += "-t " + twtxt
}
cmd := exec.Command("./build.sh", cmdArgs)
cmd.Dir = self.Root
out, err := cmd.CombinedOutput()
return BuildStatus{
Success: err == nil,
Message: string(out),
}
}
func (self *EurekaAdapter) readCfg() error {
configPath := filepath.Join(self.Root, "config.h")
_, err := os.Stat(filepath.Join(self.Root, "config.h"))
if os.IsNotExist(err) {
configPath = filepath.Join(self.Root, "config.def.h")
}
f, err := os.ReadFile(configPath)
if err != nil {
return err
}
fileData := string(f[:])
macros := strings.Split(fileData, "#define ")[1:]
for _, macro := range macros {
tokens := strings.Split(strings.TrimSpace(macro), " ")
k := tokens[0]
v := strings.TrimSpace(strings.Join(tokens[1:], " "))
if strings.Contains(v, "\"") {
if strings.Contains(v, "\\\r\n") || strings.Contains(v, "\\\n") {
// process multiline string
lines := strings.Split(v, "\n")
cleanedString := ""
for _, l := range lines {
l = strings.TrimSuffix(l, "\r")
l = strings.TrimSuffix(l, "\\")
l = strings.TrimSpace(l)
l = strings.TrimPrefix(l, "\"")
l = strings.TrimSuffix(l, "\"")
l = strings.ReplaceAll(l, "\\\"", "\"")
l = strings.ReplaceAll(l, "\\n", "\n")
cleanedString += l
}
self.Config[ConfigOption{
Name: k,
Type: "multilinestring",
}] = cleanedString
} else {
cleanedString := strings.TrimSuffix(strings.TrimPrefix(v, "\""), "\"")
cleanedString = strings.ReplaceAll(cleanedString, "\\n", "\n")
cleanedString = strings.ReplaceAll(cleanedString, "\r", "")
cleanedString = strings.ReplaceAll(cleanedString, "\\\"", "\"")
self.Config[ConfigOption{
Name: k,
Type: "string",
}] = cleanedString
}
} else if strings.Contains(v, ".") {
_, err := strconv.ParseFloat(v, 64)
if err != nil {
return err
}
self.Config[ConfigOption{
Name: k,
Type: "float",
}] = v
} else {
_, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return err
}
self.Config[ConfigOption{
Name: k,
Type: "int",
}] = v
}
}
return nil
}
func (self *EurekaAdapter) writeCfg() error {
f, err := os.Create(filepath.Join(self.Root, "config.h"))
if err != nil {
return err
}
defer f.Close()
for k, v := range self.Config {
switch k.Type {
case "int":
_, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return err
}
f.WriteString("#define " + k.Name + " " + v + "\n")
case "float":
_, err := strconv.ParseFloat(v, 64)
if err != nil {
return err
}
f.WriteString("#define " + k.Name + " " + v + "\n")
case "string":
fallthrough
case "multilinestring":
v = strings.ReplaceAll(v, "\"", "\\\"")
v = strings.ReplaceAll(v, "\n", "\\n\" \\\n\"")
v = strings.ReplaceAll(v, "\r", "")
f.WriteString("#define " + k.Name + " \"" + v + "\"\n")
default:
return errors.New("Unsupported config value type: " + k.Type)
}
}
return nil
}