Skip to content

Instantly share code, notes, and snippets.

@zoffixznet
Created August 25, 2016 17:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zoffixznet/552ca464a473fbbf4c6aa5710ea1c327 to your computer and use it in GitHub Desktop.
Save zoffixznet/552ca464a473fbbf4c6aa5710ea1c327 to your computer and use it in GitHub Desktop.
=begin pod
=NAME
Date::Dump::Tree - Renders data structures in a tree fashion
=SYNOPSIS
use Data::Dump::Tree ;
dump @your_data, title => 'a_title', ... ;
my $d = Data::Dump::Tree.new ;
$d.dump @your_data ;
$dumper does role { .... } ;
$d.dump @your_data ;
=DESCRIPTION
Data::Dump::Tree renders your data structures in a tree fashion.
It also
=item can display two data structures side by side
=item can display the difference between two data structures
=item can be used to "visit" a data structure and call callbacks you define
If you have Term::ANSIColor installed, the output will be so colored.
=INTERFACE
=item sub dump($structure_to_dump, named_argument, ...)
Says the dump of the data structure
=item sub get_dump($structure_to_dump, named_argument, ...)
returns a string containing the dump of the data structure
=item sub get_dump_lines($structure_to_dump, named_argument, ...)
returns an array containing the dump of the data structure
=item method dump(($structure_to_dump, named_argument, ...)
Says the dump of the data structure
=item method get_dump($structure_to_dump, named_argument, ...)
returns a string containing the dump of the data structure
=item method get_dump_lines($structure_to_dump, named_argument, ...)
returns an array containing the dump of the data structure
=USAGE
use Data::Dump::Tree ;
class MyClass { has Int $.size ; has Str $.name }
my $s = [
'text',
Rat.new(31, 10),
{
a => 1,
b => 'string',
},
MyClass.new(size => 6, name => 'P6 class'),
'aaa' ~~ m:g/(a)/,
] ;
dump $s, title => 'A complex structure', color => 0 ;
=head2 Output
A complex structure [5] @0
|- 0 = text.Str
|- 1 = 3.1 (31/10).Rat
|- 2 = {2} @1
| |- a => 1.Int
| `- b => string.Str
|- 3 = .MyClass @2
| |- $.size = 6.Str
| `- $.name = P6 class.Str
`- 4 = (3) @3
|- 0 = a[0..1]
|- 1 = a[1..2]
`- 2 = a[2..3]
=head1 DUMP
Each line of output consists 5 elements, 2 elements, the tree and the address,
are under the control of Data::Dump::Tree, The three remaining elements can
be under your control but Data::Dump::Tree provides defaults.
Refer to section 'handling specific types' to learn how to render specific
types with your own type handlers.
=head2 Elements of the dump
|- key = value .MyClass @2
=head3 tree (glyphs)
The tree portion of the output shows the relationship between the data elements.
The data is indented under its container.
You can control the color of the tree portion and if it is rendered with
ASCII, ANSI code or Unicode.
=head3 key
The key is the name of the element being displayed; in the examples above, the
container is an array; Data:Dump::Tree gives the index of the element as the
key of the element. IE: '0 =', '3 =', '4 ='
=head3 value
The value of the element being displayed; Data::Dump::Tree displays the value of
"terminal" variables, eg: Str, Int, Rat. For containers, nothing is displayed.
=head3 Types
The type of the variable with a '.' appended. IE: '.Str', '.MyClass'
Data::DumpTree will display
=item Hashes as '{x}' where x is the number of element of the hash
=item Arrays as '[x]' where x is the number of element of the Array
=item Lists as '(x)' where x is the number of element of the list
=item Maches as '[x..y|' where x..y is the match range
=head3 address
the Data::Dump::Tree address is added to every container in the form of a '@'
and an index that is incremented for heach container. If a container is found
multiple times in the output, it will be rendered once only then referred to
by '@current_address = @first_time_seen'
=head2 Configuration and Overrides
There are multiple ways to configure the Dumper. You can pass a configuration
to the dump() sub or you can create a dumper object that you configure at
creation time.
# sub interface
dump($s, title => 'text', width => 115, color => False) ;
# basic object
my $dumper = Data::Dump::Tree.new ;
# pass you configuration at every call
$dumper.get_dump($s, width => 115, color => False) ;
# configure object at creation time
my $dumper = Data::Dump::Tree.new(width => 79) ;
# use as configured
$dumper.dump($s) ;
# or with a call time configuration
$dumper.dump($s, width => 115, max_depth => 3) ;
# see roles for roles configuration
=head3 colors
=head4 $color = True
By default coloring is on if Term::ANSIColor is installed.
Setting this option to False forces the output to be monochrome.
=head4 %colors
You can pass your own colors. The default are:
%.colors =
<
ddt_address blue perl_address yellow link green
header magenta key cyan value reset
wrap yellow
glyph_0 yellow glyph_1 reset glyph_2 green glyph_3 red
> ;
where colors are ANSI colors. I<reset> means default color.
By default the glyphs will not be colored. For certain renderings, with many
and very long continuation lines, having colored glyphs (per level) helps
greatly.
=head4 $color_glyphs
Will set a default glyph color cycle. you can also define your own with
B<@glyph_colors>.
my @s = [ ... ] ;
# monochrome glyphs
dump(@s) ;
# colored glyphs, will cycle
dump(@s, color_glyphs => True) ; #uses < glyph_0 glyph_1 glyph_2 glyph_3 >
=head4 @glyph_colors
my @s = [ ... ] ;
# monochrome glyphs
dump(@s) ;
# colored glyphs, will cycle (note that the colors must be defined)
dump(@s, glyph_colors => < glyph_0 glyph_1 glyph_2 glyph_3 >) ;
=head3 $width = terminal width
Note that the width you pass is reduced with the width of the glyps as we use
that space when displaying multiline values in the dump.
When using ANSI colors, the last character of each wrapped line is colored.
=head3 $max_depth
Limit the depth of a dump to the option. by default there is no limit.
=head3 $max_depth_message = True
A line showing that you have reached the $max_depth limit, setting this flag
to false disable the message.
=head3 $display_info = True
By default, this option is set. When set to false, neither the type not the
address are displayed.
=head3 $display_type = True
By default this option is set.
=head3 $display_address = True
By default this option is set.
=head3 $display_perl_address = False
Display the internal address of the objects.
=head3 ANSI vs ASCII vs Unicode tree drawing
The tree is draw with ANSI codes, if possible, otherwise in ASCII. See roles
AsciiGyphs and UnicodeGlyphs.
=head3 handling specific types
This section will show you how to write specific handlers in the classes that
you create and to create a custom rendering for a specific class, even if it
is not under your control.
=head4 you own classes
When Data::Dump::Tree renders an object, it first checks if it has an internal
handler for that type; if no handler is found, the object is queried and its
handler is used, if none; finally, Data::Dump::Tree uses a generic handler.
It is important to understand that you precisely control how your class is
rendered, You can even return completely different data than the one contained
in your class.
Data::Dump::Tree uses two methods, you can define either one or both.
=head5 ddt_get_header, code is defined in your class
method ddt_get_header
{
# some comment, usually blank # class type
"something about this class", '.' ~ self.^name
}
=head5 ddt_get_elements, code is defined in your class
method ddt_get_elements
{
#key #binder #value
(1, ' = ', 'has no name'),
(3, ' => ', 'abc'),
('attribute', ': ', '' ~ 1),
('sub object', '--> ', [1 .. 3]),
('from sub', '', something()),
}
In the type handler, you can:
=item Remove/add elements
=item Change the keys, values description
If your keys or values are text string and they contain embedded "\n",
Data::Dump::Tree will display them on multiple lines. See the Role section.
The module tests, and examples diretory, and
Data::Dump::Tree::DescribeBaseobjects are a good place to look at for more
examples.
=head4 classes defined by someone else and base types
You can not add methods to classes that you do not control. Data::Dump::Tree
has handlers, via roles, that it uses to display the elements of the structure
you pass to dump(). You can override those handler and add new handlers.
=item get_header
=item get_elements
Both work in the same fashion.
role your_hash_handler
{
# ..................... Type you want to handle is Hash
# ||||
# vvvv
multi method get_header (Hash $h)
{ '', '{' ~ $h.elems ~ '}' }
multi method get_elements (Hash $h)
{ $h.sort(*.key)>>.kv.map: -> ($k, $v) {$k, ' => ', $v} }
}
To make that handler active, make your dumper do the role
# using 'does'
my $d = Data::Dump::Tree.new(width => 80) ;
$d does your_hash_handler ;
$d.dump(@your_data) ;
# by passing roles to the constructor
my $d = Data::Dump::Tree.new(does => (DDTR::MatchDetails, your_hash_handler)) ;
# by passing roles to dump() method
my $d = Data::Dump::Tree.new ;
$d.dump($m, does => ( DDTR::MatchDetails, your_hash_handler) ) ;
# by passing roles to dump() sub
dump($m, does => ( DDTR::MatchDetails, your_hash_handler) ) ;
=head4 FINAL elements
So far we have seen how to render containers but sometimes we want to handle a
type as if it was a Str or an Int, EG: not display its elements but instead
display it on a single line.
You can, in a handler, tell Data::Dump::Tree that a type rendering is DDT_FINAL.
The default role has a handler that is specific for the Rat class. Rather than
show a floating number, as "say $rat;" would, or render the Rat type with it's
attributes, we display the Rat on a single line. Here is the handler:
multi method get_header (Rat $r)
{
# the rendering of the Rat
$r ~ ' (' ~ $r.numerator ~ '/' ~ $r.denominator ~ ')',
#its type
'.' ~ $r.^name,
# optional hint to DDT that this is final
DDT_FINAL,
# optional hint to DDT that this is has an address or not
DDT_HAS_ADDRESS,
}
=head3 handling specific objects
In the previous section we discussed rendering types with specific handler.
The handlers apply to all the objects of the type. Sometimes you want to
handle instances differently. An example would be having three Hashes,
the Hash handler will display each key and it's value; If you want to handle
the first Hash and the third Hash differently, you can write a filter.
To pass a filter to the dumper:
dump(
$s,
header_filters => (&header_filter, ...),
elements_filters => (&elements_filter,),
footer_filters => (&footer_filters,),
) ;
Filters are called three times during the rendering of each object.
=item DDT_HEADER
This is called just after the type's U<get_header> is called, this allows you, EG,
to insert something in the tree rendering
sub header_filter(
$r, # replacement
$s, # "read only" object
($depth, $path, $glyph, @renderings), # info about tree
($k, $b, $v, $f, $final, $want_address) # element info
)
{
# Add something to the tree
@renderings.append: $glyph ~ "HEADER" ;
}
or change the B<rendering> of the object
sub header_filter(
\r, # replacement
Int $s, # filter Ints
($depth, $path, $glyph, @renderings), # info about the tree
(\k, \b, \v, \f, \final, \want_address) # reference, can change
)
{
@renderings.append: $glyph ~ "Int HEADER " ~ $depth ;
# in this trivial example we are replacing Ints by Hashes
# since the Hash we use also contains an Int, we make sure we don't
# recurs indefinitely
if $depth < 3
{
r = {a => 'str', b => 1 } ;
# k, b, v, f are text
k = k ~ ' will be replaced by Hash ' ;
#b = '' ;
#v = '' ;
#f = '' ;
final = DDT_NOT_FINAL ;
want_address = True ;
}
}
Note: You can not filter elements of type U<Mu> with DDT_HEADER filters but
you can in DDT_SUB_ELEMENTS filters.
=item DDT_SUB_ELEMENTS
Called after the type's U<get_elements> ; you can change the elements.
sub elements_filter(
Hash $s,
($depth, $glyph, @renderings),
@sub_elements
)
{
@renderings.append: $glyph ~ "SUB ELEMENTS" ;
@sub_elements = (('key', ' => ', 'value'), ('other_key', ': ', 1)) ;
}
=item DDT_FOOTER
Called when the element rendering is done.
sub footer_filter($s, ($depth, $filter_glyph, @renderings))
{
@renderings.append: $filter_glyph ~ "FOOTER for {$s.^name}" ;
}
=head2 Data::Dump::Tree::Type::Nothing
You can use this type to make DDT "vanish" some parts of a rendering
=head3 Type element with only a name
If you return a Data::Dump::Tree::Type::Nothing in a type handler, then only
the key will be displayed.
method ddt_get_elements
{
[
...
('your key', '', Data::Dump::Tree::Type::Nothing),
...
]
}
=head3 Filtering an element away
If you return a Data::Dump::Tree::Type::Nothing replacement in your filter,
the element will not be displayed at all.
sub my_filter(\r, Tomatoe $s, $, $)
{
r = Data::Dump::Tree::Type::Nothing ;
}
As DDT streams the rendering, it can not go back to fix the glyphs when an
element is filtered away, this will probably show as slightly wrong glyph
lines. Nevertheless, when duping big structures which contains elements you
don't want to see, this is an easy an effective manner; the other, better,
ways are
=item Create a type handler
You can minimize the rendering of the type.
See I<Type element with only a name> above.
=item Create a filter for containers
if the element you don't want to see only appears in some containers, you can
create a type handler, or filter, for that container type and weed out any
reference to the element you don't want to see. This will draw proper glyph
lines as the element, you don't want to see, is never seen by DDT.
=head2 Roles provided with Data::Dump::Tree
Data::Dump::Tree comes with a few extra roles that are not "played" by the
object returned by new()
Please feel free to send me roles you think would be useful to other and that
you believe fit well in the same bundle.
You are welcome to make your own distribution too, I recommend using namespace
DDTR::YourRole.
=item DDTR::AsciiGlyphs
Uses ASCII codes rather than ANSI to render the tree.
=item DDTR::UnicodeGlyphs
Uses Unicode to render the tree. this is the tightest rendering as only one
character is used to display the tree.
=item DDTR::PerlString
Renders string containing control codes (eg: \n, ANSI, ...) with backslashed
codes and hex values.
=item DDTR::PerlSub
Sub objects will be rendered with .perl
=item DDTR::FixedGlyphs
Replace all the glyphs by a single glyph; default is a three spaces glyph.
my $d = Data::Dump::Tree.new does DDTR::FixedGlyphs(' . ') ;
=item DDTR::NumberedLevel
Will add the level of the elements to the tree glyphs, useful with huge trees.
If a Superscribe role is on, the level umber will also be superscribed
=item DDTR::SuperscribeType
Use this role to display the type in Unicode superscript letters.
=item DDTR::SuperscribeAddress
Use this role to display the address in Unicode superscript letters.
You can also use the method it provides in your type handlers and filters.
=item DDTR::Superscribe
Use this role to display the type and address in Unicode superscript letters.
=head3 Match objects
U<Match> objects are displayed as the string that it matched as well as the
match start and end position.
# multiline match
aaaaa
aaaaa
aaaaa
aaaaa
[0..24|
# same match with PerlString role
"'aaaaa\naaaaa\naaaaa\naaaaa\n'[0..24|"
Some Roles are provided to allows you to change how Match object are displayed.
=item DDTR::MatchLimitString
Limits the length of the match string.
# default max length of 10 characters
aaaaa\naaaa(+14)[0..24|
You can set the maximum string length either by specifying a length when the
role is added to the dumper.
$dumper does DDTR::MatchLimit(15) ;
aaaaa\naaaaa\n(+12)'[0..24|
or by setting the U<$.match_string_limit> member variable
$dumper does DDTR::MatchLimit ;
...
$dumper.match_string_limit = 15 ;
aaaaa\naaaaa\n(+12)[0..24|
The specified length is displayed, the length of the remaining part is
displayed within parenthesis.
=item DDTR::MatchDetails
Give complete details about a Match. The match string is displayed as well as
the match start and end position.
You can set the maximum string length either by specifying a length when the
role is added to the dumper or by setting the U<$.match_string_limit> member
variable.
# from examples/named_captures.pl
$dumper does (DDTR::MatchDetails, DDTR::FixedGlyphs) ;
config [2]
0 = \{ \\s* '[' (\\w+) ']' \\h* \\n+ }.Regex
1 = [passwords]\n jack=password1\n (+74)[0..114|
section => [passwords]\n jack=password1\n (+29)[0..69|
header => [passwords]\n[0..16|
0 => passwords[5..14|
kvpair => jack=password1\n[16..39|
identifier => jack[24..28|
key => jack[24..28|
identifier => password1[29..38|
value => password1[29..38|
kvpair => joy=muchmoresecure123\n[39..69|
identifier => joy[47..50|
key => joy[47..50|
=head1 BUGS
Submit bugs (preferably as executable tests) and feel free to make
suggestions.
This module will mature with time, and with your suggestions, it
is intended to replace my P5 Data::TreeDumper module for P6.
=head2 Dumper Exception
As this module uses the MOP interface, it happens that it may use interfaces
not implemented by some internal classes.
An example is Grammar that I tried to dump and got an exception about a class
that I didn't even know existed.
Those exception are caught and displayed by the dumper as
"DDT Exception: the_caught_exception_class"
Please let me know about them so I can add the necessary handlers to the
distribution.
=AUTHOR
Nadim ibn hamouda el Khemir
https://github.com/nkh
=LICENSE
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl6 itself.
=SEE-ALSO
Data::Dump
=end pod
DOC INIT {use Pod::To::Text ; pod2text($=pod) ; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment