use strict; my $data_suffix = q[.dat]; my $data_dir_name = q[data/]; my $data2_dir_name = q[data2/]; my $data2_suffix = q[.dat]; my $lock_suffix = q[.lock]; my $fallback_file_name = $data2_dir_name . 'fallback' . $data2_suffix; our $UseCVS //= 1; sub normalize ($) { my $s = shift; $s =~ s/\s+/ /g; $s =~ s/^ //; $s =~ s/ $//g; return $s; } # normalize sub get_hash ($) { require Digest::MD5; return Digest::MD5::md5_hex (normalize ($_[0])); } # get_hash sub create_pattern1 ($) { my $s = quotemeta shift; $s =~ s/\\\*/(.+)/g; return $s; } # create_pattern1 sub replace_pattern2 ($@) { my $s = shift; my @arg = @_; $s =~ s/\$(\d+)/$arg[$1 - 1]/g; return $s; } # replace_pattern2 sub load_data_file ($$$) { my ($data_file_name, $exact_data, $pattern_data) = @_; open my $data_file, '<:utf8', $data_file_name or die "$0: $data_file_name: $!"; local $/ = undef; my $data = <$data_file>; $data =~ s/\x0D?\x0A/\n/g; for (split /\n\n+(?=#)/, $data) { my ($en, $ja) = split /\n#ja\n/, $_; if ($en =~ s/^#en\n//) { $exact_data->{normalize ($en)} = $ja; } elsif ($en =~ s/^#pattern\n//) { $pattern_data->{create_pattern1 (normalize ($en))} = $ja; } } } # load_data_file sub for_each_data_file ($) { my ($code) = @_; opendir my $data_dir, $data_dir_name or die "$0: $data_dir_name: $!"; for (sort {$a cmp $b} readdir $data_dir) { next if /^\./; # hidden files next if /^_/; # editable but not-used files my $data_file_name = qq[$data_dir_name$_]; next unless $data_file_name =~ /\Q$data_suffix\E$/; $code->($data_file_name, $_); } } # for_each_data_file sub read_data_file ($) { my $file_name = shift; if (-f $file_name) { warn "Loading $file_name...\n"; return do $file_name; } else { warn "File $file_name not found\n"; return {}; } } # read_data_file sub write_data_file ($$) { my ($file_name, $data) = @_; require Data::Dumper; local $Data::Dumper::Sortkeys = 1; my $had_file = -f $file_name; open my $file, '>:encoding(utf8)', $file_name or die "$0: $file_name: $!"; print $file Data::Dumper::Dumper ($data); close $file; unless ($had_file) { system_ ('cvs', 'add', $file_name) if $UseCVS; } } # write_data_file sub hash_to_file_name ($) { return $data2_dir_name . substr ($_[0], 0, 2) . $data2_suffix; } # hash_to_file_name my $Entry = {}; sub get_entry ($) { my $hash = shift; my $file_name = hash_to_file_name ($hash); unless ($Entry->{$file_name}) { $Entry->{$file_name} = read_data_file ($file_name); } if ($Entry->{$file_name}->{exact}->{$hash}) { return (0, $Entry->{$file_name}->{exact}->{$hash}); } elsif ($Entry->{$file_name}->{pattern}->{$hash}) { return (1, $Entry->{$file_name}->{pattern}->{$hash}); } else { return (undef, undef); } } # get_entry sub set_entry ($$$) { my ($hash, $is_pattern, $value) = @_; my $file_name = hash_to_file_name ($hash); unless ($Entry->{$file_name}) { $Entry->{$file_name} = read_data_file ($file_name); } unless ($value) { delete $Entry->{$file_name}->{exact}->{$hash}; delete $Entry->{$file_name}->{pattern}->{$hash}; } elsif ($is_pattern) { delete $Entry->{$file_name}->{exact}->{$hash}; $Entry->{$file_name}->{pattern}->{$hash} = $value; } else { $Entry->{$file_name}->{exact}->{$hash} = $value; delete $Entry->{$file_name}->{pattern}->{$hash}; } $Entry->{$file_name}->{modified} = 1; } # set_entry use Fcntl ':flock'; my $Lock; sub lock_entry ($) { if ($Lock) { die "$0: lock_entry: Another entry is locked"; } my $hash = shift; my $file_name = hash_to_file_name ($hash) . $lock_suffix; open $Lock, '>', $file_name or die "$0: $file_name: $!"; flock $Lock, LOCK_EX; } # lock_entry sub commit_entries ($) { for my $file_name (keys %{$Entry}) { if ($Entry->{$file_name}->{modified}) { write_data_file ($file_name => $Entry->{$file_name}); } } my $msg = shift // $0; system_ ('cvs', 'commit', -m => $msg) if $UseCVS; } # commit_entries sub get_all_entries () { opendir my $dir, $data2_dir_name or die "$0: $data2_dir_name: $!"; for (readdir $dir) { next unless /\Q$data2_suffix\E$/; my $file_name = $data2_dir_name . $_; next if $Entry->{$file_name}; $Entry->{$file_name} = read_data_file ($file_name); } return $Entry; } # get_all_entries my $FallbackEntry; sub get_fallback_entry ($) { my $hash = shift; unless (defined $FallbackEntry) { $FallbackEntry = read_data_file ($fallback_file_name); } return $FallbackEntry->{$hash} // {}; } # get_fallback_entry sub set_fallback_entry ($$) { my ($hash, $value) = @_; unless (defined $FallbackEntry) { $FallbackEntry = read_data_file ($fallback_file_name); } $FallbackEntry->{$hash} = $value; } # set_fallback_entry sub clear_fallback_entries () { $FallbackEntry = {}; } # clear_fallback_entries sub save_fallback_entries () { write_data_file ($fallback_file_name => $FallbackEntry) if defined $FallbackEntry; } # save_fallback_entries sub htescape ($) { my $s = shift; $s =~ s/&/&/g; $s =~ s/