Repository: comonicon/Comonicon.jl
Pull request #205 (2022-06-18 20:49:04)

support `@lazyload` for lazy loading of script dependencies
supersedes #204 

codecov[bot]: # [Codecov](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#205](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (4e6150a) into [master](https://codecov.io/gh/comonicon/Comonicon.jl/commit/9f12779cfd3a0d68cd6b41855b64485bff4ff736?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (9f12779) will **decrease** coverage by `82.13%`.
> The diff coverage is `0.00%`.

```diff
@@            Coverage Diff             @@
##           master    #205       +/-   ##
==========================================
- Coverage   83.24%   1.10%   -82.14%     
==========================================
  Files          21      20        -1     
  Lines        1468    1445       -23     
==========================================
- Hits         1222      16     -1206     
- Misses        246    1429     +1183     
```


| [Impacted Files](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [src/frontend/cast.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2Zyb250ZW5kL2Nhc3Quamw=) | `0.00% <0.00%> (-81.43%)` | :arrow_down: |
| [src/compat.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2NvbXBhdC5qbA==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [src/exceptions.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2V4Y2VwdGlvbnMuamw=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [src/builder/app.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2J1aWxkZXIvYXBwLmps) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [src/frontend/markdown.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2Zyb250ZW5kL21hcmtkb3duLmps) | `0.00% <0.00%> (-94.12%)` | :arrow_down: |
| [src/builder/install.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2J1aWxkZXIvaW5zdGFsbC5qbA==) | `0.00% <0.00%> (-93.25%)` | :arrow_down: |
| [src/codegen/zsh.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2NvZGVnZW4venNoLmps) | `0.00% <0.00%> (-92.11%)` | :arrow_down: |
| [src/codegen/bash.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2NvZGVnZW4vYmFzaC5qbA==) | `0.00% <0.00%> (-90.48%)` | :arrow_down: |
| [src/ast/printing.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2FzdC9wcmludGluZy5qbA==) | `0.00% <0.00%> (-89.95%)` | :arrow_down: |
| [src/builder/cli.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2J1aWxkZXIvY2xpLmps) | `0.00% <0.00%> (-89.71%)` | :arrow_down: |
| ... and [11 more](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [9f12779...4e6150a](https://codecov.io/gh/comonicon/Comonicon.jl/pull/205?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/example/lazyload.jl b/example/lazyload.jl
new file mode 100644
index 0000000..1a50a46
--- /dev/null
+++ b/example/lazyload.jl
@@ -0,0 +1,30 @@
+using Comonicon
+
+@lazyload using Random @cast function random()
+    isdefined(Main, :Random) && println("Random is loaded")
+    isdefined(Main, :LinearAlgebra) && println("LinearAlgebra is loaded")
+end
+
+@lazyload using Random, LinearAlgebra @cast function both()
+    isdefined(Main, :Random) && println("Random is loaded")
+    isdefined(Main, :LinearAlgebra) && println("LinearAlgebra is loaded")
+end
+
+# this will throw an error
+@cast function none()
+    isdefined(Main, :Random) && println("Random is loaded")
+    isdefined(Main, :LinearAlgebra) && println("LinearAlgebra is loaded")
+end
+
+@main
+
+# You will see the following output if you run this script.
+#
+# shell> julia --project example/lazyload.jl random
+# Random is loaded
+
+# shell> julia --project example/lazyload.jl both
+# Random is loaded
+# LinearAlgebra is loaded
+
+# shell> julia --project example/lazyload.jl none
\ No newline at end of file
diff --git a/src/Comonicon.jl b/src/Comonicon.jl
index 539769e..be2c261 100644
--- a/src/Comonicon.jl
+++ b/src/Comonicon.jl
@@ -10,7 +10,7 @@ using Pkg
 using Markdown
 using ExproniconLite
 
-export @cast, @main, cmd_error, cmd_exit
+export @cast, @main, cmd_error, cmd_exit, @lazyload
 
 include("compat.jl")
 include("configs.jl")
diff --git a/src/frontend/cast.jl b/src/frontend/cast.jl
index 8ec9a3a..7204d65 100644
--- a/src/frontend/cast.jl
+++ b/src/frontend/cast.jl
@@ -652,3 +652,44 @@ function cast_options(doc::JLMD, options::Vector{JLOption}, line)
     end
     return cmd_options
 end
+
+"""
+    @lazyload expr @cast <valid castable expr>
+
+Evaluate `expr` if command `f` is called. This is useful
+for reducing the latency of scripts that has subcmds depend
+on plotting, serialization etc.
+
+Please see [`@cast`](@ref) for valid expression specifications.
+
+!!! warning
+
+    This macro only works at top-level command and thus can only be
+    used in `Main` and only works in scripts. Using this macro in any
+    other module or a project module will not work.
+"""
+macro lazyload(load_expr, casted_expr)
+    return esc(lazyload_m(__module__, __source__, load_expr, casted_expr))
+end
+
+function lazyload_m(m::Module, line, load_expr, casted_expr::Expr)
+    m === Main || throw(ArgumentError("`@lazyload` can only be used in `Main`, got $m"))
+    Meta.isexpr(casted_expr, :macrocall) && casted_expr.args[1] === Symbol("@cast") ||
+        throw(ArgumentError("`@lazyload` only accept `@cast`ed objects in `Main`"))
+
+    if is_function(casted_expr.args[3])
+        fname = JLFunction(casted_expr.args[3]).name
+        cmd_name = default_name(fname)
+    elseif Meta.isexpr(casted_expr.args[3], :module)
+        cmd_name = default_name(casted_expr.args[3].args[2])
+    end
+
+    return quote
+        if !isempty(ARGS) && ARGS[1] == $cmd_name
+            $load_expr
+        end
+
+        $line
+        Core.@__doc__ $casted_expr
+    end
+end


Test patch:
diff --git a/test/Project.toml b/test/Project.toml
index 7a3f939..5c57662 100644
--- a/test/Project.toml
+++ b/test/Project.toml
@@ -1,12 +1,13 @@
 [deps]
-FromFile = "ff7dd447-1dcb-4ce3-b8ac-22a812192de7"
-PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
 ComoniconTestUtils = "53b67e40-53a7-4335-973f-e3e901fe4865"
 ExproniconLite = "55351af7-c7e9-48d6-89ff-24e801d99491"
 Faker = "0efc519c-db33-5916-ab87-703215c3906f"
+FromFile = "ff7dd447-1dcb-4ce3-b8ac-22a812192de7"
+LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
+PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
 Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
 Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+Scratch = "6c6a2e73-6563-6170-7368-637461726353"
 SimpleMock = "a896ed2c-15a5-4479-b61d-a0e88e2a1d25"
 Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
-Scratch = "6c6a2e73-6563-6170-7368-637461726353"
diff --git a/test/examples.jl b/test/examples.jl
new file mode 100644
index 0000000..bc66418
--- /dev/null
+++ b/test/examples.jl
@@ -0,0 +1,20 @@
+using Test
+using Comonicon
+
+example_dir(xs...) = pkgdir(Comonicon, "example", xs...)
+
+function execute(name, args::String="")
+    path = example_dir("$name.jl")
+    cmd = Base.julia_cmd()
+    return run(`$cmd --project=$(Base.active_project()) $(path) $args`)
+end
+
+@testset "lazyload $args" for args in [
+        "",
+        "random",
+        "both",
+        "none",
+    ]
+    p = execute("lazyload", args)
+    @test p.exitcode == 0
+end
diff --git a/test/frontend/markdown.jl b/test/frontend/markdown.jl
index 612a9d2..fa49a46 100644
--- a/test/frontend/markdown.jl
+++ b/test/frontend/markdown.jl
@@ -235,3 +235,45 @@ end
 end
 
 end # TestMarkdown
+
+module TestLazyLoad
+
+using Test
+using Comonicon
+using ExproniconLite
+
+@testset "lazyload" begin
+    ex = @expr @cast function f()
+    end
+    generated = Comonicon.lazyload_m(Main, nothing, :(using Pkg), ex)
+
+    @test_expr generated == quote
+        if !(isempty(ARGS)) && ARGS[1] == "f"
+            using Pkg
+        end
+
+        Core.@__doc__ @cast function f()
+        end
+    end
+
+    ex = @expr @cast module nodecmd
+        using Comonicon
+        @cast function f()
+        end
+    end
+
+    generated = Comonicon.lazyload_m(Main, nothing, :(using Pkg), ex)
+    @test_expr generated == quote
+        if !(isempty(ARGS)) && ARGS[1] == "nodecmd"
+            using Pkg
+        end
+
+        Core.@__doc__ @cast module nodecmd
+            using Comonicon
+            @cast function f()
+            end
+        end
+    end
+end
+
+end
diff --git a/test/runtests.jl b/test/runtests.jl
index 0a01100..ab8491f 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -37,6 +37,10 @@ end
     include("scripts.jl")
 end
 
+@testset "example scripts" begin
+    include("examples.jl")
+end
+
 # the script workaround doesn't work on MacOS
 # https://github.com/actions/runner/issues/241
 # @testset "tools" begin