Compare commits
1 Commits
release/20
...
version/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0d6016d64 |
23
.gitattributes
vendored
23
.gitattributes
vendored
@@ -1,23 +0,0 @@
|
||||
# Set default behaviour, in case users don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
.gitattributes text export-ignore
|
||||
.gitignore text export-ignore
|
||||
*.py text
|
||||
*.txt text
|
||||
*.rst text
|
||||
*.xml text
|
||||
*.desktop text
|
||||
*.svg text
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.bat text eol=crlf
|
||||
AUTHORS text
|
||||
COPYING text
|
||||
COPYING.* text
|
||||
README.* text
|
||||
Makefile text
|
||||
ChangeLog text
|
||||
ChangeLog.* text
|
||||
/download_and_compile.sh text ident
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,4 +21,3 @@ osgbuild
|
||||
CMakeCache.txt
|
||||
*.pyc
|
||||
build-*
|
||||
testOutput*
|
||||
|
||||
10
.gitmodules
vendored
10
.gitmodules
vendored
@@ -1,11 +1,11 @@
|
||||
[submodule "simgear"]
|
||||
path = simgear
|
||||
url = https://git.code.sf.net/p/flightgear/simgear
|
||||
branch = release/2017.3
|
||||
branch = next
|
||||
[submodule "flightgear"]
|
||||
path = flightgear
|
||||
url = https://git.code.sf.net/p/flightgear/flightgear
|
||||
branch = release/2017.3
|
||||
branch = next
|
||||
[submodule "fgrun"]
|
||||
path = fgrun
|
||||
url = https://git.code.sf.net/p/flightgear/fgrun
|
||||
@@ -13,12 +13,8 @@
|
||||
[submodule "fgdata"]
|
||||
path = fgdata
|
||||
url = git://git.code.sf.net/p/flightgear/fgdata
|
||||
branch = release/2017.3
|
||||
branch = next
|
||||
[submodule "windows-3rd-party"]
|
||||
path = windows-3rd-party
|
||||
url = https://git.code.sf.net/p/flightgear/windows-3rd-party
|
||||
branch = master
|
||||
[submodule "getstart"]
|
||||
path = getstart
|
||||
url = https://git.code.sf.net/p/flightgear/getstart
|
||||
branch = release/2017.3
|
||||
|
||||
@@ -14,6 +14,7 @@ mkdir -p sgBuild
|
||||
mkdir -p fgBuild
|
||||
mkdir -p output
|
||||
rm -rf output/*
|
||||
rm -rf dist/*
|
||||
|
||||
#####################################################################################
|
||||
echo "Starting on SimGear"
|
||||
|
||||
@@ -23,7 +23,6 @@ cd build-sg32
|
||||
cmake ..\simgear -G "Visual Studio 14" ^
|
||||
-DMSVC_3RDPARTY_ROOT=%WORKSPACE%/windows-3rd-party/msvc140 ^
|
||||
-DBOOST_ROOT=%WORKSPACE%/windows-3rd-party ^
|
||||
-DOSG_FSTREAM_EXPORT_FIXED=1 ^
|
||||
-DCMAKE_PREFIX_PATH:PATH=%OSG32% ^
|
||||
-DCMAKE_INSTALL_PREFIX:PATH=%WORKSPACE%/install/msvc140
|
||||
cmake --build . --config RelWithDebInfo --target INSTALL
|
||||
@@ -34,7 +33,6 @@ cmake ..\flightgear -G "Visual Studio 14" ^
|
||||
-DCMAKE_INSTALL_PREFIX:PATH=%WORKSPACE%/install/msvc140 ^
|
||||
-DCMAKE_PREFIX_PATH:PATH=%WORKSPACE%/install/msvc140/OpenSceneGraph ^
|
||||
-DBOOST_ROOT=%WORKSPACE%/windows-3rd-party ^
|
||||
-DOSG_FSTREAM_EXPORT_FIXED=1 ^
|
||||
-DCMAKE_PREFIX_PATH=%QT5SDK32%;%OSG32% ^
|
||||
-DFG_BUILD_TYPE=%FGBUILDTYPE%
|
||||
cmake --build . --config RelWithDebInfo --target INSTALL
|
||||
@@ -49,7 +47,6 @@ cd build-sg64
|
||||
cmake ..\SimGear -G "Visual Studio 14 Win64" ^
|
||||
-DMSVC_3RDPARTY_ROOT=%WORKSPACE%/windows-3rd-party/msvc140 ^
|
||||
-DBOOST_ROOT=%WORKSPACE%/windows-3rd-party ^
|
||||
-DOSG_FSTREAM_EXPORT_FIXED=1 ^
|
||||
-DCMAKE_PREFIX_PATH:PATH=%OSG64% ^
|
||||
-DCMAKE_INSTALL_PREFIX:PATH=%WORKSPACE%/install/msvc140-64
|
||||
cmake --build . --config RelWithDebInfo --target INSTALL
|
||||
@@ -60,7 +57,6 @@ cmake ..\flightgear -G "Visual Studio 14 Win64" ^
|
||||
-DBOOST_ROOT=%WORKSPACE%/windows-3rd-party ^
|
||||
-DCMAKE_INSTALL_PREFIX:PATH=%WORKSPACE%/install/msvc140-64 ^
|
||||
-DCMAKE_PREFIX_PATH=%QT5SDK64%;%OSG64% ^
|
||||
-DOSG_FSTREAM_EXPORT_FIXED=1 ^
|
||||
-DFG_BUILD_TYPE=%FGBUILDTYPE%
|
||||
cmake --build . --config RelWithDebInfo --target INSTALL
|
||||
|
||||
@@ -79,10 +75,9 @@ subst X: %WORKSPACE%.
|
||||
REM ensure output dir is clean since we upload the entirety of it
|
||||
rmdir /S /Q output
|
||||
|
||||
SET CRASHFIX_UPLOAD_URL=http://crashes.flightgear.org/index.php/debugInfo/uploadExternal
|
||||
SET FGFS_PDB=src\Main\RelWithDebInfo\fgfs.pdb
|
||||
ECHO Uploading PDB files to %CRASHFIX_UPLOAD_URL%
|
||||
upload -v -u %CRASHFIX_UPLOAD_URL% FlightGear %WORKSPACE%\build-fg32\%FGFS_PDB% %WORKSPACE%\build-fg64\%FGFS_PDB%
|
||||
REM archiving PDB files
|
||||
copy %WORKSPACE%\build-fg32\src\Main\RelWithDebInfo\fgfs.pdb %WORKSPACE%\Output\fgfs-x86-%BUILD_NUMBER%.pdb
|
||||
copy %WORKSPACE%\build-fg64\src\Main\RelWithDebInfo\fgfs.pdb %WORKSPACE%\Output\fgfs-x64-%BUILD_NUMBER%.pdb
|
||||
|
||||
REM indirect way to get command output into an environment variable
|
||||
set PATH=%OSG32%\bin;%PATH%
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import lxml.etree as ET
|
||||
import os
|
||||
import re
|
||||
import sgprops
|
||||
import sys
|
||||
import catalogTags
|
||||
|
||||
CATALOG_VERSION = 4
|
||||
|
||||
# xml node (robust) get text helper
|
||||
def get_xml_text(e):
|
||||
if e != None and e.text != None:
|
||||
return e.text
|
||||
else:
|
||||
return ''
|
||||
|
||||
# return all available aircraft information from the set file as a
|
||||
# dict
|
||||
def scan_set_file(aircraft_dir, set_file, includes):
|
||||
base_file = os.path.basename(set_file)
|
||||
base_id = base_file[:-8]
|
||||
set_path = os.path.join(aircraft_dir, set_file)
|
||||
|
||||
includes.append(aircraft_dir)
|
||||
root_node = sgprops.readProps(set_path, includePaths = includes)
|
||||
|
||||
if not root_node.hasChild("sim"):
|
||||
return None
|
||||
|
||||
sim_node = root_node.getChild("sim")
|
||||
if sim_node == None:
|
||||
return None
|
||||
|
||||
variant = {}
|
||||
variant['name'] = sim_node.getValue("description", None)
|
||||
variant['status'] = sim_node.getValue("status", None)
|
||||
|
||||
if sim_node.hasChild('author'):
|
||||
variant['author'] = sim_node.getValue("author", None)
|
||||
|
||||
if sim_node.hasChild('long-description'):
|
||||
variant['description'] = sim_node.getValue("long-description", None)
|
||||
variant['id'] = base_id
|
||||
|
||||
# allow -set.xml files to declare themselves as primary.
|
||||
# we use this avoid needing a variant-of in every other -set.xml
|
||||
variant['primary-set'] = sim_node.getValue('primary-set', False)
|
||||
|
||||
# extract and record previews for each variant
|
||||
if sim_node.hasChild('previews'):
|
||||
variant['previews'] = extract_previews(sim_node.getChild('previews'), aircraft_dir)
|
||||
|
||||
if sim_node.hasChild('rating'):
|
||||
rating_node = sim_node.getChild("rating")
|
||||
variant['rating_FDM'] = rating_node.getValue("FDM", 0)
|
||||
variant['rating_systems'] = rating_node.getValue("systems", 0)
|
||||
variant['rating_cockpit'] = rating_node.getValue("cockpit", 0)
|
||||
variant['rating_model'] = rating_node.getValue("model", 0)
|
||||
|
||||
if sim_node.hasChild('tags'):
|
||||
variant['tags'] = extract_tags(sim_node.getChild('tags'), set_file)
|
||||
|
||||
if sim_node.hasChild('thumbnail'):
|
||||
variant['thumbnail'] = sim_node.getValue("thumbnail", None)
|
||||
|
||||
variant['variant-of'] = sim_node.getValue("variant-of", None)
|
||||
|
||||
if sim_node.hasChild('minimum-fg-version'):
|
||||
variant['minimum-fg-version'] = sim_node.getValue('minimum-fg-version', None)
|
||||
|
||||
#print ' ', variant
|
||||
return variant
|
||||
|
||||
def extract_previews(previews_node, aircraft_dir):
|
||||
result = []
|
||||
for node in previews_node.getChildren("preview"):
|
||||
previewType = node.getValue("type", None)
|
||||
previewPath = node.getValue("path", None)
|
||||
|
||||
# check path exists in base-name-dir
|
||||
fullPath = os.path.join(aircraft_dir, previewPath)
|
||||
if not os.path.isfile(fullPath):
|
||||
print "Bad preview path, skipping:" + fullPath
|
||||
continue
|
||||
result.append({'type':previewType, 'path':previewPath})
|
||||
|
||||
return result
|
||||
|
||||
def extract_tags(tags_node, set_path):
|
||||
result = []
|
||||
for node in tags_node.getChildren("tag"):
|
||||
tag = node.value
|
||||
# check tag is in the allowed list
|
||||
if not catalogTags.isValidTag(tag):
|
||||
print "Unknown tag value:", tag, " in ", set_path
|
||||
result.append(tag)
|
||||
|
||||
return result
|
||||
|
||||
# scan all the -set.xml files in an aircraft directory. Returns a
|
||||
# package dict and a list of variants.
|
||||
def scan_aircraft_dir(aircraft_dir, includes):
|
||||
setDicts = []
|
||||
primaryAircraft = []
|
||||
package = None
|
||||
|
||||
files = os.listdir(aircraft_dir)
|
||||
for file in sorted(files, key=lambda s: s.lower()):
|
||||
if file.endswith('-set.xml'):
|
||||
try:
|
||||
d = scan_set_file(aircraft_dir, file, includes)
|
||||
if d == None:
|
||||
continue
|
||||
except:
|
||||
print "Skipping set file since couldn't be parsed:", os.path.join(aircraft_dir, file), sys.exc_info()[0]
|
||||
continue
|
||||
|
||||
setDicts.append(d)
|
||||
if d['primary-set']:
|
||||
# ensure explicit primary-set aircraft goes first
|
||||
primaryAircraft.insert(0, d)
|
||||
elif d['variant-of'] == None:
|
||||
primaryAircraft.append(d)
|
||||
|
||||
if len(setDicts) == 0:
|
||||
return None
|
||||
|
||||
# use the first one
|
||||
if len(primaryAircraft) == 0:
|
||||
print "Aircraft has no primary aircraft at all:", aircraft_dir
|
||||
primaryAircraft = [setDicts[0]]
|
||||
|
||||
package = primaryAircraft[0]
|
||||
if not 'thumbnail' in package:
|
||||
if (os.path.exists(os.path.join(aircraft_dir, "thumbnail.jpg"))):
|
||||
package['thumbnail'] = "thumbnail.jpg"
|
||||
|
||||
# variants is just all the set dicts except the first one
|
||||
variants = setDicts
|
||||
variants.remove(package)
|
||||
return (package, variants)
|
||||
|
||||
# create an xml node with text content
|
||||
def make_xml_leaf(name, text):
|
||||
leaf = ET.Element(name)
|
||||
if text != None:
|
||||
if isinstance(text, (int, long)):
|
||||
leaf.text = str(text)
|
||||
else:
|
||||
leaf.text = text
|
||||
else:
|
||||
leaf.text = ''
|
||||
return leaf
|
||||
|
||||
def append_preview_nodes(node, variant, download_base, package_name):
|
||||
if not 'previews' in variant:
|
||||
return
|
||||
|
||||
for preview in variant['previews']:
|
||||
preview_node = ET.Element('preview')
|
||||
preview_url = download_base + 'previews/' + package_name + '_' + preview['path']
|
||||
preview_node.append( make_xml_leaf('type', preview['type']) )
|
||||
preview_node.append( make_xml_leaf('url', preview_url) )
|
||||
preview_node.append( make_xml_leaf('path', preview['path']) )
|
||||
node.append(preview_node)
|
||||
|
||||
def append_tag_nodes(node, variant):
|
||||
if not 'tags' in variant:
|
||||
return
|
||||
|
||||
for tag in variant['tags']:
|
||||
node.append(make_xml_leaf('tag', tag))
|
||||
|
||||
def make_aircraft_node(aircraftDirName, package, variants, downloadBase):
|
||||
#print "package:", package
|
||||
#print "variants:", variants
|
||||
package_node = ET.Element('package')
|
||||
package_node.append( make_xml_leaf('name', package['name']) )
|
||||
package_node.append( make_xml_leaf('status', package['status']) )
|
||||
|
||||
if 'author' in package:
|
||||
package_node.append( make_xml_leaf('author', package['author']) )
|
||||
|
||||
if 'description' in package:
|
||||
package_node.append( make_xml_leaf('description', package['description']) )
|
||||
|
||||
if 'minimum-fg-version' in package:
|
||||
package_node.append( make_xml_leaf('minimum-fg-version', package['minimum-fg-version']) )
|
||||
|
||||
if 'rating_FDM' in package or 'rating_systems' in package \
|
||||
or 'rating_cockpit' in package or 'rating_model' in package:
|
||||
rating_node = ET.Element('rating')
|
||||
package_node.append(rating_node)
|
||||
rating_node.append( make_xml_leaf('FDM',
|
||||
package['rating_FDM']) )
|
||||
rating_node.append( make_xml_leaf('systems',
|
||||
package['rating_systems']) )
|
||||
rating_node.append( make_xml_leaf('cockpit',
|
||||
package['rating_cockpit']) )
|
||||
rating_node.append( make_xml_leaf('model',
|
||||
package['rating_model']) )
|
||||
package_node.append( make_xml_leaf('id', package['id']) )
|
||||
for variant in variants:
|
||||
variant_node = ET.Element('variant')
|
||||
package_node.append(variant_node)
|
||||
variant_node.append( make_xml_leaf('id', variant['id']) )
|
||||
variant_node.append( make_xml_leaf('name', variant['name']) )
|
||||
if 'description' in variant:
|
||||
variant_node.append( make_xml_leaf('description', variant['description']) )
|
||||
|
||||
if 'author' in variant:
|
||||
variant_node.append( make_xml_leaf('author', variant['author']) )
|
||||
|
||||
if 'thumbnail' in variant:
|
||||
# note here we prefix with the package name, since the thumbnail path
|
||||
# is assumed to be unique within the package
|
||||
thumbUrl = downloadBase + "thumbnails/" + aircraftDirName + '_' + variant['thumbnail']
|
||||
variant_node.append(make_xml_leaf('thumbnail', thumbUrl))
|
||||
variant_node.append(make_xml_leaf('thumbnail-path', variant['thumbnail']))
|
||||
|
||||
variantOf = variant['variant-of']
|
||||
if variantOf is None:
|
||||
variant_node.append(make_xml_leaf('variant-of', '_primary_'))
|
||||
else:
|
||||
variant_node.append(make_xml_leaf('variant-of', variantOf))
|
||||
|
||||
append_preview_nodes(variant_node, variant, downloadBase, aircraftDirName)
|
||||
append_tag_nodes(variant_node, variant)
|
||||
|
||||
package_node.append( make_xml_leaf('dir', aircraftDirName) )
|
||||
|
||||
download_url = downloadBase + aircraftDirName + '.zip'
|
||||
package_node.append( make_xml_leaf('url', download_url) )
|
||||
|
||||
if 'thumbnail' in package:
|
||||
thumbnail_url = downloadBase + 'thumbnails/' + aircraftDirName + '_' + package['thumbnail']
|
||||
package_node.append( make_xml_leaf('thumbnail', thumbnail_url) )
|
||||
package_node.append( make_xml_leaf('thumbnail-path', package['thumbnail']))
|
||||
|
||||
append_preview_nodes(package_node, package, downloadBase, aircraftDirName)
|
||||
append_tag_nodes(package_node, package)
|
||||
|
||||
return package_node
|
||||
@@ -1,161 +1,80 @@
|
||||
aircraftTypeTags = [
|
||||
"aerobatic",
|
||||
"airship",
|
||||
"balloon",
|
||||
"bizjet",
|
||||
"bomber",
|
||||
"cargo",
|
||||
"carrier",
|
||||
"fighter",
|
||||
"ga",
|
||||
"glider",
|
||||
"groundvehicle",
|
||||
"helicopter",
|
||||
"racer",
|
||||
"spaceship",
|
||||
"tanker",
|
||||
"trainer",
|
||||
"transport",
|
||||
"ultralight",
|
||||
"reconnaissance",
|
||||
"seacraft",
|
||||
"crop-duster",
|
||||
"bush-plane"
|
||||
"ga", "fighter", "helicopter", "glider", "spaceship", "bomber", "groundvehicle",
|
||||
"tanker", "cargo", "transport", "bizjet", "trainer", "airship", "balloon"
|
||||
]
|
||||
|
||||
manufacturerTags = [
|
||||
"airbus",
|
||||
"antonov",
|
||||
"atr",
|
||||
"avro",
|
||||
"bell",
|
||||
"bleriot",
|
||||
"boeing",
|
||||
"bombardier",
|
||||
"caudron",
|
||||
"cessna",
|
||||
"consolidated",
|
||||
"dassault",
|
||||
"diamond",
|
||||
"dornier",
|
||||
"douglas",
|
||||
"embraer",
|
||||
"eurocopter",
|
||||
"fairchild",
|
||||
"fairey",
|
||||
"focke-wulf",
|
||||
"fokker",
|
||||
"general-dynamics",
|
||||
"gotha",
|
||||
"grumman",
|
||||
"handley-page",
|
||||
"hawker",
|
||||
"heinkel",
|
||||
"ilyushin",
|
||||
"junkers",
|
||||
"lockheed",
|
||||
"mc-donnell-douglas",
|
||||
"messerschmitt",
|
||||
"mikoyan-gurevich",
|
||||
"mitsubishi",
|
||||
"north-american",
|
||||
"northrop",
|
||||
"pilatus",
|
||||
"piper",
|
||||
"republic",
|
||||
"robin",
|
||||
"saab",
|
||||
"short",
|
||||
"sopwith",
|
||||
"spad",
|
||||
"sukhoi",
|
||||
"supermarine",
|
||||
"tupolev",
|
||||
"vickers",
|
||||
"vought",
|
||||
"yakovlev"
|
||||
"boeing", "cessna", "diamond", "douglas", "bell", "piper",
|
||||
"airbus", "vickers", "lockheed", "fokker",
|
||||
"embraer", "bombardier", "pilatus", "robin",
|
||||
"eurocopter"
|
||||
]
|
||||
|
||||
eraTags = [
|
||||
"1910s",
|
||||
"early-pioneers",
|
||||
"ww1",
|
||||
"1920s",
|
||||
"1930s",
|
||||
"1940s",
|
||||
"golden-age",
|
||||
"ww2",
|
||||
"coldwar", "vietnam",
|
||||
"1950s",
|
||||
"1960s",
|
||||
"1970s",
|
||||
"1980s",
|
||||
"1990s",
|
||||
"2000s",
|
||||
"2010s",
|
||||
"coldwar",
|
||||
"early-pioneers",
|
||||
"golden-age",
|
||||
"gulfwar1",
|
||||
"gulfwar2",
|
||||
"vietnam",
|
||||
"ww1",
|
||||
"ww2"
|
||||
"gulfwar2"
|
||||
]
|
||||
|
||||
featureTags = [
|
||||
"aerobatic",
|
||||
"airship",
|
||||
"amphibious",
|
||||
"biplane",
|
||||
"canard",
|
||||
"castering-wheel",
|
||||
"delta",
|
||||
"etops",
|
||||
"experimental",
|
||||
"fictional",
|
||||
"fixed-gear",
|
||||
"floats",
|
||||
"glass-cockpit",
|
||||
"high-wing",
|
||||
"h-tail",
|
||||
"hud",
|
||||
"ifr",
|
||||
"prototype",
|
||||
"refuel",
|
||||
"retractable-gear",
|
||||
"fixed-gear",
|
||||
"tail-dragger",
|
||||
"seaplane",
|
||||
"skis",
|
||||
"vtol",
|
||||
"stol",
|
||||
"experimental",
|
||||
"prototype",
|
||||
"fictional",
|
||||
"biplane",
|
||||
"triplane",
|
||||
"supersonic",
|
||||
"t-tail",
|
||||
"tail-dragger",
|
||||
"tricycle",
|
||||
"tail-hook",
|
||||
"triplane",
|
||||
"v-tail",
|
||||
"high-wing",
|
||||
"cannard",
|
||||
"tail-hook",
|
||||
"refuel",
|
||||
"delta",
|
||||
"variable-geometry",
|
||||
"vtol",
|
||||
"wing-fold"
|
||||
"glass-cockpit",
|
||||
"hud",
|
||||
"etops",
|
||||
"floats",
|
||||
"amphibious",
|
||||
"airship",
|
||||
"aerobatic"
|
||||
]
|
||||
|
||||
propulsionTags = [
|
||||
"afterburner",
|
||||
"piston", "radial",
|
||||
"diesel",
|
||||
"electric",
|
||||
"jet",
|
||||
"propeller",
|
||||
"piston",
|
||||
"radial",
|
||||
"rocket",
|
||||
"single-engine",
|
||||
"variable-pitch",
|
||||
"supercharged",
|
||||
"turboprop",
|
||||
"jet", "afterburner", "rocket",
|
||||
"electric",
|
||||
"twin-engine",
|
||||
"variable-pitch",
|
||||
"fixed-pitch"
|
||||
"single-engine"
|
||||
]
|
||||
|
||||
simFeatureTags = [
|
||||
"dual-controls",
|
||||
"rembrandt",
|
||||
"tow",
|
||||
"wildfire"
|
||||
"dual-controls",
|
||||
"rembrandt"
|
||||
]
|
||||
|
||||
tags = aircraftTypeTags + manufacturerTags + eraTags + simFeatureTags + propulsionTags + featureTags
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<PropertyList>
|
||||
<sim>
|
||||
<name>c150</name>
|
||||
<description>Cessna 150</description>
|
||||
</sim>
|
||||
|
||||
</PropertyList>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<PropertyList>
|
||||
<sim include="settings-common.xml">
|
||||
<author>Wilbur Wright</author>
|
||||
<tags>
|
||||
<tag>fighter</tag>
|
||||
<tag>1980s</tag>
|
||||
<tag>glass-cockpit</tag>
|
||||
</tags>
|
||||
<minimum-fg-version>2017.4</minimum-fg-version>
|
||||
</sim>
|
||||
|
||||
</PropertyList>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<PropertyList include="f16-common.xml">
|
||||
<sim>
|
||||
<name>f16-trainer</name>
|
||||
<description>F16 Trainer</description>
|
||||
<long-description>Twin-seat trainer version of the F16</long-description>
|
||||
|
||||
|
||||
</sim>
|
||||
|
||||
</PropertyList>
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<PropertyList include="f16-common.xml">
|
||||
<sim>
|
||||
<name>f16a</name>
|
||||
<description>F16-A</description>
|
||||
<long-description>The F16 is compact, light-weight multi-role fighter used around the world</long-description>
|
||||
|
||||
<primary-set type="bool">true</primary-set>
|
||||
|
||||
<rating>
|
||||
<FDM type="int">3</FDM>
|
||||
<systems type="int">1</systems>
|
||||
<cockpit type="int">2</cockpit>
|
||||
<model type="int">5</model>
|
||||
</rating>
|
||||
|
||||
<previews>
|
||||
<preview>
|
||||
<type>exterior</type>
|
||||
<splash type="bool">true</splash>
|
||||
<path>Previews/exterior-1.png</path>
|
||||
</preview>
|
||||
<preview>
|
||||
<type>exterior</type>
|
||||
<splash type="bool">true</splash>
|
||||
<path>Previews/exterior-f16a-2.png</path>
|
||||
</preview>
|
||||
<preview>
|
||||
<type>panel</type>
|
||||
<splash type="bool">false</splash>
|
||||
<path>Previews/cockpit.png</path>
|
||||
</preview>
|
||||
</previews>
|
||||
</sim>
|
||||
|
||||
</PropertyList>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<PropertyList include="f16-common.xml">
|
||||
<sim>
|
||||
<name>f16b</name>
|
||||
<description>F16-B</description>
|
||||
<long-description>The F16-B is an upgraded version of the F16A.</long-description>
|
||||
<variant-of>f16a</variant-of>
|
||||
|
||||
<author>James T Kirk</author>
|
||||
</sim>
|
||||
|
||||
</PropertyList>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<PropertyList include="f16-common.xml">
|
||||
<sim>
|
||||
<name>f16c</name>
|
||||
<description>F16-C</description>
|
||||
<long-description>The F16-C is an upgraded version of the F16A.</long-description>
|
||||
<variant-of>f16a</variant-of>
|
||||
</sim>
|
||||
|
||||
</PropertyList>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
|
||||
<!-- this file exists to test including + overlaying in the set XML -->
|
||||
|
||||
<PropertyList>
|
||||
<views>
|
||||
<view n="0">
|
||||
<foo>dsdddd</foo>
|
||||
</view>
|
||||
</views>
|
||||
|
||||
</PropertyList>
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PropertyList>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PropertyList>
|
||||
<sim>
|
||||
<!-- found in the F-15 XML -->
|
||||
<uhf n="[0]">
|
||||
<frequencies>
|
||||
<selected-mhz type="int">225000</selected-mhz>
|
||||
</frequencies>
|
||||
</uhf>
|
||||
</sim>
|
||||
</PropertyList>
|
||||
<uhf n="[0]">
|
||||
<frequencies>
|
||||
<selected-mhz type="int">225000</selected-mhz>
|
||||
</frequencies>
|
||||
</uhf>
|
||||
</sim>
|
||||
</PropertyList>
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import unittest
|
||||
import sgprops
|
||||
import os
|
||||
import catalog
|
||||
import lxml.etree as ET
|
||||
|
||||
class UpdateCatalogTests(unittest.TestCase):
|
||||
def test_scan_set(self):
|
||||
info = catalog.scan_set_file("testData/Aircraft/f16", "f16a-set.xml", ["testData/OtherDir"])
|
||||
self.assertEqual(info['id'], 'f16a')
|
||||
self.assertEqual(info['name'], 'F16-A')
|
||||
self.assertEqual(info['primary-set'], True)
|
||||
self.assertEqual(info['variant-of'], None)
|
||||
self.assertEqual(info['author'], 'Wilbur Wright')
|
||||
self.assertEqual(info['rating_FDM'], 3)
|
||||
self.assertEqual(info['rating_model'], 5)
|
||||
self.assertEqual(len(info['tags']), 3)
|
||||
self.assertEqual(info['minimum-fg-version'], '2017.4')
|
||||
|
||||
def test_scan_dir(self):
|
||||
(pkg, variants) = catalog.scan_aircraft_dir("testData/Aircraft/f16", ["testData/OtherDir"])
|
||||
|
||||
self.assertEqual(pkg['id'], 'f16a')
|
||||
f16trainer = next(v for v in variants if v['id'] == 'f16-trainer')
|
||||
self.assertEqual(pkg['author'], 'Wilbur Wright')
|
||||
self.assertEqual(len(variants), 3)
|
||||
self.assertEqual(pkg['minimum-fg-version'], '2017.4')
|
||||
|
||||
# test variant relatonship between
|
||||
self.assertEqual(pkg['variant-of'], None)
|
||||
self.assertEqual(pkg['primary-set'], True)
|
||||
|
||||
self.assertEqual(f16trainer['variant-of'], None)
|
||||
self.assertEqual(f16trainer['primary-set'], False)
|
||||
|
||||
f16b = next(v for v in variants if v['id'] == 'f16b')
|
||||
self.assertEqual(f16b['variant-of'], 'f16a')
|
||||
self.assertEqual(f16b['primary-set'], False)
|
||||
self.assertEqual(f16b['author'], 'James T Kirk')
|
||||
|
||||
f16c = next(v for v in variants if v['id'] == 'f16c')
|
||||
self.assertEqual(f16c['variant-of'], 'f16a')
|
||||
self.assertEqual(f16c['primary-set'], False)
|
||||
|
||||
self.assertEqual(f16c['author'], 'Wilbur Wright')
|
||||
|
||||
|
||||
def test_extract_previews(self):
|
||||
info = catalog.scan_set_file("testData/Aircraft/f16", "f16a-set.xml", ["testData/OtherDir"])
|
||||
previews = info['previews']
|
||||
self.assertEqual(len(previews), 3)
|
||||
self.assertEqual(2, len([p for p in previews if p['type'] == 'exterior']))
|
||||
self.assertEqual(1, len([p for p in previews if p['type'] == 'panel']))
|
||||
self.assertEqual(1, len([p for p in previews if p['path'] == 'Previews/exterior-1.png']))
|
||||
|
||||
def test_extract_tags(self):
|
||||
info = catalog.scan_set_file("testData/Aircraft/f16", "f16a-set.xml", ["testData/OtherDir"])
|
||||
tags = info['tags']
|
||||
|
||||
def test_node_creation(self):
|
||||
(pkg, variants) = catalog.scan_aircraft_dir("testData/Aircraft/f16", ["testData/OtherDir"])
|
||||
|
||||
catalog_node = ET.Element('PropertyList')
|
||||
catalog_root = ET.ElementTree(catalog_node)
|
||||
|
||||
pkgNode = catalog.make_aircraft_node('f16', pkg, variants, "http://foo.com/testOutput/")
|
||||
catalog_node.append(pkgNode)
|
||||
|
||||
# write out so we can parse using sgprops
|
||||
# yes we are round-tripping via the disk, if you can improve
|
||||
# then feel free..
|
||||
if not os.path.isdir("testOutput"):
|
||||
os.mkdir("testOutput")
|
||||
|
||||
cat_file = os.path.join("testOutput", 'catalog_fragment.xml')
|
||||
catalog_root.write(cat_file, encoding='utf-8', xml_declaration=True, pretty_print=True)
|
||||
|
||||
parsed = sgprops.readProps(cat_file)
|
||||
parsedPkgNode = parsed.getChild("package")
|
||||
|
||||
self.assertEqual(parsedPkgNode.name, "package");
|
||||
|
||||
self.assertEqual(parsedPkgNode.getValue('id'), pkg['id']);
|
||||
self.assertEqual(parsedPkgNode.getValue('dir'), 'f16');
|
||||
self.assertEqual(parsedPkgNode.getValue('url'), 'http://foo.com/testOutput/f16.zip');
|
||||
self.assertEqual(parsedPkgNode.getValue('thumbnail'), 'http://foo.com/testOutput/thumbnails/f16_thumbnail.jpg');
|
||||
self.assertEqual(parsedPkgNode.getValue('thumbnail-path'), 'thumbnail.jpg');
|
||||
|
||||
self.assertEqual(parsedPkgNode.getValue('name'), pkg['name']);
|
||||
self.assertEqual(parsedPkgNode.getValue('description'), pkg['description']);
|
||||
self.assertEqual(parsedPkgNode.getValue('author'), "Wilbur Wright");
|
||||
|
||||
self.assertEqual(parsedPkgNode.getValue('minimum-fg-version'), "2017.4");
|
||||
|
||||
parsedVariants = parsedPkgNode.getChildren("variant")
|
||||
self.assertEqual(len(parsedVariants), 3)
|
||||
|
||||
f16ANode = parsedPkgNode
|
||||
self.assertEqual(f16ANode.getValue('name'), 'F16-A');
|
||||
|
||||
for index, pv in enumerate(parsedVariants):
|
||||
var = variants[index]
|
||||
self.assertEqual(pv.getValue('name'), var['name']);
|
||||
self.assertEqual(pv.getValue('description'), var['description']);
|
||||
|
||||
if (var['id'] == 'f16-trainer'):
|
||||
self.assertEqual(pv.getValue('variant-of'), '_primary_')
|
||||
self.assertEqual(pv.getValue('author'), "Wilbur Wright");
|
||||
elif (var['id'] == 'f16b'):
|
||||
self.assertEqual(pv.getValue('variant-of'), 'f16a')
|
||||
self.assertEqual(pv.getValue('description'), 'The F16-B is an upgraded version of the F16A.')
|
||||
self.assertEqual(pv.getValue('author'), "James T Kirk");
|
||||
|
||||
def test_minimalAircraft(self):
|
||||
# test an aircraft with a deliberately spartan -set.xml file with
|
||||
# most interesting data missing
|
||||
(pkg, variants) = catalog.scan_aircraft_dir("testData/Aircraft/c150", ["testData/OtherDir"])
|
||||
|
||||
catalog_node = ET.Element('PropertyList')
|
||||
catalog_root = ET.ElementTree(catalog_node)
|
||||
|
||||
pkgNode = catalog.make_aircraft_node('c150', pkg, variants, "http://foo.com/testOutput/")
|
||||
catalog_node.append(pkgNode)
|
||||
|
||||
if not os.path.isdir("testOutput2"):
|
||||
os.mkdir("testOutput2")
|
||||
|
||||
cat_file = os.path.join("testOutput2", 'catalog_fragment.xml')
|
||||
catalog_root.write(cat_file, encoding='utf-8', xml_declaration=True, pretty_print=True)
|
||||
|
||||
parsed = sgprops.readProps(cat_file)
|
||||
parsedPkgNode = parsed.getChild("package")
|
||||
|
||||
self.assertEqual(parsedPkgNode.getValue('id'), pkg['id'])
|
||||
self.assertEqual(parsedPkgNode.getValue('dir'), 'c150')
|
||||
self.assertEqual(parsedPkgNode.getValue('url'), 'http://foo.com/testOutput/c150.zip')
|
||||
self.assertFalse(parsedPkgNode.hasChild('thumbnail'))
|
||||
self.assertFalse(parsedPkgNode.hasChild('thumbnail-path'));
|
||||
|
||||
self.assertEqual(parsedPkgNode.getValue('name'), pkg['name']);
|
||||
self.assertFalse(parsedPkgNode.hasChild('description'));
|
||||
self.assertFalse(parsedPkgNode.hasChild('author'));
|
||||
self.assertFalse(parsedPkgNode.hasChild('minimum-fg-version'));
|
||||
self.assertFalse(parsedPkgNode.hasChild('variant'));
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -12,7 +12,6 @@ import time
|
||||
import sgprops
|
||||
import sys
|
||||
import catalogTags
|
||||
import catalog
|
||||
|
||||
CATALOG_VERSION = 4
|
||||
|
||||
@@ -36,6 +35,141 @@ def get_xml_text(e):
|
||||
else:
|
||||
return ''
|
||||
|
||||
# create an xml node with text content
|
||||
def make_xml_leaf(name, text):
|
||||
leaf = ET.Element(name)
|
||||
if text != None:
|
||||
if isinstance(text, (int, long)):
|
||||
leaf.text = str(text)
|
||||
else:
|
||||
leaf.text = text
|
||||
else:
|
||||
leaf.text = ''
|
||||
return leaf
|
||||
|
||||
# return all available aircraft information from the set file as a
|
||||
# dict
|
||||
def scan_set_file(aircraft_dir, set_file):
|
||||
global includes
|
||||
|
||||
base_file = os.path.basename(set_file)
|
||||
base_id = base_file[:-8]
|
||||
set_path = os.path.join(aircraft_dir, set_file)
|
||||
|
||||
local_includes = includes
|
||||
local_includes.append(aircraft_dir)
|
||||
root_node = sgprops.readProps(set_path, includePaths = local_includes)
|
||||
|
||||
if not root_node.hasChild("sim"):
|
||||
return None
|
||||
|
||||
sim_node = root_node.getChild("sim")
|
||||
if sim_node == None:
|
||||
return None
|
||||
|
||||
variant = {}
|
||||
variant['name'] = sim_node.getValue("description", None)
|
||||
variant['status'] = sim_node.getValue("status", None)
|
||||
variant['author'] = sim_node.getValue("author", None)
|
||||
variant['description'] = sim_node.getValue("long-description", None)
|
||||
variant['id'] = base_id
|
||||
|
||||
# allow -set.xml files to declare themselves as primary.
|
||||
# we use this avoid needing a variant-of in every other -set.xml
|
||||
variant['primary-set'] = sim_node.getValue('primary-set', False)
|
||||
|
||||
# extract and record previews for each variant
|
||||
if sim_node.hasChild('previews'):
|
||||
print "has previews ..."
|
||||
variant['previews'] = extract_previews(sim_node.getChild('previews'), aircraft_dir)
|
||||
|
||||
if sim_node.hasChild('rating'):
|
||||
rating_node = sim_node.getChild("rating")
|
||||
variant['rating_FDM'] = rating_node.getValue("FDM", 0)
|
||||
variant['rating_systems'] = rating_node.getValue("systems", 0)
|
||||
variant['rating_cockpit'] = rating_node.getValue("cockpit", 0)
|
||||
variant['rating_model'] = rating_node.getValue("model", 0)
|
||||
|
||||
if sim_node.hasChild('tags'):
|
||||
variant['tags'] = extract_tags(sim_node.getChild('tags'), set_file)
|
||||
|
||||
variant['variant-of'] = sim_node.getValue("variant-of", None)
|
||||
#print ' ', variant
|
||||
return variant
|
||||
|
||||
def extract_previews(previews_node, aircraft_dir):
|
||||
result = []
|
||||
for node in previews_node.getChildren("preview"):
|
||||
previewType = node.getValue("type", None)
|
||||
previewPath = node.getValue("path", None)
|
||||
|
||||
# check path exists in base-name-dir
|
||||
fullPath = os.path.join(aircraft_dir, previewPath)
|
||||
if not os.path.isfile(fullPath):
|
||||
print "Bad preview path, skipping:" + fullPath
|
||||
continue
|
||||
result.append({'type':previewType, 'path':previewPath})
|
||||
|
||||
return result
|
||||
|
||||
def extract_tags(tags_node, set_path):
|
||||
result = []
|
||||
for node in tags_node.getChildren("tag"):
|
||||
tag = node.value
|
||||
# check tag is in the allowed list
|
||||
if not catalogTags.isValidTag(tag):
|
||||
print "Unknown tag value:", tag, " in ", set_path
|
||||
result.append(tag)
|
||||
|
||||
return result
|
||||
|
||||
# scan all the -set.xml files in an aircraft directory. Returns a
|
||||
# package dict and a list of variants.
|
||||
def scan_aircraft_dir(aircraft_dir):
|
||||
# old way of finding the master aircraft: it's the only one whose
|
||||
# variant-of is empty. All the others have an actual value
|
||||
# newer alternative is to specify one -set.xml as the primary. All the
|
||||
# others are therefore variants.
|
||||
setDicts = []
|
||||
found_master = False
|
||||
package = None
|
||||
|
||||
files = os.listdir(aircraft_dir)
|
||||
for file in sorted(files, key=lambda s: s.lower()):
|
||||
if file.endswith('-set.xml'):
|
||||
try:
|
||||
d = scan_set_file(aircraft_dir, file)
|
||||
if d == None:
|
||||
continue
|
||||
except:
|
||||
print "Skipping set file since couldn't be parsed:", os.path.join(aircraft_dir, file), sys.exc_info()[0]
|
||||
continue
|
||||
|
||||
setDicts.append(d)
|
||||
if d['primary-set']:
|
||||
found_master = True
|
||||
package = d
|
||||
|
||||
# didn't find a dict identified explicitly as the primary, look for one
|
||||
# with an undefined variant-of
|
||||
if not found_master:
|
||||
for d in setDicts:
|
||||
if d['variant-of'] == '':
|
||||
found_master = True
|
||||
package = d
|
||||
break
|
||||
|
||||
if not found_master:
|
||||
if len(setDicts) > 1:
|
||||
print "Warning, no explicit primary set.xml in " + aircraft_dir
|
||||
# use the first one
|
||||
package = setDicts[0]
|
||||
|
||||
# variants is just all the set dicts except the master
|
||||
variants = setDicts
|
||||
variants.remove(package)
|
||||
return (package, variants)
|
||||
|
||||
# use svn commands to report the last change date within dir
|
||||
def last_change_date_svn(dir):
|
||||
command = [ 'svn', 'info', dir ]
|
||||
@@ -84,6 +218,18 @@ def get_md5sum(file):
|
||||
f.close()
|
||||
return md5sum
|
||||
|
||||
def append_preview_nodes(node, variant, download_base, package_name):
|
||||
if not 'previews' in variant:
|
||||
return
|
||||
|
||||
for preview in variant['previews']:
|
||||
preview_node = ET.Element('preview')
|
||||
preview_url = download_base + 'previews/' + package_name + '_' + preview['path']
|
||||
preview_node.append( make_xml_leaf('type', preview['type']) )
|
||||
preview_node.append( make_xml_leaf('url', preview_url) )
|
||||
preview_node.append( make_xml_leaf('path', preview['path']) )
|
||||
node.append(preview_node)
|
||||
|
||||
def copy_previews_for_variant(variant, package_name, package_dir, previews_dir):
|
||||
if not 'previews' in variant:
|
||||
return
|
||||
@@ -103,94 +249,12 @@ def copy_previews_for_package(package, variants, package_name, package_dir, prev
|
||||
for v in variants:
|
||||
copy_previews_for_variant(v, package_name, package_dir, previews_dir)
|
||||
|
||||
def copy_thumbnail_for_variant(variant, package_name, package_dir, thumbnails_dir):
|
||||
if not 'thumbnail' in variant:
|
||||
def append_tag_nodes(node, variant):
|
||||
if not 'tags' in variant:
|
||||
return
|
||||
|
||||
thumb_src = os.path.join(package_dir, variant['thumbnail'])
|
||||
thumb_dst = os.path.join(thumbnails_dir, package_name + '_' + variant['thumbnail'])
|
||||
|
||||
dir = os.path.dirname(thumb_dst)
|
||||
if not os.path.isdir(dir):
|
||||
os.makedirs(dir)
|
||||
if os.path.exists(thumb_src):
|
||||
shutil.copy2(thumb_src, thumb_dst)
|
||||
|
||||
def copy_thumbnails_for_package(package, variants, package_name, package_dir, thumbnails_dir):
|
||||
copy_thumbnail_for_variant(package, package_name, package_dir, thumbnails_dir)
|
||||
|
||||
# and now each variant in turn
|
||||
for v in variants:
|
||||
copy_thumbnail_for_variant(v, package_name, package_dir, thumbnails_dir)
|
||||
|
||||
def process_aircraft_dir(name, repo_path):
|
||||
global includes
|
||||
global download_base
|
||||
global output_dir
|
||||
global valid_zips
|
||||
global previews_dir
|
||||
|
||||
aircraft_dir = os.path.join(repo_path, name)
|
||||
if not os.path.isdir(aircraft_dir):
|
||||
return
|
||||
|
||||
(package, variants) = catalog.scan_aircraft_dir(aircraft_dir, includes)
|
||||
if package == None:
|
||||
print "skipping:", name, "(no -set.xml files)"
|
||||
return
|
||||
|
||||
print "%s:" % name,
|
||||
|
||||
package_node = catalog.make_aircraft_node(name, package, variants, download_base)
|
||||
|
||||
download_url = download_base + name + '.zip'
|
||||
thumbnail_url = download_base + 'thumbnails/' + name + '_' + package['thumbnail']
|
||||
|
||||
# get cached md5sum if it exists
|
||||
md5sum = get_xml_text(md5sum_root.find(str('aircraft_' + name)))
|
||||
|
||||
# now do the packaging and rev number stuff
|
||||
dir_mtime = scan_dir_for_change_date_mtime(aircraft_dir)
|
||||
if repo_type == 'svn':
|
||||
rev = last_change_date_svn(aircraft_dir)
|
||||
else:
|
||||
d = datetime.datetime.utcfromtimestamp(dir_mtime)
|
||||
rev = d.strftime("%Y%m%d")
|
||||
package_node.append( catalog.make_xml_leaf('revision', rev) )
|
||||
#print "rev:", rev
|
||||
#print "dir mtime:", dir_mtime
|
||||
zipfile = os.path.join( output_dir, name + '.zip' )
|
||||
valid_zips.append(name + '.zip')
|
||||
if not os.path.exists(zipfile) \
|
||||
or dir_mtime > os.path.getmtime(zipfile) \
|
||||
or args.clean:
|
||||
# rebuild zip file
|
||||
print "updating:", zipfile
|
||||
make_aircraft_zip(repo_path, name, zipfile)
|
||||
md5sum = get_md5sum(zipfile)
|
||||
else:
|
||||
print "(no change)"
|
||||
if md5sum == "":
|
||||
md5sum = get_md5sum(zipfile)
|
||||
filesize = os.path.getsize(zipfile)
|
||||
package_node.append( catalog.make_xml_leaf('md5', md5sum) )
|
||||
package_node.append( catalog.make_xml_leaf('file-size-bytes', filesize) )
|
||||
|
||||
# handle md5sum cache
|
||||
node = md5sum_root.find('aircraft_' + name)
|
||||
if node != None:
|
||||
node.text = md5sum
|
||||
else:
|
||||
md5sum_root.append( catalog.make_xml_leaf('aircraft_' + name, md5sum) )
|
||||
|
||||
# handle thumbnails
|
||||
copy_thumbnails_for_package(package, variants, name, aircraft_dir, thumbnail_dir)
|
||||
|
||||
catalog_node.append(package_node)
|
||||
|
||||
# copy previews for the package and variants into the
|
||||
# output directory
|
||||
copy_previews_for_package(package, variants, name, aircraft_dir, previews_dir)
|
||||
for tag in variant['tags']:
|
||||
node.append(make_xml_leaf('tag', tag))
|
||||
|
||||
#def get_file_stats(file):
|
||||
# f = open(file, 'r')
|
||||
@@ -224,9 +288,6 @@ else:
|
||||
scm_list = config_node.findall('scm')
|
||||
upload_node = config_node.find('upload')
|
||||
download_base = get_xml_text(config_node.find('download-url'))
|
||||
if not download_base.endswith('/'):
|
||||
download_base += '/'
|
||||
|
||||
output_dir = get_xml_text(config_node.find('local-output'))
|
||||
if output_dir == '':
|
||||
output_dir = os.path.join(args.dir, 'output')
|
||||
@@ -300,8 +361,108 @@ for scm in scm_list:
|
||||
print "skipping:", name
|
||||
continue
|
||||
|
||||
# process each aircraft in turn
|
||||
process_aircraft_dir(name, repo_path)
|
||||
aircraft_dir = os.path.join(repo_path, name)
|
||||
if os.path.isdir(aircraft_dir):
|
||||
print "%s:" % name,
|
||||
(package, variants) = scan_aircraft_dir(aircraft_dir)
|
||||
if package == None:
|
||||
print "skipping:", name, "(no -set.xml files)"
|
||||
continue
|
||||
#print "package:", package
|
||||
#print "variants:", variants
|
||||
package_node = ET.Element('package')
|
||||
package_node.append( make_xml_leaf('name', package['name']) )
|
||||
package_node.append( make_xml_leaf('status', package['status']) )
|
||||
package_node.append( make_xml_leaf('author', package['author']) )
|
||||
package_node.append( make_xml_leaf('description', package['description']) )
|
||||
if 'rating_FDM' in package or 'rating_systems' in package \
|
||||
or 'rating_cockpit' in package or 'rating_model' in package:
|
||||
rating_node = ET.Element('rating')
|
||||
package_node.append(rating_node)
|
||||
rating_node.append( make_xml_leaf('FDM',
|
||||
package['rating_FDM']) )
|
||||
rating_node.append( make_xml_leaf('systems',
|
||||
package['rating_systems']) )
|
||||
rating_node.append( make_xml_leaf('cockpit',
|
||||
package['rating_cockpit']) )
|
||||
rating_node.append( make_xml_leaf('model',
|
||||
package['rating_model']) )
|
||||
package_node.append( make_xml_leaf('id', package['id']) )
|
||||
for variant in variants:
|
||||
variant_node = ET.Element('variant')
|
||||
package_node.append(variant_node)
|
||||
variant_node.append( make_xml_leaf('id', variant['id']) )
|
||||
variant_node.append( make_xml_leaf('name', variant['name']) )
|
||||
if 'description' in variant:
|
||||
variant_node.append( make_xml_leaf('description', variant['description']) )
|
||||
|
||||
if 'author' in variant:
|
||||
variant_node.append( make_xml_leaf('author', variant['author']) )
|
||||
|
||||
append_preview_nodes(variant_node, variant, download_base, name)
|
||||
append_tag_nodes(variant_node, variant)
|
||||
|
||||
package_node.append( make_xml_leaf('dir', name) )
|
||||
if not download_base.endswith('/'):
|
||||
download_base += '/'
|
||||
download_url = download_base + name + '.zip'
|
||||
thumbnail_url = download_base + 'thumbnails/' + name + '_thumbnail.jpg'
|
||||
package_node.append( make_xml_leaf('url', download_url) )
|
||||
package_node.append( make_xml_leaf('thumbnail', thumbnail_url) )
|
||||
|
||||
append_preview_nodes(package_node, package, download_base, name)
|
||||
append_tag_nodes(package_node, package)
|
||||
|
||||
# todo: url (download), thumbnail (download url)
|
||||
|
||||
# get cached md5sum if it exists
|
||||
md5sum = get_xml_text(md5sum_root.find(str('aircraft_' + name)))
|
||||
|
||||
# now do the packaging and rev number stuff
|
||||
dir_mtime = scan_dir_for_change_date_mtime(aircraft_dir)
|
||||
if repo_type == 'svn':
|
||||
rev = last_change_date_svn(aircraft_dir)
|
||||
else:
|
||||
d = datetime.datetime.utcfromtimestamp(dir_mtime)
|
||||
rev = d.strftime("%Y%m%d")
|
||||
package_node.append( make_xml_leaf('revision', rev) )
|
||||
#print "rev:", rev
|
||||
#print "dir mtime:", dir_mtime
|
||||
zipfile = os.path.join( output_dir, name + '.zip' )
|
||||
valid_zips.append(name + '.zip')
|
||||
if not os.path.exists(zipfile) \
|
||||
or dir_mtime > os.path.getmtime(zipfile) \
|
||||
or args.clean:
|
||||
# rebuild zip file
|
||||
print "updating:", zipfile
|
||||
make_aircraft_zip(repo_path, name, zipfile)
|
||||
md5sum = get_md5sum(zipfile)
|
||||
else:
|
||||
print "(no change)"
|
||||
if md5sum == "":
|
||||
md5sum = get_md5sum(zipfile)
|
||||
filesize = os.path.getsize(zipfile)
|
||||
package_node.append( make_xml_leaf('md5', md5sum) )
|
||||
package_node.append( make_xml_leaf('file-size-bytes', filesize) )
|
||||
|
||||
# handle md5sum cache
|
||||
node = md5sum_root.find('aircraft_' + name)
|
||||
if node != None:
|
||||
node.text = md5sum
|
||||
else:
|
||||
md5sum_root.append( make_xml_leaf('aircraft_' + name, md5sum) )
|
||||
|
||||
# handle thumbnails
|
||||
thumbnail_src = os.path.join(aircraft_dir, 'thumbnail.jpg')
|
||||
thumbnail_dst = os.path.join(thumbnail_dir, name + '_thumbnail.jpg')
|
||||
if os.path.exists(thumbnail_src):
|
||||
shutil.copy2(thumbnail_src, thumbnail_dst)
|
||||
catalog_node.append(package_node)
|
||||
package_node.append( make_xml_leaf('thumbnail-path', 'thumbnail.jpg') )
|
||||
|
||||
# copy previews for the package and variants into the
|
||||
# output directory
|
||||
copy_previews_for_package(package, variants, name, aircraft_dir, previews_dir)
|
||||
|
||||
# write out the master catalog file
|
||||
cat_file = os.path.join(output_dir, 'catalog.xml')
|
||||
|
||||
@@ -17,15 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
script_blob_id='$Id$'
|
||||
# Slightly tricky substitution to avoid our regexp being wildly replaced with
|
||||
# the blob name (id) when the script is checked out:
|
||||
#
|
||||
# First extract the hexadecimal blob object name followed by a '$'
|
||||
VERSION="$(echo "$script_blob_id" | sed 's@\$Id: *\([0-9a-f]\+\) *@\1@')"
|
||||
# Then remove the trailing '$'
|
||||
VERSION="${VERSION%\$}"
|
||||
|
||||
VERSION="2.34"
|
||||
FGVERSION="release/$(git ls-remote --heads https://git.code.sf.net/p/flightgear/flightgear|grep '\/release\/'|cut -f4 -d'/'|sort -t . -k 1,1n -k2,2n -k3,3n|tail -1)"
|
||||
|
||||
#######################################################
|
||||
@@ -108,16 +100,6 @@ function _logSep(){
|
||||
echo "***********************************" >> $LOGFILE
|
||||
}
|
||||
|
||||
function _aptUpdate(){
|
||||
echo "Asking password for 'apt-get update'..."
|
||||
sudo apt-get update
|
||||
}
|
||||
|
||||
function _aptInstall(){
|
||||
echo "Asking password for 'apt-get install $*'..."
|
||||
sudo apt-get install "$@"
|
||||
}
|
||||
|
||||
function _gitUpdate(){
|
||||
if [ "$DOWNLOAD" != "y" ]; then
|
||||
return
|
||||
@@ -161,48 +143,6 @@ function _make(){
|
||||
fi
|
||||
}
|
||||
|
||||
# Find an available, non-virtual package matching one of the given regexps.
|
||||
#
|
||||
# Each positional parameter is interpreted as a POSIX extended regular
|
||||
# expression. These parameters are examined from left to right, and the first
|
||||
# available matching package is added to the global PKG variable. If no match
|
||||
# is found, the script aborts.
|
||||
function _package_alternative(){
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Empty package alternative: this is a bug in the script, aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Considering a package alternative:" "$@"
|
||||
_package_alternative_inner "$@"
|
||||
}
|
||||
|
||||
# This function requires the 'dctrl-tools' package
|
||||
function _package_alternative_inner(){
|
||||
local pkg
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "No match found for the package alternative, aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# This finds non-virtual packages only (on purpose)
|
||||
pkg="$(apt-cache dumpavail | \
|
||||
grep-dctrl -e -sPackage -FPackage \
|
||||
"^[[:space:]]*($1)[[:space:]]*\$" - | \
|
||||
sed -ne '1s/^Package:[[:space:]]*//gp')"
|
||||
|
||||
if [[ -n "$pkg" ]]; then
|
||||
echo "Package alternative matched for $pkg"
|
||||
PKG="$PKG $pkg"
|
||||
return 0
|
||||
else
|
||||
# Try with the next regexp
|
||||
shift
|
||||
_package_alternative_inner "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################################
|
||||
# set script to stop if an error occours
|
||||
set -e
|
||||
@@ -261,23 +201,6 @@ _logSep
|
||||
|
||||
#######################################################
|
||||
#######################################################
|
||||
|
||||
if [[ "$DOWNLOAD_PACKAGES" = "y" ]] && [[ "$APT_GET_UPDATE" = "y" ]]; then
|
||||
_aptUpdate
|
||||
fi
|
||||
|
||||
# Ensure 'dctrl-tools' is installed
|
||||
if [[ "$(dpkg-query --showformat='${db:Status-Status}\n' --show dctrl-tools \
|
||||
2>/dev/null)" != "installed" ]]; then
|
||||
if [[ "$DOWNLOAD_PACKAGES" = "y" ]]; then
|
||||
_aptInstall dctrl-tools
|
||||
else
|
||||
echo -n "The 'dctrl-tools' package is needed, but DOWNLOAD_PACKAGES is "
|
||||
echo -e "not set to 'y'.\nAborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Minimum
|
||||
PKG="build-essential cmake git"
|
||||
# cmake
|
||||
@@ -287,12 +210,9 @@ PKG="$PKG libcgal-dev libgdal-dev libtiff5-dev"
|
||||
# TGGUI/OpenRTI
|
||||
PKG="$PKG libqt4-dev"
|
||||
# SG/FG
|
||||
PKG="$PKG zlib1g-dev freeglut3-dev libboost-dev"
|
||||
_package_alternative libopenscenegraph-3.4-dev libopenscenegraph-dev \
|
||||
'libopenscenegraph-[0-9]+\.[0-9]+-dev'
|
||||
PKG="$PKG zlib1g-dev freeglut3-dev libboost-dev libopenscenegraph-dev"
|
||||
# FG
|
||||
PKG="$PKG libopenal-dev libudev-dev qt5-default qtdeclarative5-dev libdbus-1-dev libplib-dev"
|
||||
_package_alternative libpng-dev libpng12-dev libpng16-dev
|
||||
PKG="$PKG libopenal-dev libudev-dev qt5-default libdbus-1-dev libpng12-dev libplib-dev"
|
||||
# FGPanel
|
||||
PKG="$PKG fluid libbz2-dev libfltk1.3-dev libxi-dev libxmu-dev"
|
||||
# FGAdmin
|
||||
@@ -304,8 +224,12 @@ PKG="$PKG python-tk"
|
||||
# FGx (FGx is not compatible with Qt5, however we have installed Qt5 by default)
|
||||
#PKG="$PKG libqt5xmlpatterns5-dev libqt5webkit5-dev"
|
||||
|
||||
if [[ "$DOWNLOAD_PACKAGES" = "y" ]]; then
|
||||
_aptInstall $PKG
|
||||
if [ "$DOWNLOAD_PACKAGES" = "y" ]; then
|
||||
echo "Asking password for apt-get operations..."
|
||||
if [ "$APT_GET_UPDATE" = "y" ]; then
|
||||
sudo apt-get update
|
||||
fi
|
||||
sudo apt-get install $PKG
|
||||
fi
|
||||
|
||||
#######################################################
|
||||
@@ -455,8 +379,8 @@ if [[ "$(declare -p WHATTOBUILD)" =~ '['([0-9]+)']="OSG"' ]]; then
|
||||
echo "****************************************" | tee -a $LOGFILE
|
||||
cd "$CBD"/openscenegraph
|
||||
_gitDownload https://github.com/openscenegraph/osg.git
|
||||
_gitUpdate OpenSceneGraph-3.4
|
||||
|
||||
_gitUpdate OpenSceneGraph-3.2
|
||||
|
||||
if [ "$RECONFIGURE" = "y" ]; then
|
||||
cd "$CBD"
|
||||
mkdir -p build/openscenegraph
|
||||
|
||||
2
fgdata
2
fgdata
Submodule fgdata updated: 5b4983c716...c8b090fa4e
Submodule flightgear updated: c9eff67409...989ebf73a6
1
getstart
1
getstart
Submodule getstart deleted from a75fedfc67
@@ -1,83 +0,0 @@
|
||||
Quick start for the localization (l10n) scripts
|
||||
===============================================
|
||||
|
||||
The following assumes that all of these are in present in
|
||||
$FG_ROOT/Translations:
|
||||
- the default translation (default/*.xml);
|
||||
- the legacy FlightGear XML localization files (<language_code>/*.xml);
|
||||
- except for 'fg-convert-translation-files' which creates them, existing
|
||||
XLIFF 1.2 files (<language_code>/FlightGear-nonQt.xlf).
|
||||
|
||||
Note: the legacy FlightGear XML localization files are only needed by
|
||||
'fg-convert-translation-files' when migrating to the XLIFF format. The
|
||||
other scripts only need the default translation and obviously, for
|
||||
'fg-update-translation-files', the current XLIFF files.
|
||||
|
||||
To get the initial XLIFF files (generated from the default translation in
|
||||
$FG_ROOT/Translations/default as well as the legacy FlightGear XML
|
||||
localization files in $FG_ROOT/Translations/<language_code>):
|
||||
|
||||
languages="de en_US es fr it nl pl pt zh_CN"
|
||||
|
||||
# Your shell must expand $languages as several words. POSIX shell does that,
|
||||
# but not zsh for instance. Otherwise, don't use a shell variable.
|
||||
fg-convert-translation-files --transl-dir="$FG_ROOT/Translations" $languages
|
||||
|
||||
# Add strings found in the default translation but missing in the legacy FG
|
||||
# XML l10n files
|
||||
fg-update-translation-files --transl-dir="$FG_ROOT/Translations" \
|
||||
merge-new-master $languages
|
||||
|
||||
When master strings[1] have changed (in a large sense, i.e.: strings added,
|
||||
modified or removed, or categories added or removed[2]):
|
||||
|
||||
fg-update-translation-files --transl-dir="$FG_ROOT/Translations" \
|
||||
merge-new-master $languages
|
||||
|
||||
To remove unused translated strings (not to be done too often in my opinion):
|
||||
|
||||
fg-update-translation-files --transl-dir="$FG_ROOT/Translations" \
|
||||
remove-unused $languages
|
||||
|
||||
(you may replace 'remove-unused' with 'mark-unused' to just mark the strings
|
||||
as not-to-be-translated, however 'merge-new-master' presented above already
|
||||
does that)
|
||||
|
||||
To create skeleton translations for new languages (e.g., for fr_BE, en_AU and
|
||||
ca):
|
||||
|
||||
1) Check (add if necessary) that flightgear/meta/i18n.py knows the plural
|
||||
forms used in the new languages. This is done by editing PLURAL_FORMS
|
||||
towards the top of this i18n.py file (very easy). If the existing entry
|
||||
for, e.g., "zh" is sufficient for zh_TW or zh_HK, just let "zh" handle
|
||||
them: it will be tried as fallback if there is no perfect match on
|
||||
language and territory.
|
||||
|
||||
2) Run a command such as:
|
||||
|
||||
fg-new-translations --transl-dir="$FG_ROOT/Translations" fr_BE en_AU ca
|
||||
|
||||
(if you do this for only one language at a time, you can use the -o
|
||||
option to precisely control where the output goes, otherwise
|
||||
fg-new-translations chooses an appropriate place based on the value
|
||||
specified for --transl-dir)
|
||||
|
||||
fg-convert-translation-files, fg-update-translation-files and
|
||||
fg-new-translations all support the --help option for more detailed
|
||||
information.
|
||||
|
||||
|
||||
Footnotes
|
||||
---------
|
||||
|
||||
[1] Strings in the default translation.
|
||||
|
||||
[2] Only empty categories are removed by this command. An obsolete category
|
||||
can be made empty by manual editing (easy, just locate the right
|
||||
<group>) or this way:
|
||||
|
||||
fg-update-translation-files --transl-dir=... mark-unused
|
||||
fg-update-translation-files --transl-dir=... remove-unused
|
||||
|
||||
(note that this will remove *all* strings marked as unused in the first
|
||||
step, not only those in some particular category!)
|
||||
@@ -1,186 +0,0 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# fg-convert-translation-files --- Convert FlightGear's translation files
|
||||
# Copyright (C) 2017 Florent Rougon
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as et
|
||||
except ImportError:
|
||||
import elementtree.ElementTree as et
|
||||
|
||||
import flightgear.meta.logging
|
||||
import flightgear.meta.i18n as fg_i18n
|
||||
|
||||
|
||||
PROGNAME = os.path.basename(sys.argv[0])
|
||||
|
||||
# Only messages with severity >= info will be printed to the terminal (it's
|
||||
# possible to also log all messages to a file regardless of their level, see
|
||||
# the Logger class). Of course, there is also the standard logging module...
|
||||
logger = flightgear.meta.logging.Logger(
|
||||
progname=PROGNAME,
|
||||
logLevel=flightgear.meta.logging.LogLevel.info,
|
||||
defaultOutputStream=sys.stderr)
|
||||
|
||||
debug = logger.debug
|
||||
info = logger.info
|
||||
notice = logger.notice
|
||||
warning = logger.warning
|
||||
error = logger.error
|
||||
critical = logger.critical
|
||||
|
||||
|
||||
# We could use Translation.__str__(): not as readable (for now) but more
|
||||
# accurate on metadata
|
||||
def printPlainText(l10nResPoolMgr, translations):
|
||||
"""Print output suitable for a quick review (by the programmer)."""
|
||||
firstLang = True
|
||||
|
||||
for langCode, (transl, nbWhitespacePbs) in translations.items():
|
||||
# 'transl' is a Translation instance
|
||||
if firstLang:
|
||||
firstLang = False
|
||||
else:
|
||||
print()
|
||||
|
||||
print("-" * 78 + "\n" + langCode + "\n" + "-" * 78)
|
||||
print("\nNumber of leading and/or trailing whitespace problems: {}"
|
||||
.format(nbWhitespacePbs))
|
||||
|
||||
for cat in transl:
|
||||
print("\nCategory: {cat}\n{underline}".format(
|
||||
cat=cat, underline="~"*(len("Category: ") + len(cat))))
|
||||
t = transl[cat]
|
||||
|
||||
for tid, translUnit in sorted(t.items()):
|
||||
# - Using '{master!r}' and '{transl!r}' prints stuff such as
|
||||
# \xa0 for nobreak spaces, which can lead to the erroneous
|
||||
# conclusion that there was an encoding problem.
|
||||
# - Only printing the first target text here (no plural forms)
|
||||
print("\n{id}\n '{sourceText}'\n '{targetText}'"
|
||||
.format(id=tid.id(), sourceText=translUnit.sourceText,
|
||||
targetText=translUnit.targetTexts[0]))
|
||||
|
||||
|
||||
def writeXliff(l10nResPoolMgr, translations):
|
||||
formatHandler = fg_i18n.XliffFormatHandler()
|
||||
|
||||
for langCode, translData in translations.items():
|
||||
translation = translData.transl # Translation instance
|
||||
|
||||
if params.output_dir is None:
|
||||
# Use default locations for the written xliff files
|
||||
l10nResPoolMgr.writeTranslation(formatHandler, translation)
|
||||
else:
|
||||
basename = "{}-{}.{}".format(
|
||||
formatHandler.defaultFileStem(langCode),
|
||||
langCode,
|
||||
formatHandler.standardExtension)
|
||||
filePath = os.path.join(params.output_dir, basename)
|
||||
formatHandler.writeTranslation(translation, filePath)
|
||||
|
||||
|
||||
def processCommandLine():
|
||||
params = argparse.Namespace()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
usage="""\
|
||||
%(prog)s [OPTION ...] LANGUAGE_CODE...
|
||||
Convert FlightGear's old XML translation files into other formats.""",
|
||||
description="""\
|
||||
Most notably, XLIFF format can be chosen for output. The script performs
|
||||
a few automated checks on the input files too.""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
# I want --help but not -h (it might be useful for something else)
|
||||
add_help=False)
|
||||
|
||||
parser.add_argument("-t", "--transl-dir",
|
||||
help="""\
|
||||
directory containing all translation subdirs (such as
|
||||
{default!r}, 'en_GB', 'fr_FR', 'de', 'it'...). This
|
||||
"option" MUST be specified.""".format(
|
||||
default=fg_i18n.DEFAULT_LANG_DIR))
|
||||
parser.add_argument("lang_code", metavar="LANGUAGE_CODE", nargs="+",
|
||||
help="""\
|
||||
codes of languages to read translations for (don't
|
||||
specify {default!r} this way, it is special and not a
|
||||
language code)"""
|
||||
.format(default=fg_i18n.DEFAULT_LANG_DIR))
|
||||
parser.add_argument("-o", "--output-dir",
|
||||
help="""\
|
||||
output directory for written XLIFF files
|
||||
(default: for each output file, use a suitable location
|
||||
under TRANSL_DIR)""")
|
||||
parser.add_argument("-f", "--output-format", default="xliff",
|
||||
choices=("xliff", "text"), help="""\
|
||||
format to use for the output files""")
|
||||
parser.add_argument("--help", action="help",
|
||||
help="display this message and exit")
|
||||
|
||||
params = parser.parse_args(namespace=params)
|
||||
|
||||
if params.transl_dir is None:
|
||||
error("--transl-dir must be given, aborting")
|
||||
sys.exit(1)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def main():
|
||||
global params
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
params = processCommandLine()
|
||||
|
||||
l10nResPoolMgr = fg_i18n.L10NResourcePoolManager(params.transl_dir, logger)
|
||||
# English version of all translatable strings
|
||||
masterTransl, nbWhitespaceProblemsInMaster = \
|
||||
l10nResPoolMgr.readFgMasterTranslation()
|
||||
translations = collections.OrderedDict()
|
||||
|
||||
# Sort elements of 'translations' according to language code (= the keys)
|
||||
for langCode in sorted(params.lang_code):
|
||||
translationData = l10nResPoolMgr.readFgTranslation(masterTransl,
|
||||
langCode)
|
||||
translations[translationData.transl.targetLanguage] = translationData
|
||||
|
||||
if params.output_format == "xliff":
|
||||
writeFunc = writeXliff # write to files
|
||||
elif params.output_format == "text":
|
||||
writeFunc = printPlainText # print to stdout
|
||||
else:
|
||||
assert False, \
|
||||
"Unexpected output format: '{}'".format(params.output_format)
|
||||
|
||||
writeFunc(l10nResPoolMgr, translations)
|
||||
|
||||
nbWhitespaceProblemsInTransl = sum(
|
||||
(translData.nbWhitespacePbs for translData in translations.values() ))
|
||||
info("total number of leading and/or trailing whitespace problems: {}"
|
||||
.format(nbWhitespaceProblemsInMaster + nbWhitespaceProblemsInTransl))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
@@ -1,125 +0,0 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# fg-new-translations --- Create new translations for FlightGear
|
||||
# Copyright (C) 2017 Florent Rougon
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as et
|
||||
except ImportError:
|
||||
import elementtree.ElementTree as et
|
||||
|
||||
import flightgear.meta.logging
|
||||
import flightgear.meta.i18n as fg_i18n
|
||||
|
||||
|
||||
PROGNAME = os.path.basename(sys.argv[0])
|
||||
|
||||
# Only messages with severity >= info will be printed to the terminal (it's
|
||||
# possible to also log all messages to a file regardless of their level, see
|
||||
# the Logger class). Of course, there is also the standard logging module...
|
||||
logger = flightgear.meta.logging.Logger(
|
||||
progname=PROGNAME,
|
||||
logLevel=flightgear.meta.logging.LogLevel.info,
|
||||
defaultOutputStream=sys.stderr)
|
||||
|
||||
|
||||
def processCommandLine():
|
||||
params = argparse.Namespace()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
usage="""\
|
||||
%(prog)s [OPTION ...] LANGUAGE_CODE...
|
||||
Write the skeleton of XLIFF translation files.""",
|
||||
description="""\
|
||||
This program writes XLIFF translation files with the strings to translate
|
||||
for the specified languages (target strings are empty). This is what you need
|
||||
to start a translation for a new language.""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
# I want --help but not -h (it might be useful for something else)
|
||||
add_help=False)
|
||||
|
||||
parser.add_argument("-t", "--transl-dir",
|
||||
help="""\
|
||||
directory containing all translation subdirs (such as
|
||||
{default!r}, 'en_GB', 'fr_FR', 'de', 'it'...). This
|
||||
"option" MUST be specified.""".format(
|
||||
default=fg_i18n.DEFAULT_LANG_DIR))
|
||||
parser.add_argument("lang_code", metavar="LANGUAGE_CODE", nargs="+",
|
||||
help="""\
|
||||
codes of languages to create translations for (e.g., fr,
|
||||
fr_BE, en_GB, it, es_ES...)""")
|
||||
parser.add_argument("-o", "--output-file",
|
||||
help="""\
|
||||
where to write the output to (use '-' for standard
|
||||
output); if not specified, a suitable file under
|
||||
TRANSL_DIR will be chosen for each LANGUAGE_CODE.
|
||||
Note: this option can only be given when exactly one
|
||||
LANGUAGE_CODE has been specified on the command
|
||||
line (it doesn't make sense otherwise).""")
|
||||
parser.add_argument("--output-format", default="xliff",
|
||||
choices=fg_i18n.FORMAT_HANDLERS_NAMES,
|
||||
help="format to use for the output files")
|
||||
parser.add_argument("--help", action="help",
|
||||
help="display this message and exit")
|
||||
|
||||
params = parser.parse_args(namespace=params)
|
||||
|
||||
if params.transl_dir is None:
|
||||
logger.error("--transl-dir must be given, aborting")
|
||||
sys.exit(1)
|
||||
|
||||
if params.output_file is not None and len(params.lang_code) > 1:
|
||||
logger.error("--output-file can only be given when exactly one "
|
||||
"LANGUAGE_CODE has been specified on the command line "
|
||||
"(it doesn't make sense otherwise)")
|
||||
sys.exit(1)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def main():
|
||||
global params
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
params = processCommandLine()
|
||||
|
||||
l10nResPoolMgr = fg_i18n.L10NResourcePoolManager(params.transl_dir, logger)
|
||||
xliffFormatHandler = fg_i18n.FORMAT_HANDLERS_MAP[params.output_format]()
|
||||
|
||||
if params.output_file is not None:
|
||||
assert len(params.lang_code) == 1, params.lang_code
|
||||
# Output to one file or to stdout
|
||||
l10nResPoolMgr.writeSkeletonTranslation(
|
||||
xliffFormatHandler, params.lang_code[0],
|
||||
filePath=params.output_file)
|
||||
else:
|
||||
# Output to several files
|
||||
for langCode in params.lang_code:
|
||||
l10nResPoolMgr.writeSkeletonTranslation(xliffFormatHandler,
|
||||
langCode)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
@@ -1,184 +0,0 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# fg-update-translation-files --- Merge new default translation,
|
||||
# remove obsolete strings from a translation
|
||||
# Copyright (C) 2017 Florent Rougon
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as et
|
||||
except ImportError:
|
||||
import elementtree.ElementTree as et
|
||||
|
||||
import flightgear.meta.logging
|
||||
import flightgear.meta.i18n as fg_i18n
|
||||
|
||||
|
||||
PROGNAME = os.path.basename(sys.argv[0])
|
||||
|
||||
# Only messages with severity >= info will be printed to the terminal (it's
|
||||
# possible to also log all messages to a file regardless of their level, see
|
||||
# the Logger class). Of course, there is also the standard logging module...
|
||||
logger = flightgear.meta.logging.Logger(
|
||||
progname=PROGNAME,
|
||||
logLevel=flightgear.meta.logging.LogLevel.info,
|
||||
defaultOutputStream=sys.stderr)
|
||||
|
||||
|
||||
def processCommandLine():
|
||||
params = argparse.Namespace()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
usage="""\
|
||||
%(prog)s [OPTION ...] ACTION LANGUAGE_CODE...
|
||||
Update FlightGear XLIFF localization files.""",
|
||||
description="""\
|
||||
This program performs the following operations (actions) on FlightGear XLIFF
|
||||
translation files (*.xlf):
|
||||
|
||||
- [merge-new-master]
|
||||
Read the default translation[1], add new translated strings it contains to
|
||||
the XLIFF localization files corresponding to the specified language(s),
|
||||
mark the translated strings in said files that need review (modified in
|
||||
the default translation) as well as those that are not used anymore
|
||||
(disappeared in the default translation, or marked in a way that says they
|
||||
don't need to be translated);
|
||||
|
||||
- [mark-unused]
|
||||
Read the default translation and mark translated strings (in the XLIFF
|
||||
localization files corresponding to the specified language(s)) that are
|
||||
not used anymore;
|
||||
|
||||
- [remove-unused]
|
||||
In the XLIFF localization files corresponding to the specified
|
||||
language(s), remove all translated strings that are marked as unused.
|
||||
|
||||
A translated string that is marked as unused is still present in the XLIFF
|
||||
localization file; it is just presented in a way that tells translators they
|
||||
don't need to worry about it. On the other hand, when a translated string is
|
||||
removed, translators don't see it anymore and the translation is lost, except
|
||||
if rescued by external means such as backups or version control systems (Git,
|
||||
Subversion, etc.)
|
||||
|
||||
Note that the 'remove-unused' action does *not* imply 'mark-unused'. It only
|
||||
removes translation units that are already marked as unused (i.e., with
|
||||
translate="no"). Thus, it makes sense to do 'mark-unused' followed by
|
||||
'remove-unused' if you really want to get rid of old translations (you need to
|
||||
invoke the program twice, or make a small change for this). Leaving unused
|
||||
translated strings marked as such in XLIFF files shouldn't harm much in
|
||||
general on the short or mid-term: they only take some space.
|
||||
|
||||
[1] FlightGear XML files in $FG_ROOT/Translations/default containing strings
|
||||
used for the default locale (English).""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
# I want --help but not -h (it might be useful for something else)
|
||||
add_help=False)
|
||||
|
||||
parser.add_argument("-t", "--transl-dir",
|
||||
help="""\
|
||||
directory containing all translation subdirs (such as
|
||||
{default!r}, 'en_GB', 'fr_FR', 'de', 'it'...). This
|
||||
"option" MUST be specified.""".format(
|
||||
default=fg_i18n.DEFAULT_LANG_DIR))
|
||||
parser.add_argument("action", metavar="ACTION",
|
||||
choices=("merge-new-master",
|
||||
"mark-unused",
|
||||
"remove-unused"),
|
||||
help="""\
|
||||
what to do: merge a new default (= master)
|
||||
translation, or mark unused translation units, or
|
||||
remove those already marked as unused from the XLIFF
|
||||
files corresponding to each given LANGUAGE_CODE (i.e.,
|
||||
those that are not in the default translation)""")
|
||||
parser.add_argument("lang_code", metavar="LANGUAGE_CODE", nargs="+",
|
||||
help="""\
|
||||
codes of languages to operate on (e.g., fr, en_GB, it,
|
||||
es_ES...)""")
|
||||
parser.add_argument("--help", action="help",
|
||||
help="display this message and exit")
|
||||
|
||||
params = parser.parse_args(namespace=params)
|
||||
|
||||
if params.transl_dir is None:
|
||||
logger.error("--transl-dir must be given, aborting")
|
||||
sys.exit(1)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
class MarkOrRemoveUnusedAction(enum.Enum):
|
||||
mark, remove = range(2)
|
||||
|
||||
|
||||
def markOrRemoveUnused(l10nResPoolMgr, action):
|
||||
formatHandler = fg_i18n.XliffFormatHandler()
|
||||
masterTransl = l10nResPoolMgr.readFgMasterTranslation().transl
|
||||
|
||||
for langCode in params.lang_code:
|
||||
xliffPath = formatHandler.defaultFilePath(params.transl_dir, langCode)
|
||||
transl = formatHandler.readTranslation(xliffPath)
|
||||
|
||||
if action == MarkOrRemoveUnusedAction.mark:
|
||||
transl.markObsoleteOrVanished(masterTransl, logger=logger)
|
||||
elif action == MarkOrRemoveUnusedAction.remove:
|
||||
transl.removeObsoleteOrVanished(logger=logger)
|
||||
else:
|
||||
assert False, "unexpected action: {!r}".format(action)
|
||||
|
||||
l10nResPoolMgr.writeTranslation(formatHandler, transl,
|
||||
filePath=xliffPath)
|
||||
|
||||
|
||||
def mergeNewMaster(l10nResPoolMgr):
|
||||
formatHandler = fg_i18n.XliffFormatHandler()
|
||||
masterTransl = l10nResPoolMgr.readFgMasterTranslation().transl
|
||||
|
||||
for langCode in params.lang_code:
|
||||
xliffPath = formatHandler.defaultFilePath(params.transl_dir, langCode)
|
||||
transl = formatHandler.readTranslation(xliffPath)
|
||||
transl.mergeMasterTranslation(masterTransl, logger=logger)
|
||||
l10nResPoolMgr.writeTranslation(formatHandler, transl,
|
||||
filePath=xliffPath)
|
||||
|
||||
|
||||
def main():
|
||||
global params
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
params = processCommandLine()
|
||||
|
||||
l10nResPoolMgr = fg_i18n.L10NResourcePoolManager(params.transl_dir, logger)
|
||||
|
||||
if params.action == "mark-unused":
|
||||
markOrRemoveUnused(l10nResPoolMgr, MarkOrRemoveUnusedAction.mark)
|
||||
elif params.action == "remove-unused":
|
||||
markOrRemoveUnused(l10nResPoolMgr, MarkOrRemoveUnusedAction.remove)
|
||||
elif params.action == "merge-new-master":
|
||||
mergeNewMaster(l10nResPoolMgr)
|
||||
else:
|
||||
assert False, "Bug: unexpected action: {!r}".format(params.action)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
@@ -1,58 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# exceptions.py --- Simple, general-purpose subclass of Exception
|
||||
#
|
||||
# Copyright (C) 2015, 2017 Florent Rougon
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""Simple, general-purpose Exception subclass."""
|
||||
|
||||
|
||||
class FGPyException(Exception):
|
||||
def __init__(self, message=None, *, mayCapitalizeMsg=True):
|
||||
"""Initialize an FGPyException instance.
|
||||
|
||||
Except in cases where 'message' starts with a proper noun or
|
||||
something like that, its first character should be given in
|
||||
lower case. Automated treatments of this exception may print the
|
||||
message with its first character changed to upper case, unless
|
||||
'mayCapitalizeMsg' is False. In other words, if the case of the
|
||||
first character of 'message' must not be changed under any
|
||||
circumstances, set 'mayCapitalizeMsg' to False.
|
||||
|
||||
"""
|
||||
self.message = message
|
||||
self.mayCapitalizeMsg = mayCapitalizeMsg
|
||||
|
||||
def __str__(self):
|
||||
return self.completeMessage()
|
||||
|
||||
def __repr__(self):
|
||||
return "{}.{}({!r})".format(__name__, type(self).__name__, self.message)
|
||||
|
||||
# Typically overridden by subclasses with a custom constructor
|
||||
def detail(self):
|
||||
return self.message
|
||||
|
||||
def completeMessage(self):
|
||||
if self.message:
|
||||
return "{shortDesc}: {detail}".format(
|
||||
shortDesc=self.ExceptionShortDescription,
|
||||
detail=self.detail())
|
||||
else:
|
||||
return self.ExceptionShortDescription
|
||||
|
||||
ExceptionShortDescription = "FlightGear Python generic exception"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,95 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# logging.py --- Simple logging infrastructure (mostly taken from FFGo)
|
||||
# Copyright (C) 2015, 2017 Florent Rougon
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import sys
|
||||
|
||||
from . import misc
|
||||
|
||||
|
||||
class LogLevel(misc.OrderedEnum):
|
||||
debug, info, notice, warning, error, critical = range(6)
|
||||
|
||||
# List containing the above log levels as strings in increasing priority order
|
||||
allLogLevels = [member.name for member in LogLevel]
|
||||
allLogLevels.sort(key=lambda n: LogLevel[n].value)
|
||||
|
||||
|
||||
def _logFuncFactory(level):
|
||||
def logFunc(self, *args, **kwargs):
|
||||
self.log(LogLevel[level], True, *args, **kwargs)
|
||||
|
||||
def logFunc_noPrefix(self, *args, **kwargs):
|
||||
self.log(LogLevel[level], False, *args, **kwargs)
|
||||
|
||||
return (logFunc, logFunc_noPrefix)
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, progname=None, logLevel=LogLevel.notice,
|
||||
defaultOutputStream=sys.stdout, logFile=None):
|
||||
self.progname = progname
|
||||
self.logLevel = logLevel
|
||||
self.defaultOutputStream = defaultOutputStream
|
||||
self.logFile = logFile
|
||||
|
||||
def setLogFile(self, *args, **kwargs):
|
||||
self.logFile = open(*args, **kwargs)
|
||||
|
||||
def log(self, level, printLogLevel, *args, **kwargs):
|
||||
if printLogLevel and level >= LogLevel.warning and args:
|
||||
args = [level.name.upper() + ": " + args[0]] + list(args[1:])
|
||||
|
||||
if level >= self.logLevel:
|
||||
if (self.progname is not None) and args:
|
||||
tArgs = [self.progname + ": " + args[0]] + list(args[1:])
|
||||
else:
|
||||
tArgs = args
|
||||
|
||||
kwargs["file"] = self.defaultOutputStream
|
||||
print(*tArgs, **kwargs)
|
||||
|
||||
if self.logFile is not None:
|
||||
kwargs["file"] = self.logFile
|
||||
print(*args, **kwargs)
|
||||
|
||||
# Don't overload log() with too many tests or too much indirection for
|
||||
# little use
|
||||
def logToFile(self, *args, **kwargs):
|
||||
kwargs["file"] = self.logFile
|
||||
print(*args, **kwargs)
|
||||
|
||||
# NP functions are “no prefix” variants which never prepend the log level
|
||||
# (otherwise, it is only prepended for warning and higher levels).
|
||||
debug, debugNP = _logFuncFactory("debug")
|
||||
info, infoNP = _logFuncFactory("info")
|
||||
notice, noticeNP = _logFuncFactory("notice")
|
||||
warning, warningNP = _logFuncFactory("warning")
|
||||
error, errorNP = _logFuncFactory("error")
|
||||
critical, criticalNP = _logFuncFactory("critical")
|
||||
|
||||
|
||||
class DummyLogger(Logger):
|
||||
def setLogFile(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def log(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def logToFile(self, *args, **kwargs):
|
||||
pass
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# misc.py --- Miscellaneous classes and/or functions
|
||||
# Copyright (C) 2015-2017 Florent Rougon
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import enum
|
||||
|
||||
# Based on an example from the 'enum' documentation
|
||||
class OrderedEnum(enum.Enum):
|
||||
"""Base class for enumerations whose members can be ordered.
|
||||
|
||||
Contrary to enum.IntEnum, this class maintains normal enum.Enum
|
||||
invariants, such as members not being comparable to members of other
|
||||
enumerations (nor of any other class, actually).
|
||||
|
||||
"""
|
||||
def __ge__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value >= other.value
|
||||
return NotImplemented
|
||||
|
||||
def __gt__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value > other.value
|
||||
return NotImplemented
|
||||
|
||||
def __le__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value <= other.value
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value < other.value
|
||||
return NotImplemented
|
||||
|
||||
def __eq__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value == other.value
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
if self.__class__ is other.__class__:
|
||||
return self.value != other.value
|
||||
return NotImplemented
|
||||
|
||||
|
||||
# Taken from <http://effbot.org/zone/element-lib.htm#prettyprint> and modified
|
||||
# by Florent Rougon
|
||||
def indentXmlTree(elem, level=0, basicOffset=2, lastChild=False):
|
||||
def indentation(level):
|
||||
return "\n" + level*basicOffset*" "
|
||||
|
||||
if len(elem):
|
||||
if not elem.text or not elem.text.strip():
|
||||
elem.text = indentation(level+1)
|
||||
|
||||
for e in elem[:-1]:
|
||||
indentXmlTree(e, level+1, basicOffset, False)
|
||||
if len(elem):
|
||||
indentXmlTree(elem[-1], level+1, basicOffset, True)
|
||||
|
||||
if level and (not elem.tail or not elem.tail.strip()):
|
||||
if lastChild:
|
||||
elem.tail = indentation(level-1)
|
||||
else:
|
||||
elem.tail = indentation(level)
|
||||
@@ -1,172 +0,0 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# rebuild-fgdata-embedded-resources -- Rebuild FGData-resources.[ch]xx
|
||||
# Copyright (C) 2017 Florent Rougon
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
# Only standard modules so that distributors can easily run this script, in
|
||||
# case they want to recreate FGData-resources.[ch]xx from source.
|
||||
import argparse
|
||||
import json
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PROGNAME = os.path.basename(sys.argv[0])
|
||||
CONFIG_FILE = os.path.join(os.path.expanduser('~'),
|
||||
".fgmeta",
|
||||
PROGNAME + ".json")
|
||||
|
||||
# chLevel: console handler level
|
||||
def setupLogging(level=logging.NOTSET, chLevel=None):
|
||||
global logger
|
||||
|
||||
if chLevel is None:
|
||||
chLevel = level
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# Effective level for all child loggers with NOTSET level
|
||||
logger.setLevel(level)
|
||||
# Create console handler and set its level
|
||||
ch = logging.StreamHandler() # Uses sys.stderr by default
|
||||
ch.setLevel(chLevel) # NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
# Logger name with :%(name)s... many other things available, including
|
||||
# %(levelname)s
|
||||
formatter = logging.Formatter("{}: %(message)s".format(PROGNAME))
|
||||
# Add formatter to ch
|
||||
ch.setFormatter(formatter)
|
||||
# Add ch to logger
|
||||
logger.addHandler(ch)
|
||||
|
||||
|
||||
# Modifies 'params' in-place
|
||||
def loadCfgFileSection(params, jsonTree, title, items):
|
||||
# NB: !!! each item is subject to os.path.expanduser() !!!
|
||||
try:
|
||||
section = jsonTree[title]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
for name in items:
|
||||
try:
|
||||
path = section[name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
setattr(params, name.lower(), os.path.expanduser(path))
|
||||
|
||||
|
||||
# Modifies 'params' in-place
|
||||
def loadConfigFile(params):
|
||||
if not os.path.isfile(CONFIG_FILE):
|
||||
return
|
||||
|
||||
# The log level is set too late for this one -> commented out
|
||||
# logger.info("Loading config file {}...".format(CONFIG_FILE))
|
||||
|
||||
with open(CONFIG_FILE, "r", encoding="utf-8") as cfgFile:
|
||||
tree = json.load(cfgFile)
|
||||
|
||||
loadCfgFileSection(params, tree, "repositories", ("FlightGear", "FGData"))
|
||||
loadCfgFileSection(params, tree, "executables", ("fgrcc",))
|
||||
|
||||
|
||||
def processCommandLine(params):
|
||||
parser = argparse.ArgumentParser(
|
||||
usage="""\
|
||||
%(prog)s [OPTION ...]
|
||||
Rebuild FGData embedded resources for FlightGear.""",
|
||||
description="""\
|
||||
|
||||
Use fgrcc with FGData-resources.xml and the corresponding files in FGData to
|
||||
(re)create the FGData-resources.[ch]xx files used in the FlightGear build. The
|
||||
existing files in the FlightGear repository are always overwritten
|
||||
(FGData-resources.[ch]xx in <FlightGear-repo>/src/EmbeddedResources).
|
||||
|
||||
This is a dumb script that simply calls fgrcc with appropriate parameters. In
|
||||
order to save some typing, you may want to use a configuration file like this
|
||||
(~/.fgmeta/%(prog)s.json):
|
||||
|
||||
{"repositories": {"FlightGear": "~/flightgear/src/flightgear",
|
||||
"FGData": "~/flightgear/src/fgdata"},
|
||||
"executables": {"fgrcc":
|
||||
"~/flightgear/src/build-fg/src/EmbeddedResources/fgrcc"
|
||||
}
|
||||
}""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
# I want --help but not -h (it might be useful for something else)
|
||||
add_help=False)
|
||||
|
||||
parser.add_argument('--flightgear', action='store', help="""\
|
||||
Path to the FlightGear repository""")
|
||||
parser.add_argument('--fgdata', action='store', help="""\
|
||||
Path to the FGData repository""")
|
||||
parser.add_argument('--fgrcc', action='store', help="""\
|
||||
Path to the fgrcc executable""")
|
||||
parser.add_argument('--log-level', action='store',
|
||||
choices=("debug", "info", "warning", "error",
|
||||
"critical"),
|
||||
default=None, help="""Set the log level""")
|
||||
parser.add_argument('--help', action="help",
|
||||
help="display this message and exit")
|
||||
|
||||
parser.parse_args(namespace=params)
|
||||
|
||||
# Don't use the 'default' argparse mechanism for this, in order to allow
|
||||
# the config file to set the log level in a meaningful way if we want (not
|
||||
# implemented at the time of this writing).
|
||||
if params.log_level is not None:
|
||||
logger.setLevel(getattr(sys.modules["logging"],
|
||||
params.log_level.upper()))
|
||||
|
||||
|
||||
def main():
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
setupLogging(level=logging.INFO) # may be overridden by options
|
||||
|
||||
params = argparse.Namespace()
|
||||
loadConfigFile(params) # could set the log level
|
||||
processCommandLine(params)
|
||||
|
||||
if (params.flightgear is None or params.fgdata is None or
|
||||
params.fgrcc is None):
|
||||
logger.error(
|
||||
"--flightgear, --fgdata and --fgrcc must all be specified (they "
|
||||
"may be set in the config file; use --help for more info)")
|
||||
sys.exit(1)
|
||||
|
||||
resDir = os.path.join(params.flightgear, "src", "EmbeddedResources")
|
||||
inputXMLFile = os.path.join(resDir, "FGData-resources.xml")
|
||||
cxxFile = os.path.join(resDir, "FGData-resources.cxx")
|
||||
hxxFile = os.path.join(resDir, "FGData-resources.hxx")
|
||||
args = [params.fgrcc,
|
||||
"--root={}".format(params.fgdata),
|
||||
"--output-cpp-file={}".format(cxxFile),
|
||||
"--init-func-name=initFGDataEmbeddedResources",
|
||||
"--output-header-file={}".format(hxxFile),
|
||||
"--output-header-identifier=_FG_FGDATA_EMBEDDED_RESOURCES",
|
||||
inputXMLFile]
|
||||
|
||||
# encoding="utf-8" requires Python >= 3.6 -> will add it later
|
||||
# (it's not really needed, as we don't process the output)
|
||||
subprocess.run(args, check=True)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
@@ -12,7 +12,7 @@
|
||||
#GNU General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU General Public License
|
||||
#along with FlightGear If not, see <http://www.gnu.org/licenses/>.
|
||||
#along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if [ -z "$1" -o -z "$2" ]; then
|
||||
echo "usage: thismajor.thisminor nextmajor.nextminor path"
|
||||
@@ -65,6 +65,8 @@ createBranch() {
|
||||
while [ $# -gt 0 ]; do
|
||||
echo "Processing $1"
|
||||
pushd $1 > /dev/null
|
||||
git config user.name "Automatic Release Builder"
|
||||
git config user.email "build@flightgear.org"
|
||||
createBranch
|
||||
popd > /dev/null
|
||||
shift
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
THIS_RELEASE="2017.3"
|
||||
NEXT_RELEASE="2017.4"
|
||||
SUBMODULES="simgear flightgear fgdata getstart"
|
||||
THIS_RELEASE="2017.1"
|
||||
NEXT_RELEASE="2017.2"
|
||||
SUBMODULES="simgear flightgear fgdata"
|
||||
|
||||
#:<< 'COMMENT_END'
|
||||
git checkout next
|
||||
@@ -28,7 +28,7 @@ read something
|
||||
for f in $SUBMODULES .; do
|
||||
pushd "$f"
|
||||
echo "Pushing $f"
|
||||
git checkout release/${THIS_RELEASE} && git push origin release/${THIS_RELEASE} && git push origin version/${THIS_RELEASE}.1 && git push origin version/${NEXT_RELEASE}.0 && git checkout next && git push
|
||||
git checkout release/${THIS_RELEASE} && git push origin release/${THIS_RELEASE} && git push origin version/${THIS_RELEASE}.1 && git push origin version/${NEXT_RELEASE}.0 && git checkout next && git push
|
||||
popd
|
||||
done
|
||||
|
||||
@@ -42,3 +42,4 @@ done
|
||||
svn copy svn+ssh://svn.code.sf.net/p/flightgear/fgaddon/trunk \
|
||||
svn+ssh://svn.code.sf.net/p/flightgear/fgaddon/branches/release-${THIS_RELEASE} \
|
||||
-m "branching for release ${THIS_RELEASE}"
|
||||
|
||||
|
||||
1
release_builder/version
Normal file
1
release_builder/version
Normal file
@@ -0,0 +1 @@
|
||||
2017.2.1
|
||||
2
simgear
2
simgear
Submodule simgear updated: 629e68428f...9840302931
Reference in New Issue
Block a user