Use {List,Sub}::Util instead of List::MoreUtils and Sub::Name
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Utils.pm
1 package # hide from PAUSE
2     DBIx::Class::Schema::Loader::Utils;
3
4 use strict;
5 use warnings;
6 use Test::More;
7 use String::CamelCase 'wordsplit';
8 use Carp::Clan qw/^DBIx::Class/;
9 use Scalar::Util 'looks_like_number';
10 use namespace::clean;
11 use Exporter 'import';
12 use Data::Dumper ();
13
14 our @EXPORT_OK = qw/split_name dumper dumper_squashed eval_package_without_redefine_warnings class_path no_warnings warnings_exist warnings_exist_silent slurp_file write_file array_eq sigwarn_silencer apply firstidx uniq/;
15
16 use constant BY_CASE_TRANSITION_V7 =>
17     qr/(?<=[[:lower:]\d])[\W_]*(?=[[:upper:]])|[\W_]+/;
18
19 use constant BY_NON_ALPHANUM =>
20     qr/[\W_]+/;
21
22 my $LF   = "\x0a";
23 my $CRLF = "\x0d\x0a";
24
25 sub split_name($;$) {
26     my ($name, $v) = @_;
27
28     my $is_camel_case = $name =~ /[[:upper:]]/ && $name =~ /[[:lower:]]/;
29
30     if ((not $v) || $v >= 8) {
31         return map split(BY_NON_ALPHANUM, $_), wordsplit($name);
32     }
33
34     return split $is_camel_case ? BY_CASE_TRANSITION_V7 : BY_NON_ALPHANUM, $name;
35 }
36
37 sub dumper($) {
38     my $val = shift;
39
40     my $dd = Data::Dumper->new([]);
41     $dd->Terse(1)->Indent(1)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1);
42     return $dd->Values([ $val ])->Dump;
43 }
44
45 sub dumper_squashed($) {
46     my $val = shift;
47
48     my $dd = Data::Dumper->new([]);
49     $dd->Terse(1)->Indent(1)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1)->Indent(0);
50     return $dd->Values([ $val ])->Dump;
51 }
52
53 # copied from DBIx::Class::_Util, import from there once it's released
54 sub sigwarn_silencer {
55     my $pattern = shift;
56
57     croak "Expecting a regexp" if ref $pattern ne 'Regexp';
58
59     my $orig_sig_warn = $SIG{__WARN__} || sub { CORE::warn(@_) };
60
61     return sub { &$orig_sig_warn unless $_[0] =~ $pattern };
62 }
63
64 # Copied with stylistic adjustments from List::MoreUtils::PP
65 sub firstidx (&@) {
66     my $f = shift;
67     foreach my $i (0..$#_) {
68         local *_ = \$_[$i];
69         return $i if $f->();
70     }
71     return -1;
72 }
73
74 sub uniq (@) {
75     my %seen = ();
76     grep { not $seen{$_}++ } @_;
77 }
78
79 sub apply (&@) {
80     my $action = shift;
81     $action->() foreach my @values = @_;
82     wantarray ? @values : $values[-1];
83 }
84
85 sub eval_package_without_redefine_warnings {
86     my ($pkg, $code) = @_;
87
88     local $SIG{__WARN__} = sigwarn_silencer(qr/^Subroutine \S+ redefined/);
89
90     # This hairiness is to handle people using "use warnings FATAL => 'all';"
91     # in their custom or external content.
92     my @delete_syms;
93     my $try_again = 1;
94
95     while ($try_again) {
96         eval $code;
97
98         if (my ($sym) = $@ =~ /^Subroutine (\S+) redefined/) {
99             delete $INC{ +class_path($pkg) };
100             push @delete_syms, $sym;
101
102             foreach my $sym (@delete_syms) {
103                 no strict 'refs';
104                 undef *{"${pkg}::${sym}"};
105             }
106         }
107         elsif ($@) {
108             die $@ if $@;
109         }
110         else {
111             $try_again = 0;
112         }
113     }
114 }
115
116 sub class_path {
117     my $class = shift;
118
119     my $class_path = $class;
120     $class_path =~ s{::}{/}g;
121     $class_path .= '.pm';
122
123     return $class_path;
124 }
125
126 sub no_warnings(&;$) {
127     my ($code, $test_name) = @_;
128
129     my $failed = 0;
130
131     my $warn_handler = $SIG{__WARN__} || sub { warn @_ };
132     local $SIG{__WARN__} = sub {
133         $failed = 1;
134         $warn_handler->(@_);
135     };
136
137     $code->();
138
139     ok ((not $failed), $test_name);
140 }
141
142 sub warnings_exist(&$$) {
143     my ($code, $re, $test_name) = @_;
144
145     my $matched = 0;
146
147     my $warn_handler = $SIG{__WARN__} || sub { warn @_ };
148     local $SIG{__WARN__} = sub {
149         if ($_[0] =~ $re) {
150             $matched = 1;
151         }
152         else {
153             $warn_handler->(@_)
154         }
155     };
156
157     $code->();
158
159     ok $matched, $test_name;
160 }
161
162 sub warnings_exist_silent(&$$) {
163     my ($code, $re, $test_name) = @_;
164
165     my $matched = 0;
166
167     local $SIG{__WARN__} = sub { $matched = 1 if $_[0] =~ $re; };
168
169     $code->();
170
171     ok $matched, $test_name;
172 }
173
174 sub slurp_file($) {
175     my $file_name = shift;
176
177     open my $fh, '<:encoding(UTF-8)', $file_name,
178         or croak "Can't open '$file_name' for reading: $!";
179
180     my $data = do { local $/; <$fh> };
181
182     close $fh;
183
184     $data =~ s/$CRLF|$LF/\n/g;
185
186     return $data;
187 }
188
189 sub write_file($$) {
190     my $file_name = shift;
191
192     open my $fh, '>:encoding(UTF-8)', $file_name,
193         or croak "Can't open '$file_name' for writing: $!";
194
195     print $fh shift;
196     close $fh;
197 }
198
199 sub array_eq($$) {
200     no warnings 'uninitialized';
201     my ($a, $b) = @_;
202
203     return unless @$a == @$b;
204
205     for (my $i = 0; $i < @$a; $i++) {
206         if (looks_like_number $a->[$i]) {
207             return unless $a->[$i] == $b->[$i];
208         }
209         else {
210             return unless $a->[$i] eq $b->[$i];
211         }
212     }
213     return 1;
214 }
215
216 1;
217 # vim:et sts=4 sw=4 tw=0: