Bug 1082: mem_free_set in abort_preloading
[elinks.git] / po / perl / msgaccel-prepare
blob4b68310feeb36f60fc52350022021f9111c66cc9
1 #! /usr/bin/perl
2 # The copyright notice and license are in the POD at the bottom.
4 use strict;
5 use warnings;
6 use Locale::PO qw();
7 use Getopt::Long qw(GetOptions :config bundling gnu_compat);
8 use autouse 'Pod::Usage' => qw(pod2usage);
9 use autouse 'File::Spec::Functions' => qw(catfile);
11 my $VERSION = "1.2";
13 sub show_version
15 print "msgaccel-prepare $VERSION\n";
16 pod2usage({-verbose => 99, -sections => "COPYRIGHT AND LICENSE",
17 -exitval => 0});
21 package Contextline;
22 use fields qw(lineno contexts);
23 sub new {
24 my($self, $lineno, $contexts) = @_;
25 $self = fields::new($self) unless ref $self;
26 $self->{lineno} = $lineno;
27 $self->{contexts} = $contexts;
28 return $self;
32 my @Srcpath;
33 my $Accelerator_tag;
35 # Each key is a file name.
36 # Each value is a reference to an array of references to Contextline
37 # pseudo-hashes. The array is in ascending order by {lineno}.
38 my %Srcfiles;
40 sub open_file_on_path ($@)
42 my($fname, @path) = @_;
43 if (@path) {
44 my @warnings;
45 foreach my $dir (@path) {
46 my $full_fname = catfile($dir, $fname);
47 if (open my $fh, "<", $full_fname) {
48 return($fh, $full_fname);
49 } else {
50 push @warnings, "$full_fname: $!\n";
53 # Didn't find $name anywhere on @path.
54 warn $_ foreach @warnings;
55 return;
56 } else { # no path
57 if (open my $fh, "<", $fname) {
58 return($fh, $fname);
59 } else {
60 warn "$fname: $!\n";
61 return;
64 # not reached
67 # Scan the file $src_fname for gettext_accelerator_context directives,
68 # cache the result in %Srcfiles, and return it in that format.
69 # Cache and return [] if the file cannot be read on @Srcpath.
70 sub contextlines ($)
72 my($src_fname) = @_;
73 return $Srcfiles{$src_fname} if exists($Srcfiles{$src_fname});
74 my @contextlines = ();
76 if (my($src_fh, $src_full_fname) = open_file_on_path($src_fname, @Srcpath)) {
77 my @prevctxs;
78 local $_;
79 while (<$src_fh>) {
80 chomp;
81 if (/^\}/ && @prevctxs) {
82 push @contextlines, Contextline->new($., [@prevctxs = ()]);
84 if (my($contexts) = /\[gettext_accelerator_context\(([^()]*)\)\]/) {
85 my @contexts = grep { $_ ne "" } split(/\s*,\s*/, $contexts);
86 s/^\./${src_fname}:/ foreach @contexts;
87 warn "$src_full_fname:$.: Previous context not closed\n"
88 if @prevctxs && @contexts;
89 warn "$src_full_fname:$.: Context already closed\n"
90 if !@prevctxs && !@contexts;
91 push @contextlines, Contextline->new($., [@prevctxs = @contexts]);
92 } elsif (/gettext_accelerator_context/) {
93 warn "$src_full_fname:$.: Suspicious non-directive: $_\n";
96 warn "$src_full_fname:$.: Last context not closed\n" if @prevctxs;
97 } # if opened ok
99 return $Srcfiles{$src_fname} = \@contextlines;
102 sub contexts ($$)
104 my($srcfile, $lineno) = @_;
105 # Could use a binary search here.
106 my $contextlines = contextlines($srcfile);
107 my @contexts = ();
108 foreach my Contextline $contextline (@{$contextlines}) {
109 return @contexts if $contextline->{lineno} > $lineno;
110 @contexts = @{$contextline->{contexts}};
112 return ();
115 sub gather_accelerator_contexts ($$)
117 my($pos, $po_fname) = @_;
118 foreach my $po (@$pos) {
119 my $automatic = $po->automatic();
120 $automatic =~ s/^\[gettext_accelerator_context\(.*(?:\n|\z)//mg
121 if defined($automatic);
123 if ($po->msgid() =~ /\Q$Accelerator_tag/s) {
124 my @po_contexts = ();
125 foreach my $ref (split(' ', $po->reference())) {
126 my @parts = split(/\:/, $ref);
127 warn "weird reference: $ref\n", next unless @parts == 2;
128 my @ref_contexts = contexts($parts[0], $parts[1]);
129 if (@ref_contexts) {
130 push @po_contexts, grep { $_ ne "IGNORE" } @ref_contexts;
131 } else {
132 warn "$ref: No accelerator context for msgid " . $po->msgid() . "\n";
135 if (@po_contexts) {
136 # sort and uniquify
137 @po_contexts = sort keys %{{map { $_ => 1 } @po_contexts}};
138 $automatic .= "\n" if defined($automatic) and $automatic ne "";
139 $automatic .= "accelerator_context(" . join(", ", @po_contexts) . ")";
142 $po->automatic($automatic);
146 GetOptions("srcdir|source-directory|S=s" => \@Srcpath,
147 "accelerator-tag=s" => sub {
148 my($option, $value) = @_;
149 die "Cannot use multiple --accelerator-tag options\n"
150 if defined($Accelerator_tag);
151 die "--accelerator-tag requires a single-character argument\n"
152 if length($value) != 1;
153 $Accelerator_tag = $value;
155 "help" => sub { pod2usage({-verbose => 1, -exitval => 0}) },
156 "version" => \&show_version)
157 or exit 2;
158 $Accelerator_tag = "~" unless defined $Accelerator_tag;
159 print(STDERR "$0: missing file operand\n"), exit 2 unless @ARGV;
160 print(STDERR "$0: too many operands\n"), exit 2 if @ARGV > 1;
162 my($po_fname) = @ARGV;
163 my $pos = Locale::PO->load_file_asarray($po_fname) or die "$po_fname: $!";
164 gather_accelerator_contexts($pos, $po_fname);
165 Locale::PO->save_file_fromarray($po_fname, $pos) or die "$po_fname: $!";
167 __END__
169 =head1 NAME
171 msgaccel-prepare - Augment a PO file with information for detecting
172 accelerator conflicts.
174 =head1 SYNOPSIS
176 B<msgaccel-prepare> [I<option> ...] F<I<program>.pot>
178 =head1 DESCRIPTION
180 B<msgaccel-prepare> is part of a framework that detects conflicting
181 accelerator keys in Gettext PO files. A conflict is when two items in
182 the same menu or two buttons in the same dialog box use the same
183 accelerator key.
185 The PO file format does not normally include any information
186 on which strings will be used in the same menu or dialog box.
187 B<msgaccel-prepare> adds this information in the form of
188 "accelerator_context" comments, which B<msgaccel-check>
189 then parses in order to detect the conflicts.
191 The PO file format also does not directly support definitions of
192 accelerator keys. Typically, the keys are encoded in C<msgstr>
193 strings, by placing a tilde in front of the character that should be
194 used as the accelerator key. That is also the syntax supported by
195 this framework and by B<msgfmt --check-accelerators> of GNU Gettext.
197 B<msgaccel-prepare> first reads the F<I<program>.pot> file named on
198 the command line. This file must include "#:" comments that point
199 to the source files from which B<xgettext> extracted each C<msgid>.
200 B<msgaccel-prepare> then scans those source files for context
201 information and rewrites F<I<program>.pot> to include the
202 "accelerator_context" comments. Finally, the standard tool
203 B<msgmerge> can be used to copy the added comments to all the
204 F<I<language>.po> files.
206 It is best to run B<msgaccel-prepare> immediately after B<xgettext>
207 so that the source references will be up to date.
209 =head2 Contexts
211 Whenever a source file refers to an C<msgid> that includes an
212 accelerator key, it must assign one or more named B<contexts> to it.
213 The C<msgstr>s in the PO files inherit these contexts. If multiple
214 C<msgstr>s use the same accelerator (case insensitive) in the same
215 context, that's a conflict and can be detected automatically.
217 If the same C<msgid> is used in multiple places in the source code,
218 and those places assign different contexts to it, then all of those
219 contexts will apply.
221 The names of contexts consist of C identifiers delimited with periods.
222 The identifier is typically the name of a function that sets up a
223 dialog, or the name of an array where the items of a menu are listed.
224 There is a special feature for file-local identifiers (C<static> in C):
225 if the name begins with a period, then the period will be replaced
226 with the name of the source file and a colon. The name "IGNORE" is
227 reserved.
229 If a menu is programmatically generated from multiple parts, of which
230 some are never used together, so that it is safe to use the same
231 accelerators in them, then it is necessary to define multiple contexts
232 for the same menu.
234 =head2 How to define contexts in source files
236 The contexts are defined with "gettext_accelerator_context" comments
237 in source files. These comments delimit regions where all C<msgid>s
238 that seem to contain accelerators are given the same contexts. There
239 must be one special comment at the top of the region; it lists the
240 contexts assigned to that region. The region automatically ends at
241 the end of the function (found with regexp C</^\}/>), but it can also
242 be closed explicitly with another special comment. The comments are
243 formatted like this:
245 /* [gettext_accelerator_context(foo, bar, baz)]
246 begins a region that uses the contexts "foo", "bar", and "baz".
247 The comma is the delimiter; whitespace is optional.
249 [gettext_accelerator_context()]
250 ends the region. */
252 B<msgaccel-prepare> removes from F<I<program>.pot> any
253 "gettext_accelerator_context" comments that B<xgettext --add-comments>
254 may have copied there.
256 B<msgaccel-prepare> warns if it does not find any contexts for some
257 use of an C<msgid> that contains the character specified with the
258 B<--accelerator-tag> option. If the character does not actually
259 indicate an accelerator in that C<msgid> (e.g. "~" in "~/.bashrc"),
260 the warning can be silenced by specifying the special context
261 "IGNORE", which B<msgaccel-prepare> otherwise ignores.
263 =head1 OPTIONS
265 =over
267 =item B<-S>F<I<srcdir>>, B<--srcdir=>F<I<srcdir>>,
268 B<--source-directory=>F<I<srcdir>>
270 The directory to which the source references in "#:" lines are
271 relative. Each use of this option adds one directory to the search
272 path. If you do not specify this option, B<msgaccel-prepare>
273 implicitly searches the current directory.
275 =item B<--accelerator-tag=>I<character>
277 Specify the character that marks accelerators in C<msgid> strings.
278 B<msgaccel-prepare> looks up accelerator contexts for any C<msgid>
279 that contains this character.
281 Omitting the B<--accelerator-tag> option implies
282 B<--accelerator-tag="~">. The option must be given to each program
283 separately because there is no standard way to save this information
284 in the PO file.
286 =back
288 =head1 ARGUMENTS
290 =over
292 =item F<I<program>.pot>
294 The file to augment with context information. B<msgaccel-prepare>
295 first reads this file and then overwrites it.
297 Although this documentation keeps referring to F<I<program>.pot>,
298 you can also use B<msgaccel-prepare> on an already translated
299 F<I<language>.po>. However, that will only work correctly if the
300 source references in the "#:" lines are still up to date.
302 =back
304 =head1 BUGS
306 B<msgaccel-prepare> assumes that source files are in the C programming
307 language: specifically, that a closing brace at the beginning of a
308 line marks the end of a function.
310 B<msgaccel-prepare> doesn't check whether the
311 "gettext_accelerator_context" comments actually are comments.
313 =head1 AUTHOR
315 Kalle Olavi Niemitalo <kon@iki.fi>
317 =head1 COPYRIGHT AND LICENSE
319 Copyright (c) 2005-2006 Kalle Olavi Niemitalo.
321 Permission to use, copy, modify, and/or distribute this software for any
322 purpose with or without fee is hereby granted, provided that the above
323 copyright notice and this permission notice appear in all copies.
325 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
326 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
327 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
328 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
329 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
330 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
331 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
333 =head1 SEE ALSO
335 L<msgaccel-check>, C<xgettext(1)>, C<msgmerge(1)>