From ac6f2c02a9fa881ba296464bdc12af8a71d47080 Mon Sep 17 00:00:00 2001
From: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com>
Date: Thu, 15 Feb 2024 07:56:29 +0100
Subject: [PATCH] Use `trixi_include` from TrixiBase.jl (#88)

* use trixi_include from TrixiBase.jl

* generate API for TrixiBase.jl in docs

* format

* Bump version of TrixiBase.jl

Co-authored-by: Hendrik Ranocha <ranocha@users.noreply.github.com>

* bump TrixiBase to v0.1.1 in docs

---------

Co-authored-by: Hendrik Ranocha <ranocha@users.noreply.github.com>
---
 .github/workflows/Format-check.yml |   2 +-
 Project.toml                       |   2 +
 docs/Project.toml                  |   2 +
 docs/make.jl                       |  11 ++-
 docs/src/ref-trixibase.md          |   9 ++
 src/DispersiveShallowWater.jl      |   3 +-
 src/util.jl                        | 133 +----------------------------
 test/test_unit.jl                  |   1 -
 test/test_util.jl                  |  44 +++++++++-
 9 files changed, 68 insertions(+), 139 deletions(-)
 create mode 100644 docs/src/ref-trixibase.md

diff --git a/.github/workflows/Format-check.yml b/.github/workflows/Format-check.yml
index 71c8bd06..cc9ac9f5 100644
--- a/.github/workflows/Format-check.yml
+++ b/.github/workflows/Format-check.yml
@@ -25,7 +25,7 @@ jobs:
       - name: Install JuliaFormatter and format
         run: |
           julia -e 'using Pkg; Pkg.add(PackageSpec(name = "JuliaFormatter", version="1.0.45"))'
-          julia -e 'using JuliaFormatter; format(["src", "test", "examples"], verbose = true)'
+          julia -e 'using JuliaFormatter; format(["src", "test", "examples", "docs"], verbose = true)'
       - name: Format check
         run: |
           julia -e '
diff --git a/Project.toml b/Project.toml
index 3a06c452..1a80c008 100644
--- a/Project.toml
+++ b/Project.toml
@@ -18,6 +18,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
 StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
 SummationByPartsOperators = "9f78cca6-572e-554e-b819-917d2f1cf240"
 TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
+TrixiBase = "9a0f1c46-06d5-4909-a5a3-ce25d3fa3284"
 
 [compat]
 DiffEqBase = "6.128"
@@ -34,4 +35,5 @@ SparseArrays = "1"
 StaticArrays = "1"
 SummationByPartsOperators = "0.5.41"
 TimerOutputs = "0.5.7"
+TrixiBase = "0.1.1"
 julia = "1.9"
diff --git a/docs/Project.toml b/docs/Project.toml
index b7023255..a7b1e702 100644
--- a/docs/Project.toml
+++ b/docs/Project.toml
@@ -3,9 +3,11 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
 OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
 Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
 SummationByPartsOperators = "9f78cca6-572e-554e-b819-917d2f1cf240"
+TrixiBase = "9a0f1c46-06d5-4909-a5a3-ce25d3fa3284"
 
 [compat]
 Documenter = "1"
 OrdinaryDiffEq = "6.49.1"
 Plots = "1.9"
 SummationByPartsOperators = "0.5.41"
+TrixiBase = "0.1.1"
diff --git a/docs/make.jl b/docs/make.jl
index fa2b03c9..282eb04b 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -1,12 +1,14 @@
-using DispersiveShallowWater
 using Documenter
+using DispersiveShallowWater
+using TrixiBase
 
 # Define module-wide setups such that the respective modules are available in doctests
 DocMeta.setdocmeta!(DispersiveShallowWater, :DocTestSetup, :(using DispersiveShallowWater);
                     recursive = true)
+DocMeta.setdocmeta!(TrixiBase, :DocTestSetup, :(using TrixiBase); recursive = true)
 
 makedocs(;
-         modules = [DispersiveShallowWater],
+         modules = [DispersiveShallowWater, TrixiBase],
          authors = "Joshua Lampert <joshua.lampert@uni-hamburg.de>",
          repo = Remotes.GitHub("JoshuaLampert", "DispersiveShallowWater.jl"),
          sitename = "DispersiveShallowWater.jl",
@@ -18,7 +20,10 @@ makedocs(;
          pages = [
              "Home" => "index.md",
              "Overview" => "overview.md",
-             "Reference" => "ref.md",
+             "Reference" => [
+                 "TrixiBase" => "ref-trixibase.md",
+                 "DispersiveShallowWater" => "ref.md",
+             ],
              "License" => "license.md",
          ])
 
diff --git a/docs/src/ref-trixibase.md b/docs/src/ref-trixibase.md
new file mode 100644
index 00000000..c7a970f8
--- /dev/null
+++ b/docs/src/ref-trixibase.md
@@ -0,0 +1,9 @@
+# TrixiBase.jl API
+
+```@meta
+CurrentModule = TrixiBase
+```
+
+```@autodocs
+Modules = [TrixiBase]
+```
diff --git a/src/DispersiveShallowWater.jl b/src/DispersiveShallowWater.jl
index 1cd8e0cd..cff382c0 100644
--- a/src/DispersiveShallowWater.jl
+++ b/src/DispersiveShallowWater.jl
@@ -22,6 +22,7 @@ using SummationByPartsOperators: AbstractDerivativeOperator,
                                  derivative_order, integrate
 import SummationByPartsOperators: grid, xmin, xmax
 using TimerOutputs: TimerOutputs, TimerOutput, @timeit, print_timer, reset_timer!
+@reexport using TrixiBase: TrixiBase, trixi_include
 
 include("boundary_conditions.jl")
 include("mesh.jl")
@@ -32,7 +33,7 @@ include("callbacks_step/callbacks_step.jl")
 include("visualization.jl")
 include("util.jl")
 
-export examples_dir, get_examples, default_example, trixi_include, convergence_test
+export examples_dir, get_examples, default_example, convergence_test
 
 export BBMBBMEquations1D, BBMBBMVariableEquations1D, SvärdKalischEquations1D,
        SvaerdKalischEquations1D
diff --git a/src/util.jl b/src/util.jl
index 46014d53..52adb42a 100644
--- a/src/util.jl
+++ b/src/util.jl
@@ -48,53 +48,6 @@ function default_example()
              "bbm_bbm_variable_bathymetry_1d_basic.jl")
 end
 
-# Note: We can't call the method below `DispersiveShallowWater.include` since that is created automatically
-# inside `module DispersiveShallowWater` to `include` source files and evaluate them within the global scope
-# of `DispersiveShallowWater`. However, users will want to evaluate in the global scope of `Main` or something
-# similar to manage dependencies on their own.
-"""
-    trixi_include([mod::Module=Main,] example::AbstractString; kwargs...)
-
-`include` the file `example` and evaluate its content in the global scope of module `mod`.
-You can override specific assignments in `example` by supplying keyword arguments.
-It's basic purpose is to make it easier to modify some parameters while running DispersiveShallowWater from the
-REPL. Additionally, this is used in tests to reduce the computational burden for CI while still
-providing examples with sensible default values for users.
-
-Before replacing assignments in `example`, the keyword argument `maxiters` is inserted
-into calls to `solve` and `DispersiveShallowWater.solve` with it's default value used in the SciML ecosystem
-for ODEs, see the "Miscellaneous" section of the
-[documentation](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/).
-
-Copied from [Trixi.jl](https://github.com/trixi-framework/Trixi.jl).
-
-# Examples
-
-```jldoctest
-julia> redirect_stdout(devnull) do
-           trixi_include(@__MODULE__, joinpath(examples_dir(), "bbm_bbm_1d", "bbm_bbm_1d_basic.jl"),
-                         tspan=(0.0, 0.1))
-           sol.t[end]
-       end
-0.1
-```
-"""
-function trixi_include(mod::Module, example::AbstractString; kwargs...)
-    # Check that all kwargs exist as assignments
-    code = read(example, String)
-    expr = Meta.parse("begin \n$code \nend")
-    expr = insert_maxiters(expr)
-
-    for (key, val) in kwargs
-        # This will throw an error when `key` is not found
-        find_assignment(expr, key)
-    end
-
-    Base.include(ex -> replace_assignments(insert_maxiters(ex); kwargs...), mod, example)
-end
-
-trixi_include(example::AbstractString; kwargs...) = trixi_include(Main, example; kwargs...)
-
 """
     convergence_test([mod::Module=Main,] example::AbstractString, iterations; kwargs...)
 
@@ -214,90 +167,6 @@ function convergence_test(example::AbstractString, iterations; kwargs...)
     convergence_test(Main, example::AbstractString, iterations; kwargs...)
 end
 
-# Helper methods used in the functions defined above, also copied from Trixi.jl
-
-# Apply the function `f` to `expr` and all sub-expressions recursively.
-walkexpr(f, expr::Expr) = f(Expr(expr.head, (walkexpr(f, arg) for arg in expr.args)...))
-walkexpr(f, x) = f(x)
-
-# Insert the keyword argument `maxiters` into calls to `solve` and `DispersiveShallowWater.solve`
-# with default value `10^5` if it is not already present.
-function insert_maxiters(expr)
-    maxiters_default = 10^5
-
-    expr = walkexpr(expr) do x
-        if x isa Expr
-            is_plain_solve = x.head === Symbol("call") && x.args[1] === Symbol("solve")
-            is_trixi_solve = (x.head === Symbol("call") && x.args[1] isa Expr &&
-                              x.args[1].head === Symbol(".") &&
-                              x.args[1].args[1] === Symbol("DispersiveShallowWater") &&
-                              x.args[1].args[2] isa QuoteNode &&
-                              x.args[1].args[2].value === Symbol("solve"))
-
-            if is_plain_solve || is_trixi_solve
-                # Do nothing if `maxiters` is already set as keyword argument...
-                for arg in x.args
-                    if arg isa Expr && arg.head === Symbol("kw") &&
-                       arg.args[1] === Symbol("maxiters")
-                        return x
-                    end
-                end
-
-                # ...and insert it otherwise.
-                push!(x.args, Expr(Symbol("kw"), Symbol("maxiters"), maxiters_default))
-            end
-        end
-        return x
-    end
-
-    return expr
-end
-
-# Replace assignments to `key` in `expr` by `key = val` for all `(key,val)` in `kwargs`.
-function replace_assignments(expr; kwargs...)
-    # replace explicit and keyword assignments
-    expr = walkexpr(expr) do x
-        if x isa Expr
-            for (key, val) in kwargs
-                if (x.head === Symbol("=") || x.head === :kw) && x.args[1] === Symbol(key)
-                    x.args[2] = :($val)
-                    # dump(x)
-                end
-            end
-        end
-        return x
-    end
-
-    return expr
-end
-
-# find a (keyword or common) assignment to `destination` in `expr`
-# and return the assigned value
-function find_assignment(expr, destination)
-    # declare result to be able to assign to it in the closure
-    local result
-    found = false
-
-    # find explicit and keyword assignments
-    walkexpr(expr) do x
-        if x isa Expr
-            if (x.head === Symbol("=") || x.head === :kw) &&
-               x.args[1] === Symbol(destination)
-                result = x.args[2]
-                found = true
-                # dump(x)
-            end
-        end
-        return x
-    end
-
-    if !found
-        throw(ArgumentError("assignment `$destination` not found in expression"))
-    end
-
-    result
-end
-
 function extract_initial_N(example, kwargs)
     code = read(example, String)
     expr = Meta.parse("begin \n$code \nend")
@@ -306,7 +175,7 @@ function extract_initial_N(example, kwargs)
         return kwargs[:N]
     else
         # get N from the example
-        N = find_assignment(expr, :N)
+        N = TrixiBase.find_assignment(expr, :N)
         return N
     end
 end
diff --git a/test/test_unit.jl b/test/test_unit.jl
index d00a7228..17130564 100644
--- a/test/test_unit.jl
+++ b/test/test_unit.jl
@@ -222,7 +222,6 @@ using SparseArrays: sparse, SparseMatrixCSC
 
     @testset "util" begin
         @test_nowarn get_examples()
-        @test_nowarn trixi_include(default_example(), tspan = (0.0, 0.1))
 
         accuracy_orders = [2, 4, 6]
         for accuracy_order in accuracy_orders
diff --git a/test/test_util.jl b/test/test_util.jl
index 81027d90..fc40501a 100644
--- a/test/test_util.jl
+++ b/test/test_util.jl
@@ -52,7 +52,7 @@ macro test_trixi_include(example, args...)
         println($example)
 
         # evaluate examples in the scope of the module they're called from
-        @test_nowarn trixi_include(@__MODULE__, $example; $kwargs...)
+        @test_nowarn_mod trixi_include(@__MODULE__, $example; $kwargs...)
 
         # if present, compare l2, linf and conservation errors against reference values
         if !isnothing($l2) || !isnothing($linf) || !isnothing($cons_error)
@@ -189,3 +189,45 @@ macro trixi_testset(name, expr)
         nothing
     end
 end
+
+# Copied from TrixiBase.jl. See https://github.com/trixi-framework/TrixiBase.jl/issues/9.
+"""
+    @test_nowarn_mod expr
+Modified version of `@test_nowarn expr` that prints the content of `stderr` when
+it is not empty and ignores some common info statements printed in DispersiveShallowWater.jl
+uses.
+"""
+macro test_nowarn_mod(expr, additional_ignore_content = String[])
+    quote
+        let fname = tempname()
+            try
+                ret = open(fname, "w") do f
+                    redirect_stderr(f) do
+                        $(esc(expr))
+                    end
+                end
+                stderr_content = read(fname, String)
+                if !isempty(stderr_content)
+                    println("Content of `stderr`:\n", stderr_content)
+                end
+
+                # Patterns matching the following ones will be ignored. Additional patterns
+                # passed as arguments can also be regular expressions, so we just use the
+                # type `Any` for `ignore_content`.
+                ignore_content = Any["[ Info: You just called `trixi_include`. Julia may now compile the code, please be patient.\n"]
+                append!(ignore_content, $additional_ignore_content)
+                for pattern in ignore_content
+                    stderr_content = replace(stderr_content, pattern => "")
+                end
+
+                # We also ignore simple module redefinitions for convenience. Thus, we
+                # check whether every line of `stderr_content` is of the form of a
+                # module replacement warning.
+                @test occursin(r"^(WARNING: replacing module .+\.\n)*$", stderr_content)
+                ret
+            finally
+                rm(fname, force = true)
+            end
+        end
+    end
+end