rafaqz / Interfaces.jl

Macros to define and implement interfaces, to ensure they are checked and correct.
MIT License
72 stars 4 forks source link

Generate `# Extended help` with all component descriptions #47

Closed MilesCranmer closed 3 days ago

MilesCranmer commented 5 days ago

This adds an automatically-generated # Extended help section with all component descriptions.

This won't appear in the normal docs for ?, only for ?? which is the extended help, as described in #46.

Using my package's NodeInterface as an example, here is the before:

(Expand) ```markdown ''' NodeInterface ''' An Interfaces.jl `Interface` with mandatory components `(:create_node, :copy, :hash, :any, :equality, :preserve_sharing, :constructorof, :eltype, :with_type_parameters, :default_allocator, :set_node!, :count_nodes, :tree_mapreduce)` and optional components `(:leaf_copy, :leaf_hash, :leaf_equal, :branch_copy, :branch_hash, :branch_equal, :count_depth, :is_node_constant, :count_constants, :filter_map, :has_constants, :get_constants, :set_constants!, :index_constants, :has_operators)`. Defines the interface for [`AbstractExpressionNode`](@ref) which can include various operations such as copying, hashing, and checking equality, as well as tree-specific operations like map-reduce and node manipulation. ```
And the after: ```markdown ''' NodeInterface ''' An Interfaces.jl `Interface` with mandatory components `(:create_node, :copy, :hash, :any, :equality, :preserve_sharing, :constructorof, :eltype, :with_type_parameters, :default_allocator, :set_node!, :count_nodes, :tree_mapreduce)` and optional components `(:leaf_copy, :leaf_hash, :leaf_equal, :branch_copy, :branch_hash, :branch_equal, :count_depth, :is_node_constant, :count_constants, :filter_map, :has_constants, :get_constants, :set_constants!, :index_constants, :has_operators)`. Defines the interface for [`AbstractExpressionNode`](@ref) which can include various operations such as copying, hashing, and checking equality, as well as tree-specific operations like map-reduce and node manipulation. # Extended help ## Mandatory keys: * `create_node`: creates a new instance of the node type * `copy`: returns a copy of the tree * `hash`: returns the hash of the tree * `any`: checks if any element of the tree satisfies a condition * `equality`: checks equality of the tree with itself and its copy * `preserve_sharing`: checks if the node type preserves sharing * `constructorof`: gets the constructor function for a node type * `eltype`: gets the element type of the node * `with_type_parameters`: applies type parameters to the node type * `default_allocator`: gets the default allocator for the node type * `set_node!`: sets the node's value * `count_nodes`: counts the number of nodes in the tree * `tree_mapreduce`: applies a function across the tree ## Optional keys: * `leaf_copy`: copies a leaf node * `leaf_hash`: computes the hash of a leaf node * `leaf_equal`: checks equality of two leaf nodes * `branch_copy`: copies a branch node * `branch_hash`: computes the hash of a branch node * `branch_equal`: checks equality of two branch nodes * `count_depth`: calculates the depth of the tree * `is_node_constant`: checks if the node is a constant * `count_constants`: counts the number of constants * `filter_map`: applies a filter and map function to the tree * `has_constants`: checks if the tree has constants * `get_constants`: gets constants from the tree, returning a tuple of: (1) a flat vector of the constants, and (2) a reference object that can be used by `set_constants!` to efficiently set them back * `set_constants!`: sets constants in the tree, given: (1) a flat vector of constants, (2) the tree, and (3) the reference object produced by `get_constants` * `index_constants`: indexes constants in the tree * `has_operators`: checks if the tree has operators ```

For multiple methods, it generates a nested list.

For example, for the GroupInterface: ```markdown ''' GroupInterface ''' An Interfaces.jl `Interface` with mandatory components `(:neutral_check, :multiplication_check, :inversion_check)` and optional components `()`. A group is a set of elements with a neutral element where you can perform multiplications and inversions. The conditions checking the interface accept an `Arguments` object with two fields named `x` and `y`. The type of the first field `x` must be the type you wish to declare as implementing `GroupInterface`. # Extended help ## Mandatory keys: * `neutral_check`: * neutral stable * `multiplication_check`: * multiplication stable * `inversion_check`: * inversion stable * inversion works ```
rafaqz commented 4 days ago

Maybe run the Base interfaces.jl test manually to see what's broken. Some may not have descriptions? (and that's allowed)

MilesCranmer commented 4 days ago

Oh yeah it looks like it's the fact that some keys don't have descriptions. I'll make a patch.

MilesCranmer commented 4 days ago

Ok it is done. I also refactored things a bit nicer and made it more robust to any future edge cases

MilesCranmer commented 4 days ago

So it looks like test/advanced.jl is being converted to markdown for the documentation?

┌ Error: failed to run `@example` block in src/advanced.md:109-134
│ ```@example advanced
│ expected_extended_help = """# Extended help
│
│ # Mandatory keys:
│
│ * `neutral_check`:
│   * neutral stable
│ * `multiplication_check`:
│   * multiplication stable
│ * `inversion_check`:
│   * inversion stable
│   * inversion works"""
│
│ @test strip(Interfaces._extended_help(Group.GroupInterface)) == strip(expected_extended_help)
│
│
│ expected_docs = """```
│     GroupInterface
│ ```
│
│ An Interfaces.jl `Interface` with mandatory components `(:neutral_check, :multiplication_check, :inversion_check)` and optional components `()`.
│
│ A group is a set of elements with a neutral element where you can perform multiplications and inversions.
│
│ The conditions checking the interface accept an `Arguments` object with two fields named `x` and `y`. The type of the first field `x` must be the type you wish to declare as implementing `GroupInterface`.
│ ```
│   exception =
│    LoadError: UndefVarError: `@test` not defined
│    Stacktrace:
│      [1] top-level scope
│        @ :0
│      [2] eval
│        @ ./boot.jl:385 [inlined]
│      [3] #58
│        @ ~/.julia/packages/Documenter/qoyeC/src/expander_pipeline.jl:754 [inlined]
│      [4] cd(f::Documenter.var"#58#60"{Module, Expr}, dir::String)
│        @ Base.Filesystem ./file.jl:112
│      [5] (::Documenter.var"#57#59"{Documenter.Page, Module, Expr})()
│        @ Documenter ~/.julia/packages/Documenter/qoyeC/src/expander_pipeline.jl:753
│      [6] (::IOCapture.var"#5#9"{DataType, Documenter.var"#57#59"{Documenter.Page, Module, Expr}, IOContext{Base.PipeEndpoint}, IOContext{Base.PipeEndpoint}, Base.TTY, Base.TTY})()
│        @ IOCapture ~/.julia/packages/IOCapture/Y5rEA/src/IOCapture.jl:170
│      [7] with_logstate(f::Function, logstate::Any)
│        @ Base.CoreLogging ./logging.jl:515
│      [8] with_logger
│        @ ./logging.jl:627 [inlined]
│      [9] capture(f::Documenter.var"#57#59"{Documenter.Page, Module, Expr}; rethrow::Type, color::Bool, passthrough::Bool, capture_buffer::IOBuffer, io_context::Vector{Any})
│        @ IOCapture ~/.julia/packages/IOCapture/Y5rEA/src/IOCapture.jl:167
│     [10] runner(::Type{Documenter.Expanders.ExampleBlocks}, node::MarkdownAST.Node{Nothing}, page::Documenter.Page, doc::Documenter.Document)
│        @ Documenter ~/.julia/packages/Documenter/qoyeC/src/expander_pipeline.jl:752
│    in expression starting at advanced.md:122
└ @ Documenter ~/.julia/packages/Documenter/qoyeC/src/utilities/utilities.jl:44
┌ Error: failed to parse exception in src/advanced.md
│   exception =
│    ParseError:
│    # Error @ none:3:3
│
│      * `neutral_check`:
│    # ╙ ── not a unary operator
└ @ Documenter ~/.julia/packages/Documenter/qoyeC/src/utilities/utilities.jl:44
ERROR: LoadError: AssertionError: Issue at #= advanced.md:138 =#:

Is there any way to disable it for a block?

MilesCranmer commented 4 days ago

Ok I fixed the docs build by just splitting out the test into a separate file and including in a line with #src

julia --project=docs/ docs/make.jl
[ Info: generating markdown page from `~/PermaDocuments/Interfaces.jl/test/basic.jl`
[ Info: writing result to `~/PermaDocuments/Interfaces.jl/docs/src/basic.md`
[ Info: generating markdown page from `~/PermaDocuments/Interfaces.jl/test/advanced.jl`
[ Info: writing result to `~/PermaDocuments/Interfaces.jl/docs/src/advanced.md`
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: Doctest: running doctests.
[ Info: ExpandTemplates: expanding markdown templates.
[ Info: CrossReferences: building cross-references.
[ Info: CheckDocument: running document checks.
[ Info: Populate: populating indices.
[ Info: RenderDocument: rendering document.
[ Info: HTMLWriter: rendering HTML pages.
[ Info: Automatic `version="0.3.0"` for inventory from ../Project.toml
┌ Warning: Documenter could not auto-detect the building environment. Skipping deployment.
└ @ Documenter ~/.julia/packages/Documenter/qoyeC/src/deployconfig.jl:76