Just going through some of the utils.jl in my projects.
I don’t think these are the best ways to do these things, nor the nicest names etc for them.
I did some cleanup, just now and these may thus also have typos.
But for want of getting something out there:
lift: make functions that are not missing propagating into missing propagating
function lift(func)
function(args...; kwargs...)
if any(ismissing.(args)) || any(ismissing.(collect(values(kwargs))))
missing
else
func(args...;kwargs...)
end
end
end
Might be nice to have that in a macro from that applies it to all functions in a block.
Would be its own package then really, or added to Missing.jl
Example
julia> length(missing)
ERROR: MethodError: no method matching length(::Missing)
julia> lift(length)(missing)
missing
julia> lift(length)([1,2,3])
3
Nothing2missing
nothing2missing(::Nothing) = missing
nothing2missing(val) = val
sometimes libraries return nothing
(i.e. programmers null),
but you know (using domain knowledge) that this actually should be interpreted as missing
(statician’s null).
This kinda thing occurs when your are parsing some data.
Something similar occurs for regex, which is Union{RegexMatch, Nothing}
.
Leaf Subtypes
Get all descendant concrete types
"""
leaf_subtypes(T)
Returns all the nonabstract types decedent from `T`.
"""
function leaf_subtypes(T)
if isleaftype(T)
T
else
vcat(leaf_subtypes.(subtypes(T))...)
end
end
Split path (opposite of joinpath)
"""
splitpath(path)
The opposite of `joinpath`,
splits a path unto each of its directories names / filename (for the last).
"""
function splitpath(path::AbstractString)
ret=String[]
prev_path = path
while(true)
path, lastpart = splitdir(path)
length(lastpart)>0 && pushfirst!(ret, lastpart)
length(path)==0 && break
if prev_path==path
# catch the casewhere path begins with a root
pushfirst!(ret, path)
break
end
prev_path = path
end
return ret
See also `splitpath` to match `joinpath` · Issue #24477 · JuliaLang/julia · GitHub
Environment variable reading functions
Safer than reading environment variables directly; as has inbuilt default.
Also handles user weirdness better.
"""
env_bool(key)
Checks for an enviroment variable and fuzzy converts it to a bool
"""
env_bool(key, default=false) = haskey(ENV, key) ? lowercase(ENV[key]) ∉ ["0","","false", "no"] : default
"""
env_list(key)
Checks for an enviroment variable and converts it to a list of strings, sperated with a colon
"""
env_list(key, default=String[]) = haskey(ENV, key) ? split(ENV[key], ":") : default
User input getting functions
Keep looping til user gives valid input.
"""
bool_input
Prompted the user for a yes or no.
"""
function input_bool(prompt="")::Bool
input_choice(prompt, 'y','n')=='y'
end
"""
input_choice
Prompted the user for one of a list of options
"""
function input_choice(prompt, options::Vararg{Char})::Char
while(true)
println(prompt)
println("["*join(options, '/')*"]")
response = readline()
length(response)==0 && continue
reply = lowercase(first(response))
for opt in lowercase.(options)
reply==opt && return opt
end
end
end
"""
input_choice
Prompts the user for one of a list of options.
Takes a vararg of tuples of Letter, Prompt, Action (0 argument function)
Example:
input_choice(
('A', "Abort -- errors out", ()->error("aborted")),
('X', "eXit -- exits normally", ()->exit()),
('C', "Continue -- continues running", ()->nothing)),
)
"""
function input_choice(options::Vararg{Tuple{Char, <:AbstractString, Any}})
acts = Dict{Char, Any}()
prompt = ""
chars = Char[]
for (cc, prmt, act) in options
prompt*="\n [$cc] $prmt"
push!(chars, cc)
acts[lowercase(cc)] = act
end
prompt*="\n"
acts[input_choice(prompt, chars...)]()
end