#!/usr/bin/env python """ Copyright (c) Abel Deuring 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ # generates an .fdi file for Freedesktop's HAL project from Sane *desc # files import os, os.path, sys, re, getopt import sgmllib # from http://quickies.seriot.ch/index.php?cat=2 class Stripper(sgmllib.SGMLParser): def __init__(self): sgmllib.SGMLParser.__init__(self) def strip(self, some_html): self.theString = "" self.feed(some_html) self.close() return self.theString def handle_data(self, data): self.theString += data whspace_re = re.compile(r'\s+') string_re = re.compile(r'\s*"(.*?)(?%s\n' % (' '*indent, tag, key, typ, val, tag)) def _fdi_devinfo(self, f): indent = 8 self.write_descr_tag(f, 'append', 'info.capabilities', 'strlist', self.device_type, indent) self.write_descr_tag(f, 'append', 'scanner.api', 'strlist', 'sane', indent) # isn't this redundant with HAL info.product ? self.write_descr_tag(f, 'append', 'scanner.sane.model', 'strlist', self.vendor+' '+self.model, indent) self.write_descr_tag(f, 'append', 'scanner.sane.backends', 'strlist', self.backend, indent) backendns = 'scanner.sane.backends.'+self.backend self.write_descr_tag(f, 'append', backendns+'.supportstatus', 'strlist', self.status, indent) if self.comment: self.write_descr_tag(f, 'append', backendns+'.comment', 'strlist', self.stripper.strip(self.comment), indent) def write_fdi(self, f, verbose): # check, if we have enough information; issue a warning # if not if not self.interfaces: if verbose: self.warn('no interface definitions') return supportstatus = getattr(self, 'status', None) if supportstatus == None: if verbose: self.warn('supportstatus not defined: no data written') return can_write = False usb_id = scsi_id = None for iface in self.interfaces: if iface == 'USB': usb_id = getattr(self, 'usb_id', None) can_write |= (usb_id != None) if usb_id == None and verbose: self.warn('USB ID missing') elif iface == 'SCSI': scsi_id = getattr(self, 'scsi_id', None) can_write |= (scsi_id != None) if scsi_id == None and verbose: self.warn('SCSI vendor/model ID missing') else: if verbose: self.warn('output for interface %s not yet possible' % iface) if can_write: f.write(' \n') if usb_id: f.write(' \n' ' \n' % usb_id) self._fdi_devinfo(f) f.write(' \n' ' \n') if scsi_id: f.write(' \n' ' \n' % scsi_id) self._fdi_devinfo(f) f.write(' \n' ' \n') f.write(' \n') def is_supported(self): return self.backend != 'unsupported' \ and getattr(self, 'supportstatus', None) != 'unsupported' class DescParserError(Exception): def __init__(self, value): self.value = value def __str__(self): return self.value class DescParser: def __init__(self, filename, output, verbose): self.filename = filename inp = open(filename) line = inp.readline() self.line_no = 1 backend = None backend_info = None backend_name = None backend_version = None device_type = None vendor = None manpage = None comment = None self.reset_model_info() while line: line = line.strip() # a line is empty, or starts with a ':keyword' or is a ';comment' if line and line[0] == ':': keyword, line = whspace_re.split(line, 1) if keyword == ':backend': backend_name, line = self.get_string(line) elif keyword == ':version': backend_version, line = self.get_string(line) elif keyword == ':devicetype': device_type, line = self.get_kw(line) if not device_type in (':scanner', ':stillcam', ':vidcam', ':meta', ':api'): raise DescParserError('file %s: Unexpected value for :devicetype in line %i: %s' % (self.filename, self.line_no, self.device_type)) device_type = device_type[1:] elif keyword == ':mfg': vendor, line = self.get_string(line) elif keyword == ':model': self.reset_model_info() model, line = self.get_string(line) backend = DeviceInfo(backend_name, backend_version, device_type, vendor, model, manpage, comment, filename, self.line_no) output.append(backend) elif keyword == ':manpage': manpage, line = self.get_string(line) elif keyword == ':comment': comment, line = self.get_string(line) elif keyword in (':url', ':desc', ':new'): # FIXME: the url "meaning" is context specific: # can mean the backend's home page, a vendor website etc # FIXME: comments might be somewhere useful # :desc is only used for non-hardware devices, # so it's safe to ignore # pass elif backend == None: raise DescParserError('file %s: unknown or backend specific keyword outside a backend definition in line %i: %s' % (self.filename, self.line_no, keyword)) elif keyword == ':status': status, line = self.get_kw(line) if not status in (':untested', ':minimal', ':basic', ':good', ':complete', ':unsupported'): raise DescParserError('file %s: Unexpected value for :status in line %i: %s' % (self.filename, self.line_no, self.status)) backend.status = status[1:] elif keyword == ':interface': interfaces, line = self.get_string(line) for test in known_interfaces: mo = test.search(interfaces) if mo: backend.interfaces.append(mo.groups()[0]) elif keyword == ':usbid': usb_vendor, line = self.get_string(line) if usb_vendor == 'ignore': # FIXME: How should this be handled? pass else: usb_product, line = self.get_string(line) try: int(usb_vendor, 16) int(usb_product, 16) backend.usb_id = (usb_vendor, usb_product) except ValueError: raise DescParserError('file %s: Invalid USB ID in line %i' % (self.filename, self.line_no)) elif keyword == ':scsiid': scsi_vendor, line = self.get_string(line) scsi_product, line = self.get_string(line) backend.scsi_id = (scsi_vendor, scsi_product) else: raise DescParserError('file %s: unexpected keyword %s in line %i' % (self.filename, keyword, self.line_no)) elif line and line[0] != ';': raise DescParserError("file %s: Syntax error in line %i" % (self.filename, self.line_no)) line = inp.readline() self.line_no += 1 inp.close() def reset_model_info(self): self.model = None self.status = None self.interfaces = [] self.usb_id = None def get_string(self, line): mo = string_re.match(line) if mo == None: raise DescParserError("file %s: Syntax error in line %i, string expected" % (self.filename, self.line_no)) return mo.groups() def get_kw(self, line): line = line.strip() if line[0] != ':': raise DescParserError("file %s: Syntax error in line %i, keyword expected" % (self.filename, self.line_no)) res = whspace_re.split(line, 1) if len(res) < 2: return res[0], '' return res def usage(): print """\ Generate a HAL fdi file from Sane's *.desc files. Works at present only for USB scanners and for "extended" desc files of SCSI scanners. usage: sanedesc2fdi.py [--OPTION]... [DESCDIR] where OPTION can be verbose prints lots of warnings and other info output= write output to . default: stdout with-unsupported include information about scanners that are not supported by Sane DESCDIR directory with *.desc files. default: pwd """ try: opts, args = getopt.getopt(sys.argv[1:], '', ('verbose', 'output=', 'with-unsupported', 'help')) except getopt.GetoptError, val: print str(val) usage() sys.exit(1) verbose = False with_unsupported = False out = sys.stdout for opt, val in opts: if opt == '--output': out = open(val, 'w') elif opt == '--verbose': verbose = True elif opt == '--with-unsupported': with_unsupported = True elif opt == '--help': usage() sys.exit(0) if len(args): dir = args[0] else: dir = '.' devlist = [] for filename in os.listdir(dir): if filename.endswith('.desc'): DescParser(os.path.join(dir, filename), devlist, verbose) out.write(""" """) for dev in devlist: # FIXME: should be possible to optionally write data # for webcam backends etc if dev.device_type == 'scanner' and (with_unsupported or dev.is_supported()): dev.write_fdi(out, verbose) out.write("\n") out.close()