much better indentation on traces
[scpubgit/DX.git] / lib / DX / TraceFormatter.pm
1 package DX::TraceFormatter;
2
3 use Scalar::Util qw(blessed);
4 use curry;
5 use List::Util qw(min);
6 use DX::Class;
7
8 our $WS;
9 our $Extra = 0;
10
11 has ambient_indent_level => (
12   is => 'rwp', lazy => 1, clearer => 1, default => 0
13 );
14
15 sub indent_by { '    ' }
16
17 sub max_width { 78 }
18
19 sub format {
20   my ($self, $thing) = @_;
21   local our $Indent_Level = $self->ambient_indent_level;
22   my $unindented = $self->_format($thing);
23   my $indent_level = min($Indent_Level, $self->ambient_indent_level);
24   (my $indented = $unindented)
25     =~ s/^/${\($self->indent_by x $indent_level)}/mg;
26   return $indented;
27 }
28
29 sub _format {
30   my ($self, $thing) = @_;
31   my ($as, $data) = @{blessed($thing) ? $thing->for_deparse : $thing};
32   local $WS = ' ';
33   my $spaced = $self->${\"_format_as_${as}"}($data);#
34   if ($spaced =~ /\n/
35       or (length($spaced)
36            > ($self->max_width -
37                ((length($self->indent_by) * our $Indent_Level) + $Extra)))
38   ) {
39     local $WS = "\n";
40     local $Extra = 0;
41     return $self->${\"_format_as_${as}"}($data);
42   }
43   return $spaced;
44 }
45
46 sub _format_as_word_and_body {
47   my ($self, $wb) = @_;
48   my ($word, $body) = @$wb;
49   my $word_f = $self->_format_as_maybe_bareword($word).' ';
50   local $Extra = length($word_f);
51   return $word_f.$self->_format($body);
52 }
53
54 sub _format_indented {
55   my ($self, $cb) = @_;
56   return $cb->() if $WS eq ' ';
57   our $Indent_Level;
58   local $Indent_Level = $Indent_Level + 1;
59   my $unindented = $cb->();
60   (my $indented = $unindented) =~ s/^/${\$self->indent_by}/mg;
61   return $indented;
62 }
63
64 sub _format_as_string {
65   my ($self, $val) = @_;
66   # TODO: multiline handling
67   if ($val =~ /^\w+$/) {
68     qq{'${val}'}
69   } else {
70     qq{{'${val}'}}
71   }
72 }
73
74 sub _format_as_symbol { $_[1] }
75
76 sub _format_as_maybe_bareword {
77   my ($self, $maybe_bareword) = @_;
78   # should stringify if required
79   return $maybe_bareword;
80 }
81
82 sub _format_as_number { $_[1] }
83
84 sub _format_as_boolean { $_[1] ? 'true' : 'false' }
85
86 sub _format_as_unset { 'unset' }
87
88 sub _format_as_array {
89   my ($self, $members) = @_;
90   join $WS,
91     '{[',
92     (map $self->_format_indented($self->curry::_format($_)), @$members)
93     , ']}';
94 }
95
96 sub _format_as_dict {
97   my ($self, $members) = @_;
98   join $WS, '{{', (
99     map $self->_format_indented($self->curry::_format(
100       [ word_and_body => [ $_, $members->{$_} ] ],
101     )), sort keys %$members
102   ), '}}';
103 }
104
105 sub _format_as_statement {
106   my ($self, $parts) = @_;
107   join ' ', map $self->_format($_), @$parts;
108 }
109
110 sub _format_as_value_path {
111   my ($self, $parts) = @_;
112   join '.', map $self->_format_as_maybe_bareword($_), @$parts;
113 }
114
115 sub _format_as_list {
116   my ($self, $members) = @_;
117   join $WS, '{', (
118     map $self->_format_indented($self->curry::_format($_)), @$members
119   ), '}';
120 }
121
122 sub _format_as_pairs {
123   my ($self, $members) = @_;
124   join $WS, '{', (
125     map $self->_format_indented($self->curry::_format(
126       [ word_and_body => $_ ]
127     )), @$members
128   ), '}';
129 }
130
131 sub _format_as_block {
132   my ($self, $members) = @_;
133   join $WS, '{', (
134     join +($WS eq ' ' ? '; ' : $WS),
135       map $self->_format_indented($self->curry::_format($_)), @$members
136   ), '}';
137 }
138
139 sub _format_as_enter_block {
140   my ($self) = @_;
141   $self->_set_ambient_indent_level($self->ambient_indent_level + 1);
142   '{'
143 }
144
145 sub _format_as_leave_block {
146   my ($self) = @_;
147   $self->_set_ambient_indent_level($self->ambient_indent_level - 1);
148   '}'
149 }
150
151 1;