use strict; use utf8; my $data_dir_name = q[data/]; my $data2_dir_name = q[data2/]; my $data2_suffix = q[.dat]; my $lock_suffix = q[.lock]; ## SEE ALSO: |Makefile|. my $fallback_file_name = $data2_dir_name . 'fallback' . $data2_suffix; ## SEE ALSO: |Makefile|. my $patch_file_name = $data2_dir_name . 'modified.txt'; 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; require Encode; return Digest::MD5::md5_hex (Encode::encode ('utf8', 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 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; local $Data::Dumper::Useqq = 1; local *Data::Dumper::qquote = sub { my $s = shift; $s =~ s/([\x24\x27\x40\x5C])/sprintf '\x%02X', ord $1/ge; return q . $s . q<'>; }; # Data::Dumper::qquote my $had_file = -f $file_name; open my $file, '>:encoding(utf8)', $file_name or die "$0: $file_name: $!"; print $file "use utf8;\n"; 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 = {}; my $ModifiedHash = {}; 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; $ModifiedHash->{$hash} = 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}) { delete $Entry->{$file_name}->{modified}; write_data_file ($file_name => $Entry->{$file_name}); } } open my $file, '>>', $patch_file_name or die "$0: $patch_file_name: $!"; for (keys %$ModifiedHash) { print $file "$_\n"; } close $file; my $msg = shift // $0; system_ ('cvs', 'commit', -m => $msg, $data2_dir_name) 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 sub for_each_entry_set ($;$) { my $code = shift; my $on_the_fly = shift; 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 $file_name eq $fallback_file_name; if ($Entry->{$file_name}) { $code->($file_name, $Entry->{$file_name}); } elsif ($on_the_fly) { $code->($file_name, read_data_file ($file_name)); } else { $Entry->{$file_name} = read_data_file ($file_name); $code->($file_name, $Entry->{$file_name}); } } } # for_each_entry_set 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 get_entry_or_fallback_entry ($) { my $hash = shift; my ($is_pattern, $entry) = get_entry ($hash); unless (defined $entry->{en}) { $entry = get_fallback_entry ($hash); } $entry->{tags} ||= []; $entry->{isPattern} = 1 if $is_pattern; return $entry; } # get_entry_or_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 get_fallback_entries () { unless (defined $FallbackEntry) { $FallbackEntry = read_data_file ($fallback_file_name); } return $FallbackEntry; } # get_fallback_entries sub save_fallback_entries () { write_data_file ($fallback_file_name => $FallbackEntry) if defined $FallbackEntry; } # save_fallback_entries sub get_modified_hashes () { open my $file, '<', $patch_file_name or die "$0: $patch_file_name: $!"; return map {tr/\x0D\x0A//d; $_} <$file>; } # get_modified_hashes sub normalize_width ($) { my $s = shift; $s =~ tr{\x{3000}\x{FF01}-\x{FF5E}\x{FF61}-\x{FF9F}\x{FFE0}-\x{FFE6}} { !-~。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン\x{3099}\x{309A}\xA2\xA3\xAC\xAF\xA6\xA5\x{20A9}}; return $s; } # normalize_width sub htescape ($) { my $s = shift; $s =~ s/&/&/g; $s =~ s/ /dev/null") == 0 or die "$0: $?"; } # system_ 1; ## Author: Wakaba . ## License: Copyright 2008 Wakaba. You are granted a license to use, ## reproduce and create derivative works of this script. ## $Date: 2008/11/05 01:29:08 $