text:
@testset "Coupling constraints" begin
    cp = convert(CoreModelCoupled, test_LP())
    @test size(cp.lm.S) == (4, 3)
    @test size(stoichiometry(convert(CoreModel, cp))) == (4, 3)
    new_cp = add_coupling_constraints(cp, stoichiometry(cp)[end, :], -1.0, 1.0)
    @test n_coupling_constraints(cp) + 1 == n_coupling_constraints(new_cp)

    new_cp =
        add_coupling_constraints(cp, stoichiometry(cp)[1:2, :], [-1.0; -1.0], [1.0; 1.0])
    @test n_coupling_constraints(cp) + 2 == n_coupling_constraints(new_cp)

    n_c = n_coupling_constraints(cp)
    add_coupling_constraints!(cp, stoichiometry(cp)[end, :], -1.0, 1.0)
    @test n_c + 1 == n_coupling_constraints(cp)
    add_coupling_constraints!(cp, stoichiometry(cp)[1:2, :], [-1.0; -1.0], [1.0; 1.0])
    @test n_c + 3 == n_coupling_constraints(cp)

    n_c = n_coupling_constraints(cp)
    remove_coupling_constraints!(cp, 1)
    @test n_c - 1 == n_coupling_constraints(cp)
    remove_coupling_constraints!(cp, [1, 2])
    @test n_c - 3 == n_coupling_constraints(cp)
    @test n_coupling_constraints(cp) == 0

    cp = test_coupledLP()
    n_c = n_coupling_constraints(cp)
    new_cp = remove_coupling_constraints(cp, 1)
    @test size(coupling(cp)) == (n_c, n_reactions(cp))
    @test n_c - 1 == n_coupling_constraints(new_cp)
    @test n_coupling_constraints(cp) == n_c
    new_cp = remove_coupling_constraints(cp, [1, 2])
    @test n_c - 2 == n_coupling_constraints(new_cp)
    new_cp = remove_coupling_constraints(cp, Vector(1:n_coupling_constraints(cp)))
    @test n_coupling_constraints(new_cp) == 0
    @test n_coupling_constraints(cp) == n_c

    cp = test_coupledLP()
    change_coupling_bounds!(cp, [3, 1], cl = [-10.0, -20], cu = [10.0, 20])
    cl, cu = coupling_bounds(cp)
    @test cl[[1, 3]] == [-20, -10]
    @test cu[[1, 3]] == [20, 10]
    change_coupling_bounds!(cp, [1000, 1001], cl = [-50.0, -60.0])
    cl, cu = coupling_bounds(cp)
    @test cl[[1000, 1001]] == [-50.0, -60.0]
end

@testset "Add reactions" begin
    cp = convert(CoreModelCoupled, test_LP())
    cp = add_coupling_constraints(cp, stoichiometry(cp)[end, :], -1.0, 1.0)

    new_cp = add_reactions(cp, 2.0 * ones(4), 3 .* ones(4), 2.0, -1.0, 1.0)
    @test new_cp isa CoreModelCoupled
    @test cp.C == new_cp.C[:, 1:end-1]
    @test cp.cl == new_cp.cl
    @test cp.cu == new_cp.cu

    new_cp = add_reactions(cp, 2.0 * ones(4), 3 .* ones(4), 2.0, -1.0, 1.0, "r4", ["m$i" for i in 1:4])
    @test cp.C == new_cp.C[:, 1:end-1]
    @test cp.cl == new_cp.cl
    @test cp.cu == new_cp.cu

    new_cp = add_reactions(
        cp,
        2.0 * ones(4, 10),
        3 .* ones(4),
        2 .* ones(10),
        -ones(10),
        ones(10),
    )
    @test cp.C == new_cp.C[:, 1:end-10]
    @test cp.cl == new_cp.cl
    @test cp.cu == new_cp.cu

    new_cp = add_reactions(
        cp,
        2.0 * ones(4, 10),
        3 .* ones(4),
        2 .* ones(10),
        -ones(10),
        ones(10),
        ["r$i" for i in 1:10],
        ["m$i" for i in 1:4]
    )
    @test cp.C == new_cp.C[:, 1:end-7] # 3 reactions were already present
    @test cp.cl == new_cp.cl
    @test cp.cu == new_cp.cu

    new_cp = add_reactions(
            cp,
            2.0 * sprand(4000, 0.5),
            3 .* sprand(4000, 0.5),
            2.0,
            -1.0,
            1.0
        )
    @test cp.C == new_cp.C[:, 1:end-1]
    @test cp.cl == new_cp.cl
    @test cp.cu == new_cp.cu

    cm = CoreModel(
            2.0 * ones(4, 10),
            3 .* ones(4),
            2 .* ones(10),
            -ones(10),
            ones(10),
            ["r$i" for i in 1:10],
            ["m$i" for i in 1:4]
        )
    new_cp = add_reactions(cp, cm)
    @test cp.C == new_cp.C[:, 1:end-7] # 3 reactions were already present
    @test cp.cl == new_cp.cl
    @test cp.cu == new_cp.cu
