/[pcsclite]/website/matrix.py
ViewVC logotype

Contents of /website/matrix.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5265 - (show annotations) (download) (as text)
Mon Sep 20 11:34:23 2010 UTC (2 years, 8 months ago) by rousseau
File MIME type: text/x-python
File size: 17111 byte(s)
Add HTML and CSS validator buttons
1 #!/usr/bin/env python
2
3 # matrix.py generate a matrix of all readers characteristics
4 # Copyright (C) 2009 Ludovic Rousseau
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 import glob
21 import os
22 import ConfigParser
23 import pprint
24 import templayer
25 import time
26 import re
27
28 pp = pprint.PrettyPrinter(indent=4)
29
30 html_escape_table = {
31 "&": "&",
32 '"': """,
33 ">": ">",
34 "<": "&lt;",
35 }
36
37
38 def html_escape(text):
39 """Produce entities within text."""
40 L = []
41 for c in text:
42 L.append(html_escape_table.get(c, c))
43 return "".join(L)
44
45
46 def parse_reader(path, reader):
47 """
48 parse a reader CCID descriptor and return a dictionnary
49 """
50 reader_dict = {}
51 reader_file = open(path + reader)
52 for line in reader_file.readlines():
53 line = line[0:-1]
54 l = line.strip(" ").split(':')
55 if (len(l) > 1):
56 reader_dict[l[0]] = l[1].strip(" ")
57 reader_file.close()
58 return reader_dict
59
60
61 def parse_all(path, reader_list):
62 """
63 parse each reader from list
64 return a dictionnary
65 """
66 readers = {}
67 for reader in reader_list:
68 p = parse_reader(path, reader)
69 readers[reader] = p
70
71 return readers
72
73
74 def parse_ini(path, section):
75 """
76 parse a foobar.ini file to extract all informations
77 """
78 config = ConfigParser.ConfigParser()
79 # do not use the default case insensitive transform for key value
80 config.optionxform = str
81 config.read(section + ".ini")
82 reader_list = config.sections()
83
84 readers = parse_all(path, reader_list)
85 for r in readers.keys():
86 readers[r]['section'] = section
87 readers[r]['iManufacturer'] = html_escape(readers[r]['iManufacturer'])
88 for o in config.options(r):
89 if o == 'features':
90 # for the features we use a list
91 readers[r][o] = [config.get(r, o)]
92 else:
93 readers[r][o] = html_escape(config.get(r, o))
94
95 bPINSupport = int(readers[r]['bPINSupport'], 16)
96 if bPINSupport != 0 and not 'features' in readers[r]:
97 readers[r]['features'] = list()
98 if bPINSupport & 1:
99 readers[r]['features'].append("PIN Verification")
100 if bPINSupport & 2:
101 readers[r]['features'].append("PIN Modification")
102 if int(readers[r]['dwFeatures'], 16) & 0x0800:
103 readers[r].setdefault('features', ["ICCD"])
104
105 #pp.pprint(readers["GemPCPinpad.txt"])
106 return readers
107
108
109 def check_list(path, reader_list):
110 """
111 Check that all .txt files are listed
112 """
113 cwd = os.getcwd()
114 os.chdir(path)
115 real_list = glob.glob("*.txt")
116 os.chdir(cwd)
117
118 # check that each reader file is listed
119 #print real_list
120 for r in reader_list:
121 #print "remove ", r
122 try:
123 real_list.remove(r)
124 except:
125 print "reader %s not yet listed" % r
126
127 # also remove the non-reader supported_readers.txt file
128 real_list.remove("supported_readers.txt")
129
130 # some USB descriptor are not listed in readers.txt?
131 if len(real_list) > 0:
132 print "Reader(s) not listed in any .ini file:"
133 print "\n".join(real_list)
134 print ""
135
136
137 def check_supported(path, all_readers):
138 """
139 Check that all .txt files are mentionned in supported_readers.txt
140 """
141 supported_readers = file(path + "supported_readers.txt").readlines()
142 # convert in a long string and in uppercase
143 s = "".join(supported_readers).upper()
144
145 unlisted = list()
146 for r in all_readers.keys():
147 pattern = "%s:%s" % (all_readers[r]['idVendor'], all_readers[r]['idProduct'])
148 if not pattern.upper() in s:
149 unlisted.append(pattern + " " + r)
150
151 if len(unlisted) > 0:
152 print "Reader(s) not in supported_readers.txt"
153 print "\n".join(unlisted)
154 print ""
155 #pp.pprint(supported_readers)
156
157
158 def check_descriptions(path, all_readers):
159 """
160 Check that all readers mentionned in supported_readers.txt have a
161 .txt descriptor
162 """
163 supported_readers = file(path + "supported_readers.txt").readlines()
164
165 unlisted = list()
166 for line in supported_readers:
167 # skip comments
168 if line.startswith("#"):
169 continue
170
171 # remove newline
172 line = line.rstrip()
173
174 # skip empty lines
175 if line is "":
176 continue
177
178 (vendor, product, name) = line.split(":")
179 vendor = int(vendor, 16)
180 product = int(product, 16)
181 found = False
182 for r in all_readers.keys():
183 reader = all_readers[r]
184 if vendor == int(reader['idVendor'], 16) and product == int(reader['idProduct'], 16):
185 found = True
186 if not found:
187 unlisted.append(line)
188
189 if len(unlisted) > 0:
190 print "Reader(s) without a .txt description"
191 print "\n".join(unlisted)
192 print ""
193 #pp.pprint(supported_readers)
194
195
196 def get_driver_version(readers):
197 """
198 set the 'release' field for each reader
199 """
200 changelog = get_changelog()
201
202 for reader in readers.keys():
203 rev = get_driver_revision(reader, changelog)
204 readers[reader]['release'] = driver_revision_to_version(rev)
205
206
207 def driver_revision_to_version(rev):
208 """
209 convert a SVN revision in the first release containing this revision
210 """
211 history = [
212 #[ SVN revision, CCID release ]
213 [273, "0.1.0"],
214 [342, "0.2.0"],
215 [423, "0.3.0"],
216 [467, "0.3.1"],
217 [552, "0.3.2"],
218 [697, "0.4.0"],
219 [703, "0.4.1"],
220 [1015, "0.9.0"],
221 [1018, "0.9.1"],
222 [1186, "0.9.2"],
223 [1400, "0.9.3"],
224 [1761, "0.9.4"],
225 [1911, "1.0.0"],
226 [2020, "1.0.1"],
227 [2135, "1.1.0"],
228 [2345, "1.2.0"],
229 [2363, "1.2.1"],
230 [2522, "1.3.0"],
231 [2692, "1.3.1"],
232 [2755, "1.3.2"],
233 [2796, "1.3.3"],
234 [2809, "1.3.4"],
235 [2842, "1.3.5"],
236 [2924, "1.3.6"],
237 [2985, "1.3.7"],
238 [3033, "1.3.8"],
239 [3208, "1.3.9"],
240 [3338, "1.3.10"],
241 [4347, "1.3.11"],
242 [4931, "1.3.12"],
243 [4979, "1.3.13"],
244 [5108, "1.4.0"]]
245 for h in history:
246 if rev <= h[0]:
247 return h[1]
248 return "SVN"
249
250
251 def get_changelog():
252 """
253 read a complete svn2cl Changelog file and merge commits on one line
254 """
255 lines = open("ccid/readers/ChangeLog").readlines()
256 changelog = list()
257 p = list()
258
259 for line in lines:
260 # the lines starts with a year (2000-2099)
261 if line.startswith('20'):
262 changelog.append("".join(p).replace('\n', ''))
263 p = [line]
264 else:
265 p.append(line)
266
267 # add the last line
268 changelog.append("".join(p).replace('\n', ''))
269
270 return changelog
271
272
273 def get_driver_revision(reader, changelog):
274 """
275 search a log line containing the reader string
276 """
277 found = None
278 for line in changelog:
279 if reader in line:
280 found = line
281
282 if found:
283 result = re.search('\\* \\[r(\d*)\\]', found)
284 if result:
285 # revision is the inner matching pattern
286 return int(result.group(1))
287 else:
288 print "reader %s not found in ChangeLog" % reader
289 # fake SVN revision number high enough to be considered as
290 # unreleased
291 return 999999999
292
293
294 def get_by_manufacturer(readers):
295 """
296 return a dict of the readers grouped by manufacturer
297 d['manufacturer'] is a list of the manufacturer's readers
298 """
299 d = {}
300 for r in readers.keys():
301 d.setdefault(readers[r]['iManufacturer'], []).append(r)
302 return d
303
304
305 def generate_page(section, title, comment, readers):
306 """
307 generate a web page for the corresponding section
308 """
309 # sort the readers by manufacturers
310 manufacturer_readers = get_by_manufacturer(readers)
311 manufacturers = list(manufacturer_readers)
312 # sort the manufacturers list alphabetically
313 manufacturers.sort(key=str.lower)
314
315 template = templayer.HTMLTemplate("webpage.template")
316 file_writer = template.start_file(file=file("ccid/" + section + ".html", "w"))
317 main_layer = file_writer.open(date=time.asctime(),
318 title=title, comment=comment, section=section)
319
320 # for each manufacturer
321 for m in manufacturers:
322 main_layer.write_layer('manufacturer', manufacturer=m)
323
324 # for each reader
325 for r in sorted(manufacturer_readers[m]):
326 note_layer = main_layer.open_layer('reader',
327 manufacturer=m,
328 product=readers[r]['iProduct'],
329 idVendor=readers[r]['idVendor'],
330 idProduct=readers[r]['idProduct'],
331 image="img/" + readers[r].get('image', "no_image.png"))
332
333 note_layer.write_layer('descriptor', descriptor="readers/%s" % r)
334
335 url = readers[r].get('url', "")
336 if url:
337 note_layer.write_layer('url',
338 url=url,
339 manufacturer=m,
340 product=readers[r]['iProduct'])
341
342 features = ", ".join(readers[r].get('features', ""))
343 if features:
344 note_layer.write_layer('features', features=features)
345
346 note = readers[r].get('note', "").split('\n')
347 for n in note:
348 note_layer.write_layer('note', contents=n)
349
350 note_layer.write_layer('release',
351 release=readers[r]['release'])
352
353 file_writer.close()
354
355
356 def generate_table(readers, field, index, fields):
357 """
358 generate a web page with all the reader attributes
359 readers are in the order given by index
360 """
361
362 header = """<?xml version="1.0" encoding="UTF-8"?>
363 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
364 <html>
365 <head>
366 <title>%s</title>
367 <link rel="stylesheet" type="text/css" href="default.css">
368 <link rel="stylesheet" type="text/css" href="matrix.css">
369 <script type="text/javascript">
370 /* <![CDATA[ */
371 (function() {
372 var s = document.createElement('script'), t = document.getElementsByTagName('script')[0];
373
374 s.type = 'text/javascript';
375 s.async = true;
376 s.src = 'http://api.flattr.com/js/0.5.0/load.js?mode=auto';
377
378 t.parentNode.insertBefore(s, t);
379 })();
380 /* ]]> */
381 </script>
382 </head>
383 <body>"""
384
385 footer = """
386 <p>
387 <a href="http://validator.w3.org/check/referer"><img src="http://www.w3.org/Icons/valid-html401" alt="Valid HTML 4.01!"></a>
388 <a href="http://jigsaw.w3.org/css-validator/"><img src="http://jigsaw.w3.org/css-validator/images/vcss" alt="Valid CSS!"></a>
389 </p>
390
391 <script type="text/javascript">
392 var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
393 document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
394 </script>
395 <script type="text/javascript">
396 try {
397 var pageTracker = _gat._getTracker("UA-2404298-2");
398 pageTracker._trackPageview();
399 } catch(err) {}</script>
400 <p>Ludovic Rousseau</p>
401 </body>
402 </html>"""
403
404 documentation = """
405 <ul>
406 <li>Click on the column header to sort by that column.</li>
407 <li>The background color indicates the section of the reader:
408 <table border="1" summary="color codes">
409 <tr>
410 <td class="supported">supported</td>
411 <td class="shouldwork">should work</td>
412 <td class="unsupported">unsupported</td>
413 </tr></table></li>
414 <li><a class="FlattrButton" style="display:none;"
415 href="http://pcsclite.alioth.debian.org/ccid.html"></a></li>
416 </ul>
417 """
418
419 file = open("ccid/" + field + ".html", "w")
420 title = "Readers sorted by '%s' field" % field
421 file.write(header % title)
422
423 file.write("<h1>" + title + "</h1>")
424 file.write(documentation)
425
426 file.write('<table border="1" summary="">\n')
427
428 file.write('<tr>')
429 file.write("<th>#</th>")
430 for f in fields:
431 file.write("<th><a href='%s'>%s</a></th>" % (f + ".html", f))
432 file.write('</tr>\n')
433
434 num = 0
435 for r in index:
436 num += 1
437 # define color of the line
438 if num % 2:
439 # even line number
440 color = readers[r]['section']
441 else:
442 # odd line number
443 color = readers[r]['section'] + '_odd'
444 file.write('<tr class="%s">' % color)
445
446 file.write("<td>%d</td>" % num)
447 for f in fields:
448 if f == 'iProduct':
449 file.write("<td><a href='%s.html#%s%s'>%s</a></td>" % (readers[r]['section'], readers[r]['idVendor'], readers[r]['idProduct'], readers[r][f]))
450 elif f == 'iManufacturer':
451 file.write('<td><a onmouseover="">%s<img src="%s" alt="image"></a></td>' % (readers[r][f], "img/" + readers[r].get('image', "no_image.png")))
452 elif f == 'image':
453 file.write('<td><img src="%s" height="100" alt="image"></td>' % ("img/" + readers[r].get('image', "no_image.png")))
454 else:
455 file.write("<td>%s</td>" % readers[r].get(f, ""))
456 file.write('</tr>\n')
457
458 file.write('</table>\n')
459
460 file.write("<hr><p>Generated: %s</p>" % time.asctime())
461
462 file.write(footer)
463 file.close()
464
465
466 def generate_tables(readers):
467 """
468 generate all the web page tables with all the fields values
469 """
470 fields = ['section', 'iManufacturer', 'iProduct', 'image', 'idVendor',
471 'idProduct', 'bNumEndpoints', 'bInterfaceClass', 'bcdCCID',
472 'bMaxSlotIndex', 'bVoltageSupport', 'dwProtocols',
473 'dwDefaultClock', 'dwMaximumClock', 'dwDataRate',
474 'dwMaxDataRate', 'dwMaxIFSD', 'dwSynchProtocols',
475 'dwMechanical', 'dwFeatures', 'dwMaxCCIDMessageLength',
476 'bClassGetResponse', 'bClassEnveloppe', 'wLcdLayout',
477 'bPINSupport', 'bMaxCCIDBusySlots', 'features', 'note',
478 'release']
479
480 for f in fields:
481 index = list()
482 for r in readers.keys():
483 index.append([readers[r].get(f, ""), r])
484 if f == 'section':
485 # hack to sort in the order supported, shouldwork, unsupported
486 index = [(s.replace('supported', 'asupported'), r) for s, r in index]
487 if f in ['dwDefaultClock', 'dwMaximumClock', 'dwDataRate',
488 'dwMaxDataRate', 'dwMaxIFSD', 'dwMaxCCIDMessageLength']:
489 # convert from text to decimal
490 index = [(float(s.split(' ')[0]) * 1000, r) for s, r in index]
491 if f == 'release':
492 index = [(release2int(s), r) for s, r in index]
493 index.sort()
494 sorted_fields = list(fields)
495 sorted_fields.remove(f)
496 sorted_fields.insert(0, f)
497 generate_table(readers, f, [r for f, r in index], sorted_fields)
498
499
500 def release2int(r):
501 rr = r.split('.')
502 if len(rr) > 1:
503 return int(rr[2]) + int(rr[1]) * 1000 + int(rr[0]) * 1000 * 1000
504 else:
505 # SVN version
506 return 99*1000*1000
507
508 if __name__ == "__main__":
509 path = "../trunk/Drivers/ccid/readers/"
510
511 supported_readers = parse_ini(path, "supported")
512 shouldwork_readers = parse_ini(path, "shouldwork")
513 unsupported_readers = parse_ini(path, "unsupported")
514
515 # all_readers contain the union of the 3 lists
516 all_readers = dict(supported_readers)
517 all_readers.update(shouldwork_readers)
518 all_readers.update(unsupported_readers)
519
520 check_list(path, all_readers.keys())
521 check_supported(path, all_readers)
522 check_descriptions(path, all_readers)
523
524 get_driver_version(supported_readers)
525 get_driver_version(shouldwork_readers)
526 get_driver_version(unsupported_readers)
527
528 generate_page("supported", "Supported CCID readers/ICCD tokens", "If you are a reader manufacturer and your reader is not listed here then contact me at ludovic.rousseau@free.fr", supported_readers)
529 generate_page("shouldwork", "Should work but untested by me", "The CCID readers and ICCD tokens listed bellow should work with the driver but have not be validated by me. I would like to get these readers to perform test and validation and move them in the supported list above. If you are one of the manufacturers, please, contact me at ludovic.rousseau@free.fr.", shouldwork_readers)
530 generate_page("unsupported", "Unsupported or partly supported CCID readers", "These readers have problems or serious limitations.", unsupported_readers)
531
532 generate_tables(all_readers)

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.5