Created
June 5, 2025 16:54
-
-
Save beeksiwaais/81d2a45a0987a4c2451013aa5a49f4fc to your computer and use it in GitHub Desktop.
A perl script to transform markdown to italic and bold in the terminal without buffering
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
| #!/usr/bin/env perl | |
| # Ollama Markdown to Terminal Colors Converter - Perl Version | |
| # Usage: ollama run llama "your prompt" | ./ollama-markdown-colorizer.pl | |
| use strict; | |
| use warnings; | |
| use Term::ReadKey; | |
| # Disable buffering for immediate output | |
| $| = 1; | |
| STDOUT->autoflush(1); | |
| # ANSI color codes | |
| my $BOLD = "\033[1m"; | |
| my $ITALIC = "\033[3m"; | |
| my $CYAN = "\033[0;36m"; | |
| my $MAGENTA = "\033[0;35m"; | |
| my $BLUE = "\033[0;34m"; | |
| my $WHITE = "\033[1;37m"; | |
| my $DIM = "\033[2m"; | |
| my $GRAY = "\033[0;90m"; | |
| my $RESET = "\033[0m"; | |
| # State variables | |
| my $bold_open = 0; | |
| my $italic_open = 0; | |
| my $code_open = 0; | |
| my $in_code_block = 0; | |
| my $line_buffer = ""; | |
| my $at_line_start = 1; | |
| my $pending_char = ""; | |
| my $waiting_for_next = 0; | |
| # Function to apply current formatting state | |
| sub apply_state { | |
| my $output = ""; | |
| if ($in_code_block) { | |
| $output .= $DIM; | |
| } else { | |
| $output .= $CYAN if $code_open; | |
| $output .= $BOLD if $bold_open; | |
| $output .= $ITALIC if $italic_open; | |
| } | |
| print $output; | |
| } | |
| # Function to toggle bold formatting | |
| sub toggle_bold { | |
| if (!$bold_open) { | |
| print $RESET . $BOLD; | |
| $bold_open = 1; | |
| apply_state(); | |
| } else { | |
| print $RESET; | |
| $bold_open = 0; | |
| apply_state(); | |
| } | |
| } | |
| # Function to toggle italic formatting | |
| sub toggle_italic { | |
| if (!$italic_open) { | |
| print $RESET . $ITALIC; | |
| $italic_open = 1; | |
| apply_state(); | |
| } else { | |
| print $RESET; | |
| $italic_open = 0; | |
| apply_state(); | |
| } | |
| } | |
| # Function to toggle code formatting | |
| sub toggle_code { | |
| if (!$code_open) { | |
| print $RESET . $CYAN; | |
| $code_open = 1; | |
| } else { | |
| print $RESET; | |
| $code_open = 0; | |
| apply_state(); | |
| } | |
| } | |
| # Function to process a character (with potential lookahead) | |
| sub process_char { | |
| my $char = shift; | |
| # Handle pending character from previous lookahead | |
| if ($waiting_for_next) { | |
| $waiting_for_next = 0; | |
| if ($pending_char eq "*") { | |
| if ($char eq "*") { | |
| # This is ** (bold) | |
| toggle_bold(); | |
| return; # Consume both characters | |
| } else { | |
| # Previous was single * (italic) | |
| toggle_italic(); | |
| # Continue processing current character | |
| } | |
| } elsif ($pending_char eq "`") { | |
| # Process the backtick we were holding | |
| toggle_code(); | |
| # Continue processing current character | |
| } | |
| $pending_char = ""; | |
| } | |
| # Add to line buffer for line-level processing | |
| $line_buffer .= $char; | |
| # Handle newlines - check for headers and code blocks | |
| if ($char eq "\n") { | |
| # Remove the newline from buffer for checking | |
| my $check_line = $line_buffer; | |
| chomp($check_line); | |
| # Check if this line is a code block delimiter | |
| if ($check_line =~ /^```[a-zA-Z]*$/) { | |
| if (!$in_code_block) { | |
| $in_code_block = 1; | |
| print $RESET . $GRAY . "┌─ Code Block ─" . $RESET . "\n"; | |
| print $DIM; | |
| } else { | |
| $in_code_block = 0; | |
| print $RESET . $GRAY . "└─────────────" . $RESET . "\n"; | |
| apply_state(); | |
| } | |
| $line_buffer = ""; | |
| $at_line_start = 1; | |
| return; | |
| } | |
| # Check for headers at start of line | |
| if ($at_line_start && $check_line =~ /^(#{1,6})\s+(.*)$/) { | |
| my $header_level = length($1); | |
| my $header_text = $2; | |
| if ($header_level == 1) { | |
| print $RESET . $BOLD . $MAGENTA; | |
| } elsif ($header_level == 2) { | |
| print $RESET . $BOLD . $BLUE; | |
| } elsif ($header_level == 3) { | |
| print $RESET . $BOLD . $CYAN; | |
| } else { | |
| print $RESET . $BOLD . $WHITE; | |
| } | |
| # Output the header text and reset | |
| print $header_text . $RESET . "\n"; | |
| $line_buffer = ""; | |
| $at_line_start = 1; | |
| apply_state(); | |
| return; | |
| } | |
| print $char; | |
| $line_buffer = ""; | |
| $at_line_start = 1; | |
| return; | |
| } | |
| # Track if we're at line start (ignoring whitespace) | |
| if ($char !~ /[ \t]/) { | |
| $at_line_start = 0; | |
| } | |
| # Skip markdown processing if in code block | |
| if ($in_code_block) { | |
| print $char; | |
| return; | |
| } | |
| # Process markdown characters that need lookahead | |
| if ($char eq "*") { | |
| # Need to wait for next character to distinguish * from ** | |
| $pending_char = "*"; | |
| $waiting_for_next = 1; | |
| return; # Don't output yet | |
| } elsif ($char eq "`") { | |
| # Inline code - immediate toggle | |
| toggle_code(); | |
| return; | |
| } else { | |
| # Regular character | |
| print $char; | |
| } | |
| } | |
| # Main processing function | |
| sub main_loop { | |
| # Set up unbuffered input | |
| ReadMode('raw'); | |
| my $char; | |
| while (defined($char = ReadKey(0))) { | |
| process_char($char); | |
| } | |
| # Handle any pending character at end of stream | |
| if ($waiting_for_next) { | |
| if ($pending_char eq "*") { | |
| toggle_italic(); | |
| } elsif ($pending_char eq "`") { | |
| toggle_code(); | |
| } | |
| } | |
| print $RESET; | |
| ReadMode('restore'); | |
| } | |
| # Main execution | |
| if (@ARGV == 2) { | |
| my ($model, $prompt) = @ARGV; | |
| open(my $fh, '-|', 'ollama', 'run', $model, $prompt) | |
| or die "Cannot run ollama: $!"; | |
| ReadMode('raw'); | |
| while (read($fh, my $char, 1)) { | |
| process_char($char); | |
| } | |
| close($fh); | |
| # Handle any pending character at end of stream | |
| if ($waiting_for_next) { | |
| if ($pending_char eq "*") { | |
| toggle_italic(); | |
| } elsif ($pending_char eq "`") { | |
| toggle_code(); | |
| } | |
| } | |
| print $RESET; | |
| ReadMode('restore'); | |
| } elsif (@ARGV == 0) { | |
| main_loop(); | |
| } else { | |
| print "Usage: $0 [model] [prompt]\n"; | |
| print " Or: ollama run model 'prompt' | $0\n"; | |
| print "\n"; | |
| print "Examples:\n"; | |
| print " $0 llama3 'Explain markdown formatting'\n"; | |
| print " ollama run llama3 'Write some **bold** and *italic* text' | $0\n"; | |
| exit 1; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment