From 32ba9d345b8b3b61d326cb9d3fdb9f12202b44a7 Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Tue, 28 May 2024 01:38:58 +0200 Subject: [PATCH] main: help message, check mode --- src/Runic.jl | 11 +++-- src/main.jl | 128 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 118 insertions(+), 21 deletions(-) diff --git a/src/Runic.jl b/src/Runic.jl index 33b9c49..2ea41f1 100644 --- a/src/Runic.jl +++ b/src/Runic.jl @@ -36,6 +36,8 @@ mutable struct Context verbose::Bool assert::Bool debug::Bool + check::Bool + diff::Bool # Current state # node::Union{JuliaSyntax.GreenNode{JuliaSyntax.SyntaxHead}, Nothing} prev_sibling::Union{JuliaSyntax.GreenNode{JuliaSyntax.SyntaxHead}, Nothing} @@ -43,7 +45,10 @@ mutable struct Context # parent::Union{JuliaSyntax.GreenNode{JuliaSyntax.SyntaxHead}, Nothing} end -function Context(src_str; assert::Bool = true, debug::Bool = false, verbose::Bool = debug) +function Context( + src_str; assert::Bool = true, debug::Bool = false, verbose::Bool = debug, + diff::Bool = false, check::Bool = false, + ) src_io = IOBuffer(src_str) src_tree = JuliaSyntax.parseall(JuliaSyntax.GreenNode, src_str; ignore_warnings = true) fmt_io = IOBuffer() @@ -52,8 +57,8 @@ function Context(src_str; assert::Bool = true, debug::Bool = false, verbose::Boo verbose = debug ? true : verbose assert = debug ? true : assert return Context( - src_str, src_tree, src_io, fmt_io, fmt_tree, verbose, assert, debug, nothing, - nothing, + src_str, src_tree, src_io, fmt_io, fmt_tree, + verbose, assert, debug, check, diff, nothing, nothing, ) end diff --git a/src/main.jl b/src/main.jl index 10654de..09d254e 100644 --- a/src/main.jl +++ b/src/main.jl @@ -20,65 +20,147 @@ function panic(msg...) return errno end +# Print a typical cli program help message +function print_help() + io = stdout + printstyled(io, "NAME\n", bold = true) + println(io, " Runic.main - format Julia source code") + println(io) + printstyled(io, "SYNOPSIS\n", bold = true) + println(io, " julia -m Runic [] ...") + println(io) + printstyled(io, "DESCRIPTION\n", bold = true) + println(io, """ + `Runic.main` (typically invoked as `julia -m Runic`) formats Julia source + code using the Runic.jl formatter. + """) + printstyled(io, "OPTIONS\n", bold = true) + println(io, """ + ... + Input path(s) (files and/or directories) to process. For directories, + all files (recursively) with the '*.jl' suffix are used as input files. + If no path is given, or if path is `-`, input is read from stdin. + + -c, --check + Do not write output and exit with a non-zero code if the input is not + formatted correctly. + + -d, --diff + Print the diff between the input and formatted output to stderr. + Requires `git` or `diff` to be installed. + + --help + Print this message. + + -i, --inplace + Edit files in place. This option is required when passing multiple input + paths. + + -o, --output + Output file to write formatted code to. If no output file is given, or + if the specified file is `-`, output is written to stdout. This option + can not be used together with multiple input paths. + """) + return +end + +function maybe_expand_directory!(outfiles, dir) + if !isdir(dir) + # Assumed a file, checked when using it + push!(outfiles, dir) + return + end + for (root, _, files) in walkdir(dir; onerror = (err) -> nothing) + for file in files + if endswith(file, ".jl") + push!(outfiles, joinpath(root, file)) + end + end + end +end + function main(argv) # Reset errno global errno = 0 + # Split argv entries with `=` + argv = mapreduce(x -> split(x, "="; limit = 2), append!, argv; init = String[]) + # Default values inputfiles = String[] outputfile = nothing verbose = false debug = false inplace = false + diff = false + check = false # Parse the arguments while length(argv) > 0 x = popfirst!(argv) - if x == "-i" + if x == "-i" || x == "--inplace" inplace = true - elseif x == "-v" + elseif x == "-v" || x == "--verbose" verbose = true - elseif x == "-vv" + elseif x == "-d" || x == "--diff" + diff = true + elseif x == "c" || x == "--check" + check = true + elseif x == "-vv" || x == "--debug" debug = verbose = true - elseif x == "-o" + elseif x == "-o" || x == "--output" if length(argv) < 1 - return panic("expected output file as argument after `-o`") + return panic("expected output file as argument after `-o, --output`") end outputfile = popfirst!(argv) else # Remaining arguments must be inputfile(s) - push!(inputfiles, x) + maybe_expand_directory!(inputfiles, x) for x in argv if x == "-" - return panic("input \"-\" can not be used with multiple files") + return panic("input `-` can not be used with multiple files") + else + maybe_expand_directory!(inputfiles, x) end - push!(inputfiles, x) end break end end + # one of --check, --diff, --inplace, or --output must be specified + if !(inplace || check || diff || outputfile !== nothing) + return panic( + "at least one of options `-c, --check`, `-d, --diff`, `-i, --inplace`, " * + "or `-o, --output` must be specified", + ) + end + + # --check can not be used with --inplace + if inplace && check + return panic("options `-c, --check` and `-i, --inplace` are mutually exclusive") + end + # stdin is the default input if isempty(inputfiles) push!(inputfiles, "-") end - # multiple files require -i and no -o + # multiple files require --inplace or --check and no --output if length(inputfiles) > 1 - if !inplace - return panic("option `-i` is required for multiple input files") + if !(inplace || check) + return panic("option `-i, --inplace` or `-c, --check` is required for multiple input files") elseif outputfile !== nothing - return panic("option `-o` is incompatible with multiple input files") + return panic("option `-o, --output` can not be used together with multiple input files") end end - # inplace = true is incompatible with given output + # --inplace can not be used when specifying output if inplace && outputfile !== nothing @assert length(inputfiles) == 1 - return panic("option `-i` is incompatible with option `-o $(outputfile)`") + return panic("options `-i, --inplace` and `-o, --output` are mutually exclusive") end - # inplace = true is incompatible with stdin as input + # --inplace is incompatible with stdin as input if inplace && first(inputfiles) == "-" return panic("option `-i` is incompatible with stdin as input") end @@ -113,6 +195,9 @@ function main(argv) @assert input_is_file # @assert length(inputfiles) == 1 # checked above output = inputfile + elseif check + @assert outputfile === nothing + output = devnull else @assert length(inputfiles) == 1 if outputfile === nothing || outputfile == "-" @@ -126,7 +211,7 @@ function main(argv) # Call the library to format the text ctx = try - ctx = Context(sourcetext; verbose = verbose, debug = debug) + ctx = Context(sourcetext; verbose = verbose, debug = debug, diff = diff, check = check) format_tree!(ctx) ctx catch err @@ -134,9 +219,13 @@ function main(argv) continue end - # Write the output, but skip if inplace and it didn't change + # Output the result changed = ctx.fmt_tree !== ctx.src_tree - if changed || !inplace + if check + if changed + global errno = 1 + end + elseif changed || !inplace try write(output, take!(ctx.fmt_io)) catch err @@ -145,6 +234,9 @@ function main(argv) else # Log if verbose perhaps end + if diff + error("todo") + end end # inputfile loop