Skip to content

Commit

Permalink
feat: Added @decorators macro (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
curtd authored Jan 31, 2024
1 parent 8cf1685 commit 950dda7
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 140 deletions.
1 change: 1 addition & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@def_pprint
@def_pprint_atomic
@hide_typename
@decorators
```

## Functions
Expand Down
2 changes: 1 addition & 1 deletion src/AutoPrettyPrinting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module AutoPrettyPrinting

export KeyValue

export @mime_type, @custom_tile, @def_pprint, @hide_typename, @def_pprint_atomic
export @mime_type, @custom_tile, @def_pprint, @hide_typename, @def_pprint_atomic, @decorators

export PPrintContext
export repr_pretty
Expand Down
2 changes: 1 addition & 1 deletion src/auto_prettyprinting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function custom_tile_expr(typename, mime_type, tile_expr; _sourceinfo=nothing, g
output = Any[]
body = Expr(:block, _sourceinfo, tile_expr)
push!(output, :($AutoPrettyPrinting.custom_tile($(object_arg)::$(typename), $(mime_arg)::$(mime_type); kwargs...) = $(body)))
append!(output, [:($AutoPrettyPrinting.$f($(object_arg)::$(typename), $(mime_arg)::$(mime_type); kwargs...) = $custom_tile($object_arg, $mime_arg; kwargs...)) for f in (:custom_tile_horiz, :custom_tile_vert)])
append!(output, [:($AutoPrettyPrinting.$f($(object_arg)::$(typename), $(mime_arg)::$(mime_type); kwargs...) = $(body)) for f in (:custom_tile_horiz, :custom_tile_vert)])
if generate_base_show
push!(output, generate_base_show_expr(typename, mime_type; _sourceinfo))
end
Expand Down
129 changes: 116 additions & 13 deletions src/prettyprinting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ end
Base.first(x::KeyValue) = x.key
Base.last(x::KeyValue) = x.value

const KV_SEPARATOR = Ref(" = ")
const PAIR_SEPARATOR = Ref(" => ")
const KV_SEPARATOR = ScopedValue(" = ")
const PAIR_SEPARATOR = ScopedValue(" => ")

kv_separator_horiz((@nospecialize T::Type{<:KeyValue})) = literal(KV_SEPARATOR[])
kv_separator_vert((@nospecialize T::Type{<:KeyValue})) = literal(KV_SEPARATOR[])
Expand All @@ -34,14 +34,15 @@ kv_separator_vert((@nospecialize T::Type{<:Pair})) = literal(PAIR_SEPARATOR[])
kv_separator_horiz((@nospecialize x)) = kv_separator_horiz(typeof(x))
kv_separator_vert((@nospecialize x)) = kv_separator_vert(typeof(x))

const vector_parentheses = ("[", "]")
const set_parentheses = ("{", "}")
const dict_parentheses = ("(", ")")
const named_tuple_parentheses = ("(; ", ")")
const vector_parentheses = ScopedValue(("[", "]"))
const set_parentheses = ScopedValue(("{", "}"))
const dict_parentheses = ScopedValue(("(", ")"))
const tuple_parentheses = ScopedValue(("(", ")"))
const named_tuple_parentheses = ScopedValue(("(; ", ")"))
const empty_parentheses = ("", "")

const default_horiz_separator = ", "
const default_vert_separator = ""
const default_horiz_separator = ScopedValue(", ")
const default_vert_separator = ScopedValue("")
const empty_layout = Union{Layout, Nothing}[]

"""
Expand All @@ -53,12 +54,109 @@ macro hide_typename(ex)
return :($AutoPrettyPrinting.@with $AutoPrettyPrinting.PPRINT_SHOW_TYPENAME => false $(esc(ex)))
end

function decorators_expr(expr; horiz_separator=nothing, kv_separator=nothing, pair_separator=nothing, vector_parentheses=nothing, set_parentheses=nothing, dict_parentheses=nothing, tuple_parentheses=nothing, named_tuple_parentheses=nothing, _sourceinfo=not_provided)
@nospecialize

output_expr = Expr(:block)
m = MacroCall(; name=:($AutoPrettyPrinting.$(Symbol("@with"))), line=_sourceinfo)
if !isnothing(horiz_separator)
push!(m.args, :($AutoPrettyPrinting.default_horiz_separator => $(esc(horiz_separator))))
end
if !isnothing(kv_separator)
push!(m.args, :($AutoPrettyPrinting.KV_SEPARATOR => $(esc(kv_separator))))
end
if !isnothing(pair_separator)
push!(m.args, :($AutoPrettyPrinting.PAIR_SEPARATOR => $(esc(pair_separator))))
end
if !isnothing(vector_parentheses)
push!(m.args, :($AutoPrettyPrinting.vector_parentheses => $(esc(vector_parentheses))))
end
if !isnothing(set_parentheses)
push!(m.args, :($AutoPrettyPrinting.set_parentheses => $(esc(set_parentheses))))
end
if !isnothing(dict_parentheses)
push!(m.args, :($AutoPrettyPrinting.dict_parentheses => $(esc(dict_parentheses))))
end
if !isnothing(tuple_parentheses)
push!(m.args, :($AutoPrettyPrinting.tuple_parentheses => $(esc(tuple_parentheses))))
end
if !isnothing(named_tuple_parentheses)
push!(m.args, :($AutoPrettyPrinting.named_tuple_parentheses => $(esc(named_tuple_parentheses))))
end
push!(output_expr.args, to_expr(m(expr)))
return output_expr
end

"""
@decorators [separators=nothing] [parentheses=nothing] expr
Executes `expr` using the prescribed decorator strings specified in `kwargs`.
If provided, `separators` must be of the form `(key1=value1, ...)` and the provided values must resolve to a `String` type
If provided, `parentheses` must be of the form `(key1=value1, ...)` and the provided values must resolve to a `Tuple{String, String}` type
# Arguments
## Separators
- `horiz` - separator string used when joining items horizontally
- `kv` - separator string used when rendering `KeyValue` pairs
- `pair` - separator string used when rendering `Pair` objects
## Parentheses
- `vector` - start + end parentheses used when rendering `AbstractVector`s
- `set` - start + end parentheses used when rendering `AbstractSet`s
- `dict` - start + end parentheses used when rendering `AbstractDict` and `AbstractDictionary`s
- `tuple` - start + end parentheses used when rendering `Tuple`s
- `named_tuple` - start + end parentheses used when rendering `NamedTuple`s
"""
macro decorators(args...)
length(args) 2 || error("Must have at least two arguments")
@parse_kwargs args[1:end-1]... begin
separators::Union{Expr, Nothing} = nothing
parentheses::Union{Expr, Nothing} = nothing
end
horiz_separator = kv_separator = pair_separator = nothing
if !isnothing(separators)
f = from_expr(NamedTupleExpr, separators; throw_error=true)
if haskey(f, :horiz)
horiz_separator = f[:horiz].value
end
if haskey(f, :kv)
kv_separator = f[:kv].value
end
if haskey(f, :pair)
pair_separator = f[:pair].value
end
end
vector_parentheses = set_parentheses = dict_parentheses = tuple_parentheses = named_tuple_parentheses = nothing
if !isnothing(parentheses)
f = from_expr(NamedTupleExpr, parentheses; throw_error=true)
if haskey(f, :vector)
vector_parentheses = f[:vector].value
end
if haskey(f, :set)
set_parentheses = f[:set].value
end
if haskey(f, :dict)
dict_parentheses = f[:dict].value
end
if haskey(f, :tuple)
tuple_parentheses = f[:tuple].value
end
if haskey(f, :named_tuple)
named_tuple_parentheses = f[:named_tuple].value
end
end

return decorators_expr(esc(args[end]); horiz_separator, kv_separator, pair_separator, vector_parentheses, set_parentheses, dict_parentheses, tuple_parentheses, named_tuple_parentheses, _sourceinfo=__source__)
end


# Adapted from PrettyPrinting.jl
function list_layout_prefer_horizontal(horizontal_items::Vector{<:Union{Nothing,Layout}}, vertical_items::Vector{<:Union{Nothing,Layout}};
prefix::Union{String,Symbol,Layout}="",
parentheses::Tuple{String,String}=vector_parentheses,
horizontal_sep::String=default_horiz_separator,
vertical_sep::String=default_vert_separator,
parentheses::Union{Tuple{String,String}, ScopedValue{Tuple{String,String}}}=vector_parentheses,
horizontal_sep::String=default_horiz_separator[],
vertical_sep::String=default_vert_separator[],
sep_brk=:end,
indent_width::Int=PP_DEFAULT_INDENT_NUM_SPACES[],
break_factor::Int=1,
Expand All @@ -70,6 +168,9 @@ function list_layout_prefer_horizontal(horizontal_items::Vector{<:Union{Nothing,
if !allow_horiz && !allow_vert
allow_horiz = true
end
if parentheses isa ScopedValue
parentheses = parentheses[]
end
prefix_lt = prefix isa Layout ? prefix : literal(prefix)
header = prefix_lt * literal(parentheses[1])
parenthesis_empty = isempty(parentheses[1]) && isempty(parentheses[2])
Expand Down Expand Up @@ -233,12 +334,12 @@ for horiz in (false, true)
@eval begin
function $f_name(x::Tuple, mime::MIME; kwargs...)
if isempty(x)
return list_layout_prefer_horizontal(empty_layout; kwargs..., allow_horiz=$allow_horiz, allow_vert=$allow_vert, parentheses = dict_parentheses)
return list_layout_prefer_horizontal(empty_layout; kwargs..., allow_horiz=$allow_horiz, allow_vert=$allow_vert, parentheses = tuple_parentheses)
else
data = @pprint_values parent_is_container=true next_level=true show_typename=true begin
[$f_child(xi, mime; kwargs...) for xi in x]
end
return list_layout_prefer_horizontal(data; kwargs..., allow_horiz=$allow_horiz, allow_vert=$allow_vert, parentheses = dict_parentheses)
return list_layout_prefer_horizontal(data; kwargs..., allow_horiz=$allow_horiz, allow_vert=$allow_vert, parentheses = tuple_parentheses)
end
end
function $f_name(x::AbstractVector, mime::MIME; kwargs...)
Expand Down Expand Up @@ -314,6 +415,8 @@ function custom_tile_horiz_or_vert(o, mime::MIME; kwargs...)
end
end

custom_tile(x::Union{Pair, KeyValue}, mime::MIME; kwargs...) = custom_tile_horiz_or_vert(x, mime; kwargs...)

function custom_tile_container(x, mime::MIME; kwargs...)
@pprint_values parent_is_container=true begin
custom_tile_horiz_or_vert(x, mime; kwargs...)
Expand Down
130 changes: 5 additions & 125 deletions test/TestAutoPrettyPrinting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,138 +53,18 @@ module TestAutoPrettyPrinting
value::Int
end

@def_pprint_atomic mime_types="" AtomicType

struct CustomTileFunc
value::Int
end
@custom_tile base_show=true CustomTileFunc => (p, mime::MIME"text/plain") -> literal("CustomTileFunc($(p.value^2))")
@custom_tile base_show=true CustomTileFunc => (p, mime::MIME"text/test_auto_pretty_printing") -> literal("CustomTileFunc($(p.value-1))")


@def_pprint_atomic mime_types="" AtomicType

@testset "AutoPrettyPrinting" begin
@testset "Utilities" begin
@test_cases begin
x | output
SimpleStruct1(1) | true
SimpleStruct2(1, "abc") | true
TooManyFields(1,2,3,4,5) | false
TupleFields((1,2), (; key1=1)) | true
TupleFields((1,2,3,4,5), (; key1=1)) | false
@test AutoPrettyPrinting.is_simple_struct(x) == output
end
show_typename = AutoPrettyPrinting.PPRINT_SHOW_TYPENAME[]
@Test show_typename
show_typename = @hide_typename AutoPrettyPrinting.PPRINT_SHOW_TYPENAME[]
@Test !show_typename
show_typename = AutoPrettyPrinting.PPRINT_SHOW_TYPENAME[]
@Test show_typename

@Test AutoPrettyPrinting.normalize_mime_type_args(["text/plain"]; generic_mime=true) isa AutoPrettyPrinting.GenericMimeType
@Test AutoPrettyPrinting.normalize_mime_type_args(nothing; generic_mime=true) isa AutoPrettyPrinting.GenericMimeType
@Test AutoPrettyPrinting.normalize_mime_type_args(["text/plain"]; generic_mime=false) == [Symbol("text/plain")]
@Test AutoPrettyPrinting.normalize_mime_type_args([""]; generic_mime=false) == Symbol[]
@Test AutoPrettyPrinting.normalize_mime_type_args(nothing; generic_mime=false) == AutoPrettyPrinting.mime_types_to_generate()
end
@testset "Expr parsing" begin
@test_cases begin
expr | result
(expr=:(MIME), result=AutoPrettyPrinting.GenericMimeType())
(expr=:(MIME"text/plain"), result="text/plain")
(expr=:(MIME{Symbol("text/plain")}), result="text/plain")
(expr=:(MIME{:a}), result="a")
@test AutoPrettyPrinting.mime_from_type_expr(expr) == result
end
expr = :((p, mime)->literal(p))
obj_arg, mime_arg, tile_expr, mime_types, generic_mime = AutoPrettyPrinting.custom_tile_func_def_expr(expr, nothing, false)
@Test obj_arg == :p
@Test mime_arg == :mime
@Test tile_expr === expr.args[2]
@Test mime_types |> isnothing
@Test generic_mime == false

expr = :((p, mime::MIME)->literal(p))
obj_arg, mime_arg, tile_expr, mime_types, generic_mime = AutoPrettyPrinting.custom_tile_func_def_expr(expr, nothing, false)
@Test obj_arg == :p
@Test mime_arg == :mime
@Test tile_expr === expr.args[2]
@Test mime_types |> isnothing
@Test generic_mime == true

expr = :((_p, _mime::MIME"text/plain")->literal(_p))
obj_arg, mime_arg, tile_expr, mime_types, generic_mime = AutoPrettyPrinting.custom_tile_func_def_expr(expr, nothing, false)
@Test obj_arg == :_p
@Test mime_arg == :_mime
@Test tile_expr === expr.args[2]
@Test mime_types == ["text/plain"]
@Test generic_mime == false

end
@testset "@custom_tile" begin
s = SimpleStruct1(1)
@Test repr(mime_plain, s) == "$SimpleStruct1(1)"
@Test repr(mime_testing, s) == "2"

s = CustomTileFunc(10)
@Test repr(mime_plain, s) == "CustomTileFunc(100)"
@Test repr(mime_testing, s) == "CustomTileFunc(9)"
@Test AutoPrettyPrinting.custom_tile(s, mime_plain) == literal("CustomTileFunc(100)")
@Test AutoPrettyPrinting.custom_tile_horiz(s, mime_plain) == literal("CustomTileFunc(100)")
@Test AutoPrettyPrinting.custom_tile_vert(s, mime_plain) == literal("CustomTileFunc(100)")

@Test AutoPrettyPrinting.custom_tile(s, mime_testing) == literal("CustomTileFunc(9)")
@Test AutoPrettyPrinting.custom_tile_horiz(s, mime_testing) == literal("CustomTileFunc(9)")
@Test AutoPrettyPrinting.custom_tile_vert(s, mime_testing) == literal("CustomTileFunc(9)")

end
@testset "@def_pprint" begin
s = SimpleStruct2(1, "abc")
@Test repr(mime_plain, s) == "$SimpleStruct2(1, \"abc\")"
@Test repr(mime_testing, s) == "SimpleStruct2(key1 = 1, key2 = \"abc\")"

t = AllMimeTypes(tuple(s))
@Test repr(mime_plain, t) == "AllMimeTypes(t = ($SimpleStruct2(1, \"abc\")))"
@Test repr(mime_testing, t) == "AllMimeTypes(t = (SimpleStruct2(key1 = 1, key2 = \"abc\")))"
@Test repr(mime_testing2, t) == "AllMimeTypes - text/test_auto_pretty_printing2"

end
@testset "@def_pprint_atomic" begin
@Test AutoPrettyPrinting._is_atomic_type(AtomicType)

end
@testset "Printing" begin
@testset "Tuples + NamedTuples" begin
x = SimpleStruct1(1)
y = SimpleStruct2(2, "b")
t = tuple(x, y)
@Test repr_pretty(mime_testing, t) == "(2, SimpleStruct2(key1 = 2, key2 = \"b\"))"

z = TupleFields(t, (; key1=1:3))
# Original text/plain mime show has not been overwritten
@Test repr("text/plain", z) == """TupleFields(t = ($SimpleStruct1(1), $SimpleStruct2(2, "b")), nt = (; key1 = [1, 2, 3]))"""
@Test repr("text/test_auto_pretty_printing", z) == """TupleFields(t = (2, SimpleStruct2(key1 = 2, key2 = "b")), nt = (; key1 = [1, 2, 3]))"""

# Original text/plain mime show has not been overwritten
x = TooManyFields(1,2,3,4,5)
@Test repr("text/plain", x) == "$TooManyFields(1, 2, 3, 4, 5)"

t = (; x=x)
ref_repr = """(; x = TooManyFields(key1 = 1, key2 = 2, key3 = 3, key4 = 4, key5 = 5))"""
@Test repr("text/plain", t) == "(x = $TooManyFields(1, 2, 3, 4, 5),)"
@Test repr_pretty(t) == ref_repr
end
@testset "PrintContext" begin
x = TooManyFields(1,2,3,4,5)
io = IOBuffer()
context = PPrintContext(io)
show(context, mime_plain, x)
str = String(take!(io))
ref_repr = "TooManyFields(\n key1 = 1\n key2 = 2\n key3 = 3\n key4 = 4\n key5 = 5\n)"
@Test str == ref_repr
@Test repr_pretty(x) == ref_repr
end

end
include("tests/test_util.jl")
include("tests/test_macros.jl")
include("tests/test_printing.jl")
end

end
Loading

0 comments on commit 950dda7

Please sign in to comment.