first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

113
scripts/tools/fg-check Executable file
View File

@@ -0,0 +1,113 @@
#!/bin/bash
#
# Checks source code and data for potential problems.
# Meant to be executed before submitting/committing.
SELF=${0##*/}
# optional apps
RLE=$(which rle 2>/dev/null) # http://members.aon.at/mfranz/rle.tar.gz (depends on Qt lib)
AC3D_SCAN=$(which ac3d-scan 2>/dev/null) # http://members.aon.at/mfranz/ac3d-scan
function ERROR { echo -e "\e[31;1m$*\e[m"; }
function LOG { echo -e "\e[35m$*\e[m"; }
function RESULT { echo -e "\t$*"; }
TMP=$(mktemp -d -t $SELF.XXX) || (echo "$0: can't create temporary dir"; exit 1)
trap "rm -rf $TMP" 0 1 2 3 13 15
LOG "checking for spaces in filenames ..."
find .|grep " "|while read i; do RESULT "$i"; done
LOG "checking for upper-case extensions ..."
find .|while read i; do
case "$i" in .|..|CVS/*|*/CVS/*|*.Opt|*.README|*.Po|*.TXT) continue ;; esac
base=${i##*/}
ext=${base##*.}
[ "$base" == "$ext" ] && continue # has no extension
ext=${ext//[^a-zA-Z]/}
[ "$ext" ] || continue # only non-letters
lcext=$(echo $ext|sed -e 's,\(.*\),\L\1,')
[ "$ext" != "$lcext" ] && RESULT "$i"
done
LOG "checking for DOS line endings ..."
find . -type f|while read i; do
desc=$(file -b "$i")
case "$desc" in *text*)
grep "
$" "$i" >/dev/null && RESULT "$i"
;;
esac
done
LOG "checking for uncompressed textures ..."
find . -iname \*.rgb -o -iname \*.rgba|while read i; do
new=$TMP/sgi.rgb
if [ -x "$RLE" ]; then
cp "$i" $new && "$RLE" $new 2>&1|grep corrupt &>/dev/null && ERROR "\t$i ... FILE CORRUPTED"
else
convert "$i" -compress RLE sgi:$new
fi
perl -e '
my $file = shift;
my $old = -s $file;
my $new = -s shift;
if ($new < $old) {
printf "\t$file: could be %0.02f%% of current size (%d bytes less)\n",
100 * $new / $old, $old - $new;
}
' "$i" $new
done
if [ -x "$AC3D_SCAN" ]; then
LOG "checking for AC3D sanity ..."
find . -iname \*.ac|while read i; do
case "$i" in configure.ac|*/configure.ac) continue ;; esac
result=$($AC3D_SCAN <$i 2>&1)
[ "$result" ] && echo -e "$result\n\t... in file \e[36m$i\e[m";
done
fi
LOG "checking for thumbnail file size (expected JPEG 171x128)"
find . -name thumbnail.jpg|while read i; do
id=$(identify "$i")
if ! echo $id|grep "JPEG 171x128" >/dev/null; then
RESULT "$i ... $id"
fi
done
LOG "checking for 'userarchive' flags (not allowed in aircraft XML files) ..."
find . -name \*.xml|while read i; do
if grep "userarchive" $i >/dev/null; then
RESULT "$i"
fi
done
LOG "checking for XML syntax ..."
find . -name \*.xml|while read i; do
xmllint $i >/dev/null || RESULT "... in file \e[36m$i\e[m"
done
LOG "checking for 'if (foo) delete foo;' ..."
find . -iregex ".*\.\([ch]\(xx\|pp\)\|cc\|h\)$"|while read i; do perl -e '
my $i = 0;
my $name = $ARGV[0];
undef $/;
$_ = <>;
s/(if\s*\(([^\)]+)\)\s*delete(?:\s+|\s*\[\]\s*)\2\s*;)/print "$1\n" and $i++/ges;
print "\t... \033[36min file $name\033[m\n" if $i;
' "$i"; done

366
scripts/tools/fg-submit Executable file
View File

