1"""
2gpscap - GPS/AIS capability dictionary class.
3
4This file is Copyright (c) 2010 by the GPSD project
5SPDX-License-Identifier: BSD-2-clause
6"""
7# This code runs compatibly under Python 2 and 3.x for x >= 2.
8# Preserve this property!
9from __future__ import absolute_import, print_function, division
10
11try:
12    import configparser
13except ImportError:
14    import ConfigParser as configparser
15
16
17class GPSDictionary(configparser.RawConfigParser):
18    def __init__(self, *files):
19        "Initialize the capability dictionary"
20        configparser.RawConfigParser.__init__(self)
21        if not files:
22            files = ["gpscap.ini", "/usr/share/gpsd/gpscap.ini"]
23        try:
24            self.read(files, encoding='utf-8')
25        except TypeError:
26            self.read(files)  # For Python 2.6
27        # Resolve uses= members
28        while True:
29            keepgoing = False
30            for section in self.sections():
31                if self.has_option(section, "uses"):
32                    parent = self.get(section, "uses")
33                    if self.has_option(parent, "uses"):
34                        continue
35                    # Found a parent section without a uses = part.
36                    for heritable in self.options(parent):
37                        if not self.has_option(section, heritable):
38                            self.set(section,
39                                     heritable,
40                                     self.get(parent, heritable))
41                            keepgoing = True
42                    self.remove_option(section, "uses")
43            if not keepgoing:
44                break
45        # Sanity check: All items must have a type field.
46        for section in self.sections():
47            if not self.has_option(section, "type"):
48                raise configparser.Error("%s has no type" % section)
49
50            if (self.get(section, "type")
51                  not in ("engine", "vendor", "device")):
52                raise configparser.Error("%s has invalid type" % section)
53        # Sanity check: All devices must point at a vendor object.
54        # Side effect: build the lists of vendors and devices.
55        self.vendors = []
56        self.devices = []
57        for section in self.sections():
58            if self.get(section, "type") == "vendor":
59                self.vendors.append(section)
60            if self.get(section, "type") == "device":
61                self.devices.append(section)
62        self.vendors.sort()
63        for section in self.sections():
64            if self.get(section, "type") == "device":
65                if not self.has_option(section, "vendor"):
66                    raise configparser.Error("%s has no vendor" % section)
67                if self.get(section, "vendor") not in self.vendors:
68                    raise configparser.Error("%s has invalid vendor" % section)
69
70    def HTMLDump(self, ofp):
71        thead = """
72<table style='border:1px solid gray;font-size:small;background-color:#CCCCCC'>
73<caption>Listing %s devices from %s vendors</caption>
74<tr>
75<th>Name</th>
76<th>Packaging</th>
77<th>Engine</th>
78<th>Interface</th>
79<th>Tested with</th>
80<th>NMEA version</th>
81<th>PPS</th>
82<th style='width:50%%'>Notes</th>
83</tr>
84"""
85        vhead1 = "<tr><td style='text-align:center;' colspan='8'>" \
86                 "<a href='%s'>%s</a></td></tr>\n"
87        vhead2 = "<tr><td style='text-align:center;' colspan='8'>" \
88                 "<a href='%s'>%s</a><br><p>%s</p></td></tr>\n"
89        hotpluggables = ("pl2303", "CP2101")
90        ofp.write(thead % (len(self.devices), len(self.vendors)))
91        for vendor in self.vendors:
92            if self.has_option(vendor, "notes"):
93                ofp.write(vhead2 % (self.get(vendor, "vendor_site"), vendor,
94                                    self.get(vendor, "notes")))
95            else:
96                ofp.write(vhead1 % (self.get(vendor, "vendor_site"), vendor))
97            relevant = []
98            for dev in self.devices:
99                if self.get(dev, "vendor") == vendor:
100                    relevant.append(dev)
101            relevant.sort()
102            for dev in relevant:
103                rowcolor = "white"
104                if self.get(dev, "packaging") == "OEM module":
105                    rowcolor = "#32CD32"
106                elif self.get(dev, "packaging") == "chipset":
107                    rowcolor = "#FFFFE0"
108                elif self.get(dev, "packaging") == "handset":
109                    rowcolor = "#00FFFF"
110                elif self.get(dev, "packaging") == "hansdfree":
111                    rowcolor = "#008B8B"
112
113                ofp.write("<tr itemscope itemtype='http://schema.org/Product'"
114                          " style='background-color:%s'>\n" % rowcolor)
115                namefield = dev
116                if self.has_option(dev, "techdoc"):
117                    namefield = "<a href='%s'>%s</a>" \
118                        % (self.get(dev, "techdoc"), dev)
119                if ((self.has_option(dev, "discontinued") and
120                     self.getboolean(dev, "discontinued"))):
121                    namefield = namefield + "&nbsp;<img title='Device " \
122                        "discontinued' src='discontinued.png' " \
123                        "alt='Discontinued icon'>"
124                ofp.write("<td itemprop='name'>%s</td>\n" % namefield)
125                ofp.write("<td>%s</td>\n" % self.get(dev, "packaging"))
126                engine = self.get(dev, "engine")
127                if self.has_option(engine, "techdoc"):
128                    engine = "<a href='%s'>%s</a>" \
129                        % (self.get(engine, "techdoc"), engine)
130                if self.has_option(dev, "subtype"):
131                    engine += " (" + self.get(dev, "subtype") + ")"
132                ofp.write("<td>%s</td>\n" % engine)
133                interfaces = self.get(dev, "interfaces")
134                if self.has_option(dev, "pps"):
135                    interfaces += ",PPS"
136                ofp.write("<td>%s</td>\n" % interfaces)
137                testfield = ""
138                if self.has_option(dev, "tested"):
139                    tested = self.get(dev, "tested")
140                    if tested == "regression":
141                        testfield += "<img title='Have regression test' " \
142                                     "src='regression.png' " \
143                                     "alt='Regression-test icon'>"
144                    else:
145                        testfield += tested
146                if ((self.has_option(dev, "configurable") and
147                     self.get(dev, "configurable") == 'insane')):
148                    testfield += "<img title='Requires -b option' " \
149                                 "src='noconfigure.png' " \
150                                 "alt='No-configure icon'>"
151                if self.get(dev, "rating") == "excellent":
152                    testfield += "<img src='star.png' alt='Star icon'>" \
153                                 "<img src='star.png' alt='Star icon'>" \
154                                 "<img src='star.png' alt='Star icon'>" \
155                                 "<img src='star.png' alt='Star icon'>"
156                elif self.get(dev, "rating") == "good":
157                    testfield += "<img src='star.png' alt='Star icon'>" \
158                                 "<img src='star.png' alt='Star icon'>" \
159                                 "<img src='star.png' alt='Star icon'>"
160                elif self.get(dev, "rating") == "fair":
161                    testfield += "<img src='star.png' alt='Star icon'>" \
162                                 "<img src='star.png' alt='Star icon'>"
163                elif self.get(dev, "rating") == "poor":
164                    testfield += "<img src='star.png' alt='Star icon'>"
165                elif self.get(dev, "rating") == "broken":
166                    testfield += "<img title='Device is broken' " \
167                                 "src='bomb.png' alt='Bomb icon'>"
168                if ((self.has_option(dev, "usbchip") and
169                     self.get(dev, "usbchip") in hotpluggables)):
170                    testfield += "<img title='udev hotplug' " \
171                                 "src='hotplug.png' alt='Hotplug icon'>"
172                ofp.write("<td>%s</td>\n" % testfield)
173                nmea = "&nbsp;"
174                if self.has_option(dev, "nmea"):
175                    nmea = self.get(dev, "nmea")
176                ofp.write("<td>%s</td>\n" % nmea)
177                if ((self.has_option(dev, "pps") and
178                     self.get(dev, "pps") == "True")):
179                    pps_accuracy = time_offset = ""
180                    if self.has_option(dev, "pps_accuracy"):
181                        pps_accuracy = self.get(dev, "pps_accuracy")
182                    if self.has_option(dev, "time_offset"):
183                        time_offset = self.get(dev, "time_offset")
184                    if pps_accuracy and time_offset:
185                        ofp.write("<td>%s<br>%s</td>\n"
186                                  % (pps_accuracy, time_offset))
187                    else:
188                        ofp.write("<td>?<br>\n")
189                else:
190                    ofp.write("<td>No</td>\n")
191                if self.has_option(dev, "notes"):
192                    notes = self.get(dev, "notes")
193                else:
194                    notes = ""
195                if self.has_option(dev, "submitter"):
196                    notes += " Reported by %s." % self.get(
197                        dev, "submitter").replace("@", "&#x40;").replace(
198                        "<", "&lt;").replace(">", "&gt;")
199                ofp.write("<td itemscope itemtype='http://schema.org/"
200                          "description'>%s</td>\n" % notes)
201                ofp.write("</tr>\n")
202        ofp.write("</table>\n")
203
204
205if __name__ == "__main__":
206    import sys
207    try:
208        d = GPSDictionary()
209        d.HTMLDump(sys.stdout)
210    except configparser.Error as e:
211        sys.stderr.write(sys.argv[0] + ":%s\n" % e)
212        raise SystemExit(1)
213