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."  |