@@ -0,0 +1,366 @@
#!/bin/bash
#
# This script called in a CVS directory compares local files with
# the repository, and prepares an update package containing all
# changes and new files for submission to a CVS maintainer. If there
# are only changes in text files, then a compressed unified diff is
# made (foo.diff.bz2). If there are also changed binary or new files,
# then an archive is made instead (foo.tar.bz2). The base name ("foo")
# can be given as command line argument. Otherwise the directory name
# is used. The script also leaves a diff in uncompressed/unpackaged
# form. This is only for developer convenience -- for a quick check
# of the diff correctness. It is not to be submitted. The script will
# not overwrite any file, but rather rename conflicting files.
#
# Usage: fg-submit [-v] [<basename>]
#
# Options:
# -v ... verbose output
#
# Example:
# $ cd $FG_ROOT/Aircraft/bo105
# $ fg-submit # -> bo105.diff.bz2 or bo105.tar.bz2
#
# $ fg-submit update # -> update.diff.bz2 or update.tar.bz2
#
#
# Spaces in the basename are replaced with "%20". People who prefer
# to have the date in the archive name can conveniently achieve this
# by defining a shell alias in ~/.bashrc:
#
# alias submit='fg-submit "${PWD##*/}-$(date +%Y-%m-%d)"'
#
#
#
# If the script finds an application named "fg-upload", then it calls
# this at the end with two arguments:
#
# $1 ... archive or compressed diff for submission
# $2 ... accessory uncompressed diff, *NOT* for submission!
#
# $1 and $2 are guaranteed not to contain spaces, only $1 is guaranteed
# to actually exist. Such a script can be used to upload the file to an
# ftp-/webserver, and/or to remove one or both files. Example using
# KDE's kfmclient for upload (alternatives: ncftpput, gnomevfs-copy):
#
# $ cat ~/bin/fg-upload
# #!/bin/bash
# echo "uploading $1"
# if kfmclient copy $1 ftp://user:password@server.com; then
# echo "deleting $1 $2"
# rm -f $1 $2
#
# URL=ftp://server.com/$1
#
# # copy URL to KDE's clipboard, so that MMB-clicking pastes it
# dcop klipper klipper setClipboardContents $URL
#
# echo "Done. --> $URL"
# else
# echo "$0: uploading failed!"
# fi
#
#
#
# Whether a file should be included in the archive or not, is decided
# by pattern rules. There is a set of reasonable default rules predefined,
# but alternative settings can be defined in a hidden configuration file
# named ".fg-submit". Such a file is searched in the current directory,
# in its parent directory, in its grand-parent directory and so on,
# and finally in the $HOME directory. The first found file is taken.
#
# A file can use a list of three keywords with arguments, each on a
# separate line:
#
# ALLOW <pattern-list> ... accept & report matching file
# DENY <pattern-list> ... reject & report matching file
# IGNORE <pattern-list> ... silently reject matching file
#
# A <pattern-list> is a space-separated list of shell pattern.
# It may also be empty, in which case it has no effect. Examples:
#
# DENY test.blend
# ALLOW *.xcf *.blend
#
# The list of pattern is checked in the same order in which it was
# built. The first match causes a file to be accepted or rejected.
# Further matches are not considered. Comments using the hash
# character '#' are allowed and ignored.
#
# Some default rules are always added at the end. If you want to
# bypass them, then finish your configuration with an "ALLOW *"
# or "DENY *", and no file will ever reach the default rules.
#
#
# Example:
#
# DENY test.xcf # throw out the test image, but ...
# ALLOW *.xcf # ... allow all other GIMP images (the default
# # rules would otherwise throw them out)
#
# ALLOW not.old # add this file, but ...
# IGNORE *.old # throw out all other "old" files (and don't
# # report that to the terminal)
#
#
# .fg-submit configuration files are "sourced" bash scripts, the
# keywords are simple shell functions. That means that you can
# also use other bash commands in that file, such as "echo", or
# write several commands on one line, separated with semicolon.
# You can even put all special rules in your ~/.fg-submit file,
# with rules depending on the working directory:
#
# case "$PWD" in
# */bo105*) DENY *.osg; ALLOW livery.xcf ;;
# */ufo*) DENY *.tiff ;;
# esac
SELF=${0##*/}
DIR=${PWD##*/}
if [ "$1" == "-v" ]; then
DBG=1
shift
fi
BASE=${1:-$DIR}
BASE=${BASE// /%20}
CVS=/usr/bin/cvs # avoid colorcvs wrapper from
[ -x $CVS ] || CVS=cvs # http://www.hakubi.us/colorcvs/
UPLOAD=$(which fg-upload 2>/dev/null)
CONFIG_FILE=".fg-submit"
ARCHIVE=$BASE.tar.bz2
DIFF=$BASE.diff
CDIFF=$DIFF.bz2
# these rules are always prepended; the first letter decides if the
# rule accepts (+), rejects (-), or ignores (!) a matching file.
PREFIX_RULES="
!$DIFF* !$CDIFF* !$ARCHIVE*
!CVS/* !*/CVS/*
"
# these rules are always appended
DEFAULT_RULES="
+.cvsignore +*/.cvsignore
-*~ -*. -*.bak -*.orig
-*.RGB -*.RGBA -*.MDL
-*.XCF -*.tga -*.TGA -*.bmp -*.BMP -*.PNG
-*.blend -*.blend[0-9] -*blend[0-9][0-9] -*.blend[0-9][0-9][0-9]
-*.gz -*.tgz -*.bz2 -*.zip -*.tar.gz* -*.tar.bz2*
"
POSTFIX_RULES="
!.* !*/.*
+*
"
function ERROR { echo -e "\e[31;1m$*\e[m"; }
function LOG { echo -e "\e[35m$*\e[m"; }
function NEW { echo -e "\e[32m\t+ $*\e[m"; }
function CHANGED { echo -e "\e[36m\t+ $*\e[m"; }
function REJECT { echo -e "\e[31m\t- $*\e[m"; }
function DEBUG { [ $DBG ] && echo -e "$*"; }
function diffstat {
# output diff statistics, similar to the "diffstat" utility
awk '
function line(a, r, c, f) {
print "\t\033[32m"a"\033[m\t\033[31m"r"\033[m\t\033[34m"c"\033[m\t"f
}
function dofile() {
if (!file) {
return
}
if (bin) {
print "\t. . . . binary . . . . \033[36m"file"\033[m"
} else {
line(a, r, c, file)
at += a; rt += r; ct += c
}
a = r = c = 0
}
BEGIN {
print "\tadded---removed-changed----------------------------------------"
a = r = c = at = rt = ct = n = bin = 0
}
/^Index: / { dofile(); scan = bin = 0; file = $2; n++; next }
/^@@/ { scan = 1; next }
/^Binary/ { if (!scan) bin = 1; next }
/^\+/ { if (scan) a++; next }
/^-/ { if (scan) r++; next }
/^!/ { if (scan) c++; next }
END {
dofile()
print "\t----------------------------------------total------------------"
line(at, rt, ct, "\033[min "n" files")
}
' <$1
}
function backup_filename {
for ((i = 1; 1; i = i + 1)); do
name=$1.$i
if ! [ -a "$name" ]; then
touch $name
echo $name
return
fi
done
}
# set up accept/reject rules
function ALLOW { for i in $*; do RULES="$RULES +$i"; done }
function DENY { for i in $*; do RULES="$RULES -$i"; done }
function IGNORE { for i in $*; do RULES="$RULES !$i"; done }
function search_config {
file="$1/$CONFIG_FILE"
DEBUG "checking for config file $file"
if [ -f "$file" ]; then
CONFIG="$file"
return 0
elif [ "$1" ]; then
search_config ${1%/${1##*/}} # parent dir
return
fi
return 1
}
set -f
RULES=
if search_config "$PWD"; then
LOG "loading config file $CONFIG"
source "$CONFIG"
elif [ -f ~/$CONFIG_FILE ]; then
DEBUG "loading config file ~/$CONFIG_FILE"
source ~/$CONFIG_FILE
elif [ -f ~/${CONFIG_FILE}rc ]; then
DEBUG "loading config file ~/${CONFIG}rc"
source ~/${CONFIG_FILE}rc
fi
RULES="$PREFIX_RULES $RULES $DEFAULT_RULES $POSTFIX_RULES"
set +f
if [ $DBG ]; then
DEBUG "using these rules: "
for i in $RULES; do echo -n "$i "; done
echo
fi
# create temporary dir that's automatically removed on exit
TMP=$(mktemp -d /tmp/$SELF.$BASE.XXXXXX) || (echo "$0: can't create temporary dir"; exit 1)
trap "rm -rf $TMP" 0 1 2 3 13 15
# move old files out of the way adding sequential suffixes
for i in $DIFF $CDIFF $ARCHIVE; do
[ -f $i ] && mv $i $(backup_filename $i)
done
LOG "updating and checking for new files ..."
$CVS -q up -dP >$TMP/up || exit 1
if grep "^C " $TMP/up &>/dev/null; then
ERROR "there are conflicts with the following files:"
grep "^C " $TMP/up
exit 1
fi
LOG "making diff ..."
if ! $CVS -q diff -up >$DIFF; then
LOG "diff statistics:"
diffstat $DIFF
echo
# add diff file itself
echo $DIFF >>$TMP/files
# add changed binary files
awk '
/^Index: / { scan = 1; file = $2; next }
/^@@/ { scan = 0; next }
/^Binary/ { if (scan) { print file } }
' <$DIFF >>$TMP/files
else
rm -f $DIFF
fi
LOG "checking for files to submit ..."
if [ -f $TMP/files ]; then
cat $TMP/files|while read i; do
CHANGED "$i"
done
fi
grep "^? " $TMP/up|while read i; do
find ${i#? } -type f >>$TMP/check
done
# filter files according to the pattern rules
if [ -f $TMP/check ]; then
for i in $(cat $TMP/check); do
DEBUG "checking whether file '$i' matches"
for r in $RULES; do
DEBUG "\t\trule $r"
class=${r:0:1}
rule=${r:1}
case "$i" in $rule)
case $class in
!) DEBUG "$i\t\t\"silently\" rejected\t\t$rule" ;;
-) REJECT "$i\t\t$rule" ;;
+) NEW "$i\t\t$rule" && echo "$i" >>$TMP/files ;;
esac
break
;;
esac
done
done
fi
if ! [ -f $TMP/files ]; then
LOG "no changed or new files found"
exit 0
fi
echo
numfiles=$(awk '//{n++}END{print n}' <$TMP/files)
if [ -f $DIFF -a $numfiles == 1 ]; then
LOG "only changed non-binary files found"
LOG "creating compressed diff \e[1;37;40m$CDIFF\e[m\e[35m ..."
bzip2 --keep $DIFF
RESULT=$CDIFF
else
LOG "changed and/or new files found"
LOG "creating archive \e[1;37;40m$ARCHIVE\e[m\e[35m ..."
tar --create --bzip2 --file=$ARCHIVE --files-from $TMP/files
RESULT=$ARCHIVE
fi
[ -x "$UPLOAD" -a -f $RESULT ] && $UPLOAD $RESULT $DIFF

302
scripts/tools/freq Executable file
View File

@@ -0,0 +1,302 @@
#!/usr/bin/perl -w
# $Id$
# Melchior FRANZ <mfranz#aon:at> Public Domain
#
# Usage: $ freq [IACO:ksfo [RANGE:15]]
#
# Examples: $ freq
# $ freq ksjc
# $ freq ksjc 30
#
# The RANGE is in km and defines which NDB, VOR, VORTAC, ... to
# display. Default is 15 km.
#
# Note that the directions given for NDB, VOR, VORTAC, ... are
# always the heading from this radio facility to the airport!
use strict;
use POSIX qw(ceil floor);
my $ID = shift || "KSFO";
my $RANGE = shift || 15; # for NDB/VOR [km]
my $FG_ROOT = $ENV{'FG_ROOT'} || "/usr/local/share/FlightGear";
my $APTFILE = "$FG_ROOT/Airports/apt.dat.gz" || die "airport file not found";
my $NAVFILE = "$FG_ROOT/Navaids/nav.dat.gz" || die "nav file not found";
$ID = uc($ID);
my $PI = 3.1415926535897932384626433832795029;
my $D2R = $PI / 180;
my $R2D = 180 / $PI;
my $ERAD = 6378138.12;
my %COLOR = (
'NONE' => "\033[m",
'DME' => "\033[34;1",
'ILS' => "\033[33;1m",
'TWR' => "\033[31;1m",
'ATIS' => "\033[32;1m",
'NDB' => "\033[36;1m",
'VOR' => "\033[35;1m",
);
my $USECOLOR = 1;
my %FREQ;
my $aptdatacnt = 0;
my $aptlat = 0;
my $aptlon = 0;
open(F, "gzip -d -c $APTFILE|") or die "can't open airport file $APTFILE";
while (<F>) {
if (/^1\s+\S+\s+\S+\s+\S+\s+$ID\s+(.+)\s+/) {
my $title = "$ID - $1";
print "$title\n";
print "=" x length($title) . "\n";
foreach (<F>) {
chomp;
last if /^\s*$/;
if (/^1.\s+(\S+)\s+(\S+)\s+/) {
my ($lat, $lon) = ($1, $2);
map { s/^(-?)0+/$1/ } ($lat, $lon);
$aptlat += $lat;
$aptlon += $lon;
$aptdatacnt++;
} elsif (/^(5\d+)\s+(\d+)\s+(.*)\s*/) {
my ($id, $freq, $desc) = ($1, $2, $3);
$freq =~ s/(..)$/.$1/;
&addfreq($freq, $desc);
}
}
last;
}
}
close F or die "can't close airport file $APTFILE";
die "no data for $ID" unless $aptdatacnt;
# calculate mean location from all structures on the airport
$aptlat /= $aptdatacnt;
$aptlon /= $aptdatacnt;
my ($aptx, $apty, $aptz) = &ll2xyz($aptlat, $aptlon);
my @OM;
my @MM;
my @IM;
my @NDB;
my @VOR;
my @DME;
my @OTHERS;
open(F, "gzip -d -c $NAVFILE|") or die "can't open airport file $NAVFILE";
while (<F>) {
chomp;
if (/^2\s/) { # NDB
my @l = split /\s+/, $_, 9;
map { s/^(-?)0+/$1/ } @l[1,2];
my $dist = &coord_dist_sq(&ll2xyz($l[1], $l[2]), $aptx, $apty, $aptz);
push @NDB, [$dist, @l];
} elsif (/^3\s/) { # VOR/VOR-DME/DME/VORTAC/TACAN
my @l = split /\s+/, $_, 9;
map { s/^(-?)0+/$1/ } @l[1,2];
my $dist = &coord_dist_sq(&ll2xyz($l[1], $l[2]), $aptx, $apty, $aptz);
if ($l[8] =~ /\b(VOR|VOR-DME)$/) {
push @VOR, [$dist, @l];
} elsif ($l[8] =~ /\bDME\b/) {
push @DME, [$dist, @l];
} else {
push @OTHERS, [$dist, @l];
}
} elsif (/^(4|5)\s/) { # LLZ
my @l = split /\s+/, $_, 11;
next unless $l[8] eq $ID;
$l[4] =~ s/(..)$/.$1/;
&addfreq($l[4], "LLZ " . $l[9]);
} elsif (/^6\s/) { # GS
my @l = split /\s+/, $_, 11;
next unless $l[8] eq $ID;
$l[4] =~ s/(..)$/.$1/;
&addfreq($l[4], "GS " . $l[9]);
} elsif (/^7\s/) { # OM
my @l = split /\s+/, $_, 11;
next unless $l[8] eq $ID;
push @OM, $l[9];
} elsif (/^8\s/) { # MM
my @l = split /\s+/, $_, 11;
next unless $l[8] eq $ID;
push @MM, $l[9];
} elsif (/^9\s/) { # IM
my @l = split /\s+/, $_, 11;
next unless $l[8] eq $ID;
push @IM, $l[9];
} elsif (/^12\s/) { # DME (ILS)
my @l = split /\s+/, $_, 11;
next unless $l[8] eq $ID;
$l[4] =~ s/(..)$/.$1/;
&addfreq($l[4], "DME " . $l[9]);
}
}
close F or die "can't close airport file $NAVFILE";
foreach my $freq (sort { $a <=> $b } keys %FREQ) {
my %h;
map { $h{$_} = 1 } @{$FREQ{$freq}};
my @uniq = keys %h;
my @desc;
my %rwy;
foreach my $d (@uniq) {
if ($d =~ /(\S*)\s*(\d\d[LRC]?)\s*(\S*)/) {
push @{$rwy{$2}}, ($1 . $3);
} else {
push @desc, $d;
}
}
foreach my $r (keys %rwy) {
push @desc, ((join "/", sort @{$rwy{$r}}) . " $r");
}
my $s;
my $k = join ", ", @desc;
if ($k =~ /\bTWR\b/) {
$s = $COLOR{'TWR'};
} elsif ($k =~ /\bATIS\b/) {
$s = $COLOR{'ATIS'};
} elsif ($k =~ /\b(GZ|LLZ)\b/) {
$s = $COLOR{'ILS'};
}
$s .= sprintf "%-7s %s\033[m\n", $freq, join ", ", $k;
print $s;
}
&printfreq(0, $COLOR{'NDB'}, @NDB);
&printfreq(1, $COLOR{'VOR'}, @VOR);
&printfreq(1, $COLOR{'DME'}, @DME);
&printfreq(1, $COLOR{'NONE'}, @OTHERS);
print " OM " . (join ", ", sort @OM) . "\n" if @OM;
print " MM " . (join ", ", sort @MM) . "\n" if @MM;
print " IM " . (join ", ", sort @IM) . "\n" if @IM;
exit 0;
sub printfreq($$$)
{
my $divfreq = shift; # divide frequency by 100?
my $color = shift;
foreach (sort { @{$a}[0] <=> @{$b}[0] } @_) {
my @l = @{$_};
my $dist = &distance($l[0]);
my $dir = &llll2dir($l[2], $l[3], $aptlat, $aptlon);
my $freq = $l[5];
$freq =~ s/(..)$/.$1/ if $divfreq;
printf "$color%-7s %s (\"%s\")\t-->\t%s km/%s nm @ %s (%s)$COLOR{'NONE'}\n",
$freq, $l[9], $l[8],
&round($dist, 0.1), # km
&round($dist / 1.852, 0.1), # nm
int $dir, &symdir($dir);
next if $l[9] =~ /\b$ID\b/;
last if $dist > $RANGE;
}
}
sub addfreq($$)
{
my ($freq, $desc) = @_;
push @{$FREQ{$freq}}, $desc;
}
sub distance($) # km
{
my $t = shift;
return $ERAD * sqrt($t) / 1000;
}
sub round($)
{
my $i = shift;
my $m = (shift or 1);
$i /= $m;
$i = $i - &floor($i) >= 0.5 ? &ceil($i) : &floor($i);
$i *= $m;
return $i;
}
sub llll2dir($$$$)
{
my $latA = (shift) * $D2R;
my $lonA = (shift) * $D2R;
my $latB = (shift) * $D2R;
my $lonB = (shift) * $D2R;
my $xdist = sin($lonB - $lonA) * $ERAD * cos(($latA + $latB) / 2);
my $ydist = sin($latB - $latA) * $ERAD;
my $dir = atan2($xdist, $ydist) * $R2D;
$dir += 360 if $dir < 0;
return $dir;
}
sub ll2xyz($$)
{
my $lat = (shift) * $D2R;
my $lon = (shift) * $D2R;
my $cosphi = cos $lat;
my $di = $cosphi * cos $lon;
my $dj = $cosphi * sin $lon;
my $dk = sin $lat;
return ($di, $dj, $dk);
}
sub xyz2ll($$$)
{
my ($di, $dj, $dk) = @_;
my $aux = $di * $di + $dj * $dj;
my $lat = atan2($dk, sqrt $aux) * $R2D;
my $lon = atan2($dj, $di) * $R2D;
return ($lat, $lon);
}
sub coord_dist_sq($$$$$$)
{
my ($xa, $ya, $za, $xb, $yb, $zb) = @_;
my $x = $xb - $xa;
my $y = $yb - $ya;
my $z = $zb - $za;
return $x * $x + $y * $y + $z * $z;
}
sub symdir($)
{
my $dir = shift;
my @names = ("N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW");
my $nnames = scalar @names;
my $idx = int($nnames * (($dir / 360) + (0.5 / $nnames)));
if ($idx >= $nnames) {
$idx = 0;
}
return $names[$idx];
}

289
scripts/tools/lsprop Executable file
View File

@@ -0,0 +1,289 @@
#!/usr/bin/python
import glob, os, sys, string, xml.sax, getopt
__doc__ = """\
List properties defined in FlightGear's <PropertyList> XML files.
Usage:
lsprop [-v] [-p] [-i|-I] [-f <format>] [<list-of-xml-files>]
lsprop -h
Options:
-h, --help print this help screen
-v, --verbose increase verbosity
-i, --all-indices also show null indices in properties
-I, --no-indices don't show any indices in properties
-p, --raw-paths don't use symbols "$FG_ROOT" and "$FG_HOME" as path prefix
-f, --format set output format (default: --format="%f +%l: %p = '%v'")
Format:
%f file path
%l line number
%c column number
%p property path
%t property type
%V raw value (unescaped)
%v cooked value (carriage return, non printable chars etc. escaped)
%q like %v, but single quotes escaped to \\'
%Q like %v, but double quotes escaped to \\"
%% percent sign
Environment:
FG_ROOT
FG_HOME
LSPROP_FORMAT overrides default format
Arguments:
If no file arguments are specified, then the following files are assumed:
$FG_ROOT/preferences.xml
$FG_ROOT/Aircraft/*/*-set.xml
Current settings:\
"""
class config:
root = "/usr/local/share/FlightGear"
home = os.environ["HOME"] + "/.fgfs"
raw_paths = 0
format = "%f +%l: %p = '%v'"
verbose = 1
indices = 1 # 0: no indices; 1: only indices != [0]; 2: all indices
def errmsg(msg, color = "31;1"):
if os.isatty(2):
print >>sys.stderr, "\033[%sm%s\033[m" % (color, msg)
else:
print >>sys.stderr, msg
def cook_path(path, force = 0):
path = os.path.normpath(os.path.abspath(path))
if config.raw_paths and not force:
return path
if path.startswith(config.root):
path = path.replace(config.root, "$FG_ROOT", 1)
elif path.startswith(config.home):
path = path.replace(config.home, "$FG_HOME", 1)
return path
class Error(Exception):
pass
class Abort(Exception):
pass
class XMLError(Exception):
def __init__(self, locator, msg):
msg = "%s in %s +%d:%d" \
% (msg.replace("\n", "\\n"), cook_path(locator.getSystemId()), \
locator.getLineNumber(), locator.getColumnNumber())
raise Error(msg)
class parse_xml_file(xml.sax.handler.ContentHandler):
def __init__(self, path, nesting = 0, stack = None):
self.level = 0
self.path = path
self.nesting = nesting
self.type = None
if stack:
self.stack = stack
else:
self.stack = [[None, None, {}, []]] # name, index, indices, data
self.pretty_path = cook_path(path)
if config.verbose > 1:
errmsg("FILE %s (%d)" % (path, nesting), "35")
if not os.path.exists(path):
raise Error("file doesn't exist: " + self.pretty_path)
try:
xml.sax.parse(path, self, self)
except ValueError:
pass # FIXME hack arount DTD error
def startElement(self, name, attrs):
self.level += 1
if self.level == 1:
if name != "PropertyList":
raise XMLError(self.locator, "XML file isn't a <PropertyList>")
else:
index = 0
if attrs.has_key("n"):
index = int(attrs["n"])
elif name in self.stack[-1][2]:
index = self.stack[-1][2][name] + 1
self.stack[-1][2][name] = index
self.type = "unspecified"
if attrs.has_key("type"):
self.type = attrs["type"]
if attrs.has_key("include"):
path = os.path.dirname(os.path.abspath(self.path)) + "/" + attrs["include"]
if attrs.has_key("omit-node") and attrs["omit-node"] == "y" or self.level == 1:
self.stack.append([None, None, self.stack[-1][2], []])
else:
self.stack.append([name, index, {}, []])
parse_xml_file(path, self.nesting + 1, self.stack)
elif self.level > 1:
self.stack.append([name, index, {}, []])
def endElement(self, name):
value = string.join(self.stack[-1][3], '')
if not len(self.stack[-1][2]) and self.level > 1:
path = self.pathname()
if path:
cooked_value = self.escape(value.encode("iso-8859-15", "backslashreplace"))
print config.cooked_format % {
"f": self.pretty_path,
"l": self.locator.getLineNumber(),
"c": self.locator.getColumnNumber(),
"p": path,
"t": self.type,
"V": value,
"v": cooked_value,
"q": cooked_value.replace("'", "\\'"),
'Q': cooked_value.replace('"', '\\"'),
}
elif len(string.strip(value)):
raise XMLError(self.locator, "garbage found '%s'" % string.strip(value))
self.level -= 1
if self.level:
self.stack.pop()
def characters(self, data):
self.stack[-1][3].append(data)
def setDocumentLocator(self, locator):
self.locator = locator
def pathname(self):
path = ""
for e in self.stack[1:]:
if e[0] == None: # omit-node
continue
path += "/" + e[0]
if e[1] and config.indices == 1 or config.indices == 2:
path += "[%d]" % e[1]
return path
def escape(self, string):
s = ""
for c in string:
if c == '\n':
s += '\\n'
elif c == '\r':
s += '\\r'
elif c == '\v':
s += '\\v'
elif c == '\\':
s += '\\\\'
elif not c.isalnum() and " \t!@#$%^&*()_+|~-=\`[]{};':\",./<>?".find(c) < 0:
s += "\\x%02x" % ord(c)
else:
s += c
return s
def warning(self, exception):
raise XMLError(self.locator, "WARNING: " + str(exception))
def error(self, exception):
raise XMLError(self.locator, "ERROR: " + str(exception))
def fatalError(self, exception):
raise XMLError(self.locator, "FATAL: " + str(exception))
def main():
if 'FG_ROOT' in os.environ:
config.root = os.environ['FG_ROOT'].lstrip().rstrip("/\\\t ")
if 'FG_HOME' in os.environ:
config.home = os.environ['FG_HOME'].lstrip().rstrip("/\\\t ")
if 'LSPROP_FORMAT' in os.environ:
config.format = os.environ['LSPROP_FORMAT']
# options
try:
opts, args = getopt.getopt(sys.argv[1:], \
"hviIpf:", \
["help", "verbose", "all-indices", "no-indices", "raw-paths", "format="])
except getopt.GetoptError, msg:
errmsg("Error: %s" % msg)
return -1
for o, a in opts:
if o in ("-h", "--help"):
print __doc__
print '\t--format="%s"' % config.format.replace('"', '\\"')
return 0
if o in ("-v", "--verbose"):
config.verbose += 1
if o in ("-i", "--all-indices"):
config.indices = 2
if o in ("-I", "--no-indices"):
config.indices = 0
if o in ("-p", "--raw-paths"):
config.raw_paths = 1
if o in ("-f", "--format"):
config.format = a
# format
f = config.format
f = f.replace("\\e", "\x1b")
f = f.replace("\\033", "\x1b")
f = f.replace("\\x1b", "\x1b")
f = f.replace("%%", "\x01\x01")
f = f.replace("%f", "\x01(f)s")
f = f.replace("%l", "\x01(l)d")
f = f.replace("%c", "\x01(c)d")
f = f.replace("%p", "\x01(p)s")
f = f.replace("%t", "\x01(t)s")
f = f.replace("%V", "\x01(V)s")
f = f.replace("%v", "\x01(v)s")
f = f.replace("%q", "\x01(q)s")
f = f.replace('%Q', '\x01(Q)s')
f = f.replace("%", "%%")
f = f.replace("\x01", "%")
config.cooked_format = f
if config.verbose > 2:
print >>sys.stderr, "internal format = [%s]" % config.cooked_format
# arguments
if not len(args):
args = [config.root + "/preferences.xml"]
if not os.path.exists(args[0]):
errmsg("Error: environment variable FG_ROOT not set or set wrongly?")
return -1
for f in glob.glob(config.root + '/Aircraft/*/*-set.xml'):
args.append(f)
for arg in args:
try:
parse_xml_file(arg)
except Abort, e:
errmsg("Abort: " + e.args[0])
return -1
except Error, e:
errmsg("Error: " + e.args[0])
except IOError, (errno, msg):
errmsg("Error: " + msg)
return errno
except KeyboardInterrupt:
print >>sys.stderr, "\033[m"
return 0
if __name__ == "__main__":
sys.exit(main())