The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#
#  "Symbolic" dump of records to stdout
#
#  This does not precisely reproduce its input, but should be
#  equivalent (some probably-meaningless fields are not preserved)
#

use Mac::Finder::DSStore::BuddyAllocator;
use Mac::Finder::DSStore;
use IO::File;
use Data::Dumper;
use Config;
use Mac::Files;
use Mac::Memory;

die "Usage: $0 /path/to/.DS_Store > result.pl\n"
    unless @ARGV == 1;

%byFilename = ( );
$want_alias = 0;

$filename = $ARGV[0];
die "$0: $filename: not a file?\n" unless -f $filename;
$store = Mac::Finder::DSStore::BuddyAllocator->open(new IO::File $filename, '<');
foreach my $rec (&Mac::Finder::DSStore::getDSDBEntries($store)) {
    push(@{$byFilename{$rec->filename}}, $rec);
    $want_alias = 1 if $rec->strucId eq 'pict';
}
undef $store;

print "#!" . $Config{perlpath} . " -w\n\n";

print "use Mac::Finder::DSStore qw( writeDSDBEntries makeEntries );\n";

# Older MacPerls have some sort of problem autoloading Handle if you
# don't explicitly import Mac::Memory
print "use Mac::Memory qw( );\n"
    if $want_alias;

print "use Mac::Files qw( NewAliasMinimal );\n"
    if $want_alias;

print "\n";

print '&writeDSDBEntries(', &repr($filename);

foreach $fn (sort keys %byFilename) {
    my(%recs) = map { $_->strucId, $_->value } @{$byFilename{$fn}};
    my(@lines);

    if (!exists($recs{'BKGD'})) {
        # pass
    } elsif ($recs{'BKGD'} =~ /^DefB/ ) {
        push(@lines, 'BKGD_default');
        delete $recs{'BKGD'};
    } elsif ($recs{'BKGD'} =~ /^ClrB/ ) {
        my(@rgb) = unpack('x4 nnn', $recs{'BKGD'});
        push(@lines, sprintf("BKGD_color => '#%02X%02X%02X'", @rgb));
        delete $recs{'BKGD'};
    } elsif ($recs{'BKGD'} =~ /^PctB/ && exists($recs{'pict'})) {
        my($l, $a, $b) = unpack('x4 N nn', $recs{'BKGD'});
        if($l == length($recs{'pict'})) {
            my($user, $alias_len) = unpack('Nn', $recs{'pict'});
            warn "Possible extra data in BKGD alias entry: udata=$user, ".($l - $alias_len)." bytes trailing data\n"
                if ($user != 0 or $alias_len != $l);
            my($hdl) = new Handle( $recs{'pict'} );
            my($unalias) = Mac::Files::ResolveAliasRelative($filename, $hdl);
            if ($unalias) {
                push(@lines, 'BKGD_alias => NewAliasMinimal('.&repr($unalias).')');
                delete $recs{'BKGD'};
                delete $recs{'pict'};
            }
        }
    }

    if(exists($recs{'Iloc'})) {
        my(@xyn) = unpack('NNnnnn', $recs{'Iloc'});
        &pop_matching(\@xyn, 65535, 65535, 65535, 0);
        push(@lines, 'Iloc_xy => '.&repr(\@xyn, 1));
        delete $recs{'Iloc'};
    }

    if(exists($recs{'icvo'}) && $recs{'icvo'} =~ /^icv4/) {
        push(@lines, "icvo => ".&as_unpacked('A4 n A4 A4 n*', $recs{'icvo'}));
        delete $recs{'icvo'};
    }

    if(exists($recs{'fwi0'}) && length($recs{'fwi0'}) == 16) {
        my(@flds) = unpack('n4 A4 n*', $recs{'fwi0'});
        push(@lines, 'fwi0_flds => '.&repr(\@flds, 1));
        delete $recs{'fwi0'};
    }

    foreach my $k (keys %recs) {
        my($qqv) = &repr($recs{$k});
        my($hexv) = "'" . unpack('H*', $recs{$k}) . "'";

        push(@lines,
             ((length($qqv) > length($hexv)) ? "${k}_hex => $hexv" : "$k => $qqv"));
    }

    print ",\n    &makeEntries(", &repr($fn);
    if (1 == @lines and length($lines[0]) < 50) {
        print ", ", $lines[0], ")";
    } else {
        print ",\n        $_" foreach sort @lines;
        print "\n    )";
    }
}
print "\n);\n\n";

sub pop_matching {
    my($from, @what) = @_;

    while(@$from && @what && ($from->[$#$from] == $what[$#what])) {
        pop(@$from);
        pop(@what);
    }
}

sub repr {
    my($v, $pack) = @_;
    my($dumper) = Data::Dumper->new([ $v ]);
    $dumper->Useqq(1);
    $dumper->Terse(1);
    my($repr) = $dumper->Dump;
    chomp $repr;
    $repr =~ s/\s*\n\s+/ /g if $pack;
    $repr;
}

sub as_unpacked {
    my($fmt, $buf) = @_;

    my(@flds) = unpack($fmt, $buf);
    return "pack('$fmt', ".join(', ', map { &repr($_, 1) } @flds).')';
}