Repository: comonicon/Comonicon.jl
Pull request #167 (2022-02-02 08:56:13)

allow auto displaysize
this change would allow one tweaking the displaysize at runtime, but at cost that the generated CLI is no longer standalone.

This has a huge impact on latency, so might not be a good idea to merge at the moment unless we figure out how to precompile the printing methods correctly

for Ion's help info, the time to first help info printing, before

```
0.000043 seconds (3 allocations: 112 bytes)
```

after

```
0.555527 seconds (1.50 M allocations: 80.512 MiB, 4.35% gc time, 99.91% compilation time)
```

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

[![Impacted file tree graph](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/graphs/tree.svg?width=650&height=150&src=pr&token=zZjCxCiFTY&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@            Coverage Diff             @@
##           master    #167       +/-   ##
==========================================
- Coverage   80.93%   1.89%   -79.05%     
==========================================
  Files          19      18        -1     
  Lines        1238    1214       -24     
==========================================
- Hits         1002      23      -979     
- Misses        236    1191      +955     
```


| [Impacted Files](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [src/codegen/julia.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2NvZGVnZW4vanVsaWEuamw=) | `0.00% <0.00%> (-88.29%)` | :arrow_down: |
| [src/exceptions.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/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/167/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/167/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%> (-93.34%)` | :arrow_down: |
| [src/codegen/zsh.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/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%> (-91.08%)` | :arrow_down: |
| [src/builder/install.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/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%> (-89.86%)` | :arrow_down: |
| [src/builder/cli.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/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: |
| [src/frontend/utils.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2Zyb250ZW5kL3V0aWxzLmps) | `0.00% <0.00%> (-86.96%)` | :arrow_down: |
| [src/builder/sysimg.jl](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2J1aWxkZXIvc3lzaW1nLmps) | `0.00% <0.00%> (-83.08%)` | :arrow_down: |
| ... and [8 more](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167/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/167?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/167?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [e7955d5...9cefc83](https://codecov.io/gh/comonicon/Comonicon.jl/pull/167?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/Project.toml b/Project.toml
index cf63224..3aa9646 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
 name = "Comonicon"
 uuid = "863f3e99-da2a-4334-8734-de3dacbe5542"
 authors = ["Roger-luo and contributors"]
-version = "0.12.4"
+version = "0.12.5"
 
 [deps]
 Configurations = "5218b696-f38b-4ac9-8b61-a12ec717816d"
diff --git a/src/Comonicon.jl b/src/Comonicon.jl
index 9224bc1..bd2bcac 100644
--- a/src/Comonicon.jl
+++ b/src/Comonicon.jl
@@ -13,7 +13,7 @@ using ExproniconLite
 export @cast, @main, cmd_error, cmd_exit
 
 include("compat.jl")
-include("options.jl")
+include("configs.jl")
 include("exceptions.jl")
 include("ast/ast.jl")
 include("codegen/julia.jl")
diff --git a/src/ast/printing.jl b/src/ast/printing.jl
index 3f3b03f..4d891b1 100644
--- a/src/ast/printing.jl
+++ b/src/ast/printing.jl
@@ -24,7 +24,7 @@ Base.@kwdef mutable struct Color
 end
 
 Base.@kwdef mutable struct Terminal
-    width::Int = displaysize(stdout)[2]
+    width::Int = max(displaysize(stdout)[2], 80) # always have a minimum size
     left::Int = floor(Int, 0.4 * width) # left column max width
     right::Int = floor(Int, 0.4 * width) # right column max width
 
diff --git a/src/ast/types.jl b/src/ast/types.jl
index 09baa1f..49ee8f8 100644
--- a/src/ast/types.jl
+++ b/src/ast/types.jl
@@ -3,18 +3,14 @@ const Maybe{T} = Union{Nothing,T}
 abstract type ComoniconExpr end
 
 Base.@kwdef struct Description <: ComoniconExpr
-    brief::Union{Nothing,String} = nothing
-    content::Union{Nothing,String} = nothing
+    brief::String = ""
+    content::String = ""
 end
 
+Base.convert(::Type{Description}, s::AbstractString) = Description(String(s))
 Base.convert(::Type{Description}, ::Nothing) = Description()
-Base.convert(::Type{Description}, x::String) = Description(x)
-Base.convert(::Type{Description}, x::AbstractString) = Description(String(x))
-
-Description(::Nothing) = Description(nothing, nothing)
-function Description(text::String)
-    return Description(brief(text), text)
-end
+Description(text::String) = Description(text, "")
+Description(::Nothing) = Description()
 
 Base.@kwdef struct Argument <: ComoniconExpr
     name::String
diff --git a/src/ast/utils.jl b/src/ast/utils.jl
index b57ca8b..0785d82 100644
--- a/src/ast/utils.jl
+++ b/src/ast/utils.jl
@@ -61,21 +61,3 @@ function splitlines(s, width = 80)
     isempty(current_line) || push!(lines, strip(join(current_line)))
     return lines
 end
-
-"""
-    brief(text::String)
-
-Use the first sentence as the brief description.
-"""
-function brief(text::String)
-    index = findfirst(". ", text)
-    if index === nothing
-        index = findfirst(".", text)
-    end
-
-    if index === nothing
-        return text
-    else
-        return text[1:first(index)]
-    end
-end
diff --git a/src/builder/app.jl b/src/builder/app.jl
index 645d009..a0d8590 100644
--- a/src/builder/app.jl
+++ b/src/builder/app.jl
@@ -29,7 +29,7 @@ function build_application(m, options)
     return
 end
 
-function build_application_completion(m::Module, options::Options.Comonicon)
+function build_application_completion(m::Module, options::Configs.Comonicon)
     isempty(options.application.shell_completions) && return
     build_dir = application_build_dir(m, options)
     completion_dir = joinpath(build_dir, "completions")
@@ -45,7 +45,7 @@ function build_application_completion(m::Module, options::Options.Comonicon)
     return
 end
 
-function bundle_assets(m::Module, options::Options.Comonicon)
+function bundle_assets(m::Module, options::Configs.Comonicon)
     isempty(options.application.assets) && return
     build_dir = application_build_dir(m, options)
     share_dir = joinpath(build_dir, "share")
@@ -57,7 +57,7 @@ function bundle_assets(m::Module, options::Options.Comonicon)
             dst = joinpath(build_dir, "share", asset.package, asset.path)
         end
         ispath(dirname(dst)) || mkpath(dirname(dst))
-        cp(Options.get_path(m, asset), dst; force = true, follow_symlinks = true)
+        cp(Configs.get_path(m, asset), dst; force = true, follow_symlinks = true)
     end
     return
 end
diff --git a/src/builder/builder.jl b/src/builder/builder.jl
index 3f794c3..f34a390 100644
--- a/src/builder/builder.jl
+++ b/src/builder/builder.jl
@@ -1,6 +1,6 @@
 module Builder
 
-using ..Options: Options
+using ..Configs: Configs
 using ..ZSHCompletions
 using ..Comonicon: get_version, Tools
 using PackageCompiler
diff --git a/src/builder/cli.jl b/src/builder/cli.jl
index cf0863e..d9999b2 100644
--- a/src/builder/cli.jl
+++ b/src/builder/cli.jl
@@ -45,11 +45,11 @@ function print_builder_help(io::IO = stdout)
 end
 
 function command_main(m::Module; kw...)::Cint
-    options = Options.read_options(m; kw...)
+    options = Configs.read_options(m; kw...)
     return command_main(m, options)
 end
 
-function command_main(m::Module, options::Options.Comonicon)
+function command_main(m::Module, options::Configs.Comonicon)
     if "-h" in ARGS || "--help" in ARGS || "help" in ARGS
         print_builder_help()
         return 0
diff --git a/src/builder/install.jl b/src/builder/install.jl
index 33dc08b..1df38b5 100644
--- a/src/builder/install.jl
+++ b/src/builder/install.jl
@@ -1,9 +1,9 @@
 function install(m::Module; kw...)
-    options = Options.read_options(m; kw...)
+    options = Configs.read_options(m; kw...)
     return install(m, options)
 end
 
-function install(m::Module, options::Options.Comonicon)
+function install(m::Module, options::Configs.Comonicon)
     # prepare env (only scratch space is mutable)
     install_project_env(m, options)
     # prepare .julia/bin/<command>
@@ -20,7 +20,7 @@ function install(m::Module, options::Options.Comonicon)
     return
 end
 
-function install_project_env(m::Module, options::Options.Comonicon)
+function install_project_env(m::Module, options::Configs.Comonicon)
     scratch_env_dir = get_scratch!(m, "env")
     # rm old environment
     ispath(scratch_env_dir) && rm(scratch_env_dir; force = true, recursive = true)
@@ -28,7 +28,7 @@ function install_project_env(m::Module, options::Options.Comonicon)
     return
 end
 
-function install_entryfile(m::Module, options::Options.Comonicon)
+function install_entryfile(m::Module, options::Configs.Comonicon)
     bin = install_path(options, "bin")
     # generate entry file at .juila/bin
     entryfile = joinpath(bin, options.name)
@@ -39,11 +39,11 @@ function install_entryfile(m::Module, options::Options.Comonicon)
     return
 end
 
-function install_path(options::Options.Comonicon, paths::String...)
+function install_path(options::Configs.Comonicon, paths::String...)
     ensure_path(expanduser(joinpath(options.install.path, paths...)))
 end
 
-function install_sysimg(m::Module, options::Options.Comonicon)
+function install_sysimg(m::Module, options::Configs.Comonicon)
     isnothing(options.sysimg) && return
     sysimg = sysimg_dylib(m, options)
 
@@ -61,7 +61,7 @@ function install_sysimg(m::Module, options::Options.Comonicon)
     return
 end
 
-function install_completion(m::Module, options::Options.Comonicon, shell::String)
+function install_completion(m::Module, options::Configs.Comonicon, shell::String)
     completions_dir = install_path(options, "completions")
     completion_file = joinpath(completions_dir, "_" * options.name)
     shell in ["zsh"] || return # only emit supported shell
@@ -71,7 +71,7 @@ function install_completion(m::Module, options::Options.Comonicon, shell::String
     return
 end
 
-function completion_script(m::Module, options::Options.Comonicon, shell::String)
+function completion_script(m::Module, options::Configs.Comonicon, shell::String)
     isdefined(m, :CASTED_COMMANDS) || error("cannot find Comonicon CLI entry")
     haskey(m.CASTED_COMMANDS, "main") || error("cannot find Comonicon CLI entry")
     main = m.CASTED_COMMANDS["main"]
@@ -86,7 +86,7 @@ function completion_script(m::Module, options::Options.Comonicon, shell::String)
     end
 end
 
-function entryfile_script(m::Module, options::Options.Comonicon)
+function entryfile_script(m::Module, options::Configs.Comonicon)
     cmds = String[]
 
     julia_exe = joinpath(Sys.BINDIR, Base.julia_exename())
diff --git a/src/builder/rcfile.jl b/src/builder/rcfile.jl
index 0f64622..2d71c98 100644
--- a/src/builder/rcfile.jl
+++ b/src/builder/rcfile.jl
@@ -1,6 +1,6 @@
 function install_env_path(
     m::Module,
-    options::Options.Comonicon = Options.read_options(m);
+    options::Configs.Comonicon = Configs.read_options(m);
     shell::String = basename(ENV["SHELL"]),
     home_dir = homedir(),
     env = ENV,
diff --git a/src/builder/sysimg.jl b/src/builder/sysimg.jl
index 3b0d650..b75b7e1 100644
--- a/src/builder/sysimg.jl
+++ b/src/builder/sysimg.jl
@@ -1,6 +1,6 @@
 function build_sysimg(
     m::Module,
-    options::Options.Comonicon;
+    options::Configs.Comonicon;
     incremental = options.sysimg.incremental,
     cpu_target = options.sysimg.cpu_target,
     filter_stdlibs = options.sysimg.filter_stdlibs,
@@ -95,7 +95,7 @@ function create_command_env(m::Module, envpath::String = mktempdir(); test_deps:
     return envpath
 end
 
-function build_sysimg_tarball(m::Module, options::Options.Comonicon)
+function build_sysimg_tarball(m::Module, options::Configs.Comonicon)
     dylib = sysimg_dylib(m, options)
     tarball = tarball_name(m, options.name, "sysimg")
 
@@ -109,7 +109,7 @@ function build_sysimg_tarball(m::Module, options::Options.Comonicon)
     return
 end
 
-function download_sysimg(m::Module, options::Options.Comonicon)
+function download_sysimg(m::Module, options::Configs.Comonicon)
     url = sysimg_url(m, options)
     isnothing(url) && error("$m does not have a host")
     tarball = download(url)
@@ -120,7 +120,7 @@ function download_sysimg(m::Module, options::Options.Comonicon)
     return
 end
 
-function sysimg_url(m::Module, options::Options.Comonicon)
+function sysimg_url(m::Module, options::Configs.Comonicon)
     isnothing(options.download) && return
     host = options.download.host
     if host == "github.com"
@@ -139,7 +139,7 @@ function sysimg_url(m::Module, options::Options.Comonicon)
     return url
 end
 
-function sysimg_dylib(m::Module, options::Options.Comonicon)
+function sysimg_dylib(m::Module, options::Configs.Comonicon)
     dylib_name = "lib$(options.name).$(Libdl.dlext)"
     sysimg_dir = get_scratch!(m, "sysimg")
     return joinpath(sysimg_dir, dylib_name)
diff --git a/src/codegen/julia.jl b/src/codegen/julia.jl
index 5120777..6fb8cdc 100644
--- a/src/codegen/julia.jl
+++ b/src/codegen/julia.jl
@@ -1,32 +1,46 @@
 module JuliaExpr
 
 using ..AST
-using ..Options
 using ..Comonicon: CommandException, CommandExit
 using ExproniconLite
 
 help_str(x; color = true) = sprint(print_cmd, x; context = :color => color)
+
+Base.@kwdef struct Configs
+    color::Bool = true
+    static::Bool = true
+end
+
+function print_help_str(x, configs::Configs)
+    color = configs.color
+    if configs.static
+        :(print($(help_str(x; color))))
+    else
+        :($print_cmd(IOContext(stdout, :color=>$color), $x))
+    end
+end
+
 # printing in expression
-emit_help(x, ptr::Int = 1; color = true) = quote
+emit_help(x, configs::Configs, ptr::Int = 1; color = true) = quote
     if !isnothing(findnext(isequal("-h"), ARGS, $ptr)) ||
        !isnothing(findnext(isequal("--help"), ARGS, $ptr))
-        print($(help_str(x; color = color)))
+       $(print_help_str(x, configs))
         return 0
     end
 end
 
-function emit_error(cmd, msg::String; color::Bool = true)
+function emit_error(cmd, configs::Configs, msg::String; color::Bool = true)
     msg = "Error: $msg, use -h or --help to check more detailed help info"
     return quote
         printstyled($msg; color = :red, bold = true)
         println()
-        print($(help_str(cmd; color = color)))
+        $(print_help_str(cmd, configs))
         println()
         return 1
     end
 end
 
-function emit_error(cmd, msg::Expr; color::Bool = true)
+function emit_error(cmd, configs::Configs, msg::Expr; color::Bool = true)
     msg.head === :string || throw(Meta.ParseError("expect string expression, got $msg"))
 
     ex = Expr(:string, "Error: ")
@@ -41,7 +55,7 @@ function emit_error(cmd, msg::Expr; color::Bool = true)
     return quote
         printstyled($msg; color = :red, bold = true)
         println()
-        print($(help_str(cmd; color = color)))
+        $(print_help_str(cmd, configs))
         println()
         return 1
     end
@@ -52,13 +66,13 @@ end
 
 Emit `Expr` from a `Entry`.
 """
-function emit(cmd::Entry, ptr::Int = 1)
+function emit(cmd::Entry, configs::Configs=Configs(), ptr::Int = 1)
     jlfn = JLFunction(;
         name = :command_main,
         args = [Expr(:kw, :(ARGS::Vector{String}), :ARGS)],
         body = quote
             $(emit_scan_version(cmd))
-            $(emit_body(cmd.root, ptr))
+            $(emit_body(cmd.root, configs, ptr))
         end,
     )
     return codegen_ast(jlfn)
@@ -73,11 +87,11 @@ function emit_scan_version(cmd::Entry)
     end
 end
 
-function emit_body(cmd::NodeCommand, ptr::Int = 1)
+function emit_body(cmd::NodeCommand, configs::Configs, ptr::Int = 1)
     nargs_assert = quote
         if length(ARGS) < $ptr
             $(emit_error(
-                cmd,
+                cmd, configs,
                 "valid sub-commands for command $(cmd.name) are: $(join(keys(cmd.subcmds), ", "))",
             ))
         end
@@ -85,12 +99,12 @@ function emit_body(cmd::NodeCommand, ptr::Int = 1)
 
     jl = JLIfElse()
     for (name, subcmd) in cmd.subcmds
-        jl[:(ARGS[$ptr] == $name)] = emit_body(subcmd, ptr + 1)
+        jl[:(ARGS[$ptr] == $name)] = emit_body(subcmd, configs, ptr + 1)
     end
-    jl.otherwise = emit_error(cmd, :("Error: unknown command $(ARGS[$ptr])"))
+    jl.otherwise = emit_error(cmd, configs, :("Error: unknown command $(ARGS[$ptr])"))
     return quote
         if length(ARGS) == $ptr && (ARGS[$(ptr)] == "-h" || ARGS[$(ptr)] == "--help")
-            print($(help_str(cmd)))
+            $(print_help_str(cmd, configs))
             return 0
         end
 
@@ -99,21 +113,21 @@ function emit_body(cmd::NodeCommand, ptr::Int = 1)
     end
 end
 
-function emit_body(cmd::LeafCommand, ptr::Int = 1)
+function emit_body(cmd::LeafCommand, configs::Configs, ptr::Int = 1)
     @gensym idx
     quote
-        $(emit_help(cmd, ptr))
+        $(emit_help(cmd, configs, ptr))
 
         $idx = findnext(isequal("--"), ARGS, $ptr)
         if isnothing($idx) # no dash
-            $(emit_norm_body(cmd, ptr))
+            $(emit_norm_body(cmd, configs, ptr))
         else # dash
-            $(emit_dash_body(cmd, idx, ptr))
+            $(emit_dash_body(cmd, configs, idx, ptr))
         end
     end
 end
 
-function emit_dash_body(cmd::LeafCommand, idx::Symbol, ptr::Int = 1)
+function emit_dash_body(cmd::LeafCommand, configs::Configs, idx::Symbol, ptr::Int = 1)
     @gensym token token_ptr args kwargs
 
     quote # parse option/flag
@@ -122,19 +136,19 @@ function emit_dash_body(cmd::LeafCommand, idx::Symbol, ptr::Int = 1)
         while $token_ptr ≤ $idx - 1
             $token = ARGS[$token_ptr]
             if startswith($token, "-")
-                $(emit_kwarg(cmd, token, kwargs, token_ptr))
+                $(emit_kwarg(cmd, configs, token, kwargs, token_ptr))
             else
-                $(emit_error(cmd, :("unknown command: $($token)")))
+                $(emit_error(cmd, configs, :("unknown command: $($token)")))
             end
             $token_ptr += 1
         end
 
         $args = ARGS[$idx+1:end]
-        $(emit_leaf_call(cmd, args, kwargs))
+        $(emit_leaf_call(cmd, configs, args, kwargs))
     end
 end
 
-function emit_norm_body(cmd::LeafCommand, ptr::Int = 1)
+function emit_norm_body(cmd::LeafCommand, configs::Configs, ptr::Int = 1)
     @gensym token_ptr args kwargs token
 
     quote
@@ -145,18 +159,18 @@ function emit_norm_body(cmd::LeafCommand, ptr::Int = 1)
         while $token_ptr ≤ length(ARGS)
             $token = ARGS[$token_ptr]
             if startswith($token, "-")
-                $(emit_kwarg(cmd, token, kwargs, token_ptr))
+                $(emit_kwarg(cmd, configs, token, kwargs, token_ptr))
             else # argument
                 push!($args, $token)
             end
             $token_ptr += 1
         end
 
-        $(emit_leaf_call(cmd, args, kwargs))
+        $(emit_leaf_call(cmd, configs, args, kwargs))
     end
 end
 
-function emit_leaf_call(cmd::LeafCommand, args::Symbol, kwargs::Symbol)
+function emit_leaf_call(cmd::LeafCommand, configs::Configs, args::Symbol, kwargs::Symbol)
     @gensym nargs
     ret = quote
         $nargs = length($args)
@@ -165,7 +179,7 @@ function emit_leaf_call(cmd::LeafCommand, args::Symbol, kwargs::Symbol)
     if cmd.nrequire > 0
         push!(ret.args, quote
             if $(cmd.nrequire) > $nargs
-                $(emit_error(cmd, "expect $(cmd.nrequire) positional arguments"))
+                $(emit_error(cmd, configs, "expect $(cmd.nrequire) positional arguments"))
             end
         end)
     end
@@ -176,7 +190,7 @@ function emit_leaf_call(cmd::LeafCommand, args::Symbol, kwargs::Symbol)
             ret.args,
             quote
                 if $(length(cmd.args)) < $nargs
-                    $(emit_error(cmd, "expect at most $(length(cmd.args)) positional arguments"))
+                    $(emit_error(cmd, configs, "expect at most $(length(cmd.args)) positional arguments"))
                 end
             end,
         )
@@ -189,27 +203,27 @@ function emit_leaf_call(cmd::LeafCommand, args::Symbol, kwargs::Symbol)
 
     for (i, arg) in enumerate(cmd.args)
         arg.require || break
-        push!(call.args, emit_parse_value(cmd, arg.type, :($args[$i])))
+        push!(call.args, emit_parse_value(cmd, configs, arg.type, :($args[$i])))
     end
 
     ifelse = JLIfElse()
     ifelse[:($nargs == $(cmd.nrequire))] = quote
-        $(emit_exception_handle(cmd, call))
+        $(emit_exception_handle(cmd, configs, call))
         return 0
     end
 
     for i in cmd.nrequire+1:length(cmd.args)
         call = copy(call)
         type = cmd.args[i].type
-        push!(call.args, emit_parse_value(cmd, type, :($args[$i])))
+        push!(call.args, emit_parse_value(cmd, configs, type, :($args[$i])))
         ifelse[:($nargs == $i)] = quote
-            $(emit_exception_handle(cmd, call))
+            $(emit_exception_handle(cmd, configs, call))
             return 0
         end
     end
 
     if isnothing(cmd.vararg)
-        ifelse.otherwise = emit_error(cmd, "expect at most $(length(cmd.args)) positional arguments")
+        ifelse.otherwise = emit_error(cmd, configs, "expect at most $(length(cmd.args)) positional arguments")
     else
         @gensym varargs
         call = copy(call)
@@ -218,15 +232,15 @@ function emit_leaf_call(cmd::LeafCommand, args::Symbol, kwargs::Symbol)
         if type === Any || type === String || type === AbstractString
             ifelse.otherwise = quote
                 $varargs = $args[$(length(cmd.args))+1:end]
-                $(emit_exception_handle(cmd, call))
+                $(emit_exception_handle(cmd, configs, call))
                 return 0
             end
         else
             ifelse.otherwise = quote
                 $varargs = map($args[$(length(cmd.args) + 1):end]) do value
-                    $(emit_parse_value(cmd, type, :value))
+                    $(emit_parse_value(cmd, configs, type, :value))
                 end
-                $(emit_exception_handle(cmd, call))
+                $(emit_exception_handle(cmd, configs, call))
                 return 0
             end
         end
@@ -236,7 +250,7 @@ function emit_leaf_call(cmd::LeafCommand, args::Symbol, kwargs::Symbol)
     return ret
 end
 
-function emit_exception_handle(cmd::LeafCommand, call, color::Bool = true)
+function emit_exception_handle(cmd::LeafCommand, configs::Configs, call, color::Bool = true)
     quote
         try
             $call
@@ -250,7 +264,7 @@ function emit_exception_handle(cmd::LeafCommand, call, color::Bool = true)
             elseif e isa $CommandException
                 showerror(stdout, e)
                 println()
-                print($(help_str(cmd; color = color)))
+                $(print_help_str(cmd, configs))
                 println()
                 return e.exitcode
             else
@@ -260,9 +274,9 @@ function emit_exception_handle(cmd::LeafCommand, call, color::Bool = true)
     end
 end
 
-function emit_kwarg(cmd::LeafCommand, token::Symbol, kwargs::Symbol, token_ptr)
+function emit_kwarg(cmd::LeafCommand, configs::Configs, token::Symbol, kwargs::Symbol, token_ptr)
     if isempty(cmd.flags) && isempty(cmd.options)
-        return emit_error(cmd, :("do not have $($token)"))
+        return emit_error(cmd, configs, :("do not have $($token)"))
     end
 
     @gensym sym key value
@@ -271,19 +285,19 @@ function emit_kwarg(cmd::LeafCommand, token::Symbol, kwargs::Symbol, token_ptr)
     # short flag
     ifelse[:(length($token) == 2)] = quote
         $key = $token[2:2]
-        $(emit_short_flag(cmd, token, sym, key, value))
+        $(emit_short_flag(cmd, configs, token, sym, key, value))
     end
 
     # long option/flag
     ifelse[:(startswith($token, "--"))] = quote
         $key = lstrip(split($token, '=')[1], '-')
-        $(emit_long_option_or_flag(cmd, token, sym, key, value, token_ptr))
+        $(emit_long_option_or_flag(cmd, configs, token, sym, key, value, token_ptr))
     end
 
     # short option
     ifelse.otherwise = quote
         $key = $token[2:2]
-        $(emit_short_option(cmd, token, sym, key, value, token_ptr))
+        $(emit_short_option(cmd, configs, token, sym, key, value, token_ptr))
     end
 
     return quote
@@ -292,7 +306,7 @@ function emit_kwarg(cmd::LeafCommand, token::Symbol, kwargs::Symbol, token_ptr)
     end
 end
 
-function emit_short_flag(cmd::LeafCommand, token::Symbol, sym::Symbol, key::Symbol, value::Symbol)
+function emit_short_flag(cmd::LeafCommand, configs::Configs, token::Symbol, sym::Symbol, key::Symbol, value::Symbol)
     ifelse = JLIfElse()
     for (_, flag) in cmd.flags
         if flag.short
@@ -303,12 +317,13 @@ function emit_short_flag(cmd::LeafCommand, token::Symbol, sym::Symbol, key::Symb
             end
         end
     end
-    ifelse.otherwise = emit_error(cmd, :("cannot find flag: $($token)"))
+    ifelse.otherwise = emit_error(cmd, configs, :("cannot find flag: $($token)"))
     return codegen_ast(ifelse)
 end
 
 function emit_short_option(
     cmd::LeafCommand,
+    configs::Configs,
     token::Symbol,
     sym::Symbol,
     key::Symbol,
@@ -325,23 +340,24 @@ function emit_short_option(
                 elseif length($token) == 2 # read next
                     $token_ptr += 1
                     if $token_ptr > length(ARGS)
-                        $(emit_error(option, "expect a value"))
+                        $(emit_error(option, configs, "expect a value"))
                     end
                     $value = ARGS[$token_ptr]
                 else # -o<value>
                     $value = $token[3:end]
                 end
                 $sym = $(QuoteNode(option.sym))
-                $value = $(emit_parse_value(option, option.type, value))
+                $value = $(emit_parse_value(option, configs, option.type, value))
             end
         end
     end
-    ifelse.otherwise = emit_error(cmd, :("cannot find $($token)"))
+    ifelse.otherwise = emit_error(cmd, configs, :("cannot find $($token)"))
     return codegen_ast(ifelse)
 end
 
 function emit_long_option_or_flag(
     cmd::LeafCommand,
+    configs::Configs,
     token::Symbol,
     sym::Symbol,
     key::Symbol,
@@ -359,15 +375,15 @@ function emit_long_option_or_flag(
     for (name, option) in cmd.options
         ifelse[:($key == $name)] = quote
             $sym = $(QuoteNode(option.sym))
-            $(emit_option(option, token, value, token_ptr))
+            $(emit_option(option, configs, token, value, token_ptr))
         end
     end
 
-    ifelse.otherwise = emit_error(cmd, :("cannot find $($token)"))
+    ifelse.otherwise = emit_error(cmd, configs, :("cannot find $($token)"))
     return codegen_ast(ifelse)
 end
 
-function emit_parse_value(cmd, type, value)
+function emit_parse_value(cmd, configs::Configs, type, value)
     if type === Any || type === String || type === AbstractString
         return value
     else
@@ -375,25 +391,25 @@ function emit_parse_value(cmd, type, value)
         return quote
             $ret = tryparse($type, $value)
             if isnothing($ret)
-                $(emit_error(cmd, "expect value of type: $(type)"))
+                $(emit_error(cmd, configs, "expect value of type: $(type)"))
             end
             $ret
         end
     end
 end
 
-function emit_option(option::Option, token::Symbol, value::Symbol, token_ptr::Symbol)
+function emit_option(option::Option, configs::Configs, token::Symbol, value::Symbol, token_ptr::Symbol)
     return quote
         if occursin('=', $token)
             _, $value = split($token, '=')
         else # read next token
             $token_ptr += 1
             if $token_ptr > length(ARGS)
-                $(emit_error(option, "expect a value"))
+                $(emit_error(option, configs, "expect a value"))
             end
             $value = ARGS[$token_ptr]
         end
-        $value = $(emit_parse_value(option, option.type, value))
+        $value = $(emit_parse_value(option, configs, option.type, value))
     end
 end
 
diff --git a/src/options.jl b/src/configs.jl
similarity index 93%
rename from src/options.jl
rename to src/configs.jl
index 51fa70f..d0d82d8 100644
--- a/src/options.jl
+++ b/src/configs.jl
@@ -1,4 +1,4 @@
-module Options
+module Configs
 
 export read_options, has_comonicon_toml, @asset_str
 
@@ -169,6 +169,22 @@ Application build configurations.
     shell_completions::Vector{String} = ["zsh"]
 end
 
+"""
+    Help
+
+Configs for printing CLI help info.
+
+# Keywords
+
+- `color`: whether print with color in help page.
+- `static`: whether genrate help info at compile time
+    (the format won't be adaptive to displaysize anymore)
+"""
+@option struct Help
+    color::Bool = true
+    static::Bool = true
+end
+
 """
     Comonicon
 
@@ -179,6 +195,8 @@ project directory and read in using [`read_configs`](@ref).
 ## Keywords
 
 - `name`: required, the name of CLI file to install.
+- `color`: whether print with color in help page.
+- `static_displaysize`: whether format the display at compile time to reduce latency, default is `false`
 - `install`: installation options, see also [`Install`](@ref).
 - `sysimg`: system image build options, see also [`SysImg`](@ref).
 - `download`: download options, see also [`Download`](@ref).
@@ -187,6 +205,7 @@ project directory and read in using [`read_configs`](@ref).
 @option struct Comonicon
     name::String
 
+    help::Help = Help()
     install::Install = Install()
     sysimg::Maybe{SysImg} = nothing
     download::Union{Download,Nothing} = nothing
@@ -240,7 +259,9 @@ end
 Read `Comonicon.toml` or `JuliaComonicon.toml` in given module's project path.
 """
 function read_toml(mod::Module)
-    return read_toml(pkgdir(mod))
+    path = pkgdir(mod)
+    path === nothing && return Dict{String, Any}()
+    return read_toml(path)
 end
 
 function has_comonicon_toml(m::Module)
diff --git a/src/exceptions.jl b/src/exceptions.jl
index c98b8da..ca2e940 100644
--- a/src/exceptions.jl
+++ b/src/exceptions.jl
@@ -15,8 +15,8 @@ struct CommandError <: CommandException
     exitcode::Int
 end
 
-cmd_error(msg::String, code::Int=1) = throw(CommandError(msg, code))
-cmd_exit(code::Int=0) = throw(CommandExit(code))
+cmd_error(msg::String, code::Int = 1) = throw(CommandError(msg, code))
+cmd_exit(code::Int = 0) = throw(CommandExit(code))
 
 function Base.showerror(io::IO, e::CommandError)
     print(io, "CommandError: ", e.msg, " exit with ", e.exitcode)
diff --git a/src/frontend/cast.jl b/src/frontend/cast.jl
index e835c0e..ae22e23 100644
--- a/src/frontend/cast.jl
+++ b/src/frontend/cast.jl
@@ -368,11 +368,13 @@ end
 
 function codegen_create_entry(m::Module, line, @nospecialize(ex))
     @gensym cmd entry
+    configs = Configs.read_options(m)
+    julia_expr_configs = JuliaExpr.Configs(;configs.help.color, configs.help.static)
     quote
-        $(codegen_entry_cmd(m::Module, line, cmd, ex))
+        $(codegen_entry_cmd(m::Module, line, cmd, configs, ex))
         $entry = $Comonicon.AST.Entry($cmd, $(get_version(m)), $line)
         $Comonicon.set_cmd!($m.CASTED_COMMANDS, $entry, "main")
-        $m.eval($JuliaExpr.emit($entry))
+        $m.eval($JuliaExpr.emit($entry, $(julia_expr_configs)))
     end
 end
 
@@ -428,21 +430,16 @@ function codegen_project_entry(m::Module, line, @nospecialize(ex))
     end
 end
 
-function codegen_entry_cmd(m::Module, line, cmd, ex)
+function codegen_entry_cmd(m::Module, line, cmd, configs, ex)
     if isnothing(ex)
-        return codegen_multiple_main_entry(m, line, cmd)
+        return codegen_multiple_main_entry(m, line, cmd, configs)
     else
         return codegen_single_main_entry(m, line, cmd, ex)
     end
 end
 
-function codegen_multiple_main_entry(m::Module, line, cmd)
-    if Options.has_comonicon_toml(m)
-        options = Options.read_options(m)
-        name = options.name
-    else
-        name = default_name(nameof(m))
-    end
+function codegen_multiple_main_entry(m::Module, line, cmd, configs)
+    name = configs.name
 
     @gensym doc
     return quote
@@ -477,7 +474,8 @@ end
 
 function cast(m::Module, name::String = default_name(m), line = LineNumberNode(0))
     isdefined(m, :CASTED_COMMANDS) || error("module $m does not contain any @cast commands")
-    NodeCommand(name, copy(m.CASTED_COMMANDS), split_docstring(m), line)
+    desc, intro = split_docstring(m)
+    NodeCommand(name, copy(m.CASTED_COMMANDS), Description(desc, intro), line)
 end
 
 function cast(
@@ -500,7 +498,7 @@ function cast(
         vararg,
         flags,
         options,
-        doc.desc,
+        Description(doc.desc, doc.intro),
         line,
     )
 end
diff --git a/src/frontend/markdown.jl b/src/frontend/markdown.jl
index 3b84609..497704d 100644
--- a/src/frontend/markdown.jl
+++ b/src/frontend/markdown.jl
@@ -1,3 +1,9 @@
+const PARSED_SECTION = [
+    "Intro", "Introduction", # long description
+    "Arguments", "Args", # arguments
+    "Options", "Flags" # kwargs
+]
+
 function split_docstring(f::Function)
     doc = Base.Docs.doc(f)
     return split_docstring(doc)
@@ -5,17 +11,18 @@ end
 
 function split_docstring(m::Module)
     doc = Base.Docs.doc(m)
-    has_docstring(doc) || return
-    return read_description(doc)
+    has_docstring(doc) || return "", ""
+    return read_description(doc), read_intro(doc)
 end
 
 function split_docstring(doc::Markdown.MD)
     has_docstring(doc) || return JLMD()
     desc = read_description(doc)
+    intro = read_intro(doc)
     args = read_arguments(doc)
     flags = read_flags(doc)
     options = read_options(doc)
-    return JLMD(desc, args, options, flags)
+    return JLMD(desc, intro, args, options, flags)
 end
 
 function has_docstring(doc::Markdown.MD)
@@ -24,10 +31,19 @@ function has_docstring(doc::Markdown.MD)
     return !flag
 end
 
+function read_intro(md::Markdown.MD)
+    sec = read_section(md, ["Intro", "Introduction"])
+    isempty(sec) && return ""
+    length(sec) == 1 || error("section Intro/Introduction can only have one paragraph")
+    return strip(md_to_string(Markdown.MD(sec, md.meta)))
+end
+
 function read_arguments(md::Markdown.MD)
     args = Dict{String,String}()
     sec = read_section(md, ["Arguments", "Args"])
-    sec === nothing && return args
+    isempty(sec) && return args
+    length(sec) == 1 || error("section Arguments/Args can only have one paragraph")
+    sec = sec[]
 
     for each in sec.items
         name, doc = read_item(each[1])
@@ -39,7 +55,9 @@ end
 function read_flags(md::Markdown.MD)
     flags = Dict{String,JLMDFlag}()
     sec = read_section(md, "Flags")
-    sec === nothing && return flags
+    isempty(sec) && return flags
+    length(sec) == 1 || error("section Flags can only have one paragraph")
+    sec = sec[]
 
     for each in sec.items
         name, doc = read_item(each[1])
@@ -53,7 +71,9 @@ end
 function read_options(md::Markdown.MD)
     options = Dict{String,JLMDOption}()
     sec = read_section(md, "Options")
-    sec === nothing && return options
+    isempty(sec) && return options
+    length(sec) == 1 || error("section Options can only have one paragraph")
+    sec = sec[]
 
     for each in sec.items
         code, doc = read_item(each[1])
@@ -72,35 +92,39 @@ function read_description(md::Markdown.MD)
     end
 
     for line in lines
-        if line isa Markdown.Header{1} && line.text[1] in ["Arguments", "Args", "Options", "Flags"]
+        if line isa Markdown.Header{1} && line.text[1] in PARSED_SECTION
             break
         else
             push!(intro, line)
         end
     end
+    isempty(intro) && return ""
     return strip(md_to_string(Markdown.MD(intro, md.meta)))
 end
 
 function read_section(md::Markdown.MD, title::Vector{String})
     for each in title
         sec = read_section(md, each)
-        sec === nothing || return sec
+        isempty(sec) || return sec
     end
-    return
+    return []
 end
 
-function read_section(md::Markdown.MD, title::String)
+function read_section(md::Markdown.MD, title::String)::Vector{Any}
     ct = read_content(md)
     nlines = length(ct)
+    content = []
     for k in 1:nlines
         line = ct[k]
         if line isa Markdown.Header{1} && line.text[1] == title
-            if k + 1 <= nlines
-                return ct[k+1]
+            k + 1 ≤ nlines || return content # return on last line
+            for idx in k+1:nlines
+                ct[idx] isa Markdown.Header{1} && return content # another title
+                push!(content, ct[idx])
             end
         end
     end
-    return
+    return content
 end
 
 read_content(x) = x
@@ -145,8 +169,12 @@ function docstring(x)
     end
 end
 
-function md_to_string(md::Markdown.MD)
-    return sprint(md; context = :color => true) do io, x
+function md_to_string(md::Markdown.MD) # don't print newlines for one paragraph
+    return sprint(md; context = (
+                :color => true,
+                :displaysize => (typemax(Int), 1000)
+            )
+        ) do io, x
         show(io, MIME"text/plain"(), x)
     end
 end
@@ -187,7 +215,7 @@ function split_hint(content::AbstractString)
     # no hint, just -o, --option
     # shouldn't contain inner space
     if m === nothing
-        any(x->isspace(x) || isequal(x, '='), content) && throw(Meta.ParseError(msg))
+        any(x -> isspace(x) || isequal(x, '='), content) && throw(Meta.ParseError(msg))
         return content, nothing
     end
 
diff --git a/src/frontend/types.jl b/src/frontend/types.jl
index d8f5d79..039ab25 100644
--- a/src/frontend/types.jl
+++ b/src/frontend/types.jl
@@ -29,6 +29,7 @@ end
 
 Base.@kwdef struct JLMD
     desc::String = ""
+    intro::String = ""
     arguments::Dict{String,String} = Dict{String,String}()
     options::Dict{String,JLMDOption} = Dict{String,JLMDOption}()
     flags::Dict{String,JLMDFlag} = Dict{String,JLMDFlag}()
diff --git a/src/tools.jl b/src/tools.jl
index 1dbdc5a..34cca72 100644
--- a/src/tools.jl
+++ b/src/tools.jl
@@ -18,7 +18,7 @@ Prompt with message to ask user Yes or No.
     3 times to ask user to input the answer then throw an [`CommandError`](@ref).
 """
 function prompt(msg::AbstractString; yes::Bool = false, require::Bool = false)
-    prompt(;yes, require) do
+    prompt(; yes, require) do
         print(msg)
     end
 end
@@ -39,7 +39,7 @@ Prompt with custom printing to ask user Yes or No.
 - `require`: require user to input the answer explicitly. This will repeat
     3 times to ask user to input the answer then throw an [`CommandError`](@ref).
 """
-prompt(f; yes::Bool=false, require::Bool = false) = prompt(f, stdin; yes, require)
+prompt(f; yes::Bool = false, require::Bool = false) = prompt(f, stdin; yes, require)
 
 function prompt(f, io::IO; yes::Bool = false, require::Bool = false)
     if yes


Test patch:
diff --git a/example/FakePkg/test/runtests.jl b/example/FakePkg/test/runtests.jl
index 7457fe7..87e579e 100644
--- a/example/FakePkg/test/runtests.jl
+++ b/example/FakePkg/test/runtests.jl
@@ -27,7 +27,7 @@ using Comonicon.Builder
     @test FakePkg.command_main(["registry", "rm", "abc"]) == 0
 end
 
-using Comonicon.Options: read_options, get_path, @asset_str
+using Comonicon.Configs: read_options, get_path, @asset_str
 
 @testset "test assets path" begin
     options = read_options(FakePkg)
diff --git a/test/ast/ast.jl b/test/ast/ast.jl
index d0c5396..6c8d771 100644
--- a/test/ast/ast.jl
+++ b/test/ast/ast.jl
@@ -2,9 +2,6 @@ using Comonicon.AST
 using Test
 using Faker
 
-desc = Description(Faker.text())
-@test endswith(desc.brief, ".") # brief should be a sentence
-
 arg = Argument(; name = "arg", type = Int)
 
 macro test_show(mime, ex)
@@ -24,13 +21,6 @@ macro test_show(mime, ex)
     return esc(ret)
 end
 
-@testset "convert(Description, ...)" begin
-    @test Description(nothing) == Description()
-    @test convert(Description, nothing) == Description()
-    @test convert(Description, "nothing") == Description("nothing")
-    @test convert(Description, split("abcd. efdasdas.", '.')[1]) == Description("abcd")
-end
-
 @test_show MIME"text/plain" begin
     "<arg>" in Argument(; name = "arg")
     "[arg...]" in Argument(; name = "arg", vararg = true)
diff --git a/test/builder/cli.jl b/test/builder/cli.jl
index 653eb42..b1c7013 100644
--- a/test/builder/cli.jl
+++ b/test/builder/cli.jl
@@ -1,7 +1,7 @@
 module TestBuilderCLI
 
 using Test
-using Comonicon.Options
+using Comonicon.Configs
 using Comonicon.Builder: print_builder_help, command_main
 using FromFile
 
@@ -10,7 +10,7 @@ using FromFile
 module TestCLI end
 
 @testset "CLI help info" begin
-    options = Options.Comonicon(name = "test")
+    options = Configs.Comonicon(name = "test")
     @test with_args(["-h"]) do
         command_main(TestCLI, options)
     end === 0
diff --git a/test/builder/install.jl b/test/builder/install.jl
index 8c74ebf..abf7635 100644
--- a/test/builder/install.jl
+++ b/test/builder/install.jl
@@ -2,7 +2,7 @@ module TestBuilderInstall
 
 using Test
 using Scratch
-using Comonicon.Options
+using Comonicon.Configs
 using Comonicon.Builder: ensure_path, entryfile_script, completion_script, detect_rcfile
 
 @testset "ensure_path" begin
@@ -24,7 +24,7 @@ using Comonicon
 end
 
 @testset "entryfile_script" begin
-    options = Options.Comonicon(name = "test")
+    options = Configs.Comonicon(name = "test")
     script = entryfile_script(TestInstall, options)
 
     @test occursin("#!/usr/bin/env bash", script)
@@ -43,7 +43,7 @@ end
 end
 
 @testset "test completion script" begin
-    options = Options.Comonicon(name = "test")
+    options = Configs.Comonicon(name = "test")
     withenv("SHELL" => "/bin/zsh") do
         script = completion_script(TestInstall, options, "zsh")
         @test occursin("#compdef _testinstall testinstall \n", script)
diff --git a/test/builder/rcfile.jl b/test/builder/rcfile.jl
index 993d6cf..3f7d064 100644
--- a/test/builder/rcfile.jl
+++ b/test/builder/rcfile.jl
@@ -9,9 +9,9 @@ home_dir = mktempdir()
 test_dir = pkgdir(Comonicon, "test")
 usr_dir = joinpath(test_dir, "usr")
 
-options = Comonicon.Options.Comonicon(
+options = Comonicon.Configs.Comonicon(
     name = "test",
-    install = Comonicon.Options.Install(path = usr_dir, completion = false),
+    install = Comonicon.Configs.Install(path = usr_dir, completion = false),
     sysimg = nothing,
 )
 
diff --git a/test/codegen/julia/arg.jl b/test/codegen/julia/arg.jl
index d801147..bcac968 100644
--- a/test/codegen/julia/arg.jl
+++ b/test/codegen/julia/arg.jl
@@ -40,7 +40,7 @@ cmd = Entry(
         "main",
         Argument[],
         0,
-        Argument("xs", Any, true, false, nothing, Description(nothing, nothing), LineNumberNode(0)),
+        Argument("xs", Any, true, false, nothing, Description(), LineNumberNode(0)),
         Dict{String,Flag}(),
         Dict{String,Option}(),
         Description("", ""),
diff --git a/test/frontend/cast.jl b/test/frontend/cast.jl
index 17d1482..a7110c7 100644
--- a/test/frontend/cast.jl
+++ b/test/frontend/cast.jl
@@ -221,8 +221,7 @@ module Test110
 
 using Comonicon
 
-@main function main(;niterations::Int=3000, seed::Int=1234, radius::Float64=1.5)
-end
+@main function main(; niterations::Int = 3000, seed::Int = 1234, radius::Float64 = 1.5) end
 
 end
 
diff --git a/test/frontend/markdown.jl b/test/frontend/markdown.jl
index bf1be5b..2ef1ea5 100644
--- a/test/frontend/markdown.jl
+++ b/test/frontend/markdown.jl
@@ -20,6 +20,7 @@ using Comonicon:
     split_leaf_command,
     split_docstring,
     read_arguments,
+    read_intro,
     read_description,
     read_options,
     read_flags,
@@ -32,6 +33,12 @@ using Comonicon:
 
     description of the command.
 
+    # Intro
+
+    asdmwds dasklsam xasdklqm dasdm, qwdjiolkasjdsa
+    dasklmdas weqwlkjmdas kljnsadlksad qwlkdnasd
+    dasklmdlqwoi, dasdasklmd qw,asd. dasdjklnmldqw.
+
     # Args
 
     - `arg1`: argument 1.
@@ -41,6 +48,9 @@ using Comonicon:
     """)
 
     @test read_description(doc) == "description of the command."
+    @test read_intro(doc) == "asdmwds dasklsam xasdklqm dasdm, qwdjiolkasjdsa " *
+    "dasklmdas weqwlkjmdas kljnsadlksad qwlkdnasd " *
+    "dasklmdlqwoi, dasdasklmd qw,asd. dasdjklnmldqw."
 
     args = read_arguments(doc)
     @test args["arg1"] == "argument 1."
diff --git a/test/options.jl b/test/options.jl
index 12e1e9b..8072ed0 100644
--- a/test/options.jl
+++ b/test/options.jl
@@ -1,20 +1,20 @@
 using Test
 using Comonicon
-using Comonicon.Options
+using Comonicon.Configs
 
 @testset "load options" begin
     options = read_options(pkgdir(Comonicon, "test"))
     display(options)
-    @test options == Options.Comonicon(;
+    @test options == Configs.Comonicon(;
         name = "foo",
-        install = Options.Install(; compile = "min"),
-        sysimg = Options.SysImg(;
+        install = Configs.Install(; compile = "min"),
+        sysimg = Configs.SysImg(;
             cpu_target = "native",
-            precompile = Options.Precompile(; execution_file = ["deps/precopmile.jl"]),
+            precompile = Configs.Precompile(; execution_file = ["deps/precopmile.jl"]),
         ),
-        download = Options.Download(; user = "Roger-luo", repo = "Foo.jl"),
-        application = Options.Application(;
-            assets = Options.Asset[asset"PkgTemplate: templates", asset"assets/images"],
+        download = Configs.Download(; user = "Roger-luo", repo = "Foo.jl"),
+        application = Configs.Application(;
+            assets = Configs.Asset[asset"PkgTemplate: templates", asset"assets/images"],
             incremental = true,
             filter_stdlibs = false,
         ),
@@ -22,5 +22,5 @@ using Comonicon.Options
 end
 
 @testset "has_comonicon_toml" begin
-    @test Options.has_comonicon_toml(Main) == false
+    @test Configs.has_comonicon_toml(Main) == false
 end
diff --git a/test/tools.jl b/test/tools.jl
index a7ab4b0..097828f 100644
--- a/test/tools.jl
+++ b/test/tools.jl
@@ -14,10 +14,9 @@ end
     readavailable(stdin.buffer)
 end
 
-@testset "prompt(msg;yes) input=$input" for input in
-        ['Y', 'y', '\n', '\r','N', 'n', 'a', 'b']
+@testset "prompt(msg;yes) input=$input" for input in ['Y', 'y', '\n', '\r', 'N', 'n', 'a', 'b']
     print(stdin.buffer, input)
-    @test prompt("input something"; yes=true) == true
+    @test prompt("input something"; yes = true) == true
     readavailable(stdin.buffer)
 end
 
@@ -25,18 +24,18 @@ end
     print(stdin.buffer, '\n')
     print(stdin.buffer, '\n')
     print(stdin.buffer, 'Y')
-    @test prompt("input something"; require=true) == true
+    @test prompt("input something"; require = true) == true
     readavailable(stdin.buffer)
 
     print(stdin.buffer, '\n')
     print(stdin.buffer, '\n')
     print(stdin.buffer, 'n')
-    @test prompt("input something"; require=true) == false
+    @test prompt("input something"; require = true) == false
     readavailable(stdin.buffer)
 
     print(stdin.buffer, '\n')
     print(stdin.buffer, '\n')
     print(stdin.buffer, '\n')
-    @test_throws Comonicon.CommandError prompt("input something"; require=true)
+    @test_throws Comonicon.CommandError prompt("input something"; require = true)
     readavailable(stdin.buffer)
 end