Browse Source

Support attaching documentation to the enum-module, and individual instances.

pull/8/head v1.0.0
Fredrik Ekre 4 years ago
parent
commit
a00c55fb3f
  1. 28
      README.md
  2. 22
      src/EnumX.jl
  3. 53
      test/runtests.jl

28
README.md

@ -56,13 +56,30 @@ Since the only reserved name in the example above is the module `Fruit` we can c
another enum with overlapping instance names (this would not be possible with `Base.@enum`): another enum with overlapping instance names (this would not be possible with `Base.@enum`):
```julia ```julia
julia> @enumx YellowFruits Banana Lemon julia> @enumx YellowFruit Banana Lemon
julia> YellowFruits.Banana julia> YellowFruit.Banana
YellowFruits.Banana = 0 YellowFruit.Banana = 0
``` ```
`@enumx` also allows for duplicate values: Instances can be documented like `struct` fields. A docstring before the macro is
attached to the *module* `Fruit` (i.e. not the "hidden" type `Fruit.T`):
```julia
julia> "Documentation for Fruit enum-module."
@enumx Fruit begin
"Documentation for Fruit.Apple instance."
Apple
end
help?> Fruit
Documentation for Fruit enum-module.
help?> Fruit.Apple
Documentation for Fruit.Apple instance.
```
`@enumx` allows for duplicate values (unlike `Base.@enum`):
```julia ```julia
julia> @enumx Fruit Apple=1 Banana=1 julia> @enumx Fruit Apple=1 Banana=1
@ -79,7 +96,8 @@ julia> Fruit.Banana
Fruit.Apple = Fruit.Banana = 1 Fruit.Apple = Fruit.Banana = 1
``` ```
`@enumx` also lets you use previous enum names for value initialization: `@enumx` lets you use previous enum names for value initialization:
```julia ```julia
julia> @enumx Fruit Apple Banana Orange=Apple julia> @enumx Fruit Apple Banana Orange=Apple

22
src/EnumX.jl

@ -39,10 +39,18 @@ function enumx(_module_, args)
syms = args syms = args
end end
name_value_map = Vector{Pair{Symbol, baseT}}() name_value_map = Vector{Pair{Symbol, baseT}}()
doc_entries = Vector{Pair{Symbol,Expr}}()
next = zero(baseT) next = zero(baseT)
first = true first = true
for s in syms for s in syms
s isa LineNumberNode && continue s isa LineNumberNode && continue
# Handle doc expressions
doc_expr = nothing
if Meta.isexpr(s, :macrocall, 4) && s.args[1] isa GlobalRef && s.args[1].mod === Core &&
s.args[1].name === Symbol("@doc")
doc_expr = s
s = s.args[4]
end
if s isa Symbol if s isa Symbol
if !first && next == typemin(baseT) if !first && next == typemin(baseT)
panic("value overflow for Enum $(modname): $(modname).$(s) = $(next).") panic("value overflow for Enum $(modname): $(modname).$(s) = $(next).")
@ -78,6 +86,11 @@ function enumx(_module_, args)
) )
end end
push!(name_value_map, sym => next) push!(name_value_map, sym => next)
if doc_expr !== nothing
# Replace the documented expression since it might be :(Apple = ...)
doc_expr.args[4] = sym
push!(doc_entries, sym => doc_expr)
end
next += oneunit(baseT) next += oneunit(baseT)
first = false first = false
@ -103,7 +116,14 @@ function enumx(_module_, args)
Expr(:const, Expr(:(=), esc(k), Expr(:call, esc(T), v))) Expr(:const, Expr(:(=), esc(k), Expr(:call, esc(T), v)))
) )
end end
return Expr(:toplevel, Expr(:module, false, esc(modname), module_block), nothing) for (_, v) in doc_entries
push!(module_block.args, v)
end
# Document the module and the type
mdoc = Expr(:block, Expr(:meta, :doc), esc(modname))
# TODO: Attach to the type too?
# Tdoc = Expr(:block, Expr(:meta, :doc), Expr(:., esc(modname), QuoteNode(T)))
return Expr(:toplevel, Expr(:module, false, esc(modname), module_block), mdoc, #=Tdoc,=# nothing)
end end
function Base.show(io::IO, ::MIME"text/plain", x::E) where E <: Enum function Base.show(io::IO, ::MIME"text/plain", x::E) where E <: Enum

53
test/runtests.jl

@ -247,4 +247,57 @@ end
@enumx T=Typ FruitEmptyT @enumx T=Typ FruitEmptyT
@test instances(FruitEmptyT.Typ) == () @test instances(FruitEmptyT.Typ) == ()
# Documented type (module) and instances
begin
"""
Documentation for FruitDoc
"""
@enumx FruitDoc begin
"Apple documentation."
Apple
"""
Banana documentation
on multiple lines.
"""
Banana = 2
Orange = Apple
end
@eval const LINENUMBER = $(@__LINE__)
@eval const FILENAME = $(@__FILE__)
@eval const MODULE = $(@__MODULE__)
end
function get_doc_metadata(mod, s)
Base.Docs.meta(mod)[Base.Docs.Binding(mod, s)].docs[Union{}].data
end
@test FruitDoc.Apple === FruitDoc.T(0)
@test FruitDoc.Banana === FruitDoc.T(2)
@test FruitDoc.Orange === FruitDoc.T(0)
mod_doc = @doc(FruitDoc)
@test sprint(show, mod_doc) == "Documentation for FruitDoc\n"
mod_doc_data = get_doc_metadata(FruitDoc, :FruitDoc)
@test mod_doc_data[:linenumber] == LINENUMBER - 13
@test mod_doc_data[:path] == FILENAME
@test mod_doc_data[:module] == MODULE
apple_doc = @doc(FruitDoc.Apple)
@test sprint(show, apple_doc) == "Apple documentation.\n"
apple_doc_data = get_doc_metadata(FruitDoc, :Apple)
@test apple_doc_data[:linenumber] == LINENUMBER - 9
@test apple_doc_data[:path] == FILENAME
@test apple_doc_data[:module] == FruitDoc
banana_doc = @doc(FruitDoc.Banana)
@test sprint(show, banana_doc) == "Banana documentation on multiple lines.\n"
banana_doc_data = get_doc_metadata(FruitDoc, :Banana)
@test banana_doc_data[:linenumber] == LINENUMBER - 7
@test banana_doc_data[:path] == FILENAME
@test banana_doc_data[:module] == FruitDoc
orange_doc = @doc(FruitDoc.Orange)
@test startswith(sprint(show, orange_doc), "No documentation found")
end # testset end # testset

Loading…
Cancel
Save