Filename | /home/hejohns/perl5/lib/perl5/Data/Printer/Config.pm |
Statements | Executed 89 statements in 1.58ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 43µs | 208µs | _get_first_rc_file_available | Data::Printer::Config::
1 | 1 | 1 | 21µs | 78µs | _project_home | Data::Printer::Config::
1 | 1 | 1 | 21µs | 21µs | CORE:glob (opcode) | Data::Printer::Config::
9 | 2 | 1 | 19µs | 19µs | CORE:ftfile (opcode) | Data::Printer::Config::
1 | 1 | 1 | 9µs | 11µs | BEGIN@2 | Data::Printer::Config::
1 | 1 | 1 | 6µs | 27µs | _my_home | Data::Printer::Config::
1 | 1 | 1 | 5µs | 6µs | BEGIN@4 | Data::Printer::Config::
1 | 1 | 1 | 4µs | 10µs | _my_cwd | Data::Printer::Config::
1 | 1 | 1 | 3µs | 23µs | BEGIN@3 | Data::Printer::Config::
1 | 1 | 1 | 3µs | 5µs | load_rc_file | Data::Printer::Config::
1 | 1 | 1 | 2µs | 2µs | CORE:ftis (opcode) | Data::Printer::Config::
1 | 1 | 1 | 300ns | 300ns | __ANON__ (xsub) | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | __ANON__[:267] | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | __ANON__[:90] | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | _convert | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | _expand_profile | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | _file_mode_is_restricted | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | _merge_options | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | _str2data | Data::Printer::Config::
0 | 0 | 0 | 0s | 0s | convert | Data::Printer::Config::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package Data::Printer::Config; | ||||
2 | 2 | 18µs | 2 | 14µs | # spent 11µs (9+3) within Data::Printer::Config::BEGIN@2 which was called:
# once (9µs+3µs) by Data::Printer::BEGIN@6 at line 2 # spent 11µs making 1 call to Data::Printer::Config::BEGIN@2
# spent 3µs making 1 call to strict::import |
3 | 2 | 13µs | 2 | 42µs | # spent 23µs (3+19) within Data::Printer::Config::BEGIN@3 which was called:
# once (3µs+19µs) by Data::Printer::BEGIN@6 at line 3 # spent 23µs making 1 call to Data::Printer::Config::BEGIN@3
# spent 19µs making 1 call to warnings::import |
4 | 2 | 1.37ms | 2 | 6µs | # spent 6µs (5+300ns) within Data::Printer::Config::BEGIN@4 which was called:
# once (5µs+300ns) by Data::Printer::BEGIN@6 at line 4 # spent 6µs making 1 call to Data::Printer::Config::BEGIN@4
# spent 300ns making 1 call to Data::Printer::Config::__ANON__ |
5 | |||||
6 | # spent 5µs (3+2) within Data::Printer::Config::load_rc_file which was called:
# once (3µs+2µs) by Data::Printer::_initialize at line 50 of Data/Printer.pm | ||||
7 | 1 | 400ns | my ($filename) = @_; | ||
8 | 1 | 200ns | if (!$filename) { | ||
9 | $filename = _get_first_rc_file_available(); | ||||
10 | } | ||||
11 | 1 | 5µs | 1 | 2µs | return unless $filename && -e $filename && !-d $filename; # spent 2µs making 1 call to Data::Printer::Config::CORE:ftis |
12 | if (open my $fh, '<', $filename) { | ||||
13 | |||||
14 | # slurp the file: | ||||
15 | my $rc_data; | ||||
16 | { local $/ = undef; $rc_data = <$fh> } | ||||
17 | close $fh; | ||||
18 | return _str2data($filename, $rc_data); | ||||
19 | } | ||||
20 | else { | ||||
21 | Data::Printer::Common::_warn(undef, "error opening '$filename': $!"); | ||||
22 | return; | ||||
23 | } | ||||
24 | } | ||||
25 | |||||
26 | # spent 208µs (43+165) within Data::Printer::Config::_get_first_rc_file_available which was called:
# once (43µs+165µs) by Data::Printer::_initialize at line 49 of Data/Printer.pm | ||||
27 | 1 | 400ns | return $ENV{DATAPRINTERRC} if exists $ENV{DATAPRINTERRC}; | ||
28 | |||||
29 | # look for a .dataprinter file on the project home up until we reach '/' | ||||
30 | 1 | 500ns | 1 | 78µs | my $dir = _project_home(); # spent 78µs making 1 call to Data::Printer::Config::_project_home |
31 | 1 | 200ns | require File::Spec; | ||
32 | 1 | 200ns | while (defined $dir) { | ||
33 | 8 | 33µs | 32 | 39µs | my $file = File::Spec->catfile($dir, '.dataprinter'); # spent 26µs making 8 calls to File::Spec::Unix::catfile, avg 3µs/call
# spent 10µs making 8 calls to File::Spec::Unix::catdir, avg 1µs/call
# spent 3µs making 16 calls to File::Spec::Unix::canonpath, avg 188ns/call |
34 | 8 | 18µs | 8 | 13µs | return $file if -f $file; # spent 13µs making 8 calls to Data::Printer::Config::CORE:ftfile, avg 2µs/call |
35 | 8 | 7µs | 8 | 6µs | my @path = File::Spec->splitdir($dir); # spent 6µs making 8 calls to File::Spec::Unix::splitdir, avg 725ns/call |
36 | 8 | 600ns | last unless @path; | ||
37 | 8 | 21µs | 16 | 13µs | my $updir = File::Spec->catdir(@path[0..$#path-1]); # spent 11µs making 8 calls to File::Spec::Unix::catdir, avg 1µs/call
# spent 2µs making 8 calls to File::Spec::Unix::canonpath, avg 212ns/call |
38 | 8 | 2µs | last if !defined $updir || $updir eq $dir; | ||
39 | 7 | 3µs | $dir = $updir; | ||
40 | } | ||||
41 | # still here? look for .dataprinter on the user's HOME: | ||||
42 | 1 | 8µs | 5 | 35µs | return File::Spec->catfile( _my_home(), '.dataprinter'); # spent 27µs making 1 call to Data::Printer::Config::_my_home
# spent 5µs making 1 call to File::Spec::Unix::catfile
# spent 2µs making 1 call to File::Spec::Unix::catdir
# spent 400ns making 2 calls to File::Spec::Unix::canonpath, avg 200ns/call |
43 | } | ||||
44 | |||||
45 | # spent 10µs (4+6) within Data::Printer::Config::_my_cwd which was called:
# once (4µs+6µs) by Data::Printer::Config::_project_home at line 66 | ||||
46 | 1 | 200ns | require Cwd; | ||
47 | 1 | 9µs | 1 | 6µs | my $cwd = Cwd::getcwd(); # spent 6µs making 1 call to Cwd::getcwd |
48 | # try harder if we can't access the current dir. | ||||
49 | 1 | 100ns | $cwd = Cwd::cwd() unless defined $cwd; | ||
50 | 1 | 2µs | return $cwd; | ||
51 | } | ||||
52 | |||||
53 | # spent 78µs (21+56) within Data::Printer::Config::_project_home which was called:
# once (21µs+56µs) by Data::Printer::Config::_get_first_rc_file_available at line 30 | ||||
54 | 1 | 700ns | require Cwd; | ||
55 | 1 | 100ns | my $path; | ||
56 | 1 | 800ns | if ($0 eq '-e' || $0 eq '-') { | ||
57 | my $cwd = _my_cwd(); | ||||
58 | $path = Cwd::abs_path($cwd) if defined $cwd; | ||||
59 | } | ||||
60 | else { | ||||
61 | 1 | 300ns | my $script = $0; | ||
62 | 1 | 10µs | 1 | 6µs | return unless -f $script; # spent 6µs making 1 call to Data::Printer::Config::CORE:ftfile |
63 | 1 | 500ns | require File::Spec; | ||
64 | 1 | 400ns | require File::Basename; | ||
65 | # we need the full path if we have chdir'd: | ||||
66 | 1 | 18µs | 6 | 27µs | $script = File::Spec->catfile(_my_cwd(), $script) # spent 10µs making 1 call to Data::Printer::Config::_my_cwd
# spent 9µs making 1 call to File::Spec::Unix::catfile
# spent 4µs making 1 call to File::Spec::Unix::file_name_is_absolute
# spent 2µs making 1 call to File::Spec::Unix::catdir
# spent 1µs making 2 calls to File::Spec::Unix::canonpath, avg 550ns/call |
67 | unless File::Spec->file_name_is_absolute($script); | ||||
68 | 1 | 2µs | 1 | 14µs | my (undef, $maybe_path) = File::Basename::fileparse($script); # spent 14µs making 1 call to File::Basename::fileparse |
69 | 1 | 15µs | 1 | 13µs | $path = Cwd::abs_path($maybe_path) if defined $maybe_path; # spent 13µs making 1 call to Cwd::abs_path |
70 | } | ||||
71 | 1 | 2µs | return $path; | ||
72 | } | ||||
73 | |||||
74 | # adapted from File::HomeDir && File::HomeDir::Tiny | ||||
75 | # spent 27µs (6+21) within Data::Printer::Config::_my_home which was called:
# once (6µs+21µs) by Data::Printer::Config::_get_first_rc_file_available at line 42 | ||||
76 | 1 | 400ns | my ($testing) = @_; | ||
77 | 1 | 2µs | if ($testing) { | ||
78 | require File::Temp; | ||||
79 | require File::Spec; | ||||
80 | my $BASE = File::Temp::tempdir( CLEANUP => 1 ); | ||||
81 | my $home = File::Spec->catdir( $BASE, 'my_home' ); | ||||
82 | $ENV{HOME} = $home; | ||||
83 | mkdir($home, 0755) unless -d $home; | ||||
84 | return $home; | ||||
85 | } | ||||
86 | elsif ($^O eq 'MSWin32' and "$]" < 5.016) { | ||||
87 | return $ENV{HOME} || $ENV{USERPROFILE}; | ||||
88 | } | ||||
89 | elsif ($^O eq 'MacOS') { | ||||
90 | my $error = _tryme(sub { require Mac::SystemDirectory; 1 }); | ||||
91 | return Mac::SystemDirectory::HomeDirectory() unless $error; | ||||
92 | } | ||||
93 | # this is the most common case, for most breeds of unix, as well as | ||||
94 | # MSWin32 in more recent perls. | ||||
95 | 1 | 24µs | 1 | 21µs | my $home = (<~>)[0]; # spent 21µs making 1 call to Data::Printer::Config::CORE:glob |
96 | 1 | 2µs | return $home if $home; | ||
97 | |||||
98 | # desperate measures that should never be needed. | ||||
99 | if (exists $ENV{LOGDIR} and $ENV{LOGDIR}) { | ||||
100 | $home = $ENV{LOGDIR}; | ||||
101 | } | ||||
102 | if (not $home and exists $ENV{HOME} and $ENV{HOME}) { | ||||
103 | $home = $ENV{HOME}; | ||||
104 | } | ||||
105 | # Light desperation on any (Unixish) platform | ||||
106 | SCOPE: { $home = (getpwuid($<))[7] if not defined $home } | ||||
107 | if (defined $home and ! -d $home ) { | ||||
108 | $home = undef; | ||||
109 | } | ||||
110 | return $home; | ||||
111 | } | ||||
112 | |||||
113 | sub _file_mode_is_restricted { | ||||
114 | my ($filename) = @_; | ||||
115 | my $mode_raw = (stat($filename))[2]; | ||||
116 | return 0 unless defined $mode_raw; | ||||
117 | my $mode = sprintf('%04o', $mode_raw & 07777); | ||||
118 | return (length($mode) == 4 && substr($mode, 2, 2) eq '00') ? 1 : 0; | ||||
119 | } | ||||
120 | |||||
121 | sub _str2data { | ||||
122 | my ($filename, $content) = @_; | ||||
123 | my $config = { _ => {} }; | ||||
124 | my $counter = 0; | ||||
125 | my $filter; | ||||
126 | my $can_use_filters; | ||||
127 | my $ns = '_'; | ||||
128 | # based on Config::Tiny | ||||
129 | foreach ( split /(?:\015{1,2}\012|\015|\012)/, $content ) { | ||||
130 | $counter++; | ||||
131 | if (defined $filter) { | ||||
132 | if ( /^end filter\s*$/ ) { | ||||
133 | if (!defined $can_use_filters) { | ||||
134 | $can_use_filters = _file_mode_is_restricted($filename); | ||||
135 | } | ||||
136 | if ($can_use_filters) { | ||||
137 | my $sub_str = 'sub { my ($obj, $ddp) = @_; ' | ||||
138 | . $filter->{code_str} | ||||
139 | . '}' | ||||
140 | ; | ||||
141 | push @{$config->{$ns}{filters}}, +{ $filter->{name} => eval $sub_str }; | ||||
142 | } | ||||
143 | else { | ||||
144 | Data::Printer::Common::_warn(undef, "ignored filter '$filter->{name}' from rc file '$filename': file is readable/writeable by others"); | ||||
145 | } | ||||
146 | $filter = undef; | ||||
147 | } | ||||
148 | elsif ( /^begin\s+filter/ ) { | ||||
149 | Data::Printer::Common::_warn(undef, "error reading rc file '$filename' line $counter: found 'begin filter' inside another filter definition ($filter->{name}). Are you missing an 'end filter' on line " . ($counter - 1) . '?'); | ||||
150 | return {}; | ||||
151 | } | ||||
152 | else { | ||||
153 | $filter->{code_str} .= $_; | ||||
154 | } | ||||
155 | } | ||||
156 | elsif ( /^\s*(?:\#|\;|$)/ ) { | ||||
157 | next # skip comments and empty lines | ||||
158 | } | ||||
159 | elsif ( /^\s*\[\s*(.+?)\s*\]\s*$/ ) { | ||||
160 | # Create the sub-hash if it doesn't exist. | ||||
161 | # Without this, sections without keys will not | ||||
162 | # appear at all in the completed struct. | ||||
163 | $config->{$ns = $1} ||= {}; | ||||
164 | } | ||||
165 | elsif ( /^\s*([^=]+?)\s*=\s*(.*?)\s*$/ ) { | ||||
166 | # Handle properties: | ||||
167 | my ($path_str, $value) = ($1, $2); | ||||
168 | # turn a.b.c.d into {a}{b}{c}{d} | ||||
169 | my @subpath = split /\./, $path_str; | ||||
170 | my $current = $config->{$ns}; | ||||
171 | |||||
172 | # remove single/double (enclosing) quotes | ||||
173 | $value =~ s/\A(['"])(.*)\1\z/$2/; | ||||
174 | |||||
175 | # the root "filters" key is a special case, because we want | ||||
176 | # it to always be an arrayref. In other words: | ||||
177 | # filters = abc,def --> filters => ['abc', 'def'] | ||||
178 | # filters = abc --> filters => ['abc'] | ||||
179 | # filters = --> filters => [] | ||||
180 | if (@subpath == 1 && $subpath[0] eq 'filters') { | ||||
181 | $value = [ split /\s*,\s*/ => $value ]; | ||||
182 | } | ||||
183 | |||||
184 | while (my $subpath = shift @subpath) { | ||||
185 | if (@subpath > 0) { | ||||
186 | $current->{$subpath} ||= {}; | ||||
187 | $current = $current->{$subpath}; | ||||
188 | } | ||||
189 | else { | ||||
190 | $current->{$subpath} = $value; | ||||
191 | } | ||||
192 | } | ||||
193 | } | ||||
194 | elsif ( /^begin\s+filter\s+([^\s]+)\s*$/ ) { | ||||
195 | my $filter_name = $1; | ||||
196 | $filter = { name => $filter_name, code_str => '' }; | ||||
197 | } | ||||
198 | else { | ||||
199 | Data::Printer::Common::_warn(undef, "error reading rc file '$filename': syntax error at line $counter: $_"); | ||||
200 | if ($counter == 1 && /\A\s*\{/s) { | ||||
201 | Data::Printer::Common::_warn( | ||||
202 | undef, | ||||
203 | "\nRC file format changed in 1.00. Usually all it takes is:\n" | ||||
204 | . " cp $filename $filename.old && perl -MData::Printer::Config -E 'say Data::Printer::Config::convert(q($filename.old))' > $filename\n" | ||||
205 | . "Please visit https://metacpan.org/pod/Data::Printer::Config for details.\n" | ||||
206 | ); | ||||
207 | } | ||||
208 | return {}; | ||||
209 | } | ||||
210 | } | ||||
211 | # now that we have loaded the config, we must expand | ||||
212 | # all existing 'rc_file' and 'profile' statements and | ||||
213 | # merge them together. | ||||
214 | foreach my $ns (keys %$config) { | ||||
215 | $config->{$ns} = _expand_profile($config->{$ns}) | ||||
216 | if exists $config->{$ns}{profile}; | ||||
217 | } | ||||
218 | return $config; | ||||
219 | } | ||||
220 | |||||
221 | sub _merge_options { | ||||
222 | my ($old, $new) = @_; | ||||
223 | if (ref $new eq 'HASH') { | ||||
224 | my %merged; | ||||
225 | my $to_merge = ref $old eq 'HASH' ? $old : {}; | ||||
226 | foreach my $k (keys %$new, keys %$to_merge) { | ||||
227 | # if the key exists in $new, we recurse into it: | ||||
228 | if (exists $new->{$k}) { | ||||
229 | $merged{$k} = _merge_options($to_merge->{$k}, $new->{$k}); | ||||
230 | } | ||||
231 | else { | ||||
232 | # otherwise we keep the old version (recursing in case of refs) | ||||
233 | $merged{$k} = _merge_options(undef, $to_merge->{$k}); | ||||
234 | } | ||||
235 | } | ||||
236 | return \%merged; | ||||
237 | } | ||||
238 | elsif (ref $new eq 'ARRAY') { | ||||
239 | # we'll only use the array on $new, but we still need to recurse | ||||
240 | # in case array elements contain other data structures. | ||||
241 | my @merged; | ||||
242 | foreach my $element (@$new) { | ||||
243 | push @merged, _merge_options(undef, $element); | ||||
244 | } | ||||
245 | return \@merged; | ||||
246 | } | ||||
247 | else { | ||||
248 | return $new; | ||||
249 | } | ||||
250 | } | ||||
251 | |||||
252 | |||||
253 | sub _expand_profile { | ||||
254 | my ($options, $ddp) = @_; | ||||
255 | my $profile = delete $options->{profile}; | ||||
256 | if ($profile !~ /\A[a-zA-Z0-9:]+\z/) { | ||||
257 | Data::Printer::Common::_warn($ddp,"invalid profile name '$profile'"); | ||||
258 | } | ||||
259 | else { | ||||
260 | my $class = 'Data::Printer::Profile::' . $profile; | ||||
261 | my $error = Data::Printer::Common::_tryme(sub { | ||||
262 | my $load_error = Data::Printer::Common::_tryme("use $class; 1;"); | ||||
263 | die $load_error if defined $load_error; | ||||
264 | my $expanded = $class->profile(); | ||||
265 | die "profile $class did not return a HASH reference" unless ref $expanded eq 'HASH'; | ||||
266 | $options = Data::Printer::Config::_merge_options($expanded, $options); | ||||
267 | }); | ||||
268 | if (defined $error) { | ||||
269 | Data::Printer::Common::_warn($ddp, "unable to load profile '$profile': $error"); | ||||
270 | } | ||||
271 | } | ||||
272 | return $options; | ||||
273 | } | ||||
274 | |||||
- - | |||||
278 | # converts the old format to the new one | ||||
279 | sub convert { | ||||
280 | my ($filename) = @_; | ||||
281 | Data::Printer::Common::_die("please provide a .dataprinter file path") | ||||
282 | unless $filename; | ||||
283 | Data::Printer::Common::_die("file '$filename' not found") | ||||
284 | unless -e $filename && !-d $filename; | ||||
285 | open my $fh, '<', $filename | ||||
286 | or Data::Printer::Common::_die("error reading file '$filename': $!"); | ||||
287 | |||||
288 | my $rc_data; | ||||
289 | { local $/; $rc_data = <$fh> } | ||||
290 | close $fh; | ||||
291 | |||||
292 | my $config = eval $rc_data; | ||||
293 | if ( $@ ) { | ||||
294 | Data::Printer::Common::_die("error loading file '$filename': $@"); | ||||
295 | } | ||||
296 | elsif (!ref $config or ref $config ne 'HASH') { | ||||
297 | Data::Printer::Common::_die("error loading file '$filename': config file must return a hash reference"); | ||||
298 | } | ||||
299 | else { | ||||
300 | return _convert('', $config); | ||||
301 | } | ||||
302 | } | ||||
303 | |||||
304 | sub _convert { | ||||
305 | my ($key_str, $value) = @_; | ||||
306 | if (ref $value eq 'HASH') { | ||||
307 | $key_str = 'colors' if $key_str eq 'color'; | ||||
308 | my $str = ''; | ||||
309 | foreach my $k (sort keys %$value) { | ||||
310 | $str .= _convert(($key_str ? "$key_str.$k" : $k), $value->{$k}); | ||||
311 | } | ||||
312 | return $str; | ||||
313 | } | ||||
314 | if ($key_str && $key_str eq 'filters.-external' && ref $value eq 'ARRAY') { | ||||
315 | return 'filters = ' . join(', ' => @$value) . "\n"; | ||||
316 | } | ||||
317 | elsif (ref $value) { | ||||
318 | Data::Printer::Common::_warn( | ||||
319 | undef, | ||||
320 | " [*] path '$key_str': expected scalar, found " . ref($value) | ||||
321 | . ". Filters must be in their own class now, loaded with 'filter'.\n" | ||||
322 | . "If you absolutely must put custom filters in, use the 'begin filter'" | ||||
323 | . " / 'end filter' options manually, as explained in the documentation," | ||||
324 | . " making sure your .dataprinter file is not readable nor writeable to" | ||||
325 | . " anyone other than your user." | ||||
326 | ); | ||||
327 | return ''; | ||||
328 | } | ||||
329 | else { | ||||
330 | $value = "'$value'" if $value =~ /\s/; | ||||
331 | return "$key_str = $value\n"; | ||||
332 | } | ||||
333 | } | ||||
334 | |||||
335 | 1 | 2µs | 1; | ||
336 | __END__ | ||||
sub Data::Printer::Config::CORE:ftfile; # opcode | |||||
# spent 2µs within Data::Printer::Config::CORE:ftis which was called:
# once (2µs+0s) by Data::Printer::Config::load_rc_file at line 11 | |||||
# spent 21µs within Data::Printer::Config::CORE:glob which was called:
# once (21µs+0s) by Data::Printer::Config::_my_home at line 95 | |||||
# spent 300ns within Data::Printer::Config::__ANON__ which was called:
# once (300ns+0s) by Data::Printer::Config::BEGIN@4 at line 4 |