end

@testset "Remove reactions" begin
    cp = convert(CoreModelCoupled, test_LP())
    cp = add_coupling_constraints(cp, 1. .* collect(1:n_reactions(cp)), -1.0, 1.0)

    new_cp = remove_reactions(cp, [3;2])
    @test new_cp isa CoreModelCoupled
    @test new_cp.C[:] == cp.C[:, 1] # because cp.C[:, 1] comes out as a Vector
    @test new_cp.cl == cp.cl
    @test new_cp.cu == cp.cu

    new_cp = remove_reactions(cp, 2)
    @test new_cp.C == cp.C[:, [1; 3]]
    @test new_cp.cl == cp.cl
    @test new_cp.cu == cp.cu

    new_cp = remove_reactions(cp, "r1")
    @test new_cp.C == cp.C[:, 2:3]
    @test new_cp.cl == cp.cl
    @test new_cp.cu == cp.cu

    new_cp = remove_reactions(cp, ["r1"; "r3"])
    @test new_cp.C[:] == cp.C[:, 2]
    @test new_cp.cl == cp.cl
    @test new_cp.cu == cp.cu

    new_cp = remove_reactions(cp, [1;4])
    @test new_cp.C == cp.C[:, 2:3]

    new_cp = remove_reactions(cp, "r4")
    @test new_cp.C == cp.C

    new_cp = remove_reactions(cp, [1;1;2])
    @test new_cp.C[:] == cp.C[:, 3]
end

@testset "Change bounds" begin
    cp = convert(CoreModelCoupled, test_LP())
    change_bounds!(cp, [3; 1], xl = [-10.0; -20], xu = [10.0; 20])
    @test cp isa CoreModelCoupled
    @test cp.lm.xl == [-20; 1; -10]
    @test cp.lm.xu == [20; 1; 10]
    change_bounds!(
        cp,
        ["gibberish1"; "r3"; "r1"; "gibberish2"],
        xl = [0; -30.0; -40; 0],
        xu = [0; 30.0; 40; 0],
    )
    @test cp.lm.xl == [-40; 1; -30]
    @test cp.lm.xu == [40; 1; 30]
    change_bounds!(cp, ["r1"; "r3"], xl = [-50.0; -60])
    @test cp.lm.xl == [-50; 1; -60]
end


text:
const VecVec = Vector{<:StaticVector{<:Any,<:Real}}

# This overwrites the default behavior for plotting vectors of static vectors
#   provides convenient methods for plotting state and control trajectories
#   plot(X; inds=1:3) will plot the first three states of the trajectory X
#   can also pass time in as the first argument, e.g. plot(t,X, inds=2:3)
@recipe function f(::Type{T}, X::T) where T <: VecVec
    hcat(Vector.(X)...)'
end

@recipe function f(X::VecVec; inds=1:length(X[1]))
    (hcat(Vector.(X)...)[inds,:])'
end

@recipe function f(t::AbstractVector{<:Real}, X::VecVec; inds=1:length(X[1]))
    t, (hcat(Vector.(X)...)[inds,:])'
end

@recipe function f(Z::T; inds=1:state_dim(Z) + control_dim(Z)) where T <: AbstractTrajectory
    default_xlabels = ["x" * string(i) for i = 1:state_dim(Z)]
    default_ulabels = ["u" * string(i) for i = 1:control_dim(Z)]
    default_labels = [default_xlabels; default_ulabels]
    xguide --> "time (s)"
    yguide --> "states"
    label --> reshape(default_labels[inds], 1, :)
    gettimes(Z), (hcat(Vector.(get_data(Z))...)[inds,:])'
end

"""
    traj2(x, y)
    traj2(X; xind=1, yind=2)
    traj2(Z::AbstractTrajectory; xind=1, yind=2)

Plot a 2D state trajectory, given the x and y positions of the robot. If given
a state trajectory, the use optional `xind` and `yind` arguments to provide the
location of the x and y positions in the state vector (typically 1 and 2, respectively).
"""
@userplot Traj2

@recipe function f(traj::Traj2; xind=1, yind=2)
    # Process input
    #   Input a vector of static vectors
    if length(traj.args) == 1 && (traj.args[1] isa VecVec || traj.args[1] isa AbstractTrajectory)
        if traj.args[1] isa VecVec
            X = traj.args[1]
        else
            X = states(traj.args[1])
        end
        xs = [x[xind] for x in X]
        ys = [x[yind] for x in X]
    #   Input the x and y vectors independently
    elseif length(traj.args) == 2 &&
            traj.args[1] isa AbstractVector{<:Real} &&
            traj.args[2] isa AbstractVector{<:Real}
        xs = traj.args[1]
        ys = traj.args[2]
    else
        throw(ArgumentError("Input must either be a Vector of StaticVector's, or two Vectors of positions"))
    end
    # Defaults
    xguide --> "x"
    yguide --> "y"
    label --> :none
    # Plot x,y
    (xs,ys)
end

ext: jl


text:
# TemplateArgument
function TemplateArgument(x::AbstractQualType, is_null::Bool=false)
    return TemplateArgument(clang_TemplateArgument_constructFromQualType(x, is_null))
end

dispose(x::TemplateArgument) = clang_TemplateArgument_dispose(x)

function getKind(x::TemplateArgument)
    @check_ptrs x
    return clang_TemplateArgument_getKind(x)
end

function isNull(x::TemplateArgument)
    @check_ptrs x
    return clang_TemplateArgument_isNull(x)
end

function isDependent(x::TemplateArgument)
    @check_ptrs x
    return clang_TemplateArgument_isDependent(x)
end

function isInstantiationDependent(x::TemplateArgument)
    @check_ptrs x
    return clang_TemplateArgument_isInstantiationDependent(x)
end

function getAsType(x::TemplateArgument)
    @check_ptrs x
    return QualType(clang_TemplateArgument_getAsType(x))
end

function getParamTypeForDecl(x::TemplateArgument)
    @check_ptrs x
    return QualType(clang_TemplateArgument_getParamTypeForDecl(x))
end

function getNullPtrType(x::TemplateArgument)
    @check_ptrs x
    return QualType(clang_TemplateArgument_getNullPtrType(x))
end

function getAsTemplate(x::TemplateArgument)
    @check_ptrs x
    return TemplateName(clang_TemplateArgument_getAsTemplate(x))
end

function getAsTemplateOrTemplatePattern(x::TemplateArgument)
    @check_ptrs x
    return TemplateName(clang_TemplateArgument_getAsTemplateOrTemplatePattern(x))
end

function getNumTemplateExpansions(x::TemplateArgument)
    @check_ptrs x
    return clang_TemplateArgument_getNumTemplateExpansions(x)
end

function getAsIntegral(x::TemplateArgument)
    @check_ptrs x
    return clang_TemplateArgument_getAsIntegral(x)
end

function getIntegralType(x::TemplateArgument)
    @check_ptrs x
    return QualType(clang_TemplateArgument_getIntegralType(x))
end

function setIntegralType(x::TemplateArgument, ty::AbstractQualType)
    @check_ptrs x
    return clang_TemplateArgument_setIntegralType(x, ty)
end

function dump(x::TemplateArgument)
    @check_ptrs x
    return clang_TemplateArgument_dump(x)
end

function getAsDecl(x::TemplateArgument)
    @check_ptrs x
    return ValueDecl(clang_TemplateArgument_getAsDecl(x))
end

function TemplateArgument(decl::ValueDecl, ty::AbstractQualType)
    @check_ptrs decl
    return TemplateArgument(clang_TemplateArgument_constructFromValueDecl(decl, ty))
end

function TemplateArgument(ctx::ASTContext, v::GenericValue, ty::AbstractQualType)
    @check_ptrs ctx
    return TemplateArgument(clang_TemplateArgument_constructFromIntegral(ctx, v, ty))
end


text:
module StomataModels

using CLIMAParameters
using CLIMAParameters.Planet
using ConstrainedRootSolvers
using DocStringExtensions
using Parameters
using ..Photosynthesis
using ..PlantHydraulics
using WaterPhysics

# Define constants
struct EarthParameterSet <: AbstractEarthParameterSet end
const EARTH        = EarthParameterSet();
CP_DRYAIR(FT)      = FT( cp_d(EARTH) );
K_0(FT)            = FT( T_freeze(EARTH) );
K_STEFAN(FT)       = FT( Stefan() );
MOLMASS_DRYAIR(FT) = FT( molmass_dryair(EARTH) );
MOLMASS_WATER(FT)  = FT( molmass_water(EARTH) );

K_25(FT)           = K_0(FT) + 25;
CP_DRYAIR_MOL(FT)  = CP_DRYAIR(FT) * MOLMASS_DRYAIR(FT);

# export public types and structs
export AbstractBetaFunction,
       AbstractBetaG,
       AbstractBetaV,
       AbstractDrive,
       AbstractStomatalModel,
       BetaGLinearKleaf,
       BetaGLinearPleaf,
       BetaGLinearPsoil,
       BetaGLinearSWC,
       BetaVLinearKleaf,
       BetaVLinearPleaf,
       BetaVLinearPsoil,
       BetaVLinearSWC,
       CanopyLayer,
       EmpiricalStomatalModel,
       ESMBallBerry,
       ESMGentine,
       ESMLeuning,
       ESMMedlyn,
       GlcDrive,
       GswDrive,
       OptimizationStomatalModel,
       OSMEller,
       OSMSperry,
       OSMWang,
       OSMWAP,
       OSMWAPMod

# export public functions
export gsw_control!,
       gas_exchange!,
       prognostic_gsw!,
       solution_diff!,
       stomatal_conductance,
       update_leaf_AK!,
       update_leaf_TP!

include("types/beta.jl"         )
include("types/canopylayer.jl"  )
include("types/drive.jl"        )
include("types/stomatalmodel.jl")

include("model/beta.jl"       )
include("model/control.jl"    )
include("model/empirical.jl"  )
include("model/gasexchange.jl")
include("model/nocturnal.jl"  )
include("model/prognostic.jl" )
include("model/refresh.jl"    )
include("model/solution.jl"   )

end # module

max_stars_repo_name: bbuman/Land

max_issues_repo_name: bbuman/Land

max_forks_repo_name: bbuman/Land


text:
include("task_func.jl")
function drawPerim(r)
    listOfShags = []
    while !isborder(r, West) || !isborder(r, Sud)
        push!(listOfShags, goToSide!(r, West, save = true))
        push!(listOfShags, goToSide!(r, Sud, save = true))
    end
    for side = (Nord, Ost, Sud, West)
        goToSide!(r, side, mark = true)
    end
    goToStart!(r, listOfShags)
end

max_stars_repo_name: Trilonka/MireaAl

max_issues_repo_name: Trilonka/MireaAl

max_forks_repo_name: Trilonka/MireaAl


text:
# Helper data for color parsing
include("names_data.jl")

# Color Parsing
# -------------

const col_pat_func3 = r"^\s*(?:rgba?|hsla?)
                               \(\s*([^\s,/\)]+)\s*
                            [,\s]\s*([^\s,/\)]+)\s*
                            [,\s]\s*([^\s,/\)]+)\s*
                            (?:[,/]\s*([^\s,/\)]+)\s*)?\)\s*$"ix
const col_pat_hex = r"^\s*(?:#|0x)([[:xdigit:]]{3,8})\s*$"
const col_pat_unitful = r"^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(.*)$"i

chop1(x) = SubString(x, 1, lastindex(x) - 1) # `chop` is slightly slow

function parse_hex(hex::SubString{String}) # It is guaranteed to be a valid hex string.
    digits = UInt32(0)
    for d in codeunits(hex)
        dl = d | 0x20
        digits = (digits << 0x4) + dl - (dl < UInt8('a') ? UInt8('0') : UInt8('a') - 0xa)
    end
    return digits
end

function tryparse_dec(dec::SubString{String})
    de = 0
    for d in codeunits(dec)
        dx = d - UInt8('0')
        0x0 <= dx <= 0x9 || return nothing
        de = de * 10 + dx
    end
    return de
end

function parse_f32(dec::SubString{String})
    v = tryparse_dec(dec)
    v === nothing && return parse(Float32, dec)
    Float32(v)
end

# Parse a number used in the "rgb()" or "hsl()" color.

function parse_rgb(num::SubString{String})
    @inbounds num[end] == '%' && throw_rgb_unification_error()
    v = tryparse_dec(num)
    if v === nothing
        v = round(Int, parse(Float32, num))
    end
    return reinterpret(N0f8, unsafe_trunc(UInt8, clamp(v, 0, 255)))
end

function parse_rgb_pc(num::SubString{String})
    @inbounds num[end] == '%' || throw_rgb_unification_error()
    v = round(Int, parse_f32(chop1(num)) * 2.55f0)
    return reinterpret(N0f8, unsafe_trunc(UInt8, clamp(v, 0, 255)))
end

function throw_rgb_unification_error()
    throw(ArgumentError("RGB values should be unified in numbers in [0,255] or percentages."))
end

function parse_hue(num::SubString{String})
    vi = tryparse_dec(num)
    vi !== nothing && return Float32(vi)
    mat = match(col_pat_unitful, num)
    if mat !== nothing
        v0, unit0 = mat.captures
        v = parse_f32(v0)
        isempty(unit0) && return v
        unit = lowercase(unit0)
        if unit == "deg"
            return v
        elseif unit == "turn"
            return v * 360f0
        elseif unit == "rad"
            return rad2deg(v)
        elseif unit == "grad"
            return v * 0.9f0
        end
    end
    throw(ArgumentError("invalid hue notation: $num"))
end

function parse_hsl_pc(num::SubString{String})
    if @inbounds num[end] != '%'
        throw(ArgumentError("saturation and lightness must end in %"))
    end
    return clamp(parse_f32(chop1(num)) / 100f0, 0.0f0, 1.0f0)
end

# Parse a number used in the alpha field of "rgba()" and "hsla()".
function parse_alpha(num::SubString{String})
    if @inbounds num[end] == '%'
        v = parse_f32(chop1(num)) / 100f0
    else
        v = parse(Float32, num)
    end
    return clamp(v, 0.0f0, 1.0f0)
end

function _parse_colorant(desc::String)
    n0f8(x) = reinterpret(N0f8, unsafe_trunc(UInt8, x))
    mat = match(col_pat_hex, desc)
    if mat !== nothing
        len = ncodeunits(mat[1])
        digits = parse_hex(mat[1])
        if len == 6
            return convert(RGB{N0f8}, reinterpret(RGB24, digits))
        elseif len == 3
            return RGB(n0f8((digits>>8) & 0xF * 0x11),
                       n0f8((digits>>4) & 0xF * 0x11),
                       n0f8((digits>>0) & 0xF * 0x11))
        elseif len == 8
            if occursin('#', desc)
                return RGBA{N0f8}(n0f8(digits>>24),
                                  n0f8(digits>>16),
                                  n0f8(digits>> 8),
                                  n0f8(digits>> 0))
            else
                return ARGB{N0f8}(n0f8(digits>>16),
                                  n0f8(digits>> 8),
                                  n0f8(digits>> 0),
                                  n0f8(digits>>24))
            end
        elseif len == 4
            if occursin('#', desc)
                return RGBA{N0f8}(n0f8((digits>>12) & 0xF * 0x11),
                                  n0f8((digits>> 8) & 0xF * 0x11),
                                  n0f8((digits>> 4) & 0xF * 0x11),
                                  n0f8((digits>> 0) & 0xF * 0x11))
            else
                return ARGB{N0f8}(n0f8((digits>> 8) & 0xF * 0x11),
                                  n0f8((digits>> 4) & 0xF * 0x11),
                                  n0f8((digits>> 0) & 0xF * 0x11),
                                  n0f8((digits>>12) & 0xF * 0x11))
            end
        end
    end
    mat = match(col_pat_func3, desc)
    if mat !== nothing #&& mat[1] !== nothing && mat[2] !== nothing && mat[3] !== nothing
        if occursin(r"^\s*rgb"i, desc)
            return _parse_colorant_rgb(mat[1], mat[2], mat[3], mat[4])
        else # occursin(r"^\s*hsl"i, desc)
            return _parse_colorant_hsl(mat[1], mat[2], mat[3], mat[4])
        end
    end

    sdesc = strip(desc)
    c = get(color_names, sdesc, nothing)
    c !== nothing && return RGB{N0f8}(n0f8(c[1]), n0f8(c[2]), n0f8(c[3]))

    # since `lowercase` is slightly slow, it is applied only when needed
    ldesc = lowercase(sdesc)
    c = get(color_names, ldesc, nothing)
    c !== nothing && return RGB{N0f8}(n0f8(c[1]), n0f8(c[2]), n0f8(c[3]))

    ldesc == "transparent" && return RGBA{N0f8}(0,0,0,0)

    wo_spaces = replace(ldesc, r"(?<=[^bfptuv ][^p ][adeghk-rtwy]) (?=[^efinq ][aeh-mo-rtuy][^kw \d][a-y]{0,7}\b)" => "")
    c = get(color_names, wo_spaces, nothing)
    if c !== nothing && sizeof(wo_spaces) > 6
        return RGB{N0f8}(n0f8(c[1]), n0f8(c[2]), n0f8(c[3]))
    end

    throw(ArgumentError("Unknown color: $desc"))
end

function _parse_colorant_rgb(p1, p2, p3, alpha)
    if @inbounds p1[end] == '%'
        r, g, b = parse_rgb_pc(p1), parse_rgb_pc(p2), parse_rgb_pc(p3)
    else
        r, g, b = parse_rgb(p1), parse_rgb(p2), parse_rgb(p3)
    end
    if alpha === nothing
        return RGB{N0f8}(r, g, b)
    else
        return RGBA{N0f8}(r, g, b, parse_alpha(alpha) % N0f8)
    end
end

function _parse_colorant_hsl(p1, p2, p3, alpha)
    h, s, l = parse_hue(p1), parse_hsl_pc(p2), parse_hsl_pc(p3)
    if alpha === nothing
        return typeof(HSL(0,0,0))(h, s, l)
    else
        return typeof(HSLA(0,0,0))(h, s, l, parse_alpha(alpha))
    end
end

"""
    parse(Colorant, desc)

Parse a color description.

This parses a subset of HTML/CSS color specifications. In particular, everything
is supported but: `currentColor`.

It does support named colors (though it uses X11 named colors, which are
slightly different than W3C named colors in some cases), `rgb()`, `hsl()`,
`#RGB`, and `#RRGGBB` syntax.

# Arguments

- `Colorant`: literal Colorant
- `desc`: color name or description

A literal Colorant will parse according to the `desc` string (usually returning
an `RGB`); any more specific choice will return a color of the specified type.

# Returns

- an `RGB{N0f8}` color, or

- an `HSL` color if `hsl(h, s, l)` was used

- an `RGBA` color if `rgba(r, g, b, a)` was used

- an `HSLA` color if `hsla(h, s, l, a)` was used

- an `ARGB{N0f8}` color if `0xAARRGGBB`/`0xARGB` was used

- a specific `Colorant` type as specified in the first argument

!!! note "Note for X11 named colors"
    The X11 color names with spaces (e.g. "sea green") are not recommended
    because they are not allowed in the SVG/CSS.

!!! note "Note for hex notations"
    You can parse not only the CSS-style hex notations `#RRGGBB`/`#RGB`, but
    also `0xRRGGBB`/`0xRGB`.

    You can also parse the 8-digit or 4-digit hex notation into an RGB color
    with alpha. However, the result depends on the prefix (i.e. `#` or `0x`).
    ```julia-repl
    julia> parse(Colorant, "#FF8800AA") # transparent orange
    RGBA{N0f8}(1.0,0.533,0.0,0.667)

    julia> parse(Colorant, "0xFF8800AA") # opaque purple
    ARGB{N0f8}(0.533,0.0,0.667,1.0)
    ```
"""
function Base.parse(::Type{C}, desc::AbstractString) where {C<:Colorant}
    c = _parse_colorant(String(desc))
    C === Colorant && return c
    c = convert(C, c)
    return isconcretetype(C) ? c::C : c
end

Base.parse(::Type{C}, desc::Symbol) where {C<:Colorant} = parse(C, string(desc))

@noinline function Base.parse(::Type{C}, c::Colorant) where {C<:Colorant}
    Base.depwarn("""
        `parse(::Type, ::Coloarant)` is deprecated.
          Do not call `parse` if the object does not need to be parsed.""", :parse)
    c
end

"""
    @colorant_str(ex)

Parse a literal color name as a Colorant.
See [`Base.parse(Colorant, desc)`](@ref).
"""
macro colorant_str(ex)
    isa(ex, AbstractString) || error("colorant requires literal strings")
    col = parse(Colorant, ex)
    :($col)
end