diff --git a/circle.yml b/circle.yml index 3d6535ec..aa25460b 100644 --- a/circle.yml +++ b/circle.yml @@ -56,7 +56,7 @@ libratbag_references: fedora_install: &fedora_install run: name: Install prerequisites - command: dnf install -y tree git gcc gcc-c++ meson check-devel libudev-devel libevdev-devel doxygen graphviz valgrind python3-gobject python3-evdev glib2-devel + command: dnf install -y tree git gcc gcc-c++ meson check-devel libudev-devel libevdev-devel doxygen graphviz valgrind python3-gobject python3-evdev glib2-devel python3-lxml fedora_settings: &fedora_settings @@ -81,7 +81,7 @@ ubuntu_settings: &ubuntu_settings apt-get install -y software-properties-common add-apt-repository universe apt-get update - apt-get install -y tree git gcc g++ pkg-config meson check libudev-dev libevdev-dev libsystemd-dev doxygen graphviz valgrind python3-gi python3-evdev libglib2.0-dev: + apt-get install -y tree git gcc g++ pkg-config meson check libudev-dev libevdev-dev libsystemd-dev doxygen graphviz valgrind python3-gi python3-evdev libglib2.0-dev python3-lxml - checkout - *start_dbus - *build_and_test diff --git a/data/gnome/check-svg.py b/data/gnome/check-svg.py new file mode 100755 index 00000000..2da3812e --- /dev/null +++ b/data/gnome/check-svg.py @@ -0,0 +1,118 @@ +#!/usr/bin/python3 +# vim: set expandtab shiftwidth=4 tabstop=4: + +import sys +from lxml import etree +import logging + +ns = {'svg': 'http://www.w3.org/2000/svg'} +style_query = '//svg:rect[@id=\"{}\"][contains(@style, \"{}\")]' + +logger = None + +class SVGLogger(logging.Logger): + def __init__(self, name, level=logging.NOTSET): + self.success = True + return super().__init__(name, level) + + def error(self, msg, *args, **kwargs): + self.success = False + return super().error(msg, *args, **kwargs) + + @classmethod + def get_logger(cls, path): + logging.setLoggerClass(SVGLogger) + logging.basicConfig(level=logging.DEBUG) + return logging.getLogger(path) + + +def check_size(root): + width = float(root.attrib['width']) + height = float(root.attrib['height']) + if not 400 < width < 500: + logger.error("Width is outside of range: {}".format(width)) + if not 400 < height < 500: + logger.error("Height is outside of range: {}".format(height)) + + +def check_layers(root): + """ + Check there are layers (well, groups) for the components we require. + """ + layer_ids = [g.attrib['id'] for g in root.iterfind('svg:g', ns)] + + for layer in ["Device", "Buttons", "LEDs"]: + if layer not in layer_ids: + logger.error("Missing layer: {}".format(layer)) + + +def check_elements(root, prefix, required=0): + """ + Checks for elements of the form 'prefixN' in the root tag. Any elements + found must be consecutive or an error is printed, i.e. if there's a + 'button8' there has to be a 'button7'. + + If required is nonzero, an error is logged for any missing element with + an index less than required. + """ + + # elements can be paths and rects + # This includes leaders and lines + element_ids = [p.attrib['id'] for p in root.xpath('//svg:path', namespaces=ns) if p.attrib['id'].startswith(prefix)] + element_ids += [p.attrib['id'] for p in root.xpath('//svg:rect', namespaces=ns) if p.attrib['id'].startswith(prefix)] + element_ids += [g.attrib['id'] for g in root.xpath('//svg:g', namespaces=ns) if g.attrib['id'].startswith(prefix)] + + idx = 0 + highest = -1 + for idx in range(0, 20): + e = '{}{}'.format(prefix, idx) + if e in element_ids: + highest = idx + previous = '{}{}'.format(prefix, idx - 1) + if idx > 0 and previous not in element_ids: + logger.error("Non-consecutive {}: {}".format(prefix, e)) + + leader = '{}{}-leader'.format(prefix, idx) + if leader not in element_ids: + logger.error("Missing {} for {}".format(leader, e)) + else: + element = root.xpath(style_query.format(leader, 'text-align'), namespaces=ns) + if element is None or len(element) != 1 or element[0] is None: + logger.error("Missing style property for {}".format(leader)) + + path = '{}{}-path'.format(prefix, idx) + if path not in element_ids: + logger.error("Missing {} for {}".format(path, e)) + elif idx < required: + logger.error("Missing {}: {}".format(prefix, e)) + + logger.info("Found {} {}s".format(highest + 1, prefix)) + + +def check_leds(root): + check_elements(root, "led") + + +def check_buttons(root): + check_elements(root, "button", 3) + + +def check_svg(path): + svg = etree.parse(path) + root = svg.getroot() + + check_size(root) + check_layers(root) + check_buttons(root) + check_leds(root) + +if __name__ == "__main__": + success = True + for path in sys.argv[1:]: + logger = SVGLogger.get_logger(path) + check_svg(path) + if not logger.success: + success = False + + if not success: + sys.exit(1) diff --git a/meson.build b/meson.build index 4475a00f..ef19914d 100644 --- a/meson.build +++ b/meson.build @@ -306,7 +306,7 @@ default_svg_files = [ install_data(default_svg_files, install_dir : join_paths(get_option('datadir'), 'libratbag', 'default')) -gnome_svg_files = [ +gnome_svg_files = files( 'data/gnome/fallback.svg', 'data/gnome/logitech-g303.svg', 'data/gnome/logitech-g403.svg', @@ -316,10 +316,13 @@ gnome_svg_files = [ 'data/gnome/logitech-g700.svg', 'data/gnome/logitech-g900.svg', 'data/gnome/roccat-kone-xtd.svg', -] +) install_data(gnome_svg_files, install_dir : join_paths(get_option('datadir'), 'libratbag', 'gnome')) +test_svg_files = find_program('data/gnome/check-svg.py') +test('check-svg', test_svg_files, args : gnome_svg_files) + #### tests #### enable_tests = get_option('enable-tests') if enable_tests