|
| 1 | +#!/usr/bin/env julia |
| 2 | + |
| 3 | +const patterns = split(""" |
| 4 | + *.jl |
| 5 | + *.md |
| 6 | + *.yml |
| 7 | + *Makefile |
| 8 | +""") |
| 9 | + |
| 10 | +const is_gha = something(tryparse(Bool, get(ENV, "GITHUB_ACTIONS", "false")), false) |
| 11 | + |
| 12 | +# Note: `git ls-files` gives `/` as a path separator on Windows, |
| 13 | +# so we just use `/` for all platforms. |
| 14 | +allow_tabs(path) = |
| 15 | + endswith(path, "Makefile") || |
| 16 | + endswith(path, ".make") || |
| 17 | + endswith(path, ".mk") |
| 18 | + |
| 19 | +function check_whitespace() |
| 20 | + errors = Set{Tuple{String,Int,String}}() |
| 21 | + files_to_check = filter(arg -> !startswith(arg, "-"), ARGS) |
| 22 | + if isempty(files_to_check) |
| 23 | + if "--stdin" in ARGS |
| 24 | + files_to_check = collect(eachline(stdin)) |
| 25 | + else |
| 26 | + files_to_check = collect(eachline(`git ls-files -- $patterns`)) |
| 27 | + end |
| 28 | + end |
| 29 | + |
| 30 | + files_fixed = 0 |
| 31 | + if "--fix" in ARGS |
| 32 | + for path in files_to_check |
| 33 | + content = newcontent = read(path, String) |
| 34 | + isempty(content) && continue |
| 35 | + if !allow_tabs(path) |
| 36 | + tabpattern = r"^([ \t]+)"m => (x -> replace(x, r"((?: {4})*)( *\t)" => s"\1 ")) # Replace tab sequences at start of line after any number of 4-space groups |
| 37 | + newcontent = replace(newcontent, tabpattern) |
| 38 | + end |
| 39 | + newcontent = replace(newcontent, |
| 40 | + r"\s*$" => '\n', # Remove trailing whitespace and normalize line ending at eof |
| 41 | + r"\s*?[\r\n]" => '\n', # Remove trailing whitespace and normalize line endings on each line |
| 42 | + r"\xa0" => ' ' # Replace non-breaking spaces |
| 43 | + ) |
| 44 | + if content != newcontent |
| 45 | + write(path, newcontent) |
| 46 | + files_fixed += 1 |
| 47 | + end |
| 48 | + end |
| 49 | + if files_fixed > 0 |
| 50 | + println(stderr, "Fixed whitespace issues in $files_fixed files.") |
| 51 | + end |
| 52 | + end |
| 53 | + |
| 54 | + for path in files_to_check |
| 55 | + lineno = 0 |
| 56 | + non_blank = 0 |
| 57 | + |
| 58 | + file_err(msg) = push!(errors, (path, 0, msg)) |
| 59 | + line_err(msg) = push!(errors, (path, lineno, msg)) |
| 60 | + |
| 61 | + isfile(path) || continue |
| 62 | + for line in eachline(path, keep=true) |
| 63 | + lineno += 1 |
| 64 | + contains(line, '\r') && file_err("non-UNIX line endings") |
| 65 | + contains(line, '\ua0') && line_err("non-breaking space") |
| 66 | + allow_tabs(path) || |
| 67 | + contains(line, '\t') && line_err("tab") |
| 68 | + endswith(line, '\n') || line_err("no trailing newline") |
| 69 | + line = chomp(line) |
| 70 | + endswith(line, r"\s") && line_err("trailing whitespace") |
| 71 | + contains(line, r"\S") && (non_blank = lineno) |
| 72 | + end |
| 73 | + non_blank < lineno && line_err("trailing blank lines") |
| 74 | + end |
| 75 | + |
| 76 | + if isempty(errors) |
| 77 | + println(stderr, "Whitespace check found no issues.") |
| 78 | + exit(0) |
| 79 | + else |
| 80 | + println(stderr, "Whitespace check found $(length(errors)) issues:") |
| 81 | + for (path, lineno, msg) in sort!(collect(errors)) |
| 82 | + if lineno == 0 |
| 83 | + println(stderr, "$path -- $msg") |
| 84 | + if is_gha |
| 85 | + println(stdout, "::warning title=Whitespace check,file=", path, "::", msg) |
| 86 | + end |
| 87 | + else |
| 88 | + println(stderr, "$path:$lineno -- $msg") |
| 89 | + if is_gha |
| 90 | + println(stdout, "::warning title=Whitespace check,file=", path, ",line=", lineno, "::", msg) |
| 91 | + end |
| 92 | + end |
| 93 | + end |
| 94 | + exit(1) |
| 95 | + end |
| 96 | +end |
| 97 | + |
| 98 | +check_whitespace() |
0 commit comments