blob: b4becf2ef5ddc9c977c94203f116096a9e0dd76b [file] [log] [blame]
#!/usr/bin/env python2
import os
import sys
import glob
import yaml
import argparse
import codecs
import datetime
import hashlib
from fcntl import lockf, LOCK_EX, LOCK_NB
from lxml.etree import Element, SubElement, Comment, tostring, CDATA
if __name__ == '__main__':
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('-f', dest='filepattern', action='store', required=True,
default='users/*.yaml',
help='Pattern of YAML files to process')
parser.add_argument('-r', dest='privround', action='store_true',
default=False,
help='Round coordinates to two decimal points for privacy')
parser.add_argument('-o', dest='outfile', action='store', required=True,
default='www/developers.kml',
help='Where to output the resulting KML file')
parser.add_argument('-d', dest='dryrun', action='store_true',
help='Do not write anything, just do a dry run')
args = parser.parse_args()
el_kml = Element('kml')
el_kml.append(Comment('Generated on %s' % datetime.datetime.today().strftime('%F %T')))
el_doc = SubElement(el_kml, 'Document')
el_fol = SubElement(el_doc, 'Folder')
# Lock before scanning the directory, unlock on exit
lockfile = os.path.join(os.path.dirname(args.outfile),
'.%s.lock' % os.path.basename(args.outfile))
lockh = open(lockfile, 'w')
try:
lockf(lockh, LOCK_EX | LOCK_NB)
except IOError:
print('Could not create exclusive lock, assuming another process is running.')
sys.exit(0)
for filename in glob.glob(args.filepattern):
with open(filename) as data_file:
try:
ydata = yaml.load(data_file)
except yaml.ParserError as ex:
print('Error parsing yaml in %s' % filename)
print(ex)
continue
if 'name' not in ydata.keys() or 'email' not in ydata.keys():
print('No name or email found in %s' % filename)
continue
if 'locations' not in ydata.keys() or len(ydata['locations']) < 1:
print('No locations found in %s' % filename)
continue
for place in ydata['locations'].keys():
location = ydata['locations'][place]
# Find coords and make sure they are sane
if 'latlong' not in location.keys():
print('No coordinates for location "%s" in file %s' % (place, filename))
continue
latlong = location['latlong'].split(',')
try:
if len(latlong) < 2:
raise ValueError('Missing comma')
# Bizarrely, KML wants longitude first
roundlevel = 6
if args.privround:
roundlevel = 2
lat = round(float(latlong[0].strip()), roundlevel)
lon = round(float(latlong[1].strip()), roundlevel)
coords = '%.6f,%.6f' % (lon, lat)
except ValueError:
print('Invalid coordinates for location "%s" in file %s' % (place, filename))
continue
el_placemark = SubElement(el_fol, 'Placemark')
el_name = SubElement(el_placemark, 'name')
el_name.text = ydata['name']
el_point = SubElement(el_placemark, 'Point')
el_coordinates = SubElement(el_point, 'coordinates')
el_coordinates.text = coords
libavhash = hashlib.md5(ydata['email'].strip().lower()).hexdigest()
desc = '''
<div style="min-width: 250pt">
<img style="float:left;margin:0 3pt 5pt 0;border:1px solid black;width:64;height:64"
src="https://seccdn.libravatar.org/avatar/%s?s=64&amp;d=retro"/>
<p><b>Contact:</b>&nbsp;<a href="mailto:%s">%s</a>
''' % (libavhash, ydata['email'], ydata['email'])
if 'key' in ydata.keys():
desc += '''<br/>
<b>Key stats:</b>&nbsp;<a href="http://pgp.cs.uu.nl/stats/%s.html">%s</a>
''' % (ydata['key'], ydata['key'])
desc += '</p><p style="clear:both">'
if 'description' in location.keys():
desc += location['description'].strip()
desc += '</p></div>'
el_description = SubElement(el_placemark, 'description')
el_description.text = CDATA(desc)
kml_data = tostring(el_kml, pretty_print=True)
if args.dryrun:
print(kml_data)
sys.exit(0)
with codecs.open(args.outfile, 'w', 'utf-8') as tfile:
tfile.write(kml_data)