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

Contents of /website/matrix.py

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.5