You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							184 lines
						
					
					
						
							4.8 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							184 lines
						
					
					
						
							4.8 KiB
						
					
					
				
								#!/usr/bin/env bash
							 | 
						|
								
							 | 
						|
								# format-markdown.sh
							 | 
						|
								# Author: Matthew Raymer
							 | 
						|
								# Date: 2025-07-09
							 | 
						|
								# Description: Format markdown files to comply with project markdown ruleset
							 | 
						|
								# Enhanced: Auto-fix, parallel lint, summary, CI-friendly, multi-linter support
							 | 
						|
								# Always fixes missing blank lines around headings.
							 | 
						|
								
							 | 
						|
								set -e
							 | 
						|
								
							 | 
						|
								# Fix missing blank lines above and below headings in a Markdown file
							 | 
						|
								fix_blank_lines_around_headings() {
							 | 
						|
								  local file="$1"
							 | 
						|
								  awk '
							 | 
						|
								    BEGIN { prev=""; }
							 | 
						|
								    {
							 | 
						|
								      if ($0 ~ /^#{1,6} /) {
							 | 
						|
								        if (NR > 1 && prev != "") print "";
							 | 
						|
								        print $0;
							 | 
						|
								        getline nextline;
							 | 
						|
								        if (nextline != "") print "";
							 | 
						|
								        print nextline;
							 | 
						|
								        prev = nextline;
							 | 
						|
								        next;
							 | 
						|
								      }
							 | 
						|
								      print $0;
							 | 
						|
								      prev = $0;
							 | 
						|
								    }
							 | 
						|
								  ' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								show_help() {
							 | 
						|
								  echo "Usage: $0 [--fix] [--ci] [--linter <markdownlint|prettier|both>] <file-or-directory> [more files...]"
							 | 
						|
								  echo "Formats and lints markdown files."
							 | 
						|
								  echo "Options:"
							 | 
						|
								  echo "  -h, --help         Show this help message"
							 | 
						|
								  echo "  --fix             Auto-fix lint errors (if supported)"
							 | 
						|
								  echo "  --ci              CI-friendly output (machine-readable)"
							 | 
						|
								  echo "  --linter <type>   Choose linter: markdownlint, prettier, or both (default: both)"
							 | 
						|
								  echo "  <file-or-directory>  Markdown files or directories to process"
							 | 
						|
								  exit 0
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Default options
							 | 
						|
								fix=0
							 | 
						|
								ci=0
							 | 
						|
								linter="both"
							 | 
						|
								args=()
							 | 
						|
								
							 | 
						|
								# Parse flags
							 | 
						|
								while [[ $# -gt 0 ]]; do
							 | 
						|
								  case "$1" in
							 | 
						|
								    -h|--help) show_help ;;
							 | 
						|
								    --fix) fix=1 ; shift ;;
							 | 
						|
								    --ci) ci=1 ; shift ;;
							 | 
						|
								    --linter) linter="$2" ; shift 2 ;;
							 | 
						|
								    --) shift ; break ;;
							 | 
						|
								    -*) echo "Unknown option: $1"; show_help ;;
							 | 
						|
								    *) args+=("$1"); shift ;;
							 | 
						|
								  esac
							 | 
						|
								done
							 | 
						|
								
							 | 
						|
								if [ ${#args[@]} -eq 0 ]; then
							 | 
						|
								  show_help
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Tool checks
							 | 
						|
								for tool in awk sed; do
							 | 
						|
								  if ! command -v $tool >/dev/null 2>&1; then
							 | 
						|
								    echo "$tool is required but not installed. Exiting."
							 | 
						|
								    exit 1
							 | 
						|
								  fi
							 | 
						|
								done
							 | 
						|
								
							 | 
						|
								if ! command -v npx >/dev/null 2>&1; then
							 | 
						|
								  echo "npx is required for linting. Exiting."
							 | 
						|
								  exit 1
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Check for prettier and markdownlint availability
							 | 
						|
								has_prettier=0
							 | 
						|
								has_markdownlint=0
							 | 
						|
								if npx prettier --version >/dev/null 2>&1; then
							 | 
						|
								  has_prettier=1
							 | 
						|
								fi
							 | 
						|
								if npx markdownlint --version >/dev/null 2>&1; then
							 | 
						|
								  has_markdownlint=1
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Respect .markdownlintignore if present
							 | 
						|
								mlint_ignore=""
							 | 
						|
								if [ -f .markdownlintignore ]; then
							 | 
						|
								  mlint_ignore="--ignore-path .markdownlintignore"
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Gather files
							 | 
						|
								all_files=()
							 | 
						|
								for target in "${args[@]}"; do
							 | 
						|
								  if [ -d "$target" ]; then
							 | 
						|
								    while IFS= read -r -d $'\0' file; do
							 | 
						|
								      all_files+=("$file")
							 | 
						|
								    done < <(find "$target" -type f -name "*.md" -print0)
							 | 
						|
								  else
							 | 
						|
								    all_files+=("$target")
							 | 
						|
								  fi
							 | 
						|
								done
							 | 
						|
								
							 | 
						|
								# Remove duplicates
							 | 
						|
								all_files=( $(printf "%s\n" "${all_files[@]}" | sort -u) )
							 | 
						|
								
							 | 
						|
								# Format and lint files
							 | 
						|
								lint_errors=0
							 | 
						|
								failed_files=()
							 | 
						|
								passed_files=()
							 | 
						|
								
							 | 
						|
								format_and_lint() {
							 | 
						|
								  file="$1"
							 | 
						|
								  # Fix missing blank lines around headings
							 | 
						|
								  fix_blank_lines_around_headings "$file"
							 | 
						|
								
							 | 
						|
								  # Basic formatting
							 | 
						|
								    sed -i 's/[[:space:]]*$//' "$file"
							 | 
						|
								    awk 'NF{blank=0} !NF{blank++} blank<2' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
							 | 
						|
								    awk '1; END{if (NR && $0!="") print ""}' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
							 | 
						|
								
							 | 
						|
								  # Auto-fix with prettier
							 | 
						|
								  if [[ $fix -eq 1 && ( "$linter" == "prettier" || "$linter" == "both" ) && $has_prettier -eq 1 ]]; then
							 | 
						|
								    npx prettier --write "$file" >/dev/null 2>&1 && echo "Auto-fixed with prettier: $file"
							 | 
						|
								  fi
							 | 
						|
								  # Auto-fix with markdownlint
							 | 
						|
								  if [[ $fix -eq 1 && ( "$linter" == "markdownlint" || "$linter" == "both" ) && $has_markdownlint -eq 1 ]]; then
							 | 
						|
								    npx markdownlint --fix $mlint_ignore "$file" >/dev/null 2>&1 && echo "Auto-fixed with markdownlint: $file"
							 | 
						|
								  fi
							 | 
						|
								
							 | 
						|
								  # Linting
							 | 
						|
								  lint_ok=1
							 | 
						|
								  if [[ "$linter" == "prettier" || "$linter" == "both" ]] && [[ $has_prettier -eq 1 ]]; then
							 | 
						|
								    if ! npx prettier --check "$file" >/dev/null 2>&1; then
							 | 
						|
								      lint_ok=0
							 | 
						|
								      echo "Prettier lint errors in $file"
							 | 
						|
								    fi
							 | 
						|
								  fi
							 | 
						|
								  if [[ "$linter" == "markdownlint" || "$linter" == "both" ]] && [[ $has_markdownlint -eq 1 ]]; then
							 | 
						|
								    if ! npx markdownlint $mlint_ignore "$file"; then
							 | 
						|
								      lint_ok=0
							 | 
						|
								      echo "Markdownlint errors in $file"
							 | 
						|
								    fi
							 | 
						|
								  fi
							 | 
						|
								  if [[ $lint_ok -eq 1 ]]; then
							 | 
						|
								    passed_files+=("$file")
							 | 
						|
								    [[ $ci -eq 0 ]] && echo "PASS: $file"
							 | 
						|
								  else
							 | 
						|
								    failed_files+=("$file")
							 | 
						|
								    lint_errors=1
							 | 
						|
								    [[ $ci -eq 0 ]] && echo "FAIL: $file"
							 | 
						|
								    [[ $ci -eq 1 ]] && echo "$file"
							 | 
						|
								  fi
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Export for xargs
							 | 
						|
								export -f format_and_lint
							 | 
						|
								export fix linter has_prettier has_markdownlint mlint_ignore ci
							 | 
						|
								
							 | 
						|
								# Run in parallel (4 at a time)
							 | 
						|
								printf "%s\n" "${all_files[@]}" | xargs -n 1 -P 4 -I {} bash -c 'format_and_lint "$@"' _ {}
							 | 
						|
								
							 | 
						|
								# Summary
							 | 
						|
								if [[ $ci -eq 0 ]]; then
							 | 
						|
								  echo
							 | 
						|
								  echo "Lint summary:"
							 | 
						|
								  echo "Passed: ${#passed_files[@]}"
							 | 
						|
								  echo "Failed: ${#failed_files[@]}"
							 | 
						|
								  if [ ${#failed_files[@]} -gt 0 ]; then
							 | 
						|
								    printf '%s\n' "${failed_files[@]}"
							 | 
						|
								  fi
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								if [ $lint_errors -ne 0 ]; then
							 | 
						|
								  echo "Some files have markdownlint or prettier errors. Please fix them."
							 | 
						|
								  exit 1
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								echo "Markdown formatting complete." 
							 |