|
|
@@ -0,0 +1,117 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+ "strings"
|
|
|
+
|
|
|
+ "github.com/sagernet/sing-box/log"
|
|
|
+)
|
|
|
+
|
|
|
+func main() {
|
|
|
+ err := filepath.Walk("docs", func(path string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if info.IsDir() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if !strings.HasSuffix(path, ".md") {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return processFile(path)
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal(err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func processFile(path string) error {
|
|
|
+ content, err := os.ReadFile(path)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ lines := strings.Split(string(content), "\n")
|
|
|
+ modified := false
|
|
|
+ result := make([]string, 0, len(lines))
|
|
|
+
|
|
|
+ inQuoteBlock := false
|
|
|
+ materialLines := []int{} // indices of :material- lines in the block
|
|
|
+
|
|
|
+ for _, line := range lines {
|
|
|
+ // Check for quote block start
|
|
|
+ if strings.HasPrefix(line, "!!! quote \"") && strings.Contains(line, "sing-box") {
|
|
|
+ inQuoteBlock = true
|
|
|
+ materialLines = nil
|
|
|
+ result = append(result, line)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Inside a quote block
|
|
|
+ if inQuoteBlock {
|
|
|
+ trimmed := strings.TrimPrefix(line, " ")
|
|
|
+ isMaterialLine := strings.HasPrefix(trimmed, ":material-")
|
|
|
+ isEmpty := strings.TrimSpace(line) == ""
|
|
|
+ isIndented := strings.HasPrefix(line, " ")
|
|
|
+
|
|
|
+ if isMaterialLine {
|
|
|
+ materialLines = append(materialLines, len(result))
|
|
|
+ result = append(result, line)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Block ends when:
|
|
|
+ // - Empty line AFTER we've seen material lines, OR
|
|
|
+ // - Non-indented, non-empty line
|
|
|
+ blockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented)
|
|
|
+ if blockEnds {
|
|
|
+ // Process collected material lines
|
|
|
+ if len(materialLines) > 0 {
|
|
|
+ for j, idx := range materialLines {
|
|
|
+ isLast := j == len(materialLines)-1
|
|
|
+ resultLine := strings.TrimRight(result[idx], " ")
|
|
|
+ if !isLast {
|
|
|
+ // Add trailing two spaces for non-last lines
|
|
|
+ resultLine += " "
|
|
|
+ }
|
|
|
+ if result[idx] != resultLine {
|
|
|
+ modified = true
|
|
|
+ result[idx] = resultLine
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ inQuoteBlock = false
|
|
|
+ materialLines = nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ result = append(result, line)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle case where file ends while still in a block
|
|
|
+ if inQuoteBlock && len(materialLines) > 0 {
|
|
|
+ for j, idx := range materialLines {
|
|
|
+ isLast := j == len(materialLines)-1
|
|
|
+ resultLine := strings.TrimRight(result[idx], " ")
|
|
|
+ if !isLast {
|
|
|
+ resultLine += " "
|
|
|
+ }
|
|
|
+ if result[idx] != resultLine {
|
|
|
+ modified = true
|
|
|
+ result[idx] = resultLine
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if modified {
|
|
|
+ newContent := strings.Join(result, "\n")
|
|
|
+ if !bytes.Equal(content, []byte(newContent)) {
|
|
|
+ log.Info("formatted: ", path)
|
|
|
+ return os.WriteFile(path, []byte(newContent), 0o644)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|