Skip to content

Instantly share code, notes, and snippets.

@toonetown
Last active November 13, 2025 16:11
Show Gist options
  • Select an option

  • Save toonetown/f7690761e2a91d6c43038e778851909d to your computer and use it in GitHub Desktop.

Select an option

Save toonetown/f7690761e2a91d6c43038e778851909d to your computer and use it in GitHub Desktop.
Fixes common markdown formatting issues to comply with GitHub Flavored Markdown standards
#!/usr/bin/env ruby
# encoding: UTF-8
# Markdown Formatter Preprocessor for Marked 2
# Fixes common markdown formatting issues to comply with GitHub Flavored Markdown standards
#
# This script reads markdown from STDIN and outputs formatted markdown to STDOUT.
# It ensures proper blank lines before lists, headings, code blocks, and tables.
#
# Usage in Marked 2:
# Settings > Advanced > Custom Preprocessor
# Path: /opt/homebrew/bin/marked2-preprocessor
# Check "Use by default"
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
def format_markdown(content)
lines = content.split("\n")
output = []
in_code_block = false
in_table = false
prev_line_blank = true
code_fence_marker = nil
lines.each_with_index do |line, index|
stripped = line.strip
prev_line = index > 0 ? lines[index - 1].strip : ""
next_line = index < lines.length - 1 ? lines[index + 1].strip : ""
# Track code blocks
if stripped.start_with?('```') || stripped.start_with?('~~~')
if !in_code_block
# Starting a code block
in_code_block = true
code_fence_marker = stripped[0..2]
# Ensure blank line before code block (unless at start of file or after blank line)
if !prev_line_blank && index > 0 && !prev_line.empty?
output << ""
end
output << line
prev_line_blank = false
next
elsif stripped.start_with?(code_fence_marker)
# Ending a code block
in_code_block = false
code_fence_marker = nil
output << line
prev_line_blank = false
next
end
end
# If inside code block, don't modify
if in_code_block
output << line
prev_line_blank = false
next
end
# Detect if line is a table row (starts with | or is a separator line)
is_table_row = stripped.start_with?('|') || (stripped.match?(/^\|?[-:\s|]+\|?$/) && stripped.include?('-'))
# Track table state
if is_table_row && !in_table
# Starting a table - ensure blank line before (unless at start or after blank line)
if !prev_line_blank && index > 0 && !prev_line.empty?
output << ""
end
in_table = true
elsif !is_table_row && in_table
# Exiting table
in_table = false
end
# Detect headings (lines starting with #)
if stripped.match?(/^[#]{1,6}\s+/)
# Ensure blank line before heading (unless at start of file or after blank line)
if !prev_line_blank && index > 0 && !prev_line.empty?
output << ""
end
output << line
prev_line_blank = false
next
end
# Detect list items (lines starting with -, *, +, or number followed by .)
is_list_item = stripped.match?(/^[-*+]\s+/) || stripped.match?(/^\d+\.\s+/)
if is_list_item
# Check if previous line was also a list item or blank
prev_is_list = prev_line.match?(/^[-*+]\s+/) || prev_line.match?(/^\d+\.\s+/)
# If starting a new list (not continuing), ensure blank line before
if !prev_is_list && !prev_line_blank && index > 0 && !prev_line.empty?
output << ""
end
output << line
prev_line_blank = false
next
end
# Handle blank lines
if stripped.empty?
# Only add blank line if the previous line wasn't already blank
# This prevents multiple consecutive blank lines
if !prev_line_blank
output << line
prev_line_blank = true
end
next
end
# Regular content line
output << line
prev_line_blank = false
end
# Join lines and ensure file ends with newline
result = output.join("\n")
result += "\n" unless result.end_with?("\n")
result
end
# Read from STDIN
input = STDIN.read
# Format the markdown
output = format_markdown(input)
# Write to STDOUT
STDOUT.write(output)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment