diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 61edc394b2..699cfee8fd 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -55,7 +55,8 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) dict = Dict{Symbol, Any}( :defaults => Dict{Symbol, Any}(), :kwargs => Dict{Symbol, Dict}(), - :structural_parameters => Dict{Symbol, Dict}() + :structural_parameters => Dict{Symbol, Dict}(), + :metadata => Dict{Symbol, Any}() ) comps = Union{Symbol, Expr}[] ext = [] @@ -127,6 +128,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) consolidate = get(dict, :consolidate, default_consolidate) description = get(dict, :description, "") + model_meta = get(dict, :metadata, Dict{Symbol, Any}()) @inline pop_structure_dict!.( Ref(dict), [:defaults, :kwargs, :structural_parameters]) @@ -145,6 +147,14 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) isconnector && push!(exprs.args, :($Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___")))) + meta_exprs = quote + for (k, v) in $model_meta + var"#___sys___" = setmetadata(var"#___sys___", $get_var($mod, k), v) + end + end + push!(exprs.args, meta_exprs) + push!(exprs.args, :(var"#___sys___")) + f = if length(where_types) == 0 :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) else @@ -678,6 +688,8 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts, parse_costs!(costs, dict, body) elseif mname == Symbol("@consolidate") parse_consolidate!(body, dict) + elseif mname == Symbol("@metadata") + parse_metadata_block!(body, dict, mod) else error("$mname is not handled.") end @@ -1254,6 +1266,21 @@ function parse_description!(body, dict) end end +function parse_metadata_block!(body, dict, mod) + Base.remove_linenums!(body) + for arg in body.args + MLStyle.@match arg begin + Expr(:(=), a, b) => begin + dict[:metadata][a] = get_var(mod, b) + end + Expr(:call, :(=>), a, b) => begin + dict[:metadata][a] = get_var(mod, b) + end + _ => error("Invalid metadata entry: $arg. Expected key = value or key => value format.") + end + end +end + ### Parsing Components: function component_args!(a, b, varexpr, kwargs; index_name = nothing) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index c48628b007..b63577df15 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1045,3 +1045,41 @@ end @test Example.structure[:constraints] == ["(EvalAt(0.3))(x) ~ 3", "y ≲ 4"] @test Example.structure[:costs] == ["x + y", "(EvalAt(1))(y) ^ 2"] end + +@testset "Model Level Metadata" begin + struct Author end + struct MyVersion end + struct License end + struct Category end + struct Tags end + + @mtkmodel TestMetadataModel begin + @metadata begin + Author = "Test Author" + MyVersion = "1.0.0" + License = "MIT" + Category => "example" + Tags = ["test", "demo", "metadata"] + end + + @parameters begin + k = 1.0, [description = "Gain parameter"] + end + + @variables begin + x(t), [description = "State variable"] + y(t), [description = "Output variable"] + end + + @equations begin + D(x) ~ -k * x + y ~ x + end + end + @named test_model = TestMetadataModel() + + struct UnknownMetaKey end + @test ModelingToolkit.getmetadata(test_model, Author, nothing) == "Test Author" + @test ModelingToolkit.getmetadata(test_model, MyVersion, nothing) == "1.0.0" + @test ModelingToolkit.getmetadata(test_model, UnknownMetaKey, nothing) === nothing +end