Created
          September 26, 2025 03:44 
        
      - 
      
 - 
        
Save anthonyrisinger/dfcd8a534a974e38160b5d45cc0cc066 to your computer and use it in GitHub Desktop.  
    The `flow-tool.go` is a testing and development utility and fake-streaming markdown renderer that generates, processes, and analyzes markdown content through Glamour. It supports flexible markdown primitive generation with customizable repetition and spacing, simulates various streaming modes (unbuffered, buffered, windowed) with artificial dela…
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | package main | |
| import ( | |
| "bufio" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "math/rand" | |
| "os" | |
| "slices" | |
| "strconv" | |
| "strings" | |
| "time" | |
| "github.com/charmbracelet/glamour" | |
| "github.com/pmezard/go-difflib/difflib" | |
| ) | |
| const GlamourBlank = " \n" | |
| type Config struct { | |
| Raw, Hex, Blanks, Diffs bool | |
| Style, Arg1, Arg2, Arg3 string | |
| Width, Flow, Repeat int | |
| } | |
| type Tool struct { | |
| renderer *glamour.TermRenderer | |
| config Config | |
| } | |
| func NewTool(config Config) (*Tool, error) { | |
| renderer, err := glamour.NewTermRenderer( | |
| glamour.WithWordWrap(config.Width), | |
| glamour.WithStylePath(config.Style), | |
| ) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &Tool{renderer: renderer, config: config}, nil | |
| } | |
| // Primitive content templates | |
| func (t *Tool) primitiveContent() map[string]string { | |
| return map[string]string{ | |
| "text": "Hello world %d_%d", | |
| "paragraph": "This is a paragraph with multiple words and the number %d_%d.", | |
| "header": "# Header %d_%d", | |
| "header1": "# Header %d_%d", | |
| "header2": "## Header %d_%d", | |
| "list": "- List item %d_%d", | |
| "olist": "1. List item %d_%d", | |
| "code": "```go\nfunc test_%d_%d() { return nil }\n```", | |
| "inline": "Text with `code%d_%d` inside", | |
| "blockquote": "> Quote from %d_%d", | |
| "table": "| A | B |\n|---|---|\n| 0 | %d_%d |", | |
| "table2": "| Col%d | Col%d | ColN |\n|------|------|------|\n| A1 | B1 | C1 |\n| A2 | B2 | C2 |", | |
| "link": "[ref%d_%d]: https://example.com/", | |
| "hrule": "---", | |
| "bold": "**Bold text %d_%d**", | |
| "italic": "*Italic text %d_%d*", | |
| "strike": "~~Strike text %d_%d~~", | |
| "emoji": "🚀 Rocket emoji %d_%d", | |
| "checklist": "- [x] Done (%d_%d)\n- [ ] Todo (0)", | |
| "yaml": "---\nkey%d_%d: value\n---", | |
| "json": "```json\n{\"key%d_%d\": \"value\"}\n```", | |
| "longtext": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat (%d_%d).", | |
| "mixedblock": "> Quote from %d_%d\n\n- List item\n\n```\ncode\n```", | |
| "empty": "", | |
| "arg1": t.config.Arg1, | |
| "arg2": t.config.Arg2, | |
| "arg3": t.config.Arg3, | |
| } | |
| } | |
| // Parse primitive with modifiers: name@count with optional spacing modifiers (--) | |
| func parsePrimitive(primitive string) (name string, count int, spacing int) { | |
| name, count, spacing = primitive, 1, 2 | |
| // Handle spacing modifiers | |
| for range 3 { | |
| if !strings.HasSuffix(name, "-") { | |
| break | |
| } | |
| name = strings.TrimSuffix(name, "-") | |
| spacing-- | |
| } | |
| // Handle count modifier | |
| if parts := strings.Split(name, "@"); len(parts) == 2 { | |
| if n, err := strconv.Atoi(parts[1]); err == nil && n > 0 { | |
| name, count = parts[0], n | |
| } | |
| } | |
| return | |
| } | |
| // Generate markdown content from primitives | |
| func (t *Tool) generateMarkdown(primitives []string) (string, error) { | |
| var result strings.Builder | |
| templates := t.primitiveContent() | |
| // Cap infinite mode at 10000 iterations to prevent infinite memory growth | |
| maxRepeat := t.config.Repeat | |
| if maxRepeat == -1 { | |
| maxRepeat = 10000 | |
| } | |
| for seqNum := range maxRepeat { | |
| needsSeqSep := false | |
| for _, primitive := range primitives { | |
| name, count, spacing := parsePrimitive(primitive) | |
| template, exists := templates[name] | |
| if !exists { | |
| return "", fmt.Errorf("Unknown primitive specified: %s", name) | |
| } | |
| for i := range count { | |
| if name == "arg1" || name == "arg2" || name == "arg3" { | |
| result.WriteString(template) | |
| } else if strings.Contains(template, "%d") { | |
| fmt.Fprintf(&result, template, seqNum+1, i+1) | |
| } else { | |
| result.WriteString(template) | |
| } | |
| // Add spacing between iterations within same primitive | |
| if i < count-1 { | |
| switch spacing { | |
| case 2: | |
| result.WriteString("\n\n") | |
| case 1: | |
| result.WriteString("\n") | |
| case 0: | |
| result.WriteString(" ") | |
| } | |
| } | |
| needsSeqSep = true | |
| } | |
| // Add spacing after primitive | |
| if spacing == 2 { | |
| result.WriteString("\n\n") | |
| } else { | |
| result.WriteString("\n") | |
| } | |
| } | |
| // Add spacing between sequence repetitions | |
| if (t.config.Repeat == -1 || seqNum < t.config.Repeat-1) && needsSeqSep { | |
| result.WriteString("\n\n") | |
| } | |
| } | |
| return result.String(), nil | |
| } | |
| // Read input from file, stdin, or generate from primitives | |
| func (t *Tool) getInput(args []string) (input, source string, err error) { | |
| if len(args) > 0 && !t.isPrimitive(args[0]) { | |
| // File input | |
| content, err := os.ReadFile(args[0]) | |
| if err != nil { | |
| return "", "", err | |
| } | |
| return string(content), "FILE", nil | |
| } | |
| if len(args) == 0 { | |
| // No args provided - read from stdin (deadman switch handles timeout) | |
| var result strings.Builder | |
| scanner := bufio.NewScanner(os.Stdin) | |
| for scanner.Scan() { | |
| result.WriteString(scanner.Text() + "\n") | |
| } | |
| return result.String(), "PIPED", scanner.Err() | |
| } | |
| if len(args) > 0 { | |
| // Generated from primitives | |
| output, err := t.generateMarkdown(args) | |
| if err != nil { | |
| return "", "", err | |
| } | |
| return output, "GENERATED", nil | |
| } | |
| return "", "", fmt.Errorf("no input provided") | |
| } | |
| // Process input: apply --raw flag decision | |
| func (t *Tool) processInput(input string) (string, error) { | |
| if t.config.Raw { | |
| return input, nil | |
| } | |
| return t.renderer.Render(input) | |
| } | |
| // Stream output with flow control and delays | |
| // NOTE: This implements "fake streaming" - content is pre-generated in memory, | |
| // then artificially chunked and delayed to simulate real streaming for testing | |
| func (t *Tool) streamOutput(w io.Writer, content string, keepalive chan<- struct{}) { | |
| if t.config.Flow == 0 { | |
| // Direct output (no streaming, no delay) | |
| fmt.Fprint(w, content) | |
| // Signal keepalive after successful write | |
| sendKeepalive(keepalive) | |
| return | |
| } | |
| if t.config.Flow == -1 { | |
| // Line-by-line streaming with random microsecond delays | |
| lines := strings.Split(content, "\n") | |
| for i, line := range lines { | |
| if i == len(lines)-1 && line == "" { | |
| // Don't print final empty line from split | |
| break | |
| } | |
| fmt.Fprint(w, line+"\n") | |
| if flusher, ok := w.(interface{ Flush() error }); ok { | |
| flusher.Flush() | |
| } | |
| // Signal keepalive after each successful line output | |
| sendKeepalive(keepalive) | |
| // Random delay like sleep 0.0$RANDOM (0-9999 microseconds) | |
| if i < len(lines)-1 { | |
| time.Sleep(time.Duration(rand.Intn(10000)) * time.Microsecond) | |
| } | |
| } | |
| return | |
| } | |
| // Buffered streaming with proportional delays | |
| data := []byte(content) | |
| bufSize := t.config.Flow | |
| for i := 0; i < len(data); i += bufSize { | |
| end := min(i+bufSize, len(data)) | |
| fmt.Fprint(w, string(data[i:end])) | |
| if flusher, ok := w.(interface{ Flush() error }); ok { | |
| flusher.Flush() | |
| } | |
| // Signal keepalive after each successful chunk output | |
| sendKeepalive(keepalive) | |
| // Delay proportional to buffer size | |
| if end < len(data) { | |
| time.Sleep(time.Duration(t.config.Flow) * 50 * time.Microsecond) | |
| } | |
| } | |
| } | |
| // Show hex dump in xxd-style format | |
| func (t *Tool) showHex(content string) { | |
| data := []byte(content) | |
| for i := 0; i < len(data); i += 16 { | |
| // Address | |
| fmt.Printf("%08x: ", i) | |
| // Hex bytes (in groups of 2) | |
| for j := range 16 { | |
| if i+j < len(data) { | |
| fmt.Printf("%02x", data[i+j]) | |
| } else { | |
| fmt.Print(" ") | |
| } | |
| if j%2 == 1 { | |
| fmt.Print(" ") | |
| } | |
| } | |
| // ASCII representation | |
| fmt.Print(" ") | |
| for j := 0; j < 16 && i+j < len(data); j++ { | |
| b := data[i+j] | |
| if b >= 32 && b <= 126 { | |
| fmt.Printf("%c", b) | |
| } else { | |
| fmt.Print(".") | |
| } | |
| } | |
| fmt.Print("\n") | |
| } | |
| } | |
| // Show unified diff | |
| func (t *Tool) showDiff(label, input, output string) { | |
| diff := difflib.UnifiedDiff{ | |
| A: difflib.SplitLines(input), | |
| B: difflib.SplitLines(output), | |
| FromFile: "a/" + label, | |
| ToFile: "b/" + label, | |
| Context: 3, | |
| } | |
| text, _ := difflib.GetUnifiedDiffString(diff) | |
| if text != "" { | |
| fmt.Print(text) | |
| } | |
| } | |
| // Get hex string representation (like showHex but returns string) | |
| func (t *Tool) getHexString(content string) string { | |
| var result strings.Builder | |
| data := []byte(content) | |
| for i := 0; i < len(data); i += 16 { | |
| // Address | |
| result.WriteString(fmt.Sprintf("%08x: ", i)) | |
| // Hex bytes (in groups of 2) | |
| for j := 0; j < 16; j++ { | |
| if i+j < len(data) { | |
| result.WriteString(fmt.Sprintf("%02x", data[i+j])) | |
| } else { | |
| result.WriteString(" ") | |
| } | |
| if j%2 == 1 { | |
| result.WriteString(" ") | |
| } | |
| } | |
| // ASCII representation | |
| result.WriteString(" ") | |
| for j := 0; j < 16 && i+j < len(data); j++ { | |
| b := data[i+j] | |
| if b >= 32 && b <= 126 { | |
| result.WriteString(fmt.Sprintf("%c", b)) | |
| } else { | |
| result.WriteString(".") | |
| } | |
| } | |
| result.WriteString("\n") | |
| } | |
| return result.String() | |
| } | |
| // Generate analysis metrics | |
| func (t *Tool) analyzeOutput(output string) (leading, trailing, blanks, lines int) { | |
| if strings.HasPrefix(output, "\n") { | |
| leading = 1 | |
| } | |
| if strings.HasSuffix(output, "\n") { | |
| trailing = 1 | |
| } | |
| blanks = strings.Count(output, GlamourBlank) | |
| lines = strings.Count(output, "\n") | |
| return | |
| } | |
| // Check if string is a known primitive | |
| func (t *Tool) isPrimitive(arg string) bool { | |
| name := arg | |
| for strings.HasSuffix(name, "-") { | |
| name = strings.TrimRight(name, "-") | |
| } | |
| if parts := strings.Split(name, "@"); len(parts) > 0 { | |
| name = parts[0] | |
| } | |
| primitives := []string{ | |
| "text", "paragraph", "header", "header1", "header2", "list", "olist", | |
| "code", "inline", "blockquote", "table", "table2", "link", "hrule", | |
| "bold", "italic", "strike", "emoji", "checklist", "yaml", "json", | |
| "longtext", "mixedblock", "empty", "arg1", "arg2", "arg3", | |
| } | |
| return slices.Contains(primitives, name) | |
| } | |
| // Send keepalive signal to reset deadman switch timer | |
| func sendKeepalive(ch chan<- struct{}) { | |
| select { | |
| case ch <- struct{}{}: | |
| default: | |
| } | |
| } | |
| // Initialize deadman switch that kills process after 2 seconds of no progress | |
| func initDeadmanSwitch() chan<- struct{} { | |
| keepalive := make(chan struct{}, 1) | |
| go func() { | |
| ticker := time.NewTicker(2 * time.Second) | |
| defer ticker.Stop() | |
| for { | |
| select { | |
| case <-ticker.C: | |
| os.Exit(124) | |
| case <-keepalive: | |
| ticker.Reset(2 * time.Second) | |
| } | |
| } | |
| }() | |
| return keepalive | |
| } | |
| func main() { | |
| // Initialize deadman switch for timeout protection | |
| keepalive := initDeadmanSwitch() | |
| // Flag definitions | |
| blanks := flag.Bool("b", false, "Show blank count analysis only") | |
| diffs := flag.Bool("d", false, "Show input/output differences only") | |
| raw := flag.Bool("r", false, "Show raw markdown (skip glamour)") | |
| hex := flag.Bool("x", false, "Show hex dump") | |
| style := flag.String("s", "notty", "Glamour style (notty, dark, light, auto)") | |
| repeat := flag.Int("n", 1, "Repeat sequence N times (-1 for infinite)") | |
| width := flag.Int("w", 0, "Set render width") | |
| flow := flag.Int("f", 0, "Stream output (0=direct, -1=lines, N=buffer size)") | |
| arg1 := flag.String("1", "", "Custom argument 1") | |
| arg2 := flag.String("2", "", "Custom argument 2") | |
| arg3 := flag.String("3", "", "Custom argument 3") | |
| help := flag.Bool("h", false, "Show help") | |
| flag.Parse() | |
| if *help { | |
| fmt.Print(`GLOW FLOW STRESS TESTER - Generate markdown for glow streaming validation | |
| MODES: | |
| -r Show raw markdown (skip glamour) | |
| -b Show blank count analysis only | |
| -d Show input/output differences only | |
| -x Show hex dump | |
| -w=N Set render width (default: no wrap) | |
| -n=N Repeat sequence N times (-1 for infinite) | |
| -f=N Stream output (0=direct, -1=lines, N=buffer) | |
| SPACING CONTROL: | |
| primitive Normal spacing (\n\n between primitives) | |
| primitive- Single spacing (\n between primitives) | |
| primitive-- Space joiner within @N iterations | |
| primitive--- No joiner within @N iterations | |
| ITERATIONS: | |
| primitive@N Generate N labeled iterations of primitive | |
| text@5 → Hello world 1_1\n\nHello world 1_2\n\n... (double newlines) | |
| text@5- → Hello world 1_1\nHello world 1_2\n... (single newlines) | |
| text@5-- → Hello world 1_1 Hello world 1_2 ... (space joiners) | |
| text@5--- → Hello world 1_1Hello world 1_2... (no joiners) | |
| PRIMITIVES: | |
| text, paragraph, header, header1, header2, list, olist | |
| code, inline, blockquote, table, table2, link, hrule | |
| bold, italic, strike, emoji, checklist, yaml, json | |
| longtext, mixedblock, empty, arg1, arg2, arg3 | |
| EXAMPLES: | |
| echo '#Test' | flow-tool -r -x | |
| flow-tool header@1000 | glow --flow=100 - | |
| flow-tool -n=-1 text@3-- header- | head -20 | |
| flow-tool text@100-- list@50-- | glow --flow=64 - | |
| `) | |
| return | |
| } | |
| // Validate analysis flags (only one allowed) | |
| analysisCount := 0 | |
| for _, f := range []*bool{blanks, diffs} { | |
| if *f { | |
| analysisCount++ | |
| } | |
| } | |
| if analysisCount > 1 { | |
| fmt.Println("Error: Multiple analysis filters specified (choose one of -b, -d)") | |
| return | |
| } | |
| // Create tool | |
| config := Config{ | |
| Raw: *raw, Hex: *hex, Blanks: *blanks, Diffs: *diffs, | |
| Style: *style, Arg1: *arg1, Arg2: *arg2, Arg3: *arg3, | |
| Width: *width, Flow: *flow, Repeat: *repeat, | |
| } | |
| tool, err := NewTool(config) | |
| if err != nil { | |
| fmt.Printf("Error creating tool: %v\n", err) | |
| return | |
| } | |
| // PHASE 1: INPUT | |
| input, source, err := tool.getInput(flag.Args()) | |
| if err != nil { | |
| fmt.Printf("Error getting input: %v\n", err) | |
| flag.Usage() | |
| return | |
| } | |
| // PHASE 2: PROCESSING | |
| output, err := tool.processInput(input) | |
| if err != nil { | |
| fmt.Printf("Error processing: %v\n", err) | |
| return | |
| } | |
| // PHASE 3: OUTPUT | |
| // Diffs mode - always compares input vs glamour (ignores --raw) | |
| if *diffs { | |
| glamourOutput, err := tool.renderer.Render(input) | |
| if err != nil { | |
| fmt.Printf("Error rendering for diff: %v\n", err) | |
| return | |
| } | |
| if *hex { | |
| // Hex diff mode - diff the hex representations | |
| tool.showDiff(source+" (hex)", tool.getHexString(input), tool.getHexString(glamourOutput)) | |
| return | |
| } | |
| // Regular diff mode | |
| tool.showDiff(source, input, glamourOutput) | |
| } | |
| // Hex mode - can work with any output (non-diff) | |
| if *hex { | |
| tool.showHex(output) | |
| return | |
| } | |
| // Blanks mode - analyze blank metrics | |
| if *blanks { | |
| // Always analyze the processed output (raw or glamour as determined by Phase 2) | |
| leading, trailing, blanks, lines := tool.analyzeOutput(output) | |
| fmt.Printf("%d %d %d %d\n", leading, trailing, blanks, lines) | |
| return | |
| } | |
| // Default: direct output mode | |
| tool.streamOutput(os.Stdout, output, keepalive) | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment