Last active
April 18, 2020 14:08
-
-
Save trueroad/f83f2fbf68aa49ea7f3ab0708bf5fb9e to your computer and use it in GitHub Desktop.
Post action for haranoaji to link or copy files for TeX Live tlgs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# haranoaji-tlpost.pl: | |
# Post action for haranoaji to link or copy files for TeX Live tlgs | |
# https://gist.github.com/trueroad/f83f2fbf68aa49ea7f3ab0708bf5fb9e | |
# | |
# Copyright (C) 2020 Masamichi Hosoda. | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions | |
# are met: | |
# | |
# * Redistributions of source code must retain the above copyright notice, | |
# this list of conditions and the following disclaimer. | |
# | |
# * Redistributions in binary form must reproduce the above copyright notice, | |
# this list of conditions and the following disclaimer in the documentation | |
# and/or other materials provided with the distribution. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
# ARE DISCLAIMED. | |
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
# SUCH DAMAGE. | |
# | |
use strict; | |
use warnings; | |
use utf8; | |
use feature 'state'; | |
use Encode; | |
my $verbose = 0; | |
if (win32 ()) { | |
# These packages are not necessarily available on non-Windows, so | |
# read them at runtime instead of compile time. | |
require Encode::Locale; | |
Encode::Locale->import (); | |
# ':encoding(console_{in|out})' requires Encode::Locale package. | |
binmode (STDIN, ':encoding(console_in)'); | |
binmode (STDOUT, ':encoding(console_out)'); | |
binmode (STDERR, ':encoding(console_out)'); | |
require Win32::API; | |
Win32::API->import (); | |
require File::Compare; | |
File::Compare->import (); | |
} else { | |
# for non-Windows, silently do nothing, to avoid tl-update-tlnet failure. | |
exit 0; | |
} | |
# Font list: PostScript name => File name in TEXMF | |
my %font_list = ( | |
'HaranoAjiMincho-ExtraLight' => 'HaranoAjiMincho-ExtraLight.otf', | |
'HaranoAjiMincho-Light' => 'HaranoAjiMincho-Light.otf', | |
'HaranoAjiMincho-Regular' => 'HaranoAjiMincho-Regular.otf', | |
'HaranoAjiMincho-Medium' => 'HaranoAjiMincho-Medium.otf', | |
'HaranoAjiMincho-SemiBold' => 'HaranoAjiMincho-SemiBold.otf', | |
'HaranoAjiMincho-Bold' => 'HaranoAjiMincho-Bold.otf', | |
'HaranoAjiMincho-Heavy' => 'HaranoAjiMincho-Heavy.otf', | |
'HaranoAjiGothic-ExtraLight' => 'HaranoAjiGothic-ExtraLight.otf', | |
'HaranoAjiGothic-Light' => 'HaranoAjiGothic-Light.otf', | |
'HaranoAjiGothic-Normal' => 'HaranoAjiGothic-Normal.otf', | |
'HaranoAjiGothic-Regular' => 'HaranoAjiGothic-Regular.otf', | |
'HaranoAjiGothic-Medium' => 'HaranoAjiGothic-Medium.otf', | |
'HaranoAjiGothic-Bold' => 'HaranoAjiGothic-Bold.otf', | |
'HaranoAjiGothic-Heavy' => 'HaranoAjiGothic-Heavy.otf' | |
); | |
# CMap list for install | |
my @cmap_list_install = ('2004-H', '2004-V'); | |
# CMap list for snippet | |
my @cmap_list_snippet = ( | |
'H', 'V', | |
'2004-H', '2004-V', | |
'UniJIS-UTF16-H', 'UniJIS-UTF16-V', | |
'UniJIS2004-UTF16-H', 'UniJIS2004-UTF16-V', | |
'UniJIS-UCS2-H' | |
); | |
my $mode = lc (decode ('locale', $ARGV[0])); | |
my $texdir = decode ('locale', $ARGV[1]); | |
unshift (@INC, "$texdir/tlpkg"); | |
require TeXLive::TLUtils; | |
my_log ("haranoaji-tlpost\n"); | |
my $resourcedir = "$texdir/tlpkg/tlgs/Resource"; | |
my $cidfontdir = "$resourcedir/CIDFont"; | |
my $cmapdir = "$resourcedir/CMap"; | |
my $fontdir = "$resourcedir/Font"; | |
if ($mode eq 'install') { | |
do_install(); | |
} elsif ($mode eq 'remove') { | |
do_remove(); | |
} else { | |
die ("unknown mode: $mode\n"); | |
} | |
my_log ("\ndone.\n"); | |
exit 0; | |
# Install | |
sub do_install { | |
my_log ("\ndo_install\n"); | |
tlgs_check (); | |
install_cidfont (); | |
install_cmap (); | |
install_snippet (); | |
} | |
# Remove | |
sub do_remove { | |
my_log ("\ndo_remove\n"); | |
tlgs_check (); | |
remove_cidfont (); | |
# We don't remove CMaps because other components may use them. | |
#remove_cmap (); | |
remove_snippet (); | |
} | |
# tlgs directories existence check | |
sub tlgs_check { | |
wrapper_isdir ($resourcedir) or | |
die ("Not found: tlgs Resource directory: ${resourcedir}\n"); | |
wrapper_isdir ($cidfontdir) or | |
die ("Not found: tlgs CIDFont directory: ${cidfontdir}\n"); | |
wrapper_isdir ($cmapdir) or | |
die ("Not found: tlgs CMap directory: ${cmapdir}\n"); | |
wrapper_isdir ($cmapdir) or | |
die ("Not found: tlgs Font directory: ${fontdir}\n"); | |
} | |
# Link or copy CID font | |
sub install_cidfont { | |
my_log ("\ninstall_cidfont\n"); | |
for my $psname (sort keys %font_list) { | |
my_log ("\nFor $psname ...\n"); | |
my $fontpath_in_texmf = search_fontpath ($font_list{$psname}); | |
if (! $fontpath_in_texmf) { | |
my_log ("Not found: Font in TEXMF: $font_list{$psname}\n"); | |
next; | |
} | |
my $fontpath_in_cidfontdir = "$cidfontdir/$psname"; | |
if (wrapper_isfile ($fontpath_in_cidfontdir)) { | |
my_log ("Already exists: ${fontpath_in_cidfontdir}\n"); | |
if (file_compare ($fontpath_in_texmf, $fontpath_in_cidfontdir)) { | |
my_log ("Different from the file in TEXMF. Deleting.\n"); | |
wrapper_unlink ($fontpath_in_cidfontdir); | |
} else { | |
my_log ("Same as the file in TEXMF. Skipping.\n"); | |
next; | |
} | |
} | |
link_or_copy ($fontpath_in_texmf, $fontpath_in_cidfontdir); | |
} | |
} | |
# Link or copy CMap | |
sub install_cmap { | |
my_log ("\ninstall_cmap\n"); | |
foreach my $cmapname (@cmap_list_install) { | |
my_log ("\nFor $cmapname ...\n"); | |
my $cmappath_in_texmf = search_cmappath ($cmapname); | |
if (! $cmappath_in_texmf) { | |
my_log ("Not found: CMap in TEXMF: $cmapname\n"); | |
next; | |
} | |
my $cmappath_in_cmapdir = "$cmapdir/$cmapname"; | |
if (wrapper_isfile ($cmappath_in_cmapdir)) { | |
my_log ("Already exists: ${cmappath_in_cmapdir}\n"); | |
if (file_compare ($cmappath_in_texmf, $cmappath_in_cmapdir)) { | |
my_log ("Different from the file in TEXMF. Deleting.\n"); | |
wrapper_unlink ($cmappath_in_cmapdir); | |
} else { | |
my_log ("Same as the file in TEXMF. Skipping.\n"); | |
next; | |
} | |
} | |
link_or_copy ($cmappath_in_texmf, $cmappath_in_cmapdir); | |
} | |
} | |
# Create snippet | |
sub install_snippet { | |
my_log ("\ninstall_snippet\n"); | |
for my $psname (sort keys %font_list) { | |
my_log ("\nFor $psname ...\n"); | |
foreach my $cmapname (@cmap_list_snippet) { | |
my $snippetpath = "$fontdir/$psname-$cmapname"; | |
if (wrapper_isfile ($snippetpath)) { | |
my_log ("Already exists: ${snippetpath}\nDeleting.\n"); | |
wrapper_unlink ($snippetpath); | |
} | |
my $contents = <<"EOS"; | |
%!PS-Adobe-3.0 Resource-Font | |
%%DocumentNeededResources: $cmapname (CMap) | |
%%IncludeResource: $cmapname (CMap) | |
%%BeginResource: Font ($psname-$cmapname) | |
($psname-$cmapname) | |
($cmapname) /CMap findresource | |
[($psname) /CIDFont findresource] | |
composefont | |
pop | |
%%EndResource | |
%%EOF | |
EOS | |
create_file_with_contents ($snippetpath, | |
encode ('UTF-8', $contents)); | |
my_log ("Created: ${snippetpath}\n"); | |
} | |
} | |
} | |
# Remove CID font | |
sub remove_cidfont { | |
my_log ("\nremove_cidfont\n"); | |
for my $psname (sort keys %font_list) { | |
my_log ("\nFor $psname ...\n"); | |
my $fontpath_in_cidfontdir = "$cidfontdir/$psname"; | |
if (wrapper_isfile ($fontpath_in_cidfontdir)) { | |
wrapper_unlink ($fontpath_in_cidfontdir); | |
my_log ("Removed: ${fontpath_in_cidfontdir}\n"); | |
} else { | |
my_log ("Not exist: ${fontpath_in_cidfontdir}\n"); | |
} | |
} | |
} | |
# Remove snippet | |
sub remove_snippet { | |
my_log ("\nremove_snippet\n"); | |
for my $psname (sort keys %font_list) { | |
my_log ("\nFor $psname ...\n"); | |
foreach my $cmapname (@cmap_list_snippet) { | |
my $snippetpath = "$fontdir/$psname-$cmapname"; | |
if (wrapper_isfile ($snippetpath)) { | |
wrapper_unlink ($snippetpath); | |
my_log ("Removed: ${snippetpath}\n"); | |
} else { | |
my_log ("Not exist: ${snippetpath}\n"); | |
} | |
} | |
} | |
} | |
# Check Windows environment | |
sub win32 { return ($^O=~/^MSWin(32|64)$/i); } | |
# Log | |
sub my_log { | |
my ($msg) = @_; | |
print $msg if ($verbose); | |
TeXLive::TLUtils::log ($msg); | |
} | |
# Warn | |
sub my_warn { | |
my ($msg) = @_; | |
TeXLive::TLUtils::tlwarn ($msg); | |
} | |
# Search TEXMF font path | |
sub search_fontpath { | |
my ($filename) = @_; | |
my $foo = kpsewhich ($filename); | |
if ($foo eq "") { | |
my_log ("Not found font path: ${filename}\n"); | |
return; | |
} | |
my_log ("Font path is ${foo}.\n"); | |
return $foo; | |
} | |
# Search TEXMF CMap path | |
sub search_cmappath { | |
my ($cmapname) = @_; | |
my $foo = kpsewhich ("-format=cmap $cmapname"); | |
if ($foo eq "") { | |
my_log ("Not found CMap path: ${cmapname}\n"); | |
return; | |
} | |
my_log ("CMap path is ${foo}.\n"); | |
return $foo; | |
} | |
# Invoke kpsewhich | |
sub kpsewhich { | |
my ($name) = @_; | |
# We use perl's active code page for invoking kpsewhich | |
# because perl passes the command line string to the invoking API. | |
my $cmdline = encode ('locale', "kpsewhich $name"); | |
chomp (my $result = `$cmdline`); | |
# !!!FIXME!!! | |
# We can not know the character code output by kpsewhich. | |
# So we assume that it is the same as perl's active code page. | |
# The active code pages for perl and kpsewhich may be different, | |
# in which case non-US-ASCII characters are garbled. | |
# There is no problem when the path name contains only US-ASCII. | |
$result = decode ('locale', $result); | |
return $result; | |
} | |
# Try symbolic link, hard link, copy | |
sub link_or_copy { | |
my ($existingfilename, $newfilename) = @_; | |
if (wrapper_symlink ($existingfilename, $newfilename)) { | |
my_log ("Symbolic link succeeded: ${newfilename}\n"); | |
return; | |
} | |
if (wrapper_hardlink ($existingfilename, $newfilename)) { | |
my_log ("Hard link succeeded: ${newfilename}\n"); | |
return; | |
} | |
if (wrapper_copy ($existingfilename, $newfilename)) { | |
my_log ("Copy succeeded: ${newfilename}\n"); | |
return; | |
} | |
my_warn ("Link/copy failed: ${newfilename}\n"); | |
} | |
# Create file with contents | |
sub create_file_with_contents { | |
my ($filename, $contents) = @_; | |
# TODO: Create file by -W API on Windows | |
open (my $fh, ">:raw", encode ('locale_fs', $filename)) or | |
die ("Open failed: ${filename}\n"); | |
print $fh $contents; | |
close $fh; | |
} | |
# File compare | |
sub file_compare { | |
my ($filename1, $filename2) = @_; | |
# TODO: Compare file by -W API on Windows | |
my $result = compare (encode ('locale_fs', $filename1), | |
encode ('locale_fs', $filename2)); | |
if ($result == -1) { | |
die ("File compare failed: ${filename1}, ${filename2}\n"); | |
} | |
return $result; | |
} | |
# Directory existence check | |
sub wrapper_isdir { | |
my ($dirname) = @_; | |
# TODO: Directory existence check by -W API on Windows | |
if ( -d encode ('locale_fs', $dirname) ) { | |
return 1; | |
} | |
return; | |
} | |
# File existence check | |
sub wrapper_isfile { | |
my ($filename) = @_; | |
# TODO: File existence check by -W API on Windows | |
if ( -f encode ('locale_fs', $filename) ) { | |
return 1; | |
} | |
return; | |
} | |
# Remove file | |
sub wrapper_unlink { | |
my ($filename) = @_; | |
# TODO: Remove file by -W API on Windows | |
return unlink (encode ('locale_fs', $filename)); | |
} | |
# Create symbolic link | |
sub wrapper_symlink { | |
my ($existingfilename, $newfilename) = @_; | |
# We use CreateSymbolicLinkW API directly for creating symbolic link | |
# because perl's symlink function may not work in Windows. | |
state $createsymboliclinkw = load_createsymboliclinkw (); | |
$existingfilename =~ s|/|\\|g; | |
$newfilename =~ s|/|\\|g; | |
my $r = $createsymboliclinkw->Call ( | |
encode ('UTF-16LE', $newfilename), | |
encode ('UTF-16LE', $existingfilename), | |
0 | |
); | |
if (ord($r) != 0) { | |
return 1; | |
} | |
my $msg = decode ('locale', | |
Win32::FormatMessage (Win32::GetLastError ())); | |
$msg =~ s/[\r\n]+\z//; | |
my_log ("CreateSymbolicLinkW failed: ${msg}\n"); | |
return 0; | |
} | |
# Create hard link | |
sub wrapper_hardlink { | |
my ($existingfilename, $newfilename) = @_; | |
# We use CreateHardLinkW API directly for creating hard link | |
# because -W API is not affected by the perl's active code page. | |
state $createhardlinkw = load_createhardlinkw (); | |
$existingfilename =~ s|/|\\|g; | |
$newfilename =~ s|/|\\|g; | |
my $r = $createhardlinkw->Call ( | |
encode ('UTF-16LE', $newfilename), | |
encode ('UTF-16LE', $existingfilename), | |
0 | |
); | |
if ($r) { | |
return 1; | |
} | |
my $msg = decode ('locale', | |
Win32::FormatMessage (Win32::GetLastError ())); | |
$msg =~ s/[\r\n]+\z//; | |
my_log ("CreateHardLinkW failed: ${msg}\n"); | |
return 0; | |
} | |
# Copy file | |
sub wrapper_copy { | |
my ($existingfilename, $newfilename) = @_; | |
# We use CopyFileW API directly for copying file | |
# because -W API is not affected by the perl's active code page. | |
state $copyfilew = load_copyfilew (); | |
$existingfilename =~ s|/|\\|g; | |
$newfilename =~ s|/|\\|g; | |
my $r = $copyfilew->Call ( | |
encode ('UTF-16LE', $existingfilename), | |
encode ('UTF-16LE', $newfilename), | |
1 | |
); | |
if ($r) { | |
return 1; | |
} | |
my $msg = decode ('locale', | |
Win32::FormatMessage (Win32::GetLastError ())); | |
$msg =~ s/[\r\n]+\z//; | |
my_log ("CopyFileW failed: ${msg}\n"); | |
return 0; | |
} | |
# Load CreateSymbolicLinkW API | |
sub load_createsymboliclinkw { | |
my $createsymboliclinkw = Win32::API::More->new ( | |
'kernel32.dll', | |
'BOOLEAN CreateSymbolicLinkW( | |
LPCWSTR lpSymlinkFileName, | |
LPCWSTR lpTargetFileName, | |
DWORD dwFlags | |
)' | |
) or die ('Failed: Win32::API::More->new CreateSymbolicLinkW'); | |
return $createsymboliclinkw; | |
} | |
# Load CreateHardLinkW API | |
sub load_createhardlinkw { | |
my $createhardlinkw = Win32::API::More->new ( | |
'kernel32.dll', | |
'BOOL CreateHardLinkW( | |
LPCWSTR lpFileName, | |
LPCWSTR lpExistingFileName, | |
UINT_PTR lpSecurityAttributes | |
)' | |
) or die ('Failed: Win32::API::More->new CreateHardLinkW'); | |
return $createhardlinkw; | |
} | |
# Load CopyFileW API | |
sub load_copyfilew { | |
my $copyfilew = Win32::API::More->new ( | |
'kernel32.dll', | |
'BOOL CopyFileW( | |
LPCWSTR lpExistingFileName, | |
LPCWSTR lpNewFileName, | |
BOOL bFailIfExists | |
)' | |
) or die ('Failed: Win32::API::More->new CopyFileW'); | |
return $copyfilew; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment