use strict; use warnings; package DataFiles; # Field numbers, so the arrays which (read|write)_file use can be accessed more pleasantly use constant STR_ID => 0; use constant STR_CONTEXT => 1; use constant STR_DESCRIPTION => 2; use constant STR_FLAGS => 3; # Export the field numbers require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(STR_ID STR_CONTEXT STR_DESCRIPTION STR_FLAGS); # Read a file containing \n-separated fields and \n\n-separated records sub read_file { my ($filename, %params) = @_; # If 'ignoremissing' has been passed, handle non-existent files as if they were empty if ($params{ignoremissing} and not -e $filename) { return []; } open my $f, '<', $filename or die "Error opening $filename for input: $!"; my (@records, @fields); while (<$f>) { chomp; if (length) { # Each line stores a separate field push @fields, unescape($_); } else { # Split records by empty lines push @records, [@fields]; @fields = (); } } push @records, [@fields] if @fields; return \@records; } # Inverse of read_file sub write_file { my ($filename, $data) = @_; open my $f, '>', $filename or die "Error opening $filename for output: $!"; for (@$data) { print $f escape($_), "\n" for @$_; print $f "\n"; } } # Convert \ to \\, newlines to \n, blank lines to \ sub unescape { my $t = $_[0]; return "" if $t eq "\\"; $t =~ s/\\n/\n/g; $t =~ s/\\\\/\\/g; $t; } sub escape { my $t = $_[0]; return "\\" unless defined $t and length $t; $t =~ s/\\/\\\\/g; $t =~ s/\n/\\n/g; $t; } 1;