- Timestamp:
- Apr 3, 2020, 8:07:12 PM (5 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
devel/pb-modules/lib/ProjectBuilder/YAML.pm
r2495 r2597 2 2 use strict; 3 3 use warnings; 4 use Data::Dumper; 5 4 6 # Original is YAML::Tiny git description: v1.72-7-g8682f63 5 7 # We rename it here to allow embedded usage during setupv … … 19 21 # exports: 20 22 23 use vars qw(@ISA @EXPORT); 21 24 use Exporter; 25 22 26 our @ISA = qw{ Exporter }; 23 our @EXPORT = qw{ pb_Load pb_Dump }; 24 our @EXPORT_OK = qw{ pb_LoadFile pb_DumpFile freeze thaw }; 27 our @EXPORT = qw{ pb_Dump pb_LoadFile }; 28 29 30 # The commented out form is simpler, but overloaded the Perl regex 31 # engine due to recursion and backtracking problems on strings 32 # larger than 32,000ish characters. Keep it for reference purposes. 33 # qr/\"((?:\\.|[^\"])*)\"/ 34 my $re_capture_double_quoted; 35 my $re_capture_single_quoted; 36 # unquoted re gets trailing space that needs to be stripped 37 my $re_capture_unquoted_key ; 38 my $re_trailing_comment ; 39 my $re_key_value_separator ; 40 41 sub pb_yaml_init { 42 $re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/; 43 $re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/; 44 # unquoted re gets trailing space that needs to be stripped 45 $re_capture_unquoted_key = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/; 46 $re_trailing_comment = qr/(?:\s+\#.*)?/; 47 $re_key_value_separator = qr/\s*:(?:\s+(?:\#.*)?|$)/; 48 } 49 25 50 26 51 ### … … 28 53 29 54 sub pb_Dump { 30 return ProjectBuilder::YAML->new(@_)->_dump_string;31 } 32 33 # XXX-INGY Returning last document seems a bad behavior. 34 # XXX-XDG I think first would seem more natural, but I don't know 35 # that it's worth changing now 36 sub pb_Load { 37 my $ self = ProjectBuilder::YAML->_load_string(@_);55 pb_yaml_init(); 56 return _dump_string(@_); 57 } 58 59 sub pb_LoadFile { 60 pb_yaml_init(); 61 my $file = shift; 62 my $ret = _load_file($file); 38 63 if ( wantarray ) { 39 return @$self; 40 } else { 41 # To match YAML.pm, return the last document 42 return $self->[-1]; 43 } 44 } 45 46 # XXX-INGY Do we really need freeze and thaw? 47 # XXX-XDG I don't think so. I'd support deprecating them. 48 BEGIN { 49 *freeze = \&pb_Dump; 50 *thaw = \&pb_Load; 51 } 52 53 sub pb_DumpFile { 54 my $file = shift; 55 return ProjectBuilder::YAML->new(@_)->_dump_file($file); 56 } 57 58 sub pb_LoadFile { 59 my $file = shift; 60 my $self = ProjectBuilder::YAML->_load_file($file); 61 if ( wantarray ) { 62 return @$self; 64 return @$ret; 63 65 } else { 64 66 # Return only the last document to match YAML.pm, 65 return $self->[-1]; 66 } 67 } 68 69 70 ### 71 # Object Oriented API: 72 73 # Create an empty ProjectBuilder::YAML object 74 # XXX-INGY Why do we use ARRAY object? 75 # NOTE: I get it now, but I think it's confusing and not needed. 76 # Will change it on a branch later, for review. 77 # 78 # XXX-XDG I don't support changing it yet. It's a very well-documented 79 # "API" of ProjectBuilder::YAML. I'd support deprecating it, but Adam suggested 80 # we not change it until YAML.pm's own OO API is established so that 81 # users only have one API change to digest, not two 82 sub new { 83 my $class = shift; 84 bless [ @_ ], $class; 85 } 86 87 # XXX-INGY It probably doesn't matter, and it's probably too late to 88 # change, but 'read/write' are the wrong names. Read and Write 89 # are actions that take data from storage to memory 90 # characters/strings. These take the data to/from storage to native 91 # Perl objects, which the terms dump and load are meant. As long as 92 # this is a legacy quirk to ProjectBuilder::YAML it's ok, but I'd prefer not 93 # to add new {read,write}_* methods to this API. 94 95 sub read_string { 96 my $self = shift; 97 $self->_load_string(@_); 98 } 99 100 sub write_string { 101 my $self = shift; 102 $self->_dump_string(@_); 103 } 104 105 sub read { 106 my $self = shift; 107 $self->_load_file(@_); 108 } 109 110 sub write { 111 my $self = shift; 112 $self->_dump_file(@_); 113 } 114 115 67 return $ret->[-1]; 68 } 69 } 116 70 117 71 … … 146 100 }; 147 101 148 # The commented out form is simpler, but overloaded the Perl regex149 # engine due to recursion and backtracking problems on strings150 # larger than 32,000ish characters. Keep it for reference purposes.151 # qr/\"((?:\\.|[^\"])*)\"/152 my $re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/;153 my $re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/;154 # unquoted re gets trailing space that needs to be stripped155 my $re_capture_unquoted_key = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/;156 my $re_trailing_comment = qr/(?:\s+\#.*)?/;157 my $re_key_value_separator = qr/\s*:(?:\s+(?:\#.*)?|$)/;158 159 160 102 161 103 … … 173 115 # Create an object from a file 174 116 sub _load_file { 175 my $class = ref $_[0] ? ref shift : shift;176 177 117 # Check the file 178 my $file = shift or $class->_error( 'You did not specify a file name' ); 179 $class->_error( "File '$file' does not exist" ) 118 #print("SPECIAL: loadfile ".Dumper(@_)."\n"); 119 my $file = shift or _error( 'You did not specify a file name' ); 120 _error( "File '$file' does not exist" ) 180 121 unless -e $file; 181 $class->_error( "'$file' is a directory, not a file" )122 _error( "'$file' is a directory, not a file" ) 182 123 unless -f _; 183 $class->_error( "Insufficient permissions to read '$file'" )124 _error( "Insufficient permissions to read '$file'" ) 184 125 unless -r _; 185 126 … … 187 128 open( my $fh, "<:unix:encoding(UTF-8)", $file ); 188 129 unless ( $fh ) { 189 $class->_error("Failed to open file '$file': $!");130 _error("Failed to open file '$file': $!"); 190 131 } 191 132 … … 203 144 }; 204 145 if ( my $err = $@ ) { 205 $class->_error("Error reading from file '$file': $err");146 _error("Error reading from file '$file': $err"); 206 147 } 207 148 208 149 # close the file (release the lock) 209 150 unless ( close $fh ) { 210 $class->_error("Failed to close file '$file': $!");211 } 212 213 $class->_load_string( $contents );151 _error("Failed to close file '$file': $!"); 152 } 153 154 _load_string( $contents ); 214 155 } 215 156 216 157 # Create an object from a string 217 158 sub _load_string { 218 my $class = ref $_[0] ? ref shift : shift;219 my $self = bless [], $class;159 #print("SPECIAL: loadstring ".Dumper(@_)."\n"); 160 my $self = []; 220 161 my $string = $_[0]; 162 print("string var: $string\n"); 221 163 eval { 222 164 unless ( defined $string ) { … … 240 182 241 183 # Check for some special cases 242 return $selfunless length $string;184 return [] unless length $string; 243 185 244 186 # Split the file into lines … … 258 200 if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) { 259 201 push @$self, 260 $self->_load_scalar( "$1", [ undef ], \@lines );202 _load_scalar( "$1", [ undef ], \@lines ); 261 203 next; 262 204 } … … 281 223 my $document = [ ]; 282 224 push @$self, $document; 283 $self->_load_array( $document, [ 0 ], \@lines );225 _load_array( $document, [ 0 ], \@lines ); 284 226 285 227 } elsif ( $lines[0] =~ /^(\s*)\S/ ) { … … 287 229 my $document = { }; 288 230 push @$self, $document; 289 $self->_load_hash( $document, [ length($1) ], \@lines );231 _load_hash( $document, [ length($1) ], \@lines ); 290 232 291 233 } else { … … 302 244 my $err = $@; 303 245 if ( ref $err eq 'SCALAR' ) { 304 $self->_error(${$err});246 _error(${$err}); 305 247 } elsif ( $err ) { 306 $self->_error($err);248 _error($err); 307 249 } 308 250 … … 311 253 312 254 sub _unquote_single { 313 my ($s elf, $string) = @_;255 my ($string) = @_; 314 256 return '' unless length $string; 315 257 $string =~ s/\'\'/\'/g; … … 318 260 319 261 sub _unquote_double { 320 my ($s elf, $string) = @_;262 my ($string) = @_; 321 263 return '' unless length $string; 322 264 $string =~ s/\\"/"/g; … … 329 271 # Load a YAML scalar string to the actual Perl scalar 330 272 sub _load_scalar { 331 my ($self, $string, $indent, $lines) = @_; 332 273 #print("SPECIAL: loadscalar ".Dumper(@_)."\n"); 274 my ($string, $indent, $lines) = @_; 275 276 #print("SPECIAL: string ***$string***\n"); 333 277 # Trim trailing whitespace 334 278 $string =~ s/\s*\z//; 279 #print("SPECIAL: string ***$string***\n"); 335 280 336 281 # Explitic null/undef 337 282 return undef if $string eq '~'; 338 283 284 #print("SPECIAL: string ***$string***\n"); 285 #print("SPECIAL: re_capture_double_quoted ***$re_capture_double_quoted***\n"); 286 #print("SPECIAL: re_capture_single_quoted ***$re_capture_single_quoted***\n"); 287 #print("SPECIAL: re_trailing_comment ***$re_trailing_comment***\n"); 339 288 # Single quote 340 289 if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) { 341 return $self->_unquote_single($1); 342 } 343 290 return _unquote_single($1); 291 } 292 293 #print("SPECIAL: string ***$string***\n"); 344 294 # Double quote. 345 295 if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) { 346 return $self->_unquote_double($1); 347 } 348 296 return _unquote_double($1); 297 } 298 299 #print("SPECIAL: string ***$string***\n"); 349 300 # Special cases 350 301 if ( $string =~ /^[\'\"!&]/ ) { … … 354 305 return [] if $string =~ /^\[\](?:\s+\#.*)?\z/; 355 306 307 #print("SPECIAL: string ***$string***\n"); 356 308 # Regular unquoted string 357 309 if ( $string !~ /^[>|]/ ) { … … 363 315 } 364 316 317 #print("SPECIAL: string ***$string***\n"); 365 318 # Error 366 319 die \"ProjectBuilder::YAML failed to find multi-line scalar content" unless @$lines; … … 388 341 # Load an array 389 342 sub _load_array { 390 my ($self, $array, $indent, $lines) = @_; 343 #print("SPECIAL: loadarray ".Dumper(@_)."\n"); 344 my ($array, $indent, $lines) = @_; 391 345 392 346 while ( @$lines ) { … … 412 366 $lines->[0] =~ s/-/ /; 413 367 push @$array, { }; 414 $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );368 _load_hash( $array->[-1], [ @$indent, $indent2 ], $lines ); 415 369 416 370 } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) { … … 428 382 # Naked indenter 429 383 push @$array, [ ]; 430 $self->_load_array(384 _load_array( 431 385 $array->[-1], [ @$indent, $indent2 ], $lines 432 386 ); … … 435 389 } elsif ( $lines->[0] =~ /^(\s*)\S/ ) { 436 390 push @$array, { }; 437 $self->_load_hash(391 _load_hash( 438 392 $array->[-1], [ @$indent, length("$1") ], $lines 439 393 ); … … 446 400 # Array entry with a value 447 401 shift @$lines; 448 push @$array, $self->_load_scalar(402 push @$array, _load_scalar( 449 403 "$2", [ @$indent, undef ], $lines 450 404 ); … … 470 424 # Load a hash 471 425 sub _load_hash { 472 my ($self, $hash, $indent, $lines) = @_; 426 #print("SPECIAL: loadhash ".Dumper(@_)."\n"); 427 my ($hash, $indent, $lines) = @_; 473 428 474 429 while ( @$lines ) { … … 496 451 s/^\s*$re_capture_single_quoted$re_key_value_separator// 497 452 ) { 498 $key = $self->_unquote_single($1);453 $key = _unquote_single($1); 499 454 } 500 455 elsif ( $lines->[0] =~ 501 456 s/^\s*$re_capture_double_quoted$re_key_value_separator// 502 457 ) { 503 $key = $self->_unquote_double($1);458 $key = _unquote_double($1); 504 459 } 505 460 elsif ( $lines->[0] =~ … … 523 478 if ( length $lines->[0] ) { 524 479 # Yes 525 $hash->{$key} = $self->_load_scalar(480 $hash->{$key} = _load_scalar( 526 481 shift(@$lines), [ @$indent, undef ], $lines 527 482 ); … … 535 490 if ( $lines->[0] =~ /^(\s*)-/ ) { 536 491 $hash->{$key} = []; 537 $self->_load_array(492 _load_array( 538 493 $hash->{$key}, [ @$indent, length($1) ], $lines 539 494 ); … … 545 500 } else { 546 501 $hash->{$key} = {}; 547 $self->_load_hash(502 _load_hash( 548 503 $hash->{$key}, [ @$indent, length($1) ], $lines 549 504 ); … … 560 515 # Dumper functions: 561 516 562 # Save an object to a file563 sub _dump_file {564 my $self = shift;565 566 require Fcntl;567 568 # Check the file569 my $file = shift or $self->_error( 'You did not specify a file name' );570 571 my $fh;572 # flock if available (or warn if not possible for OS-specific reasons)573 if ( _can_flock() ) {574 # Open without truncation (truncate comes after lock)575 my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();576 sysopen( $fh, $file, $flags )577 or $self->_error("Failed to open file '$file' for writing: $!");578 579 # Use no translation and strict UTF-8580 binmode( $fh, ":raw:encoding(UTF-8)");581 582 flock( $fh, Fcntl::LOCK_EX() )583 or warn "Couldn't lock '$file' for reading: $!";584 585 # truncate and spew contents586 truncate $fh, 0;587 seek $fh, 0, 0;588 }589 else {590 open $fh, ">:unix:encoding(UTF-8)", $file;591 }592 593 # serialize and spew to the handle594 print {$fh} $self->_dump_string;595 596 # close the file (release the lock)597 unless ( close $fh ) {598 $self->_error("Failed to close file '$file': $!");599 }600 601 return 1;602 }603 604 517 # Save an object to a string 605 518 sub _dump_string { 519 #print("SPECIAL: dumpstring ".Dumper(@_)."\n"); 606 520 my $self = shift; 607 521 return '' unless ref $self && @$self; … … 621 535 # A scalar document 622 536 } elsif ( ! ref $cursor ) { 623 $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );537 $lines[-1] .= ' ' . _dump_scalar( $cursor ); 624 538 625 539 # A list at the root … … 629 543 next; 630 544 } 631 push @lines, $self->_dump_array( $cursor, $indent, {} );545 push @lines, _dump_array( $cursor, $indent, {} ); 632 546 633 547 # A hash at the root … … 637 551 next; 638 552 } 639 push @lines, $self->_dump_hash( $cursor, $indent, {} );553 push @lines, _dump_hash( $cursor, $indent, {} ); 640 554 641 555 } else { … … 645 559 }; 646 560 if ( ref $@ eq 'SCALAR' ) { 647 $self->_error(${$@});561 _error(${$@}); 648 562 } elsif ( $@ ) { 649 $self->_error($@);563 _error($@); 650 564 } 651 565 … … 660 574 661 575 sub _dump_scalar { 576 #print("SPECIAL: dumpscalar ".Dumper(@_)."\n"); 662 577 my $string = $_[1]; 663 578 my $is_key = $_[2]; … … 693 608 694 609 sub _dump_array { 695 my ($self, $array, $indent, $seen) = @_; 610 #print("SPECIAL: dumparray ".Dumper(@_)."\n"); 611 my ($array, $indent, $seen) = @_; 696 612 if ( $seen->{refaddr($array)}++ ) { 697 613 die \"ProjectBuilder::YAML does not support circular references"; … … 702 618 my $type = ref $el; 703 619 if ( ! $type ) { 704 $line .= ' ' . $self->_dump_scalar( $el );620 $line .= ' ' . _dump_scalar( $el ); 705 621 push @lines, $line; 706 622 … … 708 624 if ( @$el ) { 709 625 push @lines, $line; 710 push @lines, $self->_dump_array( $el, $indent + 1, $seen );626 push @lines, _dump_array( $el, $indent + 1, $seen ); 711 627 } else { 712 628 $line .= ' []'; … … 717 633 if ( keys %$el ) { 718 634 push @lines, $line; 719 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );635 push @lines, _dump_hash( $el, $indent + 1, $seen ); 720 636 } else { 721 637 $line .= ' {}'; … … 732 648 733 649 sub _dump_hash { 734 my ($self, $hash, $indent, $seen) = @_; 650 #print("SPECIAL: dumphash ".Dumper(@_)."\n"); 651 my ($hash, $indent, $seen) = @_; 735 652 if ( $seen->{refaddr($hash)}++ ) { 736 653 die \"ProjectBuilder::YAML does not support circular references"; … … 739 656 foreach my $name ( sort keys %$hash ) { 740 657 my $el = $hash->{$name}; 741 my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":";658 my $line = (' ' x $indent) . _dump_scalar($name, 1) . ":"; 742 659 my $type = ref $el; 743 660 if ( ! $type ) { 744 $line .= ' ' . $self->_dump_scalar( $el );661 $line .= ' ' . _dump_scalar( $el ); 745 662 push @lines, $line; 746 663 … … 748 665 if ( @$el ) { 749 666 push @lines, $line; 750 push @lines, $self->_dump_array( $el, $indent + 1, $seen );667 push @lines, _dump_array( $el, $indent + 1, $seen ); 751 668 } else { 752 669 $line .= ' []'; … … 757 674 if ( keys %$el ) { 758 675 push @lines, $line; 759 push @lines, $self->_dump_hash( $el, $indent + 1, $seen );676 push @lines, _dump_hash( $el, $indent + 1, $seen ); 760 677 } else { 761 678 $line .= ' {}'; … … 782 699 sub _error { 783 700 require Carp; 784 $errstr = $_[1]; 785 $errstr =~ s/ at \S+ line \d+.*//; 701 $errstr = $_[0]; 702 #print("SPECIAL: error: ".Dumper(@_)."\n"); 703 #$errstr =~ s/ at \S+ line \d+.*//; 786 704 Carp::croak( $errstr ); 787 705 }
Note:
See TracChangeset
for help on using the changeset viewer.