Back to index

system-config-printer  1.3.9+20120706
ppds.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## system-config-printer
00004 
00005 ## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
00006 ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
00007 ## Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
00008 
00009 ## This program is free software; you can redistribute it and/or modify
00010 ## it under the terms of the GNU General Public License as published by
00011 ## the Free Software Foundation; either version 2 of the License, or
00012 ## (at your option) any later version.
00013 
00014 ## This program is distributed in the hope that it will be useful,
00015 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017 ## GNU General Public License for more details.
00018 
00019 ## You should have received a copy of the GNU General Public License
00020 ## along with this program; if not, write to the Free Software
00021 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00022 
00023 import cups
00024 from .cupshelpers import parseDeviceID
00025 import xmldriverprefs
00026 import itertools
00027 import string
00028 import time
00029 import locale
00030 import os.path
00031 import re
00032 from . import _debugprint, set_debugprint_fn
00033 
00034 __all__ = ['ppdMakeModelSplit',
00035            'PPDs']
00036 
00037 _MFR_BY_RANGE = [
00038     # Fill in missing manufacturer names based on model name
00039     ("HP", re.compile("deskjet"
00040                       "|dj[ 0-9]?"
00041                       "|laserjet"
00042                       "|lj"
00043                       "|color laserjet"
00044                       "|color lj"
00045                       "|designjet"
00046                       "|officejet"
00047                       "|oj"
00048                       "|photosmart"
00049                       "|ps "
00050                       "|psc"
00051                       "|edgeline")),
00052     ("Epson", re.compile("stylus|aculaser")),
00053     ("Apple", re.compile("stylewriter"
00054                          "|imagewriter"
00055                          "|deskwriter"
00056                          "|laserwriter")),
00057     ("Canon", re.compile("pixus"
00058                          "|pixma"
00059                          "|selphy"
00060                          "|imagerunner"
00061                          "|bj"
00062                          "|lbp")),
00063     ("Brother", re.compile("hl|dcp|mfc")),
00064     ("Xerox", re.compile("docuprint"
00065                          "|docupage"
00066                          "|phaser"
00067                          "|workcentre"
00068                          "|homecentre")),
00069     ("Lexmark", re.compile("optra|(:color )?jetprinter")),
00070     ("KONICA MINOLTA", re.compile("magicolor"
00071                                   "|pageworks"
00072                                   "|pagepro")),
00073     ("Kyocera Mita", re.compile("fs-"
00074                                 "|km-"
00075                                 "|taskalfa")),
00076     ("Ricoh", re.compile("aficio")),
00077     ("Oce", re.compile("varioprint")),
00078     ("Oki", re.compile("okipage|microline"))
00079     ]
00080 
00081 _MFR_NAMES_BY_LOWER = {}
00082 for mfr, regexp in _MFR_BY_RANGE:
00083     _MFR_NAMES_BY_LOWER[mfr.lower ()] = mfr
00084 
00085 _HP_MODEL_BY_NAME = {
00086     "dj": "DeskJet",
00087     "lj": "LaserJet",
00088     "oj": "OfficeJet",
00089     "color lj": "Color LaserJet",
00090     "ps ": "PhotoSmart",
00091     "hp ": ""
00092 }
00093 
00094 _RE_turboprint = re.compile ("turboprint")
00095 _RE_version_numbers = re.compile (" v\d(?:\d*\.\d+)?(?: |$)")
00096 _RE_ignore_suffix = re.compile (","
00097                                 "| hpijs"
00098                                 "| foomatic/"
00099                                 "| - "
00100                                 "| w/"
00101                                 "| \("
00102                                 "| postscript"
00103                                 "| ps"
00104                                 "| pxl"
00105                                 "| zjs"         # hpcups
00106                                 "| zxs"         # hpcups
00107                                 "| pcl3"        # hpcups
00108                                 "| printer"     # hpcups
00109                                 "|_bt"
00110                                 "| pcl"         # Canon CQue
00111                                 "| ufr ii"      # Canon UFR II
00112                                 "| br-script"  # Brother PPDs
00113                                 )
00114 _RE_ignore_series = re.compile (" series| all-in-one", re.I)
00115 
00116 def ppdMakeModelSplit (ppd_make_and_model):
00117     """
00118     Split a ppd-make-and-model string into a canonical make and model pair.
00119 
00120     @type ppd_make_and_model: string
00121     @param ppd_make_and_model: IPP ppd-make-and-model attribute
00122     @return: a string pair representing the make and the model
00123     """
00124 
00125     # If the string starts with a known model name (like "LaserJet") assume
00126     # that the manufacturer name is missing and add the manufacturer name
00127     # corresponding to the model name
00128     ppd_make_and_model.strip ()
00129     make = None
00130     cleanup_make = False
00131     l = ppd_make_and_model.lower ()
00132     for mfr, regexp in _MFR_BY_RANGE:
00133         if regexp.match (l):
00134             make = mfr
00135             model = ppd_make_and_model
00136             break
00137 
00138     # Handle PPDs provided by Turboprint
00139     if make == None and _RE_turboprint.search (l):
00140         t = ppd_make_and_model.find (" TurboPrint")
00141         if t != -1:
00142             t2 = ppd_make_and_model.rfind (" TurboPrint")
00143             if t != t2:
00144                 ppd_make_and_model = ppd_make_and_model[t + 12:t2]
00145             else:
00146                 ppd_make_and_model = ppd_make_and_model[:t]
00147         try:
00148             make, model = ppd_make_and_model.split("_", 1)
00149         except:
00150             make = ppd_make_and_model
00151             model = ''
00152         make = re.sub (r"(?<=[a-z])(?=[0-9])", " ", make)
00153         make = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", make)
00154         model = re.sub (r"(?<=[a-z])(?=[0-9])", " ", model)
00155         model = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", model)
00156         model = re.sub (r" Jet", "Jet", model)
00157         model = re.sub (r"Photo Smart", "PhotoSmart", model)
00158         cleanup_make = True
00159 
00160     # Special handling for two-word manufacturers
00161     elif l.startswith ("konica minolta "):
00162         make = "KONICA MINOLTA"
00163         model = ppd_make_and_model[15:]
00164     elif l.startswith ("lexmark international "):
00165         make = "Lexmark"
00166         model = ppd_make_and_model[22:]
00167     elif l.startswith ("kyocera mita "):
00168         make = "Kyocera Mita"
00169         model = ppd_make_and_model[13:]
00170     elif l.startswith ("kyocera "):
00171         make = "Kyocera Mita"
00172         model = ppd_make_and_model[8:]
00173 
00174     # Finally, take the first word as the name of the manufacturer.
00175     else:
00176         cleanup_make = True
00177         try:
00178             make, model = ppd_make_and_model.split(" ", 1)
00179         except:
00180             make = ppd_make_and_model
00181             model = ''
00182 
00183     # Standardised names for manufacturers.
00184     makel = make.lower ()
00185     if cleanup_make:
00186         if (makel.startswith ("hewlett") and
00187             makel.endswith ("packard")):
00188             make = "HP"
00189             makel = "hp"
00190         elif (makel.startswith ("konica") and
00191               makel.endswith ("minolta")):
00192             make = "KONICA MINOLTA"
00193             makel = "konica minolta"
00194         else:
00195             # Fix case errors.
00196             mfr = _MFR_NAMES_BY_LOWER.get (makel)
00197             if mfr:
00198                 make = mfr
00199 
00200     # HP PostScript PPDs give NickNames like:
00201     # *NickName: "HP LaserJet 4 Plus v2013.111 Postscript (recommended)"
00202     # Find the version number and truncate at that point.  But beware,
00203     # other model names can legitimately look like version numbers,
00204     # e.g. Epson PX V500.
00205     # Truncate only if the version number has only one digit, or a dot
00206     # with digits before and after.
00207     modell = model.lower ()
00208     v = modell.find (" v")
00209     if v != -1:
00210         # Look for " v" followed by a digit, optionally followed by more
00211         # digits, a dot, and more digits; and terminated by a space of the
00212         # end of the line.
00213         vmatch = _RE_version_numbers.search (modell)
00214         if vmatch:
00215             # Found it -- truncate at that point.
00216             vstart = vmatch.start ()
00217             modell = modell[:vstart]
00218             model = model[:vstart]
00219 
00220     suffix = _RE_ignore_suffix.search (modell)
00221     if suffix:
00222         suffixstart = suffix.start ()
00223         modell = modell[:suffixstart]
00224         model = model[:suffixstart]
00225 
00226     # Remove the word "Series" if present.  Some models are referred
00227     # to as e.g. HP OfficeJet Series 300 (from hpcups, and in the
00228     # Device IDs of such models), and other groups of models are
00229     # referred to in drivers as e.g. Epson Stylus Color Series (CUPS).
00230     (model, n) = _RE_ignore_series.subn ("", model, count=1)
00231     if n:
00232         modell = model.lower ()
00233 
00234     if makel == "hp":
00235         for (name, fullname) in _HP_MODEL_BY_NAME.iteritems ():
00236             if modell.startswith (name):
00237                 model = fullname + model[len (name):]
00238                 modell = model.lower ()
00239                 break
00240 
00241     model = model.strip ()
00242     return (make, model)
00243 
00244 def normalize (strin):
00245     # This function normalizes manufacturer and model names for comparing.
00246     # The string is turned to lower case and leading and trailing white
00247     # space is removed. After that each sequence of non-alphanumeric
00248     # characters (including white space) is replaced by a single space and
00249     # also at each change between letters and numbers a single space is added.
00250     # This makes the comparison only done by alphanumeric characters and the
00251     # words formed from them. So mostly two strings which sound the same when
00252     # you pronounce them are considered equal. Printer manufacturers do not
00253     # market two models whose names sound the same but differ only by
00254     # upper/lower case, spaces, dashes, ..., but in printer drivers names can
00255     # be easily supplied with these details of the name written in the wrong
00256     # way, especially if the IEEE-1284 device ID of the printer is not known.
00257     # This way we get a very reliable matching of printer model names.
00258     # Examples:
00259     # - Epson PM-A820 -> epson pm a 820
00260     # - Epson PM A820 -> epson pm a 820
00261     # - HP PhotoSmart C 8100 -> hp photosmart c 8100
00262     # - hp Photosmart C8100  -> hp photosmart c 8100
00263     lstrin = strin.strip ().lower ()
00264     normalized = ""
00265 
00266     BLANK=0
00267     ALPHA=1
00268     DIGIT=2
00269     lastchar = BLANK
00270 
00271     alnumfound = False
00272     for i in range (len (lstrin)):
00273         if lstrin[i].isalpha ():
00274             if lastchar != ALPHA and alnumfound:
00275                 normalized += " ";
00276             lastchar = ALPHA
00277         elif lstrin[i].isdigit ():
00278             if lastchar != DIGIT and alnumfound:
00279                 normalized += " ";
00280             lastchar = DIGIT
00281         else:
00282             lastchar = BLANK
00283 
00284         if lstrin[i].isalnum ():
00285             normalized += lstrin[i]
00286             alnumfound = True
00287 
00288     return normalized
00289 
00290 def _singleton (x):
00291     """If we don't know whether getPPDs() or getPPDs2() was used, this
00292     function can unwrap an item from a list in either case."""
00293     if isinstance (x, list):
00294         return x[0]
00295     return x
00296 
00297 class PPDs:
00298     """
00299     This class is for handling the list of PPDs returned by CUPS.  It
00300     indexes by PPD name and device ID, filters by natural language so
00301     that foreign-language PPDs are not included, and sorts by driver
00302     type.  If an exactly-matching PPD is not available, it can
00303     substitute with a PPD for a similar model or for a generic driver.
00304     """
00305 
00306     # Status of match.
00307     STATUS_SUCCESS = 0
00308     STATUS_MODEL_MISMATCH = 1
00309     STATUS_GENERIC_DRIVER = 2
00310     STATUS_NO_DRIVER = 3
00311 
00312     FIT_EXACT_CMD = xmldriverprefs.DriverType.FIT_EXACT_CMD
00313     FIT_EXACT = xmldriverprefs.DriverType.FIT_EXACT
00314     FIT_CLOSE = xmldriverprefs.DriverType.FIT_CLOSE
00315     FIT_GENERIC = xmldriverprefs.DriverType.FIT_GENERIC
00316     FIT_NONE = xmldriverprefs.DriverType.FIT_NONE
00317 
00318     _fit_to_status = { FIT_EXACT_CMD: STATUS_SUCCESS,
00319                        FIT_EXACT: STATUS_SUCCESS,
00320                        FIT_CLOSE: STATUS_MODEL_MISMATCH,
00321                        FIT_GENERIC: STATUS_GENERIC_DRIVER,
00322                        FIT_NONE: STATUS_NO_DRIVER }
00323 
00324     def __init__ (self, ppds, language=None, xml_dir=None):
00325         """
00326         @type ppds: dict
00327         @param ppds: dict of PPDs as returned by cups.Connection.getPPDs()
00328         or cups.Connection.getPPDs2()
00329 
00330         @type language: string
00331        @param language: language name, as given by the first element
00332         of the pair returned by locale.getlocale()
00333         """
00334         self.ppds = ppds.copy ()
00335         self.makes = None
00336         self.ids = None
00337 
00338         self.drivertypes = xmldriverprefs.DriverTypes ()
00339         self.preforder = xmldriverprefs.PreferenceOrder ()
00340         if xml_dir == None:
00341             xml_dir = os.environ.get ("CUPSHELPERS_XMLDIR")
00342             if xml_dir == None:
00343                 import config
00344                 xml_dir = os.path.join (config.sysconfdir, "cupshelpers")
00345 
00346         try:
00347             xmlfile = os.path.join (xml_dir, "preferreddrivers.xml")
00348             (drivertypes, preferenceorder) = \
00349                 xmldriverprefs.PreferredDrivers (xmlfile)
00350             self.drivertypes.load (drivertypes)
00351             self.preforder.load (preferenceorder)
00352         except Exception, e:
00353             print "Error loading %s: %s" % (xmlfile, e)
00354             self.drivertypes = None
00355             self.preforder = None
00356 
00357         if (language == None or
00358             language == "C" or
00359             language == "POSIX"):
00360             language = "en_US"
00361 
00362         u = language.find ("_")
00363         if u != -1:
00364             short_language = language[:u]
00365         else:
00366             short_language = language
00367 
00368         to_remove = []
00369         for ppdname, ppddict in self.ppds.iteritems ():
00370             try:
00371                 natural_language = _singleton (ppddict['ppd-natural-language'])
00372             except KeyError:
00373                 continue
00374 
00375             if natural_language == "en":
00376                 # Some manufacturer's PPDs are only available in this
00377                 # language, so always let them though.
00378                 continue
00379 
00380             if natural_language == language:
00381                 continue
00382 
00383             if natural_language == short_language:
00384                 continue
00385 
00386             to_remove.append (ppdname)
00387 
00388         for ppdname in to_remove:
00389             del self.ppds[ppdname]
00390 
00391         # CUPS sets the 'raw' model's ppd-make-and-model to 'Raw Queue'
00392         # which unfortunately then appears as manufacturer Raw and
00393         # model Queue.  Use 'Generic' for this model.
00394         if self.ppds.has_key ('raw'):
00395             makemodel = _singleton (self.ppds['raw']['ppd-make-and-model'])
00396             if not makemodel.startswith ("Generic "):
00397                 self.ppds['raw']['ppd-make-and-model'] = "Generic " + makemodel
00398 
00399     def getMakes (self):
00400         """
00401        @returns: a list of strings representing makes, sorted according
00402         to the current locale
00403        """
00404         self._init_makes ()
00405         makes_list = self.makes.keys ()
00406         makes_list.sort (locale.strcoll)
00407         try:
00408             # "Generic" should be listed first.
00409             makes_list.remove ("Generic")
00410             makes_list.insert (0, "Generic")
00411         except ValueError:
00412             pass
00413         return makes_list
00414 
00415     def getModels (self, make):
00416         """
00417        @returns: a list of strings representing models, sorted using
00418        cups.modelSort()
00419        """
00420         self._init_makes ()
00421         try:
00422             models_list = self.makes[make].keys ()
00423         except KeyError:
00424             return []
00425         models_list.sort (key=lambda x: x.lower(), cmp=cups.modelSort)
00426         return models_list
00427 
00428     def getInfoFromModel (self, make, model):
00429         """
00430        Obtain a list of PPDs that are suitable for use with a
00431         particular printer model, given its make and model name.
00432 
00433        @returns: a dict, indexed by ppd-name, of dicts representing
00434         PPDs (as given by cups.Connection.getPPDs)
00435        """
00436         self._init_makes ()
00437         try:
00438             return self.makes[make][model]
00439         except KeyError:
00440             return {}
00441 
00442     def getInfoFromPPDName (self, ppdname):
00443         """
00444        @returns: a dict representing a PPD, as given by
00445        cups.Connection.getPPDs
00446        """
00447         return self.ppds[ppdname]
00448 
00449     def getStatusFromFit (self, fit):
00450         return self._fit_to_status.get (fit, xmldriverprefs.DriverType.FIT_NONE)
00451 
00452     def orderPPDNamesByPreference (self, ppdnamelist=[],
00453                                    downloadedfiles=[],
00454                                    make_and_model=None,
00455                                    devid=None, fit=None):
00456         """
00457 
00458        Sort a list of PPD names by preferred driver type.
00459 
00460        @param ppdnamelist: PPD names
00461        @type ppdnamelist: string list
00462         @param downloadedfiles: Filenames from packages downloaded
00463         @type downloadedfiles: string list
00464         @param make_and_model: device-make-and-model name
00465         @type make_and_model: string
00466         @param devid: Device ID dict
00467         @type devid: dict indexed by Device ID field name, of strings;
00468         except for CMD field which must be a string list
00469         @param fit: Driver fit string for each PPD name
00470         @type fit: dict of PPD name:fit
00471        @returns: string list
00472        """
00473         if fit == None:
00474             fit = {}
00475 
00476         if self.drivertypes and self.preforder:
00477             ppds = {}
00478             for ppdname in ppdnamelist:
00479                 ppds[ppdname] = self.ppds[ppdname]
00480 
00481             orderedtypes = self.preforder.get_ordered_types (self.drivertypes,
00482                                                              make_and_model,
00483                                                              devid)
00484             orderedppds = self.drivertypes.get_ordered_ppdnames (orderedtypes,
00485                                                                  ppds, fit)
00486             ppdnamelist = map (lambda (typ, name): name, orderedppds)
00487 
00488         # Special handling for files we've downloaded.  First collect
00489         # their basenames.
00490         downloadedfnames = set()
00491         for downloadedfile in downloadedfiles:
00492             (path, slash, fname) = downloadedfile.rpartition ("/")
00493             downloadedfnames.add (fname)
00494 
00495         if downloadedfnames:
00496             # Next compare the basenames of each ppdname
00497             downloadedppdnames = []
00498             for ppdname in ppdnamelist:
00499                 (path, slash, ppdfname) = ppdname.rpartition ("/")
00500                 if ppdfname in downloadedfnames:
00501                     downloadedppdnames.add (ppdname)
00502 
00503             # Finally, promote the matching ones to the head of the list.
00504             if downloadedppdnames:
00505                 for ppdname in ppdnamelist:
00506                     if ppdname not in downloadedppdnames:
00507                         downloadedppdnames.append (ppdname)
00508 
00509                 ppdnamelist = downloadedppdnames
00510 
00511         return ppdnamelist
00512 
00513     def getPPDNamesFromDeviceID (self, mfg, mdl, description="",
00514                                  commandsets=[], uri=None,
00515                                  make_and_model=None):
00516         """
00517        Obtain a best-effort PPD match for an IEEE 1284 Device ID.
00518 
00519        @param mfg: MFG or MANUFACTURER field
00520        @type mfg: string
00521        @param mdl: MDL or MODEL field
00522        @type mdl: string
00523        @param description: DES or DESCRIPTION field, optional
00524        @type description: string
00525        @param commandsets: CMD or COMMANDSET field, optional
00526        @type commandsets: string
00527        @param uri: device URI, optional (only needed for debugging)
00528        @type uri: string
00529         @param make_and_model: device-make-and-model string
00530         @type make_and_model: string
00531        @returns: a dict of fit (string) indexed by PPD name
00532        """
00533         _debugprint ("\n%s %s" % (mfg, mdl))
00534         orig_mfg = mfg
00535         orig_mdl = mdl
00536         self._init_ids ()
00537 
00538         # Start with an empty result list and build it up using
00539         # several search methods, in increasing order of fuzziness.
00540         fit = {}
00541 
00542         # First, try looking up the device using the manufacturer and
00543         # model fields from the Device ID exactly as they appear (but
00544         # case-insensitively).
00545         mfgl = mfg.lower ()
00546         mdll = mdl.lower ()
00547 
00548         id_matched = False
00549         try:
00550             for each in self.ids[mfgl][mdll]:
00551                 fit[each] = self.FIT_EXACT
00552             id_matched = True
00553         except KeyError:
00554             pass
00555 
00556         # The HP PPDs say "HP" not "Hewlett-Packard", so try that.
00557         if mfgl == "hewlett-packard":
00558             try:
00559                 for each in self.ids["hp"][mdll]:
00560                     fit[each] = self.FIT_EXACT
00561                 print ("**** Incorrect IEEE 1284 Device ID: %s" %
00562                        self.ids["hp"][mdll])
00563                 print "**** Actual ID is MFG:%s;MDL:%s;" % (mfg, mdl)
00564                 print "**** Please report a bug against the HPLIP component"
00565                 id_matched = True
00566             except KeyError:
00567                 pass
00568 
00569         # Now try looking up the device by ppd-make-and-model.
00570         _debugprint ("Trying make/model names")
00571         mdls = None
00572         self._init_makes ()
00573         make = None
00574         if mfgl == "":
00575             (mfg, mdl) = ppdMakeModelSplit (mdl)
00576             mfgl = normalize (mfg)
00577             mdll = normalize (mdl)
00578 
00579         _debugprint ("mfgl: %s" % mfgl)
00580         _debugprint ("mdll: %s" % mdll)
00581         mfgrepl = {"hewlett-packard": "hp",
00582                    "lexmark international": "lexmark",
00583                    "kyocera": "kyocera mita"}
00584         if self.lmakes.has_key (mfgl):
00585             # Found manufacturer.
00586             make = self.lmakes[mfgl]
00587         elif mfgrepl.has_key (mfgl):
00588             rmfg = mfgrepl[mfgl]
00589             if self.lmakes.has_key (rmfg):
00590                 mfg = rmfg
00591                 mfgl = mfg
00592                 # Found manufacturer (after mapping to canonical name)
00593                 _debugprint ("remapped mfgl: %s" % mfgl)
00594                 make = self.lmakes[mfgl]
00595 
00596         _debugprint ("make: %s" % make)
00597         if make != None:
00598             mdls = self.makes[make]
00599             mdlsl = self.lmodels[normalize(make)]
00600 
00601             # Remove manufacturer name from model field
00602             for prefix in [mfgl, 'hewlett-packard', 'hp']:
00603                 if mdll.startswith (prefix + ' '):
00604                     mdl = mdl[len (prefix) + 1:]
00605                     mdll = normalize (mdl)
00606                     _debugprint ("unprefixed mdll: %s" % mdll)
00607 
00608             if self.lmodels[mfgl].has_key (mdll):
00609                 model = mdlsl[mdll]
00610                 for each in mdls[model].keys ():
00611                     fit[each] = self.FIT_EXACT
00612                     _debugprint ("%s: %s" % (fit[each], each))
00613             else:
00614                 # Make use of the model name clean-up in the
00615                 # ppdMakeModelSplit () function
00616                 (mfg2, mdl2) = ppdMakeModelSplit (mfg + " " + mdl)
00617                 mdl2l = normalize (mdl2)
00618                 _debugprint ("re-split mdll: %s" % mdl2l)
00619                 if self.lmodels[mfgl].has_key (mdl2l):
00620                     model = mdlsl[mdl2l]
00621                     for each in mdls[model].keys ():
00622                         fit[each] = self.FIT_EXACT
00623                         _debugprint ("%s: %s" % (fit[each], each))
00624       
00625         if not fit and mdls:
00626             (s, ppds) = self._findBestMatchPPDs (mdls, mdl)
00627             if s != self.FIT_NONE:
00628                 for each in ppds:
00629                     fit[each] = s
00630                     _debugprint ("%s: %s" % (fit[each], each))
00631 
00632         if commandsets:
00633             if type (commandsets) != list:
00634                 commandsets = commandsets.split (',')
00635 
00636             _debugprint ("Checking CMD field")
00637             generic = self._getPPDNameFromCommandSet (commandsets)
00638             if generic:
00639                 for driver in generic:
00640                     fit[driver] = self.FIT_GENERIC
00641                     _debugprint ("%s: %s" % (fit[driver], driver))
00642 
00643         # What about the CMD field of the Device ID?  Some devices
00644         # have optional units for page description languages, such as
00645         # PostScript, and they will report different CMD strings
00646         # accordingly.
00647         #
00648         # By convention, if a PPD contains a Device ID with a CMD
00649         # field, that PPD can only be used whenever any of the
00650         # comma-separated words in the CMD field appear in the
00651         # device's ID.
00652         # (See Red Hat bug #630058).
00653         #
00654         # We'll do that check now, and any PPDs that fail
00655         # (e.g. PostScript PPD for non-PostScript printer) can be
00656         # eliminated from the list.
00657         #
00658         # The reason we don't do this check any earlier is that we
00659         # don't want to eliminate PPDs only to have the fuzzy matcher
00660         # add them back in afterwards.
00661         #
00662         # While doing this, any drivers that we can positively confirm
00663         # as using a command set understood by the printer will be
00664         # converted from FIT_EXACT to FIT_EXACT_CMD.
00665         if id_matched and len (commandsets) > 0:
00666             failed = set()
00667             exact_cmd = set()
00668             for ppdname in fit.keys ():
00669                 ppd_cmd_field = None
00670                 ppd = self.ppds[ppdname]
00671                 ppd_device_id = _singleton (ppd.get ('ppd-device-id'))
00672                 if ppd_device_id:
00673                     ppd_device_id_dict = parseDeviceID (ppd_device_id)
00674                     ppd_cmd_field = ppd_device_id_dict["CMD"]
00675 
00676                 if (not ppd_cmd_field and
00677                     # ppd-type is not reliable for driver-generated
00678                     # PPDs (see CUPS STR #3720).  Neither gutenprint
00679                     # nor foomatic specify ppd-type in their CUPS
00680                     # drivers.
00681                     ppdname.find (":") == -1):
00682                     # If this is a PostScript PPD we know which
00683                     # command set it will use.
00684                     ppd_type = _singleton (ppd.get ('ppd-type'))
00685                     if ppd_type == "postscript":
00686                         ppd_cmd_field = ["POSTSCRIPT"]
00687 
00688                 if not ppd_cmd_field:
00689                     # We can't be sure which command set this driver
00690                     # uses.
00691                     continue
00692 
00693                 usable = False
00694                 for pdl in ppd_cmd_field:
00695                     if pdl in commandsets:
00696                         usable = True
00697                         break
00698 
00699                 if usable:
00700                     exact_cmd.add (ppdname)
00701                 else:
00702                     failed.add (ppdname)
00703 
00704             # Assign the more specific fit "exact-cmd" to those that
00705             # positively matched the CMD field.
00706             for each in exact_cmd:
00707                 if fit[each] == self.FIT_EXACT:
00708                     fit[each] = self.FIT_EXACT_CMD
00709                     _debugprint (self.FIT_EXACT_CMD + ": %s" % each)
00710 
00711             _debugprint ("Removed %s due to CMD mis-match" % failed)
00712             for each in failed:
00713                 del fit[each]
00714 
00715         if not fit:
00716             fallbacks = ["textonly.ppd", "postscript.ppd"]
00717             found = False
00718             for fallback in fallbacks:
00719                 _debugprint ("'%s' fallback" % fallback)
00720                 fallbackgz = fallback + ".gz"
00721                 for ppdpath in self.ppds.keys ():
00722                     if (ppdpath.endswith (fallback) or
00723                         ppdpath.endswith (fallbackgz)):
00724                         fit[ppdpath] = self.FIT_NONE
00725                         found = True
00726                         break
00727 
00728                 if found:
00729                     break
00730 
00731                 _debugprint ("Fallback '%s' not available" % fallback)
00732 
00733             if not found:
00734                 _debugprint ("No fallback available; choosing any")
00735                 fit[self.ppds.keys ()[0]] = self.FIT_NONE
00736 
00737         if not id_matched:
00738             sanitised_uri = re.sub (pattern="//[^@]*@/?", repl="//",
00739                                     string=str (uri))
00740             try:
00741                 cmd = reduce (lambda x, y: x + ","+ y, commandsets)
00742             except TypeError:
00743                 cmd = ""
00744             id = "MFG:%s;MDL:%s;" % (orig_mfg, orig_mdl)
00745             if cmd:
00746                 id += "CMD:%s;" % cmd
00747             if description:
00748                 id += "DES:%s;" % description
00749 
00750             print "No ID match for device %s:" % sanitised_uri
00751             print id
00752 
00753         return fit
00754 
00755     def getPPDNameFromDeviceID (self, mfg, mdl, description="",
00756                                 commandsets=[], uri=None,
00757                                 downloadedfiles=[],
00758                                 make_and_model=None):
00759         """
00760        Obtain a best-effort PPD match for an IEEE 1284 Device ID.
00761        The status is one of:
00762 
00763          - L{STATUS_SUCCESS}: the match was successful, and an exact
00764             match was found
00765 
00766          - L{STATUS_MODEL_MISMATCH}: a similar match was found, but
00767             the model name does not exactly match
00768 
00769          - L{STATUS_GENERIC_DRIVER}: no match was found, but a
00770             generic driver is available that can drive this device
00771             according to its command set list
00772 
00773          - L{STATUS_NO_DRIVER}: no match was found at all, and the
00774             returned PPD name is a last resort
00775 
00776        @param mfg: MFG or MANUFACTURER field
00777        @type mfg: string
00778        @param mdl: MDL or MODEL field
00779        @type mdl: string
00780        @param description: DES or DESCRIPTION field, optional
00781        @type description: string
00782        @param commandsets: CMD or COMMANDSET field, optional
00783        @type commandsets: string
00784        @param uri: device URI, optional (only needed for debugging)
00785        @type uri: string
00786         @param downloadedfiles: filenames from downloaded packages
00787         @type downloadedfiles: string list
00788         @param make_and_model: device-make-and-model string
00789         @type make_and_model: string
00790        @returns: an integer,string pair of (status,ppd-name)
00791        """
00792 
00793         fit = self.getPPDNamesFromDeviceID (mfg, mdl, description,
00794                                             commandsets, uri,
00795                                             make_and_model)
00796 
00797         # We've got a set of PPDs, any of which will drive the device.
00798         # Now we have to choose the "best" one.  This is quite tricky
00799         # to decide, so let's sort them in order of preference and
00800         # take the first.
00801         devid = { "MFG": mfg, "MDL": mdl,
00802                   "DES": description,
00803                   "CMD": commandsets }
00804         ppdnamelist = self.orderPPDNamesByPreference (fit.keys (),
00805                                                       downloadedfiles,
00806                                                       make_and_model,
00807                                                       devid, fit)
00808         _debugprint ("Found PPDs: %s" % str (ppdnamelist))
00809 
00810         status = self.getStatusFromFit (fit[ppdnamelist[0]])
00811         print "Using %s (status: %d)" % (ppdnamelist[0], status)
00812         return (status, ppdnamelist[0])
00813 
00814     def _findBestMatchPPDs (self, mdls, mdl):
00815         """
00816         Find the best-matching PPDs based on the MDL Device ID.
00817         This function could be made a lot smarter.
00818         """
00819 
00820         _debugprint ("Trying best match")
00821         mdll = mdl.lower ()
00822         if mdll.endswith (" series"):
00823             # Strip " series" from the end of the MDL field.
00824             mdll = mdll[:-7]
00825             mdl = mdl[:-7]
00826         best_mdl = None
00827         best_matchlen = 0
00828         mdlnames = mdls.keys ()
00829 
00830         # Perform a case-insensitive model sort on the names.
00831         mdlnamesl = map (lambda x: (x, x.lower()), mdlnames)
00832         mdlnamesl.append ((mdl, mdll))
00833         mdlnamesl.sort (lambda x, y: cups.modelSort(x[1], y[1]))
00834         i = mdlnamesl.index ((mdl, mdll))
00835         candidates = [mdlnamesl[i - 1]]
00836         if i + 1 < len (mdlnamesl):
00837             candidates.append (mdlnamesl[i + 1])
00838             _debugprint (candidates[0][0] + " <= " + mdl + " <= " +
00839                         candidates[1][0])
00840         else:
00841             _debugprint (candidates[0][0] + " <= " + mdl)
00842 
00843         # Look at the models immediately before and after ours in the
00844         # sorted list, and pick the one with the longest initial match.
00845         for (candidate, candidatel) in candidates:
00846             prefix = os.path.commonprefix ([candidatel, mdll])
00847             if len (prefix) > best_matchlen:
00848                 best_mdl = mdls[candidate].keys ()
00849                 best_matchlen = len (prefix)
00850                 _debugprint ("%s: match length %d" % (candidate, best_matchlen))
00851 
00852         # Did we match more than half of the model name?
00853         if best_mdl and best_matchlen > (len (mdll) / 2):
00854             ppdnamelist = best_mdl
00855             if best_matchlen == len (mdll):
00856                 fit = self.FIT_EXACT
00857             else:
00858                 fit = self.FIT_CLOSE
00859         else:
00860             fit = self.FIT_NONE
00861             ppdnamelist = None
00862 
00863             # Last resort.  Find the "most important" word in the MDL
00864             # field and look for a match based solely on that.  If
00865             # there are digits, try lowering the number of
00866             # significant figures.
00867             mdlnames.sort (cups.modelSort)
00868             mdlitems = map (lambda x: (x.lower (), mdls[x]), mdlnames)
00869             modelid = None
00870             for word in mdll.split (' '):
00871                 if modelid == None:
00872                     modelid = word
00873 
00874                 have_digits = False
00875                 for i in range (len (word)):
00876                     if word[i].isdigit ():
00877                         have_digits = True
00878                         break
00879 
00880                 if have_digits:
00881                     modelid = word
00882                     break
00883 
00884             digits = 0
00885             digits_start = -1
00886             digits_end = -1
00887             for i in range (len (modelid)):
00888                 if modelid[i].isdigit ():
00889                     if digits_start == -1:
00890                         digits_start = i
00891                     digits_end = i
00892                     digits += 1
00893                 elif digits_start != -1:
00894                     break
00895             digits_end += 1
00896             modelnumber = 0
00897             if digits > 0:
00898                 modelnumber = int (modelid[digits_start:digits_end])
00899                 modelpattern = (modelid[:digits_start] + "%d" +
00900                                 modelid[digits_end:])
00901                 _debugprint ("Searching for model ID '%s', '%s' %% %d" %
00902                              (modelid, modelpattern, modelnumber))
00903                 ignore_digits = 0
00904                 best_mdl = None
00905                 found = False
00906                 while ignore_digits < digits:
00907                     div = pow (10, ignore_digits)
00908                     modelid = modelpattern % ((modelnumber / div) * div)
00909                     _debugprint ("Ignoring %d of %d digits, trying %s" %
00910                                  (ignore_digits, digits, modelid))
00911 
00912                     for (name, ppds) in mdlitems:
00913                         for word in name.split (' '):
00914                             if word.lower () == modelid:
00915                                 found = True
00916                                 break
00917 
00918                         if found:
00919                             best_mdl = ppds.keys ()
00920                             break
00921 
00922                     if found:
00923                         break
00924 
00925                     ignore_digits += 1
00926                     if digits < 2:
00927                         break
00928 
00929                 if found:
00930                     ppdnamelist = best_mdl
00931                     fit = self.FIT_CLOSE
00932 
00933         return (fit, ppdnamelist)
00934 
00935     def _getPPDNameFromCommandSet (self, commandsets=[]):
00936         """Return ppd-name list or None, given a list of strings representing
00937         the command sets supported."""
00938         try:
00939             self._init_makes ()
00940             models = self.makes["Generic"]
00941         except KeyError:
00942             return None
00943 
00944         def get (*candidates):
00945             for model in candidates:
00946                 (s, ppds) = self._findBestMatchPPDs (models, model)
00947                 if s == self.FIT_EXACT:
00948                     return ppds
00949             return None
00950 
00951         cmdsets = map (lambda x: x.lower (), commandsets)
00952         if (("postscript" in cmdsets) or ("postscript2" in cmdsets) or
00953             ("postscript level 2 emulation" in cmdsets)):
00954             return get ("PostScript")
00955         elif (("pclxl" in cmdsets) or ("pcl-xl" in cmdsets) or
00956               ("pcl6" in cmdsets) or ("pcl 6 emulation" in cmdsets)):
00957             return get ("PCL 6/PCL XL", "PCL Laser")
00958         elif "pcl5e" in cmdsets:
00959             return get ("PCL 5e", "PCL Laser")
00960         elif "pcl5c" in cmdsets:
00961             return get ("PCL 5c", "PCL Laser")
00962         elif ("pcl5" in cmdsets) or ("pcl 5 emulation" in cmdsets):
00963             return get ("PCL 5", "PCL Laser")
00964         elif "pcl" in cmdsets:
00965             return get ("PCL 3", "PCL Laser")
00966         elif (("escpl2" in cmdsets) or ("esc/p2" in cmdsets) or
00967               ("escp2e" in cmdsets)):
00968             return get ("ESC/P Dot Matrix")
00969         return None
00970 
00971     def _init_makes (self):
00972         if self.makes:
00973             return
00974 
00975         tstart = time.time ()
00976         makes = {}
00977         lmakes = {}
00978         lmodels = {}
00979         aliases = {} # Generic model name: set(specific model names)
00980         for ppdname, ppddict in self.ppds.iteritems ():
00981             # One entry for ppd-make-and-model
00982             ppd_make_and_model = _singleton (ppddict['ppd-make-and-model'])
00983             ppd_mm_split = ppdMakeModelSplit (ppd_make_and_model)
00984             ppd_makes_and_models = set([ppd_mm_split])
00985 
00986             # The ppd-product IPP attribute contains values from each
00987             # Product PPD attribute as well as the value from the
00988             # ModelName attribute if present.  The Product attribute
00989             # values are surrounded by parentheses; the ModelName
00990             # attribute value is not.
00991 
00992             # Add another entry for each ppd-product that came from a
00993             # Product attribute in the PPD file.
00994             ppd_products = ppddict.get ('ppd-product', [])
00995             if not isinstance (ppd_products, list):
00996                 ppd_products = [ppd_products]
00997             ppd_products = set (filter (lambda x: x.startswith ("("),
00998                                         ppd_products))
00999             if ppd_products:
01000                 # If there is only one ppd-product value it is
01001                 # unlikely to be useful.
01002                 if len (ppd_products) == 1:
01003                     ppd_products = set()
01004 
01005                 make = _singleton (ppddict.get ('ppd-make', '')).rstrip ()
01006                 if make:
01007                     make += ' '
01008                 lmake = normalize (make)
01009                 for ppd_product in ppd_products:
01010                     # *Product: attribute is "(text)"
01011                     if (ppd_product.startswith ("(") and
01012                         ppd_product.endswith (")")):
01013                         ppd_product = ppd_product[1:len (ppd_product) - 1]
01014 
01015                     if not ppd_product:
01016                         continue
01017 
01018                     # If manufacturer name missing, take it from ppd-make
01019                     lprod = normalize (ppd_product)
01020                     if not lprod.startswith (lmake):
01021                         ppd_product = make + ppd_product
01022 
01023                     ppd_makes_and_models.add (ppdMakeModelSplit (ppd_product))
01024 
01025             # Add the entries to our dictionary
01026             for make, model in ppd_makes_and_models:
01027                 lmake = normalize (make)
01028                 lmodel = normalize (model)
01029                 if not lmakes.has_key (lmake):
01030                     lmakes[lmake] = make
01031                     lmodels[lmake] = {}
01032                     makes[make] = {}
01033                 else:
01034                     make = lmakes[lmake]
01035 
01036                 if not lmodels[lmake].has_key (lmodel):
01037                     lmodels[lmake][lmodel] = model
01038                     makes[make][model] = {}
01039                 else:
01040                     model = lmodels[lmake][lmodel]
01041 
01042                 makes[make][model][ppdname] = ppddict
01043 
01044             # Build list of model aliases
01045             if ppd_mm_split in ppd_makes_and_models:
01046                 ppd_makes_and_models.remove (ppd_mm_split)
01047 
01048             if ppd_makes_and_models:
01049                 (make, model) = ppd_mm_split
01050                 if aliases.has_key (make):
01051                     models = aliases[make].get (model, set())
01052                 else:
01053                     aliases[make] = {}
01054                     models = set()
01055 
01056                 models = models.union (map (lambda x: x[1],
01057                                             ppd_makes_and_models))
01058                 aliases[make][model] = models
01059 
01060         # Now, for each set of model aliases, add all drivers from the
01061         # "main" (generic) model name to each of the specific models.
01062         for make, models in aliases.iteritems ():
01063             lmake = normalize (make)
01064             main_make = lmakes[lmake]
01065             for model, modelnames in models.iteritems ():
01066                 main_model = lmodels[lmake].get (normalize (model))
01067                 if not main_model:
01068                     continue
01069 
01070                 main_ppds = makes[main_make][main_model]
01071 
01072                 for eachmodel in modelnames:
01073                     this_model = lmodels[lmake].get (normalize (eachmodel))
01074                     ppds = makes[main_make][this_model]
01075                     ppds.update (main_ppds)
01076 
01077         self.makes = makes
01078         self.lmakes = lmakes
01079         self.lmodels = lmodels
01080         _debugprint ("init_makes: %.3fs" % (time.time () - tstart))
01081 
01082     def _init_ids (self):
01083         if self.ids:
01084             return
01085 
01086         ids = {}
01087         for ppdname, ppddict in self.ppds.iteritems ():
01088             id = _singleton (ppddict.get ('ppd-device-id'))
01089             if not id:
01090                 continue
01091 
01092             id_dict = parseDeviceID (id)
01093             lmfg = id_dict['MFG'].lower ()
01094             lmdl = id_dict['MDL'].lower ()
01095 
01096             bad = False
01097             if len (lmfg) == 0:
01098                 bad = True
01099             if len (lmdl) == 0:
01100                 bad = True
01101             if bad:
01102                 continue
01103 
01104             if not ids.has_key (lmfg):
01105                 ids[lmfg] = {}
01106 
01107             if not ids[lmfg].has_key (lmdl):
01108                 ids[lmfg][lmdl] = []
01109 
01110             ids[lmfg][lmdl].append (ppdname)
01111 
01112         self.ids = ids
01113 
01114 def _show_help():
01115     print "usage: ppds.py [--deviceid] [--list-models] [--list-ids] [--debug]"
01116 
01117 def _self_test(argv):
01118     import sys, getopt
01119     try:
01120         opts, args = getopt.gnu_getopt (argv[1:], '',
01121                                         ['help',
01122                                          'deviceid',
01123                                          'list-models',
01124                                          'list-ids',
01125                                          'debug'])
01126     except getopt.GetoptError:
01127         _show_help()
01128         sys.exit (1)
01129 
01130     stdin_deviceid = False
01131     list_models = False
01132     list_ids = False
01133 
01134     for opt, optarg in opts:
01135         if opt == "--help":
01136             _show_help ()
01137             sys.exit (0)
01138         if opt == "--deviceid":
01139             stdin_deviceid = True
01140         elif opt == "--list-models":
01141             list_models = True
01142         elif opt == "--list-ids":
01143             list_ids = True
01144         elif opt == "--debug":
01145             def _dprint(x):
01146                 try:
01147                     print x
01148                 except:
01149                     pass
01150 
01151             set_debugprint_fn (_dprint)
01152 
01153     picklefile="pickled-ppds"
01154     import pickle
01155     try:
01156         f = open (picklefile, "r")
01157         cupsppds = pickle.load (f)
01158     except IOError:
01159         f = open (picklefile, "w")
01160         c = cups.Connection ()
01161         try:
01162             cupsppds = c.getPPDs2 ()
01163             print "Using getPPDs2()"
01164         except AttributeError:
01165             # Need pycups >= 1.9.52 for getPPDs2
01166             cupsppds = c.getPPDs ()
01167             print "Using getPPDs()"
01168 
01169         pickle.dump (cupsppds, f)
01170 
01171     xml_dir = os.environ.get ("top_srcdir")
01172     if xml_dir:
01173         xml_dir = os.path.join (xml_dir, "xml")
01174 
01175     ppds = PPDs (cupsppds, xml_dir=xml_dir)
01176     makes = ppds.getMakes ()
01177     models_count = 0
01178     for make in makes:
01179         models = ppds.getModels (make)
01180         models_count += len (models)
01181         if list_models:
01182             print make
01183             for model in models:
01184                 print "  " + model
01185     print "%d makes, %d models" % (len (makes), models_count)
01186     ppds.getPPDNameFromDeviceID ("HP", "PSC 2200 Series")
01187     makes = ppds.ids.keys ()
01188     models_count = 0
01189     for make in makes:
01190         models = ppds.ids[make]
01191         models_count += len (models)
01192         if list_ids:
01193             print make
01194             for model in models:
01195                 print "  %s (%d)" % (model, len (ppds.ids[make][model]))
01196                 for driver in ppds.ids[make][model]:
01197                     print "    " + driver
01198     print "%d ID makes, %d ID models" % (len (makes), models_count)
01199 
01200     print "\nID matching tests\n"
01201 
01202     MASK_STATUS = (1 << 2) - 1
01203     FLAG_INVERT = (1 << 2)
01204     FLAG_IGNORE_STATUS = (1 << 3)
01205     idlist = [
01206         # Format is:
01207         # (ID string, max status code (plus flags),
01208         #  expected ppd-make-and-model RE match)
01209 
01210         # Specific models
01211         ("MFG:EPSON;CMD:ESCPL2,BDC,D4,D4PX;MDL:Stylus D78;CLS:PRINTER;"
01212          "DES:EPSON Stylus D78;", 1, 'Epson Stylus D68'),
01213         ("MFG:Hewlett-Packard;MDL:LaserJet 1200 Series;"
01214          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 1200'),
01215         ("MFG:Hewlett-Packard;MDL:LaserJet 3390 Series;"
01216          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 3390'),
01217         ("MFG:Hewlett-Packard;MDL:PSC 2200 Series;CMD:MLC,PCL,PML,DW-PCL,DYN;"
01218          "CLS:PRINTER;1284.4DL:4d,4e,1;", 0, "HP PSC 22[01]0"),
01219         ("MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;"
01220          "DES:Hewlett-Packard DeskJet 990C;", 0, "HP DeskJet 990C"),
01221         ("CLASS:PRINTER;MODEL:HP LaserJet 6MP;MANUFACTURER:Hewlett-Packard;"
01222          "DESCRIPTION:Hewlett-Packard LaserJet 6MP Printer;"
01223          "COMMAND SET:PJL,MLC,PCLXL,PCL,POSTSCRIPT;", 0, "HP LaserJet 6MP"),
01224         # Canon PIXMA iP3000 (from gutenprint)
01225         ("MFG:Canon;CMD:BJL,BJRaster3,BSCCe;SOJ:TXT01;MDL:iP3000;CLS:PRINTER;"
01226          "DES:Canon iP3000;VER:1.09;STA:10;FSI:03;", 1, "Canon PIXMA iP3000"),
01227         ("MFG:HP;MDL:Deskjet 5400 series;CMD:MLC,PCL,PML,DW-PCL,DESKJET,DYN;"
01228          "1284.4DL:4d,4e,1;CLS:PRINTER;DES:5440;",
01229          1, "HP DeskJet (5440|5550)"), # foomatic-db-hpijs used to say 5440
01230         ("MFG:Hewlett-Packard;MDL:HP LaserJet 3390;"
01231          "CMD:PJL,MLC,PCL,POSTSCRIPT,PCLXL;",
01232          0, "HP LaserJet 3390"),
01233         # Ricoh printers should use PostScript versions of
01234         # manufacturer's PPDs (bug #550315 comment #8).
01235         ("MFG:RICOH;MDL:Aficio 3045;",
01236          0, "Ricoh Aficio 3045 PS"),
01237         # Don't mind which driver gets used here so long as it isn't
01238         # gutenprint (bug #645993).
01239         ("MFG:Brother;MDL:HL-2030;",
01240          0 | FLAG_INVERT | FLAG_IGNORE_STATUS, ".*Gutenprint"),
01241         # Make sure we get a colour driver for this one, see launchpad
01242         # #669152.
01243         ("MFG:Xerox;MDL:6250DP;",
01244          1, ".*(Postscript|pcl5e)"),
01245 
01246         # Generic models
01247         ("MFG:New;MDL:Unknown PS Printer;CMD:POSTSCRIPT;",
01248          2, "Generic postscript printer"),
01249         # Make sure pxlcolor is used for PCLXL.  The gutenprint driver
01250         # is black and white, and pxlcolor is the foomatic-recommended
01251         # generic driver for "Generic PCL 6/PCL XL Printer".
01252         ("MFG:New;MDL:Unknown PCL6 Printer;CMD:PCLXL;", 2,
01253          "Generic PCL 6.*pxlcolor"),
01254         ("MFG:New;MDL:Unknown PCL5e Printer;CMD:PCL5e;", 2, "Generic PCL 5e"),
01255         ("MFG:New;MDL:Unknown PCL5c Printer;CMD:PCL5c;", 2, "Generic PCL 5c"),
01256         ("MFG:New;MDL:Unknown PCL5 Printer;CMD:PCL5;", 2, "Generic PCL 5"),
01257         ("MFG:New;MDL:Unknown PCL3 Printer;CMD:PCL;", 2, "Generic PCL"),
01258         ("MFG:New;MDL:Unknown Printer;", 100, None),
01259         ]
01260 
01261     if stdin_deviceid:
01262         idlist = [(raw_input ('Device ID: '), 2, '')]
01263 
01264     all_passed = True
01265     for id, max_status_code, modelre in idlist:
01266         flags = max_status_code & ~MASK_STATUS
01267         max_status_code &= MASK_STATUS
01268         id_dict = parseDeviceID (id)
01269         (status, ppdname) = ppds.getPPDNameFromDeviceID (id_dict["MFG"],
01270                                                          id_dict["MDL"],
01271                                                          id_dict["DES"],
01272                                                          id_dict["CMD"])
01273         ppddict = ppds.getInfoFromPPDName (ppdname)
01274         if flags & FLAG_IGNORE_STATUS:
01275             status = max_status_code
01276 
01277         if status < max_status_code:
01278             success = True
01279         else:
01280             if status == max_status_code:
01281                 match = re.match (modelre,
01282                                   _singleton (ppddict['ppd-make-and-model']),
01283                                   re.I)
01284                 success = match != None
01285             else:
01286                 success = False
01287 
01288 
01289         if flags & FLAG_INVERT:
01290             success = not success
01291 
01292         if success:
01293             result = "PASS"
01294         else:
01295             result = "*** FAIL ***"
01296 
01297         print "%s: %s %s (%s)" % (result, id_dict["MFG"], id_dict["MDL"],
01298                                   _singleton (ppddict['ppd-make-and-model']))
01299         all_passed = all_passed and success
01300 
01301     if not all_passed:
01302         raise RuntimeError