sort untranslated_keys output again
[rails.git] / script / locale / diff
1 #!/usr/bin/env perl
2 use strict;
3 use warnings;
4 use YAML::Syck qw(Load LoadFile);
5 use Test::Differences;
6 use Pod::Usage ();
7 use Getopt::Long ();
8
9 =head1 NAME
10
11 locale-diff - Compare two YAML files and print how their datastructures differ
12
13 =head1 SYNOPSIS
14
15     # --keys is the default
16     diff en.yml is.yml
17     diff --keys en.yml is.yml
18
19     # --untranslated-values compares prints keys whose values don't differ
20     diff --untranslated-values-all en.yml is.yml
21
22     # --untranslated-values-all compares prints keys whose values
23     # don't differ. Ignoring the blacklist which prunes things
24     # unlikley to be translated
25     diff --untranslated-values-all en.yml is.yml
26
27 =head1 DESCRIPTION
28
29 This utility prints the differences between two YAML files using
30 L<Test::Differences>. The purpose of it is to diff the files is
31 F<config/locales> to find out what keys need to be added to the
32 translated files when F<en.yml> changes.
33
34 =head1 OPTIONS
35
36 =over
37
38 =item -h, --help
39
40 Print this help message.
41
42 =item --keys
43
44 Show the hash keys that differ between the two files, useful merging
45 new entries from F<en.yml> to a local file.
46
47 =item --untranslated-values
48
49 Show keys whose values are either exactly the same between the two
50 files, or don't exist in the target file (the latter file
51 specified). The values are pruned according to global and language
52 specific blacklists found in the C<__DATA__> section of this script.
53
54 This helps to find untranslated values.
55
56 =item --untranslated-values-all
57
58 Like C<--untranslated-values> but ignores blacklists.
59
60 =back
61
62 =head1 AUTHOR
63
64 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@f-prot.com>
65
66 =cut
67
68 # Get the command-line options
69 Getopt::Long::Parser->new(
70     config => [ qw< bundling no_ignore_case no_require_order pass_through > ],
71 )->getoptions(
72     'h|help' => \my $help,
73     'keys' => \my $keys,
74     'untranslated-values' => \my $untranslated_values,
75     'untranslated-values-all' => \my $untranslated_values_all,
76 ) or help();
77
78 # --keys is the default
79 $keys = 1 if not $untranslated_values_all and not $untranslated_values;
80
81 # On --help
82 help() if $help;
83
84 # If we're not given two .yml files
85 help() if @ARGV != 2 or (!-f $ARGV[0] or !-f $ARGV[1]);
86
87 my ($from, $to) = @ARGV;
88
89 my $from_data = LoadFile($from);
90 my $to_data   = LoadFile($to);
91
92 my $from_parsed = { iterate($from_data->{basename($from)}) };
93 my $to_parsed = { iterate($to_data->{basename($to)}) };
94
95 # Since this used to be the default, support that...
96 if ($keys)
97 {
98     print_key_differences();
99 }
100 elsif ($untranslated_values or $untranslated_values_all)
101 {
102     my @untranslated = untranslated_keys($from_parsed, $to_parsed);
103
104     # Prune according to blacklist
105     if ($untranslated_values) {
106         @untranslated = prune_untranslated_with_blacklist(basename($to), @untranslated);
107     }
108
109     print $_, "\n" for @untranslated;
110 }
111
112 exit 0;
113
114 sub print_key_differences
115 {
116     # Hack around Test::Differences wanting a Test::* module loaded
117     $INC{"Test.pm"} = 1;
118     sub Test::ok { print shift }
119
120     # Diff the tree
121     eq_or_diff([ sort keys %$from_parsed ], [ sort keys %$to_parsed ]);
122 }
123
124 sub untranslated_keys
125 {
126     my ($from_parsed, $to_parsed) = @_;
127     sort grep { not exists $to_parsed->{$_} or $from_parsed->{$_} eq $to_parsed->{$_} } keys %$from_parsed;
128 }
129
130 sub prune_untranslated_with_blacklist
131 {
132     my ($language, @keys) = @_;
133     my %keys;
134     @keys{@keys} = ();
135
136     my $end_yaml = Load(join '', <DATA>);
137     my $untranslated_values = $end_yaml->{untranslated_values};
138     my $default = $untranslated_values->{default};
139     my $this_language = $untranslated_values->{$language} || {};
140
141     my %bw_list = (%$default, %$this_language);
142     
143     use feature ':5.10';
144     use Data::Dump 'dump';
145     say STDERR dump \%bw_list;
146
147     while (my ($key, $blacklisted) = each %bw_list)
148     {
149         # FIXME: Does syck actually support true/false booleans in yaml?
150         delete $keys{$key} if $blacklisted eq 'true'
151     }
152
153     sort keys %keys;
154 }
155
156 sub iterate
157 {
158     my ($hash, @path) = @_;
159     my @ret;
160         
161     while (my ($k, $v) = each %$hash)
162     {
163         if (ref $v eq 'HASH')
164         {
165              push @ret => iterate($v, @path, $k);
166         }
167         else
168         {
169             push @ret => join(".",@path, $k), $v;
170         }
171     }
172
173     return @ret;
174 }
175
176 sub basename
177 {
178     my $name = shift;
179     $name =~ s[\..*?$][];
180     $name;
181 }
182
183 sub help
184 {
185     my %arg = @_;
186
187     Pod::Usage::pod2usage(
188         -verbose => $arg{ verbose },
189         -exitval => $arg{ exitval } || 0,
190     );
191 }
192
193 __DATA__
194 untranslated_values:
195
196   # Default/Per language blacklist/whitelist for the
197   # --untranslated-values switch. "true" as a value indicates that the
198   # key is to be blacklisted, and "false" that it's to be
199   # whitelisted. "false" is only required to whitelist a key
200   # blacklisted by default on a per-language basis.
201
202   default:
203     html.dir: true
204     layouts.intro_3_bytemark: true
205     layouts.intro_3_ucl: true
206   de:
207     layouts.export: true
208