File Coverage

blib/lib/Audio/Nama/Assign.pm
Criterion Covered Total %
statement 130 193 67.3
branch 31 72 43.0
condition 7 18 38.8
subroutine 19 31 61.2
pod 0 10 0.0
total 187 324 57.7


line stmt bran cond sub pod time code
1             package Audio::Nama::Assign;
2 5     5   22902 use Modern::Perl;
  5         13533  
  5         38  
3             our $VERSION = 1.0;
4 5     5   860 use 5.008;
  5         17  
5 5     5   27 use feature 'state';
  5         11  
  5         330  
6 5     5   28 use strict;
  5         13  
  5         112  
7 5     5   49 use warnings;
  5         10  
  5         187  
8 5     5   51 no warnings q(uninitialized);
  5         10  
  5         221  
9 5     5   26 use Carp qw(carp confess croak cluck);
  5         14  
  5         361  
10 5     5   5347 use YAML::Tiny;
  5         30003  
  5         344  
11 5     5   15747 use File::Slurp;
  5         72415  
  5         436  
12 5     5   5986 use File::HomeDir;
  5         31475  
  5         306  
13 5     5   2242 use Audio::Nama::Log qw(logsub logpkg);
  5         17  
  5         362  
14 5     5   4275 use Storable qw(nstore retrieve);
  5         22782  
  5         390  
15 5     5   6179 use JSON::XS;
  5         43118  
  5         320  
16 5     5   3261 use Data::Dumper::Concise;
  5         27289  
  5         891  
17              
18             require Exporter;
19              
20             our @ISA = qw(Exporter);
21             our %EXPORT_TAGS = ( 'all' => [ qw(
22            
23             serialize
24             assign
25             assign_singletons
26             store_vars
27             json_out
28             yaml_in
29             json_in
30             json_out
31             quote_yaml_scalars
32             var_map
33             config_vars
34             ) ] );
35              
36             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
37              
38             our @EXPORT = ();
39              
40             our $to_json = JSON::XS->new->utf8->allow_blessed->pretty->canonical(1) ;
41 5     5   33 use Carp;
  5         12  
  5         15867  
