need $join having removed the space-after-), and resetting the proto was bad
[scpubgit/Q-Branch.git] / lib / SQL / Abstract / Formatter.pm
1 package SQL::Abstract::Formatter;
2
3 require SQL::Abstract::Parts; # it loads us too, don't cross the streams
4
5 use Moo;
6
7 has indent_by => (is => 'ro', default => '  ');
8 has max_width => (is => 'ro', default => 78);
9
10 sub _join {
11   shift;
12   return SQL::Abstract::Parts::stringify(\@_);
13 }
14
15 sub format {
16   my ($self, $join, @parts) = @_;
17   $self->_fold_sql('', '', @{$self->_simplify($join, @parts)});
18 }
19
20 sub _simplify {
21   my ($self, $join, @parts) = @_;
22   return '' unless @parts;
23   return $parts[0] if @parts == 1 and !ref($parts[0]);
24   return $self->_simplify(@{$parts[0]}) if @parts == 1;
25   return [ $join, map ref() ? $self->_simplify(@$_) : $_, @parts ];
26 }
27
28 sub _fold_sql {
29   my ($self, $indent0, $indent, $join, @parts) = @_;
30   my @res;
31   my $w = $self->max_width;
32   my $join_len = 0;
33   (s/, \Z/,\n/ and $join_len = 1)
34     or s/\A /\n/
35     or $_ = "\n"
36       for my $line_join = $join;
37   my ($nl_pre, $nl_post) = split "\n", $line_join;
38   my $line_orig = my $line = $indent0;
39   my $next_indent = $indent.$self->indent_by;
40   my $line_proto = $indent.$nl_post;
41   PART: foreach my $idx (0..$#parts) {
42     my $p = $parts[$idx];
43     my $pre = ($line ne $line_orig ? $join : '');
44     my $j_part = $pre.(my $j = ref($p) ? $self->_join(@$p) : $p);
45     if (length($j_part) + length($line) + $join_len <= $w) {
46       $line .= $j_part;
47       next PART;
48     }
49     if (ref($p) and $p->[1] eq '(' and $p->[-1] eq ')') {
50       my $already = !($line eq $indent0 or $line eq $line_orig);
51       push @res, $line.($already ? $join : '').'('."\n";
52       my (undef, undef, $inner) = @$p;
53       my $folded = $self->_fold_sql($next_indent, $next_indent, @$inner);
54       $folded =~ s/\n\Z//;
55       push @res, $folded."\n";
56       $line_orig = $line
57          = $indent0.')'.($idx == $#parts ? '' : $join);
58       next PART;
59     }
60     if ($line ne $line_orig) {
61       push @res, $line.($idx == $#parts ? '' : $nl_pre)."\n";
62     }
63     if (length($line = $line_proto.$j) <= $w) {
64       next PART;
65     }
66     my $innerdent = @res ? $indent : $next_indent;
67     my $folded = $self->_fold_sql($line_proto, $innerdent, @$p);
68     $folded =~ s/\n\Z//;
69     push @res, $folded.($idx == $#parts ? '' : $nl_pre)."\n";
70     $line_orig = $line = $idx == $#parts ? '' : $line_proto;
71   }
72   return join '', @res, $line;
73 }
74
75 1;