Genie.jl icon indicating copy to clipboard operation
Genie.jl copied to clipboard

Genie template system cannot render custom elements (Web Components)

Open ProvocaTeach opened this issue 1 year ago β€’ 3 comments

Describe the bug Genie throws an error when it encounters autonomous custom elements in a .jl.html template.

(Autonomous custom elements are basically just HTML elements with custom, hyphenated tag names, e.g. <my-element></my-element>. They are used to implement Web Components, a WHATWG standard for building reusable UI components.)

Error stacktrace

β”Œ Error: 2022-07-29 05:33:49 UndefVarError: prte__navigation not defined
β”‚ Stacktrace:
β”‚   [1] (::Genie.Renderer.Html.var"#1086#1088"{ProvocaWWWAppTier.TheoryTypes.CoverPage})()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/0b3d20051a772f4ace7e3c5f511d2c37da9a4a57.jl:7
β”‚   [2] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚   [3] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚   [4] normal_element(f::Function, elem::String, args::Vector{Any}, attrs::Vector{Pair{Symbol, Any}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:137
β”‚   [5] #div#184
β”‚     @ ./none:2 [inlined]
β”‚   [6] func_0b3d20051a772f4ace7e3c5f511d2c37da9a4a57(; context::Module, coverpage::ProvocaWWWAppTier.TheoryTypes.CoverPage)
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/0b3d20051a772f4ace7e3c5f511d2c37da9a4a57.jl:5
β”‚   [7] func_0b3d20051a772f4ace7e3c5f511d2c37da9a4a57()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/0b3d20051a772f4ace7e3c5f511d2c37da9a4a57.jl:5
β”‚   [8] (::Genie.Renderer.Html.var"#1080#1084")()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:31
β”‚   [9] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚  [10] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚  [11] normal_element(f::Function, elem::String, args::Vector{Any}, attrs::Vector{Pair{Symbol, Any}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:137
β”‚  [12] #body#76
β”‚     @ ./none:2 [inlined]
β”‚  [13] (::Genie.Renderer.Html.var"#1077#1081"{ProvocaWWWAppTier.TheoryTypes.CoverPage})()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:9
β”‚  [14] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚  [15] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚  [16] normal_element(f::Function, elem::String, args::Vector{Any}, attrs::Vector{Pair{Symbol, Any}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:137
β”‚  [17] html(::Function; attrs::Base.Pairs{Symbol, String, NTuple{4, Symbol}, NamedTuple{(:htmlsourceindent, :lang, :xmlns, Symbol("xml:lang")), NTuple{4, String}}})
β”‚     @ Genie.Renderer.Html ./none:2
β”‚  [18] func_3d028e305c5b9c16a676c61917530fcc0f1b3866(; context::Module, coverpage::ProvocaWWWAppTier.TheoryTypes.CoverPage)
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:5
β”‚  [19] func_3d028e305c5b9c16a676c61917530fcc0f1b3866()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:5
β”‚  [20] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚  [21] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚  [22] Genie.Renderer.WebRenderable(::Function)
β”‚     @ Genie.Renderer ~/.julia/packages/Genie/3nusr/src/Renderer.jl:158
β”‚  [23] |>
β”‚     @ ./operators.jl:966 [inlined]
β”‚  [24] render(::Type{MIME{Symbol("text/html")}}, viewfile::FilePathsBase.PosixPath; layout::FilePathsBase.PosixPath, context::Module, vars::Base.Pairs{Symbol, ProvocaWWWAppTier.TheoryTypes.CoverPage, Tuple{Symbol}, NamedTuple{(:coverpage,), Tuple{ProvocaWWWAppTier.TheoryTypes.CoverPage}}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:475
β”‚  [25] #html#30
β”‚     @ ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:574 [inlined]
β”‚  [26] html(resource::Symbol, action::Symbol; layout::Symbol, context::Module, status::Int64, headers::Dict{String, String}, vars::Base.Pairs{Symbol, ProvocaWWWAppTier.TheoryTypes.CoverPage, Tuple{Symbol}, NamedTuple{(:coverpage,), Tuple{ProvocaWWWAppTier.TheoryTypes.CoverPage}}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:486
β”‚  [27] render_coverpage
β”‚     @ ~/active/ProvocaTeach/ProvocaWWW/ProvocaWWW_App_Tier/app/resources/pieces/PiecesController.jl:119 [inlined]
β”‚  [28] render_coverpage(slug::SubString{String}, db_uri::ProvocaWWWAppTier.FantasyLoader.SparqlEndpoint)
β”‚     @ ProvocaWWWAppTier.PiecesController ~/active/ProvocaTeach/ProvocaWWW/ProvocaWWW_App_Tier/app/resources/pieces/PiecesController.jl:129
β”‚  [29] (::ProvocaWWWAppTier.var"#9#10")()
β”‚     @ ProvocaWWWAppTier ~/active/ProvocaTeach/ProvocaWWW/ProvocaWWW_App_Tier/routes.jl:14
β”‚  [30] run_route(r::Genie.Router.Route)
β”‚     @ Genie.Router ~/.julia/packages/Genie/3nusr/src/Router.jl:527
β”‚  [31] route_request(req::HTTP.Messages.Request, res::HTTP.Messages.Response)
β”‚     @ Genie.Router ~/.julia/packages/Genie/3nusr/src/Router.jl:169
β”‚  [32] handle_request(req::HTTP.Messages.Request, res::HTTP.Messages.Response)
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:269
β”‚  [33] #29
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/macros.jl:123 [inlined]
β”‚  [34] run_work_thunk(thunk::Genie.Server.var"#29#30"{HTTP.Messages.Request, HTTP.Messages.Response}, print_error::Bool)
β”‚     @ Distributed ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/process_messages.jl:63
β”‚  [35] #remotecall_fetch#158
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:454 [inlined]
β”‚  [36] remotecall_fetch
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:454 [inlined]
β”‚  [37] #remotecall_fetch#162
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:496 [inlined]
β”‚  [38] remotecall_fetch
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:496 [inlined]
β”‚  [39] setup_http_listener(req::HTTP.Messages.Request, res::HTTP.Messages.Response)
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:296
β”‚  [40] setup_http_listener
β”‚     @ ~/.julia/packages/Genie/3nusr/src/Server.jl:295 [inlined]
β”‚  [41] handle
β”‚     @ ~/.julia/packages/HTTP/aTjcj/src/Handlers.jl:254 [inlined]
β”‚  [42] handle(::HTTP.Handlers.RequestHandlerFunction{typeof(Genie.Server.setup_http_listener)}, ::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}})
β”‚     @ HTTP.Handlers ~/.julia/packages/HTTP/aTjcj/src/Handlers.jl:277
β”‚  [43] setup_http_streamer(http::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}})
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:285
β”‚  [44] (::Genie.Server.var"#7#14"{Int64})(http::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}})
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:117
β”‚  [45] macro expansion
β”‚     @ ~/.julia/packages/HTTP/aTjcj/src/Servers.jl:415 [inlined]
β”‚  [46] (::HTTP.Servers.var"#13#14"{Genie.Server.var"#7#14"{Int64}, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}, HTTP.Servers.Server{Nothing, Sockets.TCPServer}, HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}}})()
β”‚     @ HTTP.Servers ./task.jl:429
β”‚ 
β”” @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:308

To reproduce Add any custom element tag, e.g. <my-element></my-element> (or in my case, <prte-navigation></prte-navigation>), to a .jl.html template and have Genie try to render it.

Expected behavior Don’t throw an error; just treat <my-element></my-element> as the valid HTML that it is.

Note Because Web Components are completely handled by the browser and client-side JavaScript, all Genie needs to do is not throw an error when it encounters them. There is no need to treat them in a special way.

Additional context

julia> versioninfo()
Julia Version 1.7.3
Commit 742b9abb4d (2022-05-06 12:58 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: AMD Ryzen 5 2600 Six-Core Processor
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-12.0.1 (ORCJIT, znver1)
(ProvocaWWWAppTier) pkg> st
     Project ProvocaWWWAppTier v0.1.0
      Status `~/active/ProvocaTeach/ProvocaWWW/ProvocaWWW_App_Tier/Project.toml`
  [e30172f5] Documenter v0.27.22
  [c43c736e] Genie v5.0.0
  [cd3eb016] HTTP v0.9.17
  [6d011eab] Inflector v1.0.1
  [0f8b85d8] JSON3 v1.9.5
  [e6f89c97] LoggingExtras v0.4.9
  [739be429] MbedTLS v1.1.1
  [295af30f] Revise v3.3.4
  [5c2747f8] URIs v1.4.0
  [2a0f44e3] Base64
  [ade2ca70] Dates
  [56ddb016] Logging

ProvocaTeach avatar Jul 29 '22 13:07 ProvocaTeach

Yes, because Genie's rendering layer maps HTML tags to Julia functions. And it doesn't know how to render this one as it doesn't have the corresponding function.

Adding Genie.Renderer.Html.register_normal_element("my__element", context = @__MODULE__) in the Controller (or in scope) will register the rendering function.

essenciary avatar Aug 01 '22 10:08 essenciary

Hm, even with that code in my controller, I still seem to be getting a 500 error:

β”Œ Error: 2022-08-01 16:47:32 UndefVarError: prte__navigation not defined
β”‚ Stacktrace:
β”‚   [1] (::Genie.Renderer.Html.var"#1054#1056"{ProvocaWWWAppTier.TheoryTypes.CoverPage})()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/0b3d20051a772f4ace7e3c5f511d2c37da9a4a57.jl:9
β”‚   [2] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚   [3] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚   [4] normal_element(f::Function, elem::String, args::Vector{Any}, attrs::Vector{Pair{Symbol, Any}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:137
β”‚   [5] #div#184
β”‚     @ ./none:2 [inlined]
β”‚   [6] func_0b3d20051a772f4ace7e3c5f511d2c37da9a4a57(; context::Module, coverpage::ProvocaWWWAppTier.TheoryTypes.CoverPage)
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/0b3d20051a772f4ace7e3c5f511d2c37da9a4a57.jl:5
β”‚   [7] func_0b3d20051a772f4ace7e3c5f511d2c37da9a4a57()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/0b3d20051a772f4ace7e3c5f511d2c37da9a4a57.jl:5
β”‚   [8] (::Genie.Renderer.Html.var"#1062#1066")()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:53
β”‚   [9] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚  [10] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚  [11] normal_element(f::Function, elem::String, args::Vector{Any}, attrs::Vector{Pair{Symbol, Any}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:137
β”‚  [12] #body#76
β”‚     @ ./none:2 [inlined]
β”‚  [13] (::Genie.Renderer.Html.var"#1059#1063"{ProvocaWWWAppTier.TheoryTypes.CoverPage})()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:9
β”‚  [14] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚  [15] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚  [16] normal_element(f::Function, elem::String, args::Vector{Any}, attrs::Vector{Pair{Symbol, Any}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:137
β”‚  [17] html(::Function; attrs::Base.Pairs{Symbol, String, NTuple{4, Symbol}, NamedTuple{(:htmlsourceindent, :lang, :xmlns, Symbol("xml:lang")), NTuple{4, String}}})
β”‚     @ Genie.Renderer.Html ./none:2
β”‚  [18] func_3d028e305c5b9c16a676c61917530fcc0f1b3866(; context::Module, coverpage::ProvocaWWWAppTier.TheoryTypes.CoverPage)
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:5
β”‚  [19] func_3d028e305c5b9c16a676c61917530fcc0f1b3866()
β”‚     @ Genie.Renderer.Html /tmp/jl_genie_build_YOIAHv/GenieViews/3d028e305c5b9c16a676c61917530fcc0f1b3866.jl:5
β”‚  [20] #invokelatest#2
β”‚     @ ./essentials.jl:716 [inlined]
β”‚  [21] invokelatest
β”‚     @ ./essentials.jl:714 [inlined]
β”‚  [22] Genie.Renderer.WebRenderable(::Function)
β”‚     @ Genie.Renderer ~/.julia/packages/Genie/3nusr/src/Renderer.jl:158
β”‚  [23] |>
β”‚     @ ./operators.jl:966 [inlined]
β”‚  [24] render(::Type{MIME{Symbol("text/html")}}, viewfile::FilePathsBase.PosixPath; layout::FilePathsBase.PosixPath, context::Module, vars::Base.Pairs{Symbol, ProvocaWWWAppTier.TheoryTypes.CoverPage, Tuple{Symbol}, NamedTuple{(:coverpage,), Tuple{ProvocaWWWAppTier.TheoryTypes.CoverPage}}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:475
β”‚  [25] #html#30
β”‚     @ ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:574 [inlined]
β”‚  [26] html(resource::Symbol, action::Symbol; layout::Symbol, context::Module, status::Int64, headers::Dict{String, String}, vars::Base.Pairs{Symbol, ProvocaWWWAppTier.TheoryTypes.CoverPage, Tuple{Symbol}, NamedTuple{(:coverpage,), Tuple{ProvocaWWWAppTier.TheoryTypes.CoverPage}}})
β”‚     @ Genie.Renderer.Html ~/.julia/packages/Genie/3nusr/src/renderers/Html.jl:486
β”‚  [27] render_coverpage
β”‚     @ ~/active/ProvocaTeach/ProvocaWWW/ProvocaWWW_App_Tier/app/resources/pieces/PiecesController.jl:128 [inlined]
β”‚  [28] render_coverpage(slug::SubString{String}, db_uri::ProvocaWWWAppTier.FantasyLoader.SparqlEndpoint)
β”‚     @ ProvocaWWWAppTier.PiecesController ~/active/ProvocaTeach/ProvocaWWW/ProvocaWWW_App_Tier/app/resources/pieces/PiecesController.jl:138
β”‚  [29] (::ProvocaWWWAppTier.var"#9#10")()
β”‚     @ ProvocaWWWAppTier ~/active/ProvocaTeach/ProvocaWWW/ProvocaWWW_App_Tier/routes.jl:14
β”‚  [30] run_route(r::Genie.Router.Route)
β”‚     @ Genie.Router ~/.julia/packages/Genie/3nusr/src/Router.jl:527
β”‚  [31] route_request(req::HTTP.Messages.Request, res::HTTP.Messages.Response)
β”‚     @ Genie.Router ~/.julia/packages/Genie/3nusr/src/Router.jl:169
β”‚  [32] handle_request(req::HTTP.Messages.Request, res::HTTP.Messages.Response)
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:269
β”‚  [33] #29
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/macros.jl:123 [inlined]
β”‚  [34] run_work_thunk(thunk::Genie.Server.var"#29#30"{HTTP.Messages.Request, HTTP.Messages.Response}, print_error::Bool)
β”‚     @ Distributed ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/process_messages.jl:63
β”‚  [35] #remotecall_fetch#158
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:454 [inlined]
β”‚  [36] remotecall_fetch
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:454 [inlined]
β”‚  [37] #remotecall_fetch#162
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:496 [inlined]
β”‚  [38] remotecall_fetch
β”‚     @ ~/packages/julias/julia-1.7/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:496 [inlined]
β”‚  [39] setup_http_listener(req::HTTP.Messages.Request, res::HTTP.Messages.Response)
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:296
β”‚  [40] setup_http_listener
β”‚     @ ~/.julia/packages/Genie/3nusr/src/Server.jl:295 [inlined]
β”‚  [41] handle
β”‚     @ ~/.julia/packages/HTTP/aTjcj/src/Handlers.jl:254 [inlined]
β”‚  [42] handle(::HTTP.Handlers.RequestHandlerFunction{typeof(Genie.Server.setup_http_listener)}, ::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}})
β”‚     @ HTTP.Handlers ~/.julia/packages/HTTP/aTjcj/src/Handlers.jl:277
β”‚  [43] setup_http_streamer(http::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}})
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:285
β”‚  [44] (::Genie.Server.var"#7#14"{Int64})(http::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}})
β”‚     @ Genie.Server ~/.julia/packages/Genie/3nusr/src/Server.jl:117
β”‚  [45] macro expansion
β”‚     @ ~/.julia/packages/HTTP/aTjcj/src/Servers.jl:415 [inlined]
β”‚  [46] (::HTTP.Servers.var"#13#14"{Genie.Server.var"#7#14"{Int64}, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}, HTTP.Servers.Server{Nothing, Sockets.TCPServer}, HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Transaction{Sockets.TCPSocket}}})()
β”‚     @ HTTP.Servers ./task.jl:429

ProvocaTeach avatar Aug 01 '22 23:08 ProvocaTeach

Can you try Genie.Renderer.Html.register_normal_element("my__element") instead? The context matters and that's dependent on how you set up the context for the view rendering (that defines the scope where this function is registered). Basically, it needs to be in the same context as the rendering of the view.

essenciary avatar Aug 02 '22 12:08 essenciary

Same error. Only workaround I could find was using a JavaScript document.createElement() on page load. (Very bad for SEO due to the cumulative layout shift.)

Is there no way to specify the tag name in Genie? Because I don’t think it has any way of knowing that "my__element" (with an underscore) corresponds to <my-element> (with a hyphen).

ProvocaTeach avatar Jul 05 '23 00:07 ProvocaTeach

So have you tried it and hasn't worked?

Is there no way to specify the tag name in Genie? Because I don’t think it has any way of knowing that "my__element" (with an underscore) corresponds to (with a hyphen).

It's how it works by design and that's how you specify the tag name in Genie.

essenciary avatar Jul 05 '23 07:07 essenciary

You can see it here (and in many other places): https://github.com/GenieFramework/StippleUI.jl/blob/2135b6522d9f4c77c992c0507af3c034b0bbea99/src/FormInputs.jl#L8

essenciary avatar Jul 05 '23 07:07 essenciary