42              
43             {my $var_map = { qw(
44              
45             %devices $config->{devices}
46             $alsa_playback_device $config->{alsa_playback_device}
47             $alsa_capture_device $config->{alsa_capture_device}
48             $soundcard_channels $config->{soundcard_channels}
49             %abbreviations $config->{abbreviations}
50             $mix_to_disk_format $config->{mix_to_disk_format}
51             $raw_to_disk_format $config->{raw_to_disk_format}
52             $cache_to_disk_format $config->{cache_to_disk_format}
53             $mixer_out_format $config->{mixer_out_format}
54             $sample_rate $config->{sample_rate}
55             $ecasound_tcp_port $config->{engine_tcp_port}
56             $ecasound_globals $config->{engine_globals}
57             $ecasound_buffersize $config->{engine_buffersize}
58             $realtime_profile $config->{realtime_profile}
59             $eq $mastering->{fx_eq}
60             $low_pass $mastering->{fx_low_pass}
61             $mid_pass $mastering->{fx_mid_pass}
62             $high_pass $mastering->{fx_high_pass}
63             $compressor $mastering->{fx_compressor}
64             $spatialiser $mastering->{fx_spatialiser}
65             $limiter $mastering->{fx_limiter}
66             $project_root $config->{root_dir}
67             $use_group_numbering $config->{use_group_numbering}
68             $press_space_to_start_transport $config->{press_space_to_start}
69             $execute_on_project_load $config->{execute_on_project_load}
70             $initial_mode $config->{initial_mode}
71             $midish_enable $config->{use_midish}
72             $quietly_remove_tracks $config->{quietly_remove_tracks}
73             $use_jack_plumbing $config->{use_jack_plumbing}
74             $jack_seek_delay $config->{engine_base_jack_seek_delay}
75             $use_monitor_version_for_mixdown $config->{sync_mixdown_and_monitor_version_numbers}
76             $mixdown_encodings $config->{mixdown_encodings}
77             $volume_control_operator $config->{volume_control_operator}
78             $serialize_formats $config->{serialize_formats}
79             $use_git $config->{use_git}
80             $autosave $config->{autosave}
81             $beep_command $config->{beep_command}
82             $hotkey_beep $config->{hotkey_beep}
83             $eager $mode->{eager}
84             $alias $config->{alias}
85             $hotkeys $config->{hotkeys}
86             $new_track_rw $config->{new_track_rw}
87             $hotkeys_always $config->{hotkeys_always}
88             $use_pager $config->{use_pager}
89             $use_placeholders $config->{use_placeholders}
90             $edit_playback_end_margin $config->{edit_playback_end_margin}
91             $edit_crossfade_time $config->{edit_crossfade_time}
92             $default_fade_length $config->{engine_fade_default_length}
93             $fade_time $config->{engine_fade_length_on_start_stop}
94             %mute_level $config->{mute_level}
95             %fade_out_level $config->{fade_out_level}
96             $fade_resolution $config->{fade_resolution}
97             %unity_level $config->{unity_level}
98             $enforce_channel_bounds $config->{enforce_channel_bounds}
99             $midi_input_dev $midi->{input_dev}
100             $midi_output_dev $midi->{output_dev}
101             $controller_ports $midi->{controller_ports}
102             $midi_inputs $midi->{inputs}
103             $osc_listener_port $config->{osc_listener_port}
104             $osc_reply_port $config->{osc_reply_port}
105             $remote_control_port $config->{remote_control_port}
106             $engines $config->{engines}
107              
108             ) };
109 0     0 0 0 sub var_map { $var_map } # to allow outside access while keeping
110             # working lexical
111 0     0 0 0 sub config_vars { grep {$_ ne '**' } keys %$var_map }
  0         0  
112              
113             sub assign {
114             # Usage:
115             # assign (
116             # data => $ref,
117             # vars => \@vars,
118             # var_map => 1,
119             # class => $class
120             # );
121              
122 4     4 0 7636 logsub("&assign");
123            
124 4         42 my %h = @_; # parameters appear in %h
125 4         6 my $class;
126 4 50       15 logpkg(__FILE__,__LINE__,'logcarp',"didn't expect scalar here") if ref $h{data} eq 'SCALAR';
127 4 50       10 logpkg(__FILE__,__LINE__,'logcarp',"didn't expect code here") if ref $h{data} eq 'CODE';
128             # print "data: $h{data}, ", ref $h{data}, $/;
129              
130 4 50       23 if ( ref $h{data} !~ /^(HASH|ARRAY|CODE|GLOB|HANDLE|FORMAT)$/){
131             # we guess object
132 0         0 $class = ref $h{data};
133 0         0 logpkg(__FILE__,__LINE__,'debug',"I found an object of class $class");
134             }
135 4         9 $class = $h{class};
136 4 100       14 $class .= "::" unless $class =~ /::$/; # SKIP_PREPROC
137 4         6 my @vars = @{ $h{vars} };
  4         11  
138 4         9 my $ref = $h{data};
139 4         6 my $type = ref $ref;
140 4         28 logpkg(__FILE__,__LINE__,'debug',<
141             data type: $type
142             data: $ref
143             class: $class
144             vars: @vars
145             ASSIGN
146             #logpkg(__FILE__,__LINE__,'debug',sub{json_out($ref)});
147              
148             # index what sigil an identifier should get
149              
150             # we need to create search-and-replace strings
151             # sigil-less old_identifier
152 4         31 my %sigil;
153             my %ident;
154             map {
155 4         8 my $oldvar = my $var = $_;
  16         27  
156 16         52 my ($dummy, $old_identifier) = /^([\$\%\@])([\-\>\w:\[\]{}]+)$/;
157 16 0 33     39 $var = $var_map->{$var} if $h{var_map} and $var_map->{$var};
158              
159 16 50       34 logpkg(__FILE__,__LINE__,'debug',"oldvar: $oldvar, newvar: $var") unless $oldvar eq $var;
160 16         46 my ($sigil, $identifier) = $var =~ /([\$\%\@])(\S+)/;
161 16         31 $sigil{$old_identifier} = $sigil;
162 16         43 $ident{$old_identifier} = $identifier;
163             } @vars;
164              
165 4     0   25 logpkg(__FILE__,__LINE__,'debug',sub{"SIGIL\n". json_out(\%sigil)});
  0         0  
166             #%ident = map{ @$_ } grep{ $_->[0] ne $_->[1] } map{ [$_, $ident{$_}] } keys %ident;
167 4         49 my %ident2 = %ident;
168 4         17 while ( my ($k,$v) = each %ident2)
169             {
170 16 50       66 delete $ident2{$k} if $k eq $v
171             }
172 4     0   20 logpkg(__FILE__,__LINE__,'debug',sub{"IDENT\n". json_out(\%ident2)});
  0         0  
173            
174             #print join " ", "Variables:\n", @vars, $/ ;
175 4 50       46 croak "expected hash" if ref $ref !~ /HASH/;
176 4         5 my @keys = keys %{ $ref }; # identifiers, *no* sigils
  4         15  
177 4     0   20 logpkg(__FILE__,__LINE__,'debug',sub{ join " ","found keys: ", keys %{ $ref },"\n---\n"});
  0         0  
  0         0  
178             map{
179 4         37 my $eval;
  16         20  
180 16         25 my $key = $_;
181 16         22 chomp $key;
182 16         26 my $sigil = $sigil{$key};
183             my $full_class_path =
184 16 50       47 $sigil . ($key =~/:\:/ ? '': $class) . $ident{$key};
185              
186             # use the supplied class unless the variable name
187             # contains \:\:
188            
189 16         64 logpkg(__FILE__,__LINE__,'debug',<
190             key: $key
191             sigil: $sigil
192             full_class_path: $full_class_path
193             DEBUG
194 16 50       132 if ( ! $sigil ){
195             logpkg(__FILE__,__LINE__,'debug',sub{
196 0     0   0 "didn't find a match for $key in ", join " ", @vars, $/;
197 0         0 });
198             }
199             else
200             {
201              
202 16         24 $eval .= $full_class_path;
203 16         21 $eval .= q( = );
204              
205 16         24 my $val = $ref->{$key};
206              
207 16 100 66     87 if (! ref $val or ref $val eq 'SCALAR') # scalar assignment
    50 66        
208             {
209              
210             # extract value
211              
212 8 100       16 if ($val) { # if we have something,
213              
214             # dereference it if needed
215            
216 7 50       17 ref $val eq q(SCALAR) and $val = $$val;
217            
218             # quoting for non-numerical
219            
220 7 100       29 $val = qq("$val") unless $val =~ /^[\d\.,+\-e]+$/
221            
222 1         3 } else { $val = q(undef) }; # or set as undefined
223              
224 8         9 $eval .= $val; # append to assignment
225              
226             }
227             elsif ( ref $val eq 'ARRAY' or ref $val eq 'HASH')
228             {
229 8 50       18 if ($sigil eq '$') # assign reference
230             {
231 0         0 $eval .= q($val) ;
232             }
233             else # dereference and assign
234             {
235 8         12 $eval .= qq($sigil) ;
236 8         12 $eval .= q({$val}) ;
237             }
238             }
239 0         0 else { die "unsupported assignment: ".ref $val }
240 16         53 logpkg(__FILE__,__LINE__,'debug',"eval string: $eval");
241 16         1121 eval($eval);
242 16 50       105 logpkg(__FILE__,__LINE__,'logcarp',"failed to eval $eval: $@") if $@;
243             } # end if sigil{key}
244             } @keys;
245 4         25 1;
246             }
247             }
248              
249             # assign_singletons() assigns hash key/value entries
250             # rather than a top-level hash reference to avoid
251             # clobbering singleton key/value pairs initialized
252             # elsewhere.
253            
254             my @singleton_idents = map{ /^.(.+)/; $1 } # remove leading '$' sigil
255             qw(
256             $ui
257             $mode
258             $file
259             $graph
260             $setup
261             $config
262             $jack
263             $fx
264             $fx_cache
265             $text
266             $gui
267             $midi
268             $help
269             $mastering
270             $project
271              
272             );
273             sub assign_singletons {
274 0     0 0 0 logsub('&assign_singletons');
275 0         0 my $ref = shift;
276 0 0       0 my $data = $ref->{data} or die "expected data got undefined";
277 0   0     0 my $class = $ref->{class} // 'Audio::Nama';
278 0         0 $class .= '::'; # SKIP_PREPROC
279             map {
280 0         0 my $ident = $_;
  0         0  
281 0 0       0 if( defined $data->{$ident}){
282 0         0 my $type = ref $data->{$ident};
283 0 0       0 $type eq 'HASH' or die "$ident: expect hash, got $type";
284             map{
285 0         0 my $key = $_;
286 0         0 my $cmd = join '',
287             '$',
288             $class,
289             $ident,
290             '->{',
291             $key,
292             '}',
293             ' = $data->{$ident}->{$key}';
294 0         0 logpkg(__FILE__,__LINE__,'debug',"eval: $cmd");
295 0         0 eval $cmd;
296 0 0       0 logpkg(__FILE__,__LINE__,'logcarp',"error during eval: $@") if $@;
297 0         0 } keys %{ $data->{$ident} }
  0         0  
298             }
299             } @singleton_idents; # list of "singleton" variables
300             }
301              
302             our %suffix =
303             (
304             storable => "bin",
305             perl => "pl",
306             json => "json",
307             yaml => "yml",
308             );
309             our %dispatch =
310             ( storable => sub { my($ref, $path) = @_; nstore($ref, $path) },
311             perl => sub { my($ref, $path) = @_; write_file($path, Dumper $ref) },
312             yaml => sub { my($ref, $path) = @_; write_file($path, json_out($ref))},
313             json => sub { my($ref, $path) = @_; write_file($path, json_out($ref))},
314             );
315              
316             sub serialize_and_write {
317 0     0 0 0 my ($ref, $path, $format) = @_;
318 0 0       0 $path .= ".$suffix{$format}" unless $path =~ /\.$suffix{$format}$/;
319 0         0 $dispatch{$format}->($ref, $path)
320             }
321              
322              
323             {
324             my $parse_re = # initialize only once
325             qr/ ^ # beginning anchor
326             ([\%\@\$]) # first character, sigil
327             ([\w:]+) # identifier, possibly perl namespace
328             (?:->\{(\w+)})? # optional hash key for new hash-singleton vars
329             $ # end anchor
330             /x;
331             sub serialize {
332 3     3 0 20 logsub("&serialize");
333              
334 3         27 my %h = @_;
335 3         6 my @vars = @{ $h{vars} };
  3         11  
336 3         4 my $class = $h{class};
337 3         6 my $file = $h{file};
338 3   50     15 my $format = $h{format} // 'perl'; # default to Data::Dumper::Concise
339              
340 3   50     8 $class //= "Audio::Nama";
341 3 100       14 $class =~ /::$/ or $class .= '::'; # SKIP_PREPROC
342 3         47 logpkg(__FILE__,__LINE__,'debug',"file: $file, class: $class\nvariables...@vars");
343              
344             # first we marshall data into %state
345              
346 3         22 my %state;
347              
348             map{
349 3         7 my ($sigil, $identifier, $key) = /$parse_re/;
  12         77  
350              
351 12         51 logpkg(__FILE__,__LINE__,'debug',"found sigil: $sigil, ident: $identifier, key: $key");
352              
353             # note: for YAML::Reader/Writer all scalars must contain values, not references
354             # more YAML adjustments
355             # restore will break if a null field is not converted to '~'
356              
357             #my $value = q(\\)
358              
359             # directly assign scalar, but take hash/array references
360             # $state{ident} = $scalar
361             # $state{ident} = \%hash
362             # $state{ident} = \@array
363              
364             # in case $key is provided
365             # $state{ident}->{$key} = $singleton->{$key};
366             #
367            
368              
369 12 100       132 my $value = ($sigil ne q($) ? q(\\) : q() )
    50          
    50          
370              
371             . $sigil
372             . ($identifier =~ /:/ ? '' : $class)
373             . $identifier
374             . ($key ? qq(->{$key}) : q());
375              
376 12         40 logpkg(__FILE__,__LINE__,'debug',"value: $value");
377              
378            
379 12 50       109 my $eval_string = q($state{')
380             . $identifier
381             . q('})
382             . ($key ? qq(->{$key}) : q() )
383             . q( = )
384             . $value;
385              
386 12 50       32 if ($identifier){
387 12         41 logpkg(__FILE__,__LINE__,'debug',"attempting to eval $eval_string");
388 12         765 eval($eval_string);
389 12 50       91 logpkg(__FILE__,__LINE__,'error', "eval failed ($@)") if $@;
390             }
391             } @vars;
392 3     0   16 logpkg(__FILE__,__LINE__,'debug',sub{join $/,'\%state', Dumper \%state});
  0         0  
393              
394             # YAML out for screen dumps
395 3 50       39 return( json_out(\%state) ) unless $h{file};
396              
397             # now we serialize %state
398            
399 0         0 my $path = $h{file};
400              
401 0         0 serialize_and_write(\%state, $path, $format);
402             }
403             }
404              
405             sub json_out {
406 3     3 0 9 logsub("&json_out");
407 3         23 my $data_ref = shift;
408 3         6 my $type = ref $data_ref;
409 3 50       19 croak "attempting to code wrong data type: $type"
410             if $type !~ /HASH|ARRAY/;
411 3         54 $to_json->encode($data_ref);
412             }
413              
414             sub json_in {
415 0     0 0   logsub("&json_in");
416 0           my $json = shift;
417 0           my $data_ref = decode_json($json);
418 0           $data_ref
419             }
420              
421             sub yaml_in {
422            
423             # logsub("&yaml_in");
424 0     0 0   my $input = shift;
425             my $yaml = $input =~ /\n/ # check whether file or text
426             ? $input # yaml text
427             : do
428 0 0         {
429 0           logpkg(__FILE__,__LINE__,'debug',"filename: $input");
430 0           read_file($input); # file name
431             };
432 0 0         if ($yaml =~ /\t/){
433 0           croak "YAML file: $input contains illegal TAB character.";
434             }
435 0           $yaml =~ s/^\n+// ; # remove leading newline at start of file
436 0           $yaml =~ s/\n*$/\n/; # make sure file ends with newline
437 0           my $y = YAML::Tiny->read_string($yaml);
438 0 0         Audio::Nama::throw("YAML::Tiny read error: $YAML::Tiny::errstr\n") if $YAML::Tiny::errstr;
439 0           $y->[0];
440             }
441              
442             sub quote_yaml_scalars {
443 0     0 0   my $yaml = shift;
444 0           my @modified;
445             map
446             {
447 0           chomp;
  0            
448 0 0         if( /^(?(\s*\w+: )|(\s+- ))(?.+)$/ ){
449 5     5   4018 my($beg,$end) = ($+{beg}, $+{end});
  5         2161  
  5         1313  
  0            
450             # quote if contains colon and not quoted
451 0 0 0       if ($end =~ /:\s/ and $end !~ /^('|")/ ){
452 0           $end =~ s(')(\\')g; # escape existing single quotes
453 0           $end = qq('$end') } # single-quote string
454 0           push @modified, "$beg$end\n";
455             }
456 0           else { push @modified, "$_\n" }
457             } split "\n", $yaml;
458 0           join "", @modified;
459             }
460            
461              
462             1;