Add German translation
[srvx.git] / languages / validate.pl
diff --git a/languages/validate.pl b/languages/validate.pl
new file mode 100644 (file)
index 0000000..c7c822f
--- /dev/null
@@ -0,0 +1,208 @@
+#! /usr/bin/perl -w
+
+use strict;
+use warnings;
+use vars qw($field_re %lang %escapes);
+use FileHandle ();
+
+$| = 1;
+
+$field_re = qr/%.*?[diouxXeEfFgGaAcspn%]/;
+
+%escapes = (
+            '"' => '"',
+            'n' => "\n",
+            '\\' => "\\"
+           );
+
+sub split_format ($$$) {
+  my ($language, $key, $format) = @_;
+  my (@fields, @sorted, $indexed, $idx);
+
+  # C indexes things from argument 1.
+  $fields[0] = { type => 'dummy' };
+
+  # Parse each format field in the string.
+  while ($format =~ /($field_re)/g) {
+    my $field = $1;
+    next if substr($field, -1) eq '%';
+    if ($field =~ /^%(\d+\$)(#?0?-? ?\+?)(\*\d+\$|\d*)(.\*\d+\$|\.\d+)?((?:hh?|ll?|L|j|z|t)?.)$/) {
+      if (not defined $indexed) {
+        $indexed = 1;
+      } elsif (not $indexed) {
+        print "MISMATCH: ${language} ${key}, mix of indexed and unindexed fields\n";
+        return ();
+      }
+      my $res = {};
+      $res->{index} = substr($1, 0, -1);
+      $res->{flags} = $2;
+      $res->{width} = $3;
+      $res->{precision} = $4;
+      $res->{type} = $5;
+      $res->{width_idx} = $1
+        if $res->{width} and $res->{width} =~ /^\*\d+\$$/;
+      $res->{prec_idx} = $1
+        if $res->{precision} and $res->{precision} =~ /^.\*(\d+)\$$/;
+      push @fields, $res;
+    } elsif ($field =~ /^%(#?0?-? ?\+?)(\*|\d*)(.\*|\.\d+)?((?:hh?|ll?|L|j|z|t)?.)$/) {
+      if (not defined $indexed) {
+        $indexed = 0;
+        $idx = 1;
+      } elsif ($indexed) {
+        print "MISMATCH: ${language} ${key}, mix of indexed and unindexed fields\n";
+        return ();
+      }
+      my $res = {};
+      $res->{flags} = $1;
+      $res->{width} = $2;
+      $res->{precision} = $3;
+      $res->{type} = $4;
+      $res->{width_idx} = $idx++
+        if $res->{width} and $res->{width} eq '*';
+      $res->{prec_idx} = $idx++
+        if $res->{precision} and $res->{precision} eq '.*';
+      $res->{index} = $idx++;
+      push @fields, $res;
+    } else {
+      print "Unparsed field ${language} ${key}: $field\n";
+      next;
+    }
+  }
+
+  # Go through and make sure they are in fully sorted order, with
+  # precision arguments marked properly.
+  foreach my $field (@fields) {
+    next if $field->{type} eq 'dummy' or $field->{type} eq 'width' or $field->{type} eq 'precision';
+    my $idx = $field->{index};
+
+    # Check for conflicts with this field.
+    if (my $old = $sorted[$idx]) {
+      if ($old->{type} ne $field->{type}) {
+        print "MISMATCH ${key}: ${language} refers to param $idx as both type ".$old->{type}." and ".$field->{type}.".\n";
+        next;
+      }
+      if ($old->{precision} or $field->{precision}) {
+        if (exists($old->{prec_idx}) != exists($field->{prec_idx})) {
+          print "MISMATCH ${key}: ${language} has param $idx with and without a precision argument.\n";
+          next;
+        } elsif ($old->{prec_idx} != $field->{prec_idx}) {
+          print "MISMATCH ${key}: ${language} has param $idx with different precision arguments.\n";
+          next;
+        }
+      }
+      if ($old->{width} or $field->{width}) {
+        if (exists($old->{width_idx}) != exists($field->{width_idx})) {
+          print "MISMATCH ${key}: ${language} has param $idx with and without width argument.\n";
+        } elsif ($old->{width_idx} != $field->{width_idx}) {
+          print "MISMATCH ${key}: ${language} has param $idx with different width arguments.\n";
+        }
+      }
+    }
+    $sorted[$idx] = $field;
+
+    if (exists($field->{width_idx})) {
+      my $width_idx = $field->{width_idx};
+      if (my $old = $sorted[$width_idx]) {
+        if ($old->{type} ne 'width') {
+          print "MISMATCH ${key}: ${language} refers to param $idx as both type ".$old->{type}." and type width.\n";
+          next;
+        }
+      }
+      $sorted[$width_idx] = { type => 'width' };
+    }
+
+    if (exists($field->{prec_idx})) {
+      my $prec_idx = $field->{prec_idx};
+      # Check for conflicts with this field's precision argument.
+      if (my $old = $sorted[$prec_idx]) {
+        if ($old->{type} ne 'precision') {
+          print "MISMATCH ${key}: ${language} refers to param $idx as both type ".$old->{type}." and type precision.\n";
+          next;
+        }
+      }
+      $sorted[$prec_idx] = { type => 'precision' };
+    }
+  }
+
+  return @sorted;
+}
+
+sub compare_formats ($$$$) {
+  my ($language, $key, $orig_fmt, $new_fmt) = @_;
+
+  my @orig_fields = split_format('C', $key, $orig_fmt);
+  my @new_fields = split_format($language, $key, $new_fmt);
+  if (scalar(@orig_fields) != scalar(@new_fields)) {
+    print "MISMATCH ${key}: C has ".scalar(@orig_fields)." fields, ${language} has ".scalar(@new_fields)."\n";
+    return;
+  }
+  for (my $x = 1; $x <= $#orig_fields; $x++) {
+    my $orig = $orig_fields[$x];
+    my $new = $new_fields[$x];
+    if (not exists $orig->{type}) {
+      print "MISMATCH ${key}: C has no type for format $x!\n";
+    } elsif (not exists $new->{type}) {
+      print "MISMATCH ${key}: ${language} has no type for format $x!\n";
+    } if ($orig->{type} ne $new->{type}) {
+      print "MISMATCH ${key}: C refers to argument $x as type ".$orig->{type}.", ${language} as type ".$new->{type}.".\n";
+      next;
+    }
+    if ($orig->{width} or $new->{width}) {
+      if (not exists ($orig->{width_idx}) and not exists($new->{width_idx})) {
+        # both used fixed widths: no problem
+      } elsif (exists($orig->{width_idx}) and not exists($new->{width_idx})) {
+        print "MISMATCH ${key}: C has a width argument for format $x, ${language} does not.\n";
+      } elsif (not exists($orig->{width_idx}) and exists($new->{width_idx})) {
+        print "MISMATCH ${key}: ${language} has a width argument for format $x, C does not.\n";
+      } elsif ($orig->{width_idx} != $new->{width_idx}) {
+        print "MISMATCH ${key}: C and ${language} disagree on width argument for format $x.\n";
+      }
+    }
+    if ($orig->{precision} or $new->{precision}) {
+      if (not exists($orig->{prec_idx}) and not exists($new->{prec_idx})) {
+        # both used fixed precisions: no problem
+      } elsif (exists($orig->{prec_idx}) and not exists($new->{prec_idx})) {
+        print "MISMATCH ${key}: C has a precision argument for format $x, ${language} does not.\n";
+        next;
+      } elsif (not exists($orig->{prec_idx}) and exists($new->{prec_idx})) {
+        print "MISMATCH ${key}: $language has a precision argument for format $x, C does not.\n";
+        next;
+      } elsif ($orig->{prec_idx} != $new->{prec_idx}) {
+        print "MISMATCH ${key}: C and $language disagree on precision argument for format $x.\n";
+        next;
+      }
+    }
+  }
+}
+
+sub read_language ($) {
+  my $fname = shift;
+  my $fh = new FileHandle($fname, "r");
+  return undef unless defined $fh;
+  my $res = {};
+  while (defined($_ = $fh->getline)) {
+    chomp;
+    if (my ($key, $val) = /^"(\w+)" "(.+)";$/) {
+      $val =~ s/\\(.)/$escapes{$1}/eg;
+      $res->{$key} = $val;
+    } else {
+      print "Unrecognized line in $fname: $_\n";
+    }
+  }
+  return $res;
+}
+
+$lang{C} = read_language("strings.db");
+
+foreach my $language (@ARGV) {
+  next if exists $lang{$language};
+  $lang{$language} = read_language("${language}/strings.db")
+    or die "Unable to read $language: $!";
+  foreach my $key (keys %{$lang{$language}}) {
+    if (not $lang{C}->{$key}) {
+      print "Extra entry in ${language}: $key\n";
+      next;
+    }
+    compare_formats($language, $key, $lang{C}->{$key}, $lang{$language}->{$key});
+  }
+}