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 { if strings.Contains(filename, "../") || strings.Contains(filename, "..\\") { return Page{ Error: "You cannot escape!", } } fullPath := filepath.Join(self.Root, "inc", filename) f, err := os.ReadFile(fullPath) if err != nil { return Page{ Error: err.Error(), } } if !strings.HasSuffix(filename, ".htm") { return Page{ Error: "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(), } } 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) if strings.Contains(slug, "../") || strings.Contains(slug, "..\\") { return errors.New("You cannot escape!") } _, 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" if strings.Contains(newSlug, "../") || strings.Contains(newSlug, "..\\") || strings.Contains(oldSlug, "../") || strings.Contains(oldSlug, "..\\") { return errors.New("You cannot escape!") } 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 { if strings.Contains(slug, "../") || strings.Contains(slug, "..\\") { return errors.New("You cannot escape!") } 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 := strings.Join(buildOptions["twtxt"], " ") cmdArgs := []string{} if twtxt != "" { cmdArgs = append(cmdArgs, "-t") cmdArgs = append(cmdArgs, 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 }