From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / OSX / update-info-plist.pl
1 #!/usr/bin/perl -w
2 # Copyright © 2006-2013 Jamie Zawinski <jwz@jwz.org>
3 #
4 # Permission to use, copy, modify, distribute, and sell this software and its
5 # documentation for any purpose is hereby granted without fee, provided that
6 # the above copyright notice appear in all copies and that both that
7 # copyright notice and this permission notice appear in supporting
8 # documentation.  No representations are made about the suitability of this
9 # software for any purpose.  It is provided "as is" without express or 
10 # implied warranty.
11 #
12 # Updates the NAME.xml file of a .saver bundle to include the current year,
13 # version number, etc.  Also updates the Info.plist file to include the
14 # short documentation, authors, etc. in the Finder "Get Info" properties.
15 #
16 # This is invoked by a final "Shell Script" build action on each of the
17 # .saver targets in the XCode project.
18 #
19 # Created:  8-Mar-2006.
20
21 require 5;
22 #use diagnostics;       # Fails on some MacOS 10.5 systems
23 use strict;
24
25 my ($exec_dir, $progname) = ($0 =~ m@^(.*?)/([^/]+)$@);
26
27 my $version = q{ $Revision: 1.24 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
28
29 $ENV{PATH} = "/usr/local/bin:$ENV{PATH}";   # for seticon
30
31 my $thumbdir = $ENV{HOME} . '/www/xscreensaver/screenshots/';
32
33
34
35 my $verbose = 1;
36
37 sub read_info_plist($);
38 sub read_info_plist($) {
39   my ($app_dir) = @_;
40   my $file  = "$app_dir/Contents/Info.plist";
41   my $file2 = "$app_dir/Info.plist";
42   $file =~ s@/+@/@g;
43   my $in;
44   if (open ($in, '<', $file)) {
45   } elsif (open ($in, '<', $file2)) {
46     $file = $file2;
47   } else {
48     error ("$file: $!");
49   }
50   print STDERR "$progname: read $file\n" if ($verbose > 2);
51   local $/ = undef;  # read entire file
52   my $body = <$in>;
53   close $in;
54
55   if ($body =~ m/^bplist/s) {
56     print STDERR "$progname: converting binary plist file: $file\n";
57     system ("plutil", "-convert", "xml1", $file);
58     return read_info_plist ($app_dir);
59   }
60
61   return ($file, $body);
62 }
63
64
65 sub read_saver_xml($) {
66   my ($app_dir) = @_;
67   error ("$app_dir: no name") 
68     unless ($app_dir =~ m@/([^/.]+).(app|saver)/?$@x);
69   my $name  = $1;
70
71   return () if ($name eq 'XScreenSaver');
72   return () if ($name eq 'SaverTester');
73
74   my $file  = "$app_dir/Contents/Resources/" . lc($name) . ".xml";
75   my $file2 = "$app_dir/" . lc($name) . ".xml";
76   my $file3 = "$app_dir/Contents/PlugIns/$name.saver/Contents/Resources/" .
77               lc($name) . ".xml";
78   $file =~ s@/+@/@g;
79   my $in;
80   if (open ($in, '<', $file)) {
81   } elsif (open ($in, '<', $file2)) { $file = $file2;
82   } elsif (open ($in, '<', $file3)) { $file = $file3;
83   } else {
84     error ("$file: $!");
85   }
86   print STDERR "$progname: read $file\n" if ($verbose > 2);
87   local $/ = undef;  # read entire file
88   my $body = <$in>;
89   close $in;
90   return ($file, $body);
91 }
92
93
94 sub update_saver_xml($$) {
95   my ($app_dir, $vers) = @_;
96   my ($filename, $body) = read_saver_xml ($app_dir);
97   my $obody = $body;
98
99   return () unless defined ($filename);
100
101   $body =~ m@<screensaver[^<>]*?[ \t]_label=\"([^\"]+)\"@m ||
102     error ("$filename: no name label");
103   my $name = $1;
104
105   $body =~ m@<_description>(.*?)</_description>@s ||
106     error ("$filename: no description tag");
107   my $desc = $1;
108   $desc =~ s/^([ \t]*\n)+//s;
109   $desc =~ s/\s*$//s;
110
111   # in case it's done already...
112   $desc =~ s@<!--.*?-->@@gs;
113   $desc =~ s/^.* version \d[^\n]*\n//s;
114   $desc =~ s/^From the XScreenSaver.*\n//m;
115   $desc =~ s@^http://www\.jwz\.org/xscreensaver.*\n@@m;
116   $desc =~
117        s/\nCopyright [^ \r\n\t]+ (\d{4})(-\d{4})? (.*)\.$/\nWritten $3; $1./s;
118   $desc =~ s/^\n+//s;
119
120   error ("$filename: description contains bad characters")
121     if ($desc =~ m/([^\t\n -~]|[<>])/);
122
123   error ("$filename: can't extract authors")
124     unless ($desc =~ m@^(.*)\nWritten by[ \t]+(.+)$@s);
125   $desc = $1;
126   my $authors = $2;
127   $desc =~ s/\s*$//s;
128
129   my $year = undef;
130   if ($authors =~ m@^(.*?)\s*[,;]\s+(\d\d\d\d)([-\s,;]+\d\d\d\d)*[.]?$@s) {
131     $authors = $1;
132     $year = $2;
133   }
134
135   error ("$filename: can't extract year") unless $year;
136   my $cyear = 1900 + ((localtime())[5]);
137   $year = "$cyear" unless $year;
138   if ($year && ! ($year =~ m/$cyear/)) {
139     $year = "$year-$cyear";
140   }
141
142   $authors =~ s/[.,;\s]+$//s;
143
144   # List me as a co-author on all of them, since I'm the one who
145   # did the OSX port, packaged it up, and built the executables.
146   #
147   my $curator = "Jamie Zawinski";
148   if (! ($authors =~ m/$curator/si)) {
149     if ($authors =~ m@^(.*?),? and (.*)$@s) {
150       $authors = "$1, $2, and $curator";
151     } else {
152       $authors .= " and $curator";
153     }
154   }
155
156   my $desc1 = ("$name, version $vers.\n\n" .            # savername.xml
157                $desc . "\n" .
158                "\n" . 
159                "From the XScreenSaver collection: " .
160                "http://www.jwz.org/xscreensaver/\n" .
161                "Copyright \251 $year by $authors.\n");
162
163   my $desc2 = ("$name $vers,\n" .                       # Info.plist
164                "\302\251 $year $authors.\n" .
165                "From the XScreenSaver collection:\n" .
166                "http://www.jwz.org/xscreensaver/\n" .
167                "\n" .
168                $desc .
169                "\n");
170
171   # unwrap lines, but only when it's obviously ok: leave blank lines,
172   # and don't unwrap if that would compress leading whitespace on a line.
173   #
174   $desc2 =~ s/^(From |http:)/\n$1/gm;
175   1 while ($desc2 =~ s/([^\s])[ \t]*\n([^\s])/$1 $2/gs);
176   $desc2 =~ s/\n\n(From |http:)/\n$1/gs;
177
178   $body =~ s@(<_description>)(.*?)(</_description>)@$1$desc1$3@s;
179
180   if ($obody eq $body) {
181     print STDERR "$progname: $filename: unchanged\n" if ($verbose > 1);
182   } else {
183     my $file_tmp = "$filename.tmp";
184     open (my $out, '>', $file_tmp) || error ("$file_tmp: $!");
185     print $out $body || error ("$file_tmp: $!");
186     close $out || error ("$file_tmp: $!");
187
188     if (!rename ("$file_tmp", "$filename")) {
189       unlink "$file_tmp";
190       error ("mv \"$file_tmp\" \"$filename\": $!");
191     }
192     print STDERR "$progname: wrote $filename\n" if ($verbose);
193   }
194
195   return ($desc1, $desc2);
196 }
197
198
199
200 sub set_plist_key($$$$) {
201   my ($filename, $body, $key, $val) = @_;
202
203   if ($body =~ m@^(.*
204                   \n\t<key>$key</key>
205                   \n\t<string>)([^<>]*)(</string>
206                   .*)$@xs) {
207 #    print STDERR "$progname: $filename: $key was: $2\n" if ($verbose);
208     $body = $1 . $val . $3;
209   } else {
210     error ("$filename: unparsable")
211       unless ($body =~ m@^(.*)(\n</dict>\n</plist>\n)$@s);
212     $body = ($1 .
213              "\n\t<key>$key</key>" .
214              "\n\t<string>$val</string>" .
215              $2);
216   }
217
218   return $body;
219 }
220
221
222 sub set_icon($) {
223   my ($app_dir) = @_;
224   $app_dir =~ s@/+$@@s;
225
226   # "seticon" is from osxutils, http://osxutils.sourceforge.net/
227
228   my $icon = ($app_dir =~ m/\.saver$/ ? 'XScreenSaver' : 'SaverRunner');
229   $icon = "$app_dir/../../../$icon.icns";
230   my @cmd = ("seticon", "-d", $icon, $app_dir);
231   print STDERR "$progname: exec: " . join(' ', @cmd) . "\n"
232     if ($verbose > 1);
233   system (@cmd);
234 }
235
236
237 sub set_thumb($) {
238   my ($app_dir) = @_;
239
240   return unless ($app_dir =~ m@\.saver/?$@s);
241
242   my @cmd = ("$exec_dir/update-thumbnail.pl", $thumbdir, $app_dir);
243   push @cmd, "-" . ("v" x $verbose) if ($verbose);
244   print STDERR "$progname: exec: " . join(' ', @cmd) . "\n"
245     if ($verbose > 1);
246   system (@cmd);
247   my $exit  = $? >> 8;
248   exit ($exit) if $exit;
249 }
250
251
252 sub update($) {
253   my ($app_dir) = @_;
254
255   error ("$app_dir: no name") 
256     unless ($app_dir =~ m@/([^/.]+).(app|saver)/?$@x);
257   my $app_name = $1;
258
259   my ($filename, $plist) = read_info_plist ($app_dir);
260   my $oplist = $plist;
261
262   error ("$filename: no version number")
263     unless ($plist =~ m@<key>CFBundleShortVersionString</key>\s*
264                         <string>([^<>]+)</string>@sx);
265   my $vers = $1;
266   my ($ignore, $info_str) = update_saver_xml ($app_dir, $vers);
267
268   if (! defined($info_str)) {
269     print STDERR "$progname: $filename: no XML file\n" if ($verbose > 1);
270   } else {
271
272     $info_str =~ m@^([^\n]+)\n@s ||
273       error ("$filename: unparsable copyright");
274     my $copyright = "$1";
275     $copyright =~ s/\b\d{4}-(\d{4})\b/$1/;
276
277     # Lose the Wikipedia URLs.
278     $info_str =~ s@http:.*?\b(wikipedia|mathworld)\b[^\s]+[ \t]*\n?@@gm;
279
280     $info_str =~ s/(\n\n)\n+/$1/gs;
281     $info_str =~ s/(^\s+|\s+$)//gs;
282     $plist = set_plist_key ($filename, $plist, 
283                             "NSHumanReadableCopyright", $copyright);
284     $plist = set_plist_key ($filename, $plist,
285                             "CFBundleLongVersionString",$copyright);
286     $plist = set_plist_key ($filename, $plist,
287                             "CFBundleGetInfoString",    $info_str);
288
289     if ($oplist eq $plist) {
290       print STDERR "$progname: $filename: unchanged\n" if ($verbose > 1);
291     } else {
292       my $file_tmp = "$filename.tmp";
293       open (my $out, '>', $file_tmp) || error ("$file_tmp: $!");
294       print $out $plist || error ("$file_tmp: $!");
295       close $out || error ("$file_tmp: $!");
296
297       if (!rename ("$file_tmp", "$filename")) {
298         unlink "$file_tmp";
299         error ("mv \"$file_tmp\" \"$filename\": $!");
300       }
301       print STDERR "$progname: wrote $filename\n" if ($verbose);
302     }
303   }
304
305   set_icon ($app_dir);
306   set_thumb ($app_dir);
307 }
308
309
310 sub error($) {
311   my ($err) = @_;
312   print STDERR "$progname: $err\n";
313   exit 1;
314 }
315
316 sub usage() {
317   print STDERR "usage: $progname [--verbose] program.app ...\n";
318   exit 1;
319 }
320
321 sub main() {
322
323   my @files = ();
324   while ($_ = $ARGV[0]) {
325     shift @ARGV;
326     if    (m/^--?verbose$/s)  { $verbose++; }
327     elsif (m/^-v+$/)          { $verbose += length($_)-1; }
328     elsif (m/^--?q(uiet)?$/s) { $verbose = 0; }
329     elsif (m/^-/s)            { usage(); }
330     else                      { push @files, $_; }
331   }
332   usage() unless ($#files >= 0);
333   foreach (@files) {
334     update ($_);
335   }
336 }
337
338 main();
339 exit 0;