diff --git a/doc/make.jl b/doc/make.jl index 71d72db..5dddced 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -1,24 +1,25 @@ using Documenter using Pkg push!(LOAD_PATH, "../src/") -Pkg.develop(path=abspath(joinpath(@__DIR__, "../"))) +Pkg.develop(path = abspath(joinpath(@__DIR__, "../"))) using BetterInputFiles -DocMeta.setdocmeta!(BetterInputFiles, :DocTestSetup, :(using BetterInputFiles); recursive=true) +DocMeta.setdocmeta!( + BetterInputFiles, + :DocTestSetup, + :(using BetterInputFiles); + recursive = true, +) makedocs( - sitename="BetterInputFiles Documentation", + sitename = "BetterInputFiles Documentation", modules = [BetterInputFiles, BetterInputFiles.SetupModule, BetterInputFiles.IOModule], pages = [ "BetterInputFiles" => "index.md", "Advanced Usage" => "advanced.md", - "API" => "api.md" + "API" => "api.md", ], - format = Documenter.HTML( - assets = ["assets/favicon.ico"], - ) + format = Documenter.HTML(assets = ["assets/favicon.ico"]), ) -deploydocs( - repo = "github.com/OmegaLambda1998/BetterInputFiles.jl.git" -) +deploydocs(repo = "github.com/OmegaLambda1998/BetterInputFiles.jl.git") diff --git a/src/BetterInputFiles.jl b/src/BetterInputFiles.jl index 1dc445d..68a0873 100644 --- a/src/BetterInputFiles.jl +++ b/src/BetterInputFiles.jl @@ -1,7 +1,7 @@ -module BetterInputFiles +module BetterInputFiles # External Packages -using OrderedCollections +using OrderedCollections # Internal Packages include("IOModule.jl") @@ -25,9 +25,25 @@ Automatically choose input file extension, then run [`setup_input(::AbstractStri - `log_path::String="output_path"`: The `"path_name"` of the directory where logging should output. `log_path` must exist in `paths` or, be defined by [`SetupModule.default_paths`](@ref) - `custom_metadata::Vector{Tuple{String, String}}=Vector{Tuple{String, String}}()`: Additonal metadata to include in the input file, in addition to creation date and `input_path` """ -function setup_input(input_path::AbstractString, verbose::Bool; paths::OrderedDict{String, Tuple{String, String}}=OrderedDict{String, Tuple{String, String}}(), log_path::String="output_path", custom_metadata::Vector{Tuple{String, String}}=Vector{Tuple{String, String}}()) - ext = splitext(input_path)[end][2:end] - return setup_input(input_path, verbose, ext; paths=paths, log_path=log_path, custom_metadata=custom_metadata) +function setup_input( + input_path::AbstractString, + verbose::Bool; + paths::OrderedDict{String,Tuple{String,String}} = OrderedDict{ + String, + Tuple{String,String}, + }(), + log_path::String = "output_path", + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) + ext = splitext(input_path)[end][2:end] + return setup_input( + input_path, + verbose, + ext; + paths = paths, + log_path = log_path, + custom_metadata = custom_metadata, + ) end """ @@ -43,10 +59,27 @@ Manually specify input file extension, then run [`setup_input(::AbstractString, - `log_path::String="output_path"`: The `"path_name"` of the directory where logging should output. `log_path` must exist in `paths` or, be defined by [`SetupModule.default_paths`](@ref) - `custom_metadata::Vector{Tuple{String, String}}=Vector{Tuple{String, String}}()`: Additonal metadata to include in the input file, in addition to creation date and `input_path` """ -function setup_input(input_path::AbstractString, verbose::Bool, ext::String; paths::OrderedDict{String, Tuple{String, String}}=OrderedDict{String, Tuple{String, String}}(), log_path::String="output_path", custom_metadata::Vector{Tuple{String, String}}=Vector{Tuple{String, String}}()) +function setup_input( + input_path::AbstractString, + verbose::Bool, + ext::String; + paths::OrderedDict{String,Tuple{String,String}} = OrderedDict{ + String, + Tuple{String,String}, + }(), + log_path::String = "output_path", + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) ext = get_InputExt(ext)() - return setup_input(input_path, verbose, ext; paths=paths, log_path=log_path, custom_metadata=custom_metadata) + return setup_input( + input_path, + verbose, + ext; + paths = paths, + log_path = log_path, + custom_metadata = custom_metadata, + ) end """ @@ -62,8 +95,18 @@ Main `BetterInputFiles` function, given a path to an input file, this will prepr - `log_path::String="output_path"`: The `"path_name"` of the directory where logging should output. `log_path` must exist in `paths` or, be defined by [`SetupModule.default_paths`](@ref) - `custom_metadata::Vector{Tuple{String, String}}=Vector{Tuple{String, String}}()`: Additonal metadata to include in the input file, in addition to creation date and `input_path` """ -function setup_input(input_path::AbstractString, verbose::Bool, ext::InputExt; paths::OrderedDict{String, Tuple{String, String}}=OrderedDict{String, Tuple{String, String}}(), log_path::String="output_path", custom_metadata::Vector{Tuple{String, String}}=Vector{Tuple{String, String}}()) - input::Dict{String, Any}, ext = preprocess_input(input_path, ext, custom_metadata) +function setup_input( + input_path::AbstractString, + verbose::Bool, + ext::InputExt; + paths::OrderedDict{String,Tuple{String,String}} = OrderedDict{ + String, + Tuple{String,String}, + }(), + log_path::String = "output_path", + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) + input::Dict{String,Any}, ext = preprocess_input(input_path, ext, custom_metadata) setup_global!(input, input_path, verbose, paths, log_path) input = postprocess_input(input) save_input(input, log_path, input_path, ext) diff --git a/src/IOModule.jl b/src/IOModule.jl index 65af98b..e2d035b 100644 --- a/src/IOModule.jl +++ b/src/IOModule.jl @@ -15,7 +15,7 @@ export load_inputfile export load_raw_inputfile export save_input export postprocess_input -export preprocess_input +export preprocess_input export get_InputExt export InputExt @@ -110,7 +110,7 @@ Ensure dictionary is of type Dict{String, Any} - `input::Dict`: Input to fix """ function fix_dict_type(input::Dict) - rtn = Dict{String, Any}() + rtn = Dict{String,Any}() for (key, value) in input if typeof(value) <: Dict rtn[key] = fix_dict_type(value) @@ -147,7 +147,7 @@ Automatically detect extension and attempt to load input into a dictionary - `input_path::AbstractString`: Input path to load """ function load_inputfile(input_path::AbstractString) - ext = splitext(input_path)[end][2:end] + ext = splitext(input_path)[end][2:end] return fix_dict_type(load_inputfile(input_path, ext)) end @@ -262,7 +262,12 @@ Save `input` to the same directory that logging is being saved. - `input_path::AbstractString`: Original path of input - `ext::InputExt`: Extension specifier """ -function save_input(input::Dict{String, Any}, log_path::String, input_path::AbstractString, ext::InputExt) +function save_input( + input::Dict{String,Any}, + log_path::String, + input_path::AbstractString, + ext::InputExt, +) config = input["GLOBAL"] output_path = get(config, uppercase(log_path), nothing) if isnothing(output_path) @@ -283,7 +288,7 @@ Save `input` to `output_file`, as a `.toml` file - `output_file::AbstractString`: Path to save - `ext::TOMLExt`: Extension specifier """ -function save_input(input::Dict{String, Any}, output_file::AbstractString, ext::TOMLExt) +function save_input(input::Dict{String,Any}, output_file::AbstractString, ext::TOMLExt) open(output_file, "w") do io TOML.print(io, input) end @@ -299,7 +304,7 @@ Save `input` to `output_file`, as a `.json` file - `output_file::AbstractString`: Path to save - `ext::JSONExt`: Extension specifier """ -function save_input(input::Dict{String, Any}, output_file::AbstractString, ext::JSONExt) +function save_input(input::Dict{String,Any}, output_file::AbstractString, ext::JSONExt) open(output_file, "w") do io JSON.print(io, input, 4) end @@ -315,7 +320,7 @@ Save `input` to `output_file`, as a `.yaml` file - `output_file::AbstractString`: Path to save - `ext::YAMLExt`: Extension specifier """ -function save_input(input::Dict{String, Any}, output_file::AbstractString, ext::YAMLExt) +function save_input(input::Dict{String,Any}, output_file::AbstractString, ext::YAMLExt) YAML.write_file(output_file, input) end @@ -341,7 +346,7 @@ Recursively ensure every key in `input` is uppercase - `input::Dict`: The input to update """ function update_case(input::Dict) - rtn = Dict{String, Any}() + rtn = Dict{String,Any}() for (key, value) in input rtn[uppercase(key)] = update_case(value) end @@ -357,7 +362,7 @@ Updated the case of every element in `input`. - `input::AbstractArray`: Input to process """ function update_case(input::AbstractArray) - return update_case.(input) + return update_case.(input) end """ @@ -382,10 +387,13 @@ Automatically detect extension type of `input_path`, then preprocess the input. Will error if the extension of `input_path` is not part of [`exts`](@ref). If you wish to manually specify the extension, use `[preprocess_input(::AbstractString, ::String)]`(@ref). After the extension is found, will run [`preprocess_input(::AbstractString, ::String)]`(@ref) """ -function preprocess_input(input_path::AbstractString, custom_metadata::Vector{Tuple{String, String}} = Vector{Tuple{String, String}}()) +function preprocess_input( + input_path::AbstractString, + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) # Get extension without leading dot - ext = splitext(input_path)[end][2:end] + ext = splitext(input_path)[end][2:end] input_ext = get_InputExt(ext)() return preprocess_input(input_path, input_ext, custom_metadata) end @@ -401,7 +409,11 @@ Specify the extension type of `input_path` manually, then preprocess the ipnut. If the extension of `input_path` is not defined by `BetterInputFiles`, but acts like a defined extension, you can specify which extension to use via this function, which will then run [`preprocess_input(::AbstractString, ::String)`](@ref) """ -function preprocess_input(input_path::AbstractString, ext::String, custom_metadata::Vector{Tuple{String, String}} = Vector{Tuple{String, String}}()) +function preprocess_input( + input_path::AbstractString, + ext::String, + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) input_ext = get_InputExt(ext)() return preprocess_input(input_path, input_ext, custom_metadata) @@ -418,11 +430,15 @@ Preprocess the input path before running setup. Preprocessing includes adding metadata comments at the top-level, including other files, inserting environmental variables, propegating default values, interpolating values, and ensuring all variables are upper-case. """ -function preprocess_input(input_path::AbstractString, ext::InputExt, custom_metadata::Vector{Tuple{String, String}} = Vector{Tuple{String, String}}()) +function preprocess_input( + input_path::AbstractString, + ext::InputExt, + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) input_path = abspath(input_path) raw = load_raw_inputfile(input_path) raw = add_metadata(raw, ext, input_path, custom_metadata) - raw = process_includes(raw, input_path) + raw = process_includes(raw, input_path) raw = process_env_vars(raw) input = process_interpolation(raw, ext) return input, ext @@ -440,7 +456,12 @@ Add metadata comment to top-level of .toml file Adds a new "METADATA" key, containing the date of creation and `input_path` """ -function add_metadata(raw::String, ::TOMLExt, input_path::AbstractString, custom_metadata::Vector{Tuple{String, String}} = Vector{Tuple{String, String}}()) +function add_metadata( + raw::String, + ::TOMLExt, + input_path::AbstractString, + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) date = today() time = Dates.format(now(), "HH:MM:SS") metadata = "[ METADATA ]\n DATE = \"$date\"\n TIME = \"$time\"\n ORIGINAL = \"$input_path\"\n" @@ -462,7 +483,12 @@ Add metadata comment to top-level of .yaml file Adds a new "METADATA" key, containing the date of creation and `input_path` """ -function add_metadata(raw::String, ::YAMLExt, input_path::AbstractString, custom_metadata::Vector{Tuple{String, String}} = Vector{Tuple{String, String}}()) +function add_metadata( + raw::String, + ::YAMLExt, + input_path::AbstractString, + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) date = today() metadata = "METADATA:\n DATE: \"$date\"\n ORIGINAL: \"$input_path\"\n" for (key, value) in custom_metadata @@ -483,7 +509,12 @@ Add metadata comment to top-level of .json file Adds a new "METADATA" key, containing the date of creation and `input_path` """ -function add_metadata(raw::String, ::JSONExt, input_path::AbstractString, custom_metadata::Vector{Tuple{String, String}} = Vector{Tuple{String, String}}()) +function add_metadata( + raw::String, + ::JSONExt, + input_path::AbstractString, + custom_metadata::Vector{Tuple{String,String}} = Vector{Tuple{String,String}}(), +) date = today() m_str = "\n \"DATE\": \"$date\",\n \"ORIGINAL\": \"$input_path\"\n" @@ -571,7 +602,7 @@ function process_interpolation(raw::String, ext::InputExt) raw = replace(raw, "<%$key>" => "\"<%$key>\"") end end - input::Dict{String, Any} = load_input(raw, ext) + input::Dict{String,Any} = load_input(raw, ext) # Get default values, checking both `default` and `DEFAULT` default = Dict() if "default" in keys(input) @@ -599,7 +630,7 @@ function process_interpolation(input::Dict, default::Dict) # Get the set of all types contained in value value_types = collect(Set(typeof.(value))) # If value is a list of key => value pairs, recurse interpolation - if (length(value_types) == 1) && (value_types[1] == Dict{String, Any}) + if (length(value_types) == 1) && (value_types[1] == Dict{String,Any}) input[key] = [process_interpolation(v, default) for v in value] end elseif typeof(value) <: AbstractString @@ -608,10 +639,14 @@ function process_interpolation(input::Dict, default::Dict) for k in m.captures if k in keys(input) input[key] = input[k] - elseif k in keys(default) + elseif k in keys(default) input[key] = default[k] else - throw(ErrorException("Can not interpolate $k, not found in $(keys(input)), nor in $(keys(default))")) + throw( + ErrorException( + "Can not interpolate $k, not found in $(keys(input)), nor in $(keys(default))", + ), + ) end end end diff --git a/src/SetupModule.jl b/src/SetupModule.jl index d5d5304..17bc2fa 100644 --- a/src/SetupModule.jl +++ b/src/SetupModule.jl @@ -29,7 +29,7 @@ This dictionary maps `("path_name" => ("relative_name", "default_path"))`, where const default_paths = OrderedDict{String,Tuple{String,String}}( # Name => relative, default "BASE_PATH" => ("INPUT_PATH", ""), - "OUTPUT_PATH" => ("BASE_PATH", "Output") + "OUTPUT_PATH" => ("BASE_PATH", "Output"), ) @@ -51,7 +51,11 @@ function setup_paths!(input::Dict, paths::OrderedDict{String,Tuple{String,String # Requires that `relative_name` already exists in `config` if !(uppercase(relative_name) in keys(config)) if !(lowercase(relative_name) in keys(config)) - throw(ErrorException("Relative path $relative_name for path $path_name doesn't exist. Make sure you have defined your paths in the correct order!")) + throw( + ErrorException( + "Relative path $relative_name for path $path_name doesn't exist. Make sure you have defined your paths in the correct order!", + ), + ) else config[uppercase(relative_name)] = config[relative_name] delete!(config, relative_name) @@ -93,7 +97,7 @@ Assumes that [`setup_paths!`](@ref) has already been run on `input` See also [`setup_global!`](@ref) """ -function setup_logging!(input::Dict, output_path::String="OUTPUT_PATH") +function setup_logging!(input::Dict, output_path::String = "OUTPUT_PATH") config = input["GLOBAL"] if !(uppercase(output_path) in keys(config)) throw(ErrorException("Output path $output_path not defined")) @@ -141,12 +145,23 @@ function setup_logger(log_file::AbstractString, verbose::Bool) color = :white bold = false end - printstyled(io, args._module, " | ", "[", args.level, "] ", args.message, "\n"; color=color, bold=bold) + printstyled( + io, + args._module, + " | ", + "[", + args.level, + "] ", + args.message, + "\n"; + color = color, + bold = bold, + ) end # Log to both `log_file` and `stdout` logger = TeeLogger( MinLevelLogger(FormatLogger(fmt, open(log_file, "w")), level), - MinLevelLogger(FormatLogger(fmt, stdout), level) + MinLevelLogger(FormatLogger(fmt, stdout), level), ) # Set global logger global_logger(logger) @@ -168,7 +183,16 @@ Setup the `"GLOBAL"` information of input, including paths and logging. See also [`setup_paths!`](@ref), and [`setup_logging!`](@ref) """ -function setup_global!(input::Dict, input_path::AbstractString, verbose::Bool, paths::OrderedDict{String,Tuple{String,String}}=OrderedDict{String,Tuple{String,String}}(), log_path::String="OUTPUT_PATH") +function setup_global!( + input::Dict, + input_path::AbstractString, + verbose::Bool, + paths::OrderedDict{String,Tuple{String,String}} = OrderedDict{ + String, + Tuple{String,String}, + }(), + log_path::String = "OUTPUT_PATH", +) if !("GLOBAL" in keys(input)) if !("global" in keys(input)) input["GLOBAL"] = Dict() diff --git a/test/runtests.jl b/test/runtests.jl index 932f77f..8bdbd90 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,14 +1,10 @@ using BetterInputFiles using Test -using OrderedCollections +using OrderedCollections using Dates -const ext_dict = Dict( - "yml" => "yaml", - "tml" => "toml", - "jsn" => "json" -) +const ext_dict = Dict("yml" => "yaml", "tml" => "toml", "jsn" => "json") @testset verbose = true "BetterInputFiles.jl" begin @@ -17,18 +13,17 @@ const ext_dict = Dict( expected_outputs = joinpath(test_files, "expected_outputs") @testset "InputExt" begin - @test BetterInputFiles.get_InputExt(".yaml") == BetterInputFiles.get_InputExt("yaml") + @test BetterInputFiles.get_InputExt(".yaml") == + BetterInputFiles.get_InputExt("yaml") end @testset "expected errors" begin test_file = joinpath(test_files, "broken.ext") @test_throws UndefVarError setup_input(test_file, false) test_file = joinpath(input_files, "blank", "blank.toml") - broken_paths = OrderedDict( - "a_path" => ("b_path", "A"), - "b_path" => ("base_path", "B") - ) - @test_throws ErrorException setup_input(test_file, false; paths=broken_paths) + broken_paths = + OrderedDict("a_path" => ("b_path", "A"), "b_path" => ("base_path", "B")) + @test_throws ErrorException setup_input(test_file, false; paths = broken_paths) end @testset "expected outputs" begin @@ -36,7 +31,7 @@ const ext_dict = Dict( ENV["B"] = 2 custom_metadata = [("A", "B")] - + for dir in readdir(input_files) input_dir = joinpath(input_files, dir) @@ -45,14 +40,24 @@ const ext_dict = Dict( for file in readdir(input_dir) if isfile(joinpath(input_dir, file)) test_file = joinpath(input_dir, file) - ext = splitext(file)[end][2:end] + ext = splitext(file)[end][2:end] if ext in keys(ext_dict) ext = ext_dict[ext] end - input_1 = setup_input(test_file, false; custom_metadata=custom_metadata) - input_2 = setup_input(test_file, false, ext; custom_metadata=custom_metadata) - expected_output_1 = BetterInputFiles.load_inputfile(joinpath(expected_dir, file)) - expected_output_2 = BetterInputFiles.load_inputfile(joinpath(expected_dir, file), ext) + input_1 = + setup_input(test_file, false; custom_metadata = custom_metadata) + input_2 = setup_input( + test_file, + false, + ext; + custom_metadata = custom_metadata, + ) + expected_output_1 = + BetterInputFiles.load_inputfile(joinpath(expected_dir, file)) + expected_output_2 = BetterInputFiles.load_inputfile( + joinpath(expected_dir, file), + ext, + ) # Deal with github's paths input_1["METADATA"] = expected_output_1["METADATA"] input_2["METADATA"] = expected_output_2["METADATA"]