package main import ( "fmt" "os" "strings" "unicode" ) // AddTag generates code for a new custom tag func AddTag(delimiter, tagName, targetElement string) { fmt.Printf("Adding tag: %s -> %s (delimiter: %s)\n", tagName, targetElement, delimiter) if err := updateTagsFile(delimiter, tagName); err != nil { fmt.Printf("Error updating templates/tags.go: %v\n", err) os.Exit(1) } if err := updateParseFile(tagName, targetElement); err != nil { fmt.Printf("Error updating parse.go: %v\n", err) os.Exit(1) } fmt.Println("Successfully added tag!") } func updateTagsFile(delimiter, tagName string) error { path := "templates/tags.go" content, err := os.ReadFile(path) if err != nil { return err } pascalTagName := toPascalCase(tagName) newLine := fmt.Sprintf("var %s = customtag.New(\"%s\", \"%s\")\n", pascalTagName, delimiter, tagName) // check if already exists if strings.Contains(string(content), newLine) { fmt.Println("Tag definition already exists in templates/tags.go") return nil } // Append to file f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return err } defer f.Close() if _, err := f.WriteString(newLine); err != nil { return err } return nil } func updateParseFile(tagName, targetElement string) error { path := "parse.go" contentBytes, err := os.ReadFile(path) if err != nil { return err } content := string(contentBytes) pascalTagName := toPascalCase(tagName) // 1. Add to Goldmark Extensions // Find `templates.SidebarTag,` or `templates.TopBanner,` and add after it // We'll look for `goldmark.WithExtensions(` and find the closing `),` extLine := fmt.Sprintf("\t\t\t\ttemplates.%s,", pascalTagName) if !strings.Contains(content, extLine) { // Simple insertion strategy: find `templates.TopBanner,` anchor := "templates.TopBanner," if idx := strings.Index(content, anchor); idx != -1 { insertAt := idx + len(anchor) content = content[:insertAt] + "\n" + extLine + content[insertAt:] } else { return fmt.Errorf("could not find anchor 'templates.TopBanner,' in parse.go to insert extension registration") } } else { fmt.Println("Extension registration already exists in parse.go") } // 2. Add Post-Processing Logic // We'll look for `// Build Full Page` and insert before it, or after the TopBanner block. // A good anchor is `// _-_- TopBanner` block's end. // Let's construct the code block to insert targetTag, targetClass := parseTargetElement(targetElement) // Prepare the replacement logic var replacementLogic string if targetClass != "" { replacementLogic = fmt.Sprintf(`return fmt.Sprintf("<%s class=\"%s\">%%s", innerContent)`, targetTag, targetClass, targetTag) } else { replacementLogic = fmt.Sprintf(`return fmt.Sprintf("<%s>%%s", innerContent)`, targetTag, targetTag) } codeBlock := fmt.Sprintf(` // %s %s %sRegex := regexp.MustCompile("(?s)<%s>.*?") if %sRegex.MatchString(htmlContent) { htmlContent = %sRegex.ReplaceAllStringFunc(htmlContent, func(match string) string { innerContent := strings.TrimPrefix(match, "<%s>") innerContent = strings.TrimSuffix(innerContent, "") %s }) } `, pascalTagName, tagName, tagName, tagName, tagName, tagName, tagName, tagName, tagName, replacementLogic) if !strings.Contains(content, fmt.Sprintf("%sRegex :=", tagName)) { // Anchor: find end of TopBanner block // We know TopBanner block ends with a closing brace for the `if topbannerRegex.MatchString` block // But strictly speaking, we are inside `if file.RoutePath == "/" { ... }`. // Let's look for `// Build Full Page` and insert BEFORE it. anchor := "// Build Full Page" if idx := strings.Index(content, anchor); idx != -1 { // Insert BEFORE "// Build Full Page", which matches the global scope (after the if block) content = content[:idx] + codeBlock + content[idx:] } else { return fmt.Errorf("could not find anchor '// Build Full Page' in parse.go") } } else { fmt.Println("Regex logic already exists in parse.go") } return os.WriteFile(path, []byte(content), 0644) } func toPascalCase(s string) string { if s == "" { return "" } runes := []rune(s) runes[0] = unicode.ToUpper(runes[0]) return string(runes) } func parseTargetElement(t string) (string, string) { parts := strings.Split(t, ".") if len(parts) == 2 { return parts[0], parts[1] } return t, "" }