Merge lp:~jml/pkgme-devportal/more-fingerprints into lp:pkgme-devportal

Proposed by Jonathan Lange
Status: Merged
Approved by: James Westby
Approved revision: 136
Merged at revision: 127
Proposed branch: lp:~jml/pkgme-devportal/more-fingerprints
Merge into: lp:pkgme-devportal
Diff against target: 571 lines (+311/-68)
15 files modified
devportalbinary/backends/desktop/all_info (+11/-0)
devportalbinary/backends/desktop/want (+11/-0)
devportalbinary/backends/diff/all_info (+11/-0)
devportalbinary/backends/diff/want (+11/-0)
devportalbinary/backends/exe/all_info (+11/-0)
devportalbinary/backends/exe/want (+11/-0)
devportalbinary/backends/image/all_info (+11/-0)
devportalbinary/backends/image/want (+11/-0)
devportalbinary/backends/shell/all_info (+11/-0)
devportalbinary/backends/shell/want (+11/-0)
devportalbinary/metadata.py (+41/-0)
devportalbinary/pdf.py (+5/-19)
devportalbinary/stubs.py (+80/-25)
devportalbinary/tests/test_stubs.py (+72/-5)
devportalbinary/unity_webapps.py (+3/-19)
To merge this branch: bzr merge lp:~jml/pkgme-devportal/more-fingerprints
Reviewer Review Type Date Requested Status
James Westby Approve
Review via email: mp+125236@code.launchpad.net

Commit message

Even more stub backends

Description of the change

I said I wasn't going to do this, but it's pretty easy and will make writing
the weekly report less of a pain.

More backends, some refactoring. Not all of the new ones have tests, and
there are no integration tests.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

Could unity-webapps perhaps also use your new want_single_file_with_metadata?

Thanks,

James

review: Approve
135. By Jonathan Lange

Move to central location.

136. By Jonathan Lange

Use the thing for unity as well.

Revision history for this message
Jonathan Lange (jml) wrote :

On 19 September 2012 16:39, James Westby <email address hidden> wrote:
> Review: Approve
>
> Could unity-webapps perhaps also use your new want_single_file_with_metadata?

Good call. Have done so, making it use the extension thing. I don't
think the factoring is ideal, btw, but I think it's an improvement on
what we've got.

jml

Revision history for this message
James Westby (james-w) wrote :

Great, thanks, I'd been wanting to do something like that when writing the webapps
backend, but couldn't figure out a very nice interface, so I'm glad you did it.

Thanks,

James

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'devportalbinary/backends/desktop'
2=== added file 'devportalbinary/backends/desktop/all_info'
3--- devportalbinary/backends/desktop/all_info 1970-01-01 00:00:00 +0000
4+++ devportalbinary/backends/desktop/all_info 2012-09-19 16:09:21 +0000
5@@ -0,0 +1,11 @@
6+#!/usr/bin/env python
7+# Copyright 2012 Canonical Ltd. This software is licensed under the
8+# GNU Affero General Public License version 3 (see the file LICENSE).
9+
10+import sys
11+
12+from devportalbinary.stubs import DesktopBackend
13+
14+
15+if __name__ == '__main__':
16+ sys.exit(DesktopBackend.all_info_script())
17
18=== added file 'devportalbinary/backends/desktop/want'
19--- devportalbinary/backends/desktop/want 1970-01-01 00:00:00 +0000
20+++ devportalbinary/backends/desktop/want 2012-09-19 16:09:21 +0000
21@@ -0,0 +1,11 @@
22+#!/usr/bin/env python
23+# Copyright 2011 Canonical Ltd. This software is licensed under the
24+# GNU Affero General Public License version 3 (see the file LICENSE).
25+
26+import sys
27+
28+from devportalbinary.stubs import DesktopBackend
29+
30+
31+if __name__ == '__main__':
32+ sys.exit(DesktopBackend.want_script())
33
34=== added directory 'devportalbinary/backends/diff'
35=== added file 'devportalbinary/backends/diff/all_info'
36--- devportalbinary/backends/diff/all_info 1970-01-01 00:00:00 +0000
37+++ devportalbinary/backends/diff/all_info 2012-09-19 16:09:21 +0000
38@@ -0,0 +1,11 @@
39+#!/usr/bin/env python
40+# Copyright 2012 Canonical Ltd. This software is licensed under the
41+# GNU Affero General Public License version 3 (see the file LICENSE).
42+
43+import sys
44+
45+from devportalbinary.stubs import DiffBackend
46+
47+
48+if __name__ == '__main__':
49+ sys.exit(DiffBackend.all_info_script())
50
51=== added file 'devportalbinary/backends/diff/want'
52--- devportalbinary/backends/diff/want 1970-01-01 00:00:00 +0000
53+++ devportalbinary/backends/diff/want 2012-09-19 16:09:21 +0000
54@@ -0,0 +1,11 @@
55+#!/usr/bin/env python
56+# Copyright 2011 Canonical Ltd. This software is licensed under the
57+# GNU Affero General Public License version 3 (see the file LICENSE).
58+
59+import sys
60+
61+from devportalbinary.stubs import DiffBackend
62+
63+
64+if __name__ == '__main__':
65+ sys.exit(DiffBackend.want_script())
66
67=== added directory 'devportalbinary/backends/exe'
68=== added file 'devportalbinary/backends/exe/all_info'
69--- devportalbinary/backends/exe/all_info 1970-01-01 00:00:00 +0000
70+++ devportalbinary/backends/exe/all_info 2012-09-19 16:09:21 +0000
71@@ -0,0 +1,11 @@
72+#!/usr/bin/env python
73+# Copyright 2012 Canonical Ltd. This software is licensed under the
74+# GNU Affero General Public License version 3 (see the file LICENSE).
75+
76+import sys
77+
78+from devportalbinary.stubs import ExeBackend
79+
80+
81+if __name__ == '__main__':
82+ sys.exit(ExeBackend.all_info_script())
83
84=== added file 'devportalbinary/backends/exe/want'
85--- devportalbinary/backends/exe/want 1970-01-01 00:00:00 +0000
86+++ devportalbinary/backends/exe/want 2012-09-19 16:09:21 +0000
87@@ -0,0 +1,11 @@
88+#!/usr/bin/env python
89+# Copyright 2011 Canonical Ltd. This software is licensed under the
90+# GNU Affero General Public License version 3 (see the file LICENSE).
91+
92+import sys
93+
94+from devportalbinary.stubs import ExeBackend
95+
96+
97+if __name__ == '__main__':
98+ sys.exit(ExeBackend.want_script())
99
100=== added directory 'devportalbinary/backends/image'
101=== added file 'devportalbinary/backends/image/all_info'
102--- devportalbinary/backends/image/all_info 1970-01-01 00:00:00 +0000
103+++ devportalbinary/backends/image/all_info 2012-09-19 16:09:21 +0000
104@@ -0,0 +1,11 @@
105+#!/usr/bin/env python
106+# Copyright 2012 Canonical Ltd. This software is licensed under the
107+# GNU Affero General Public License version 3 (see the file LICENSE).
108+
109+import sys
110+
111+from devportalbinary.stubs import ImageBackend
112+
113+
114+if __name__ == '__main__':
115+ sys.exit(ImageBackend.all_info_script())
116
117=== added file 'devportalbinary/backends/image/want'
118--- devportalbinary/backends/image/want 1970-01-01 00:00:00 +0000
119+++ devportalbinary/backends/image/want 2012-09-19 16:09:21 +0000
120@@ -0,0 +1,11 @@
121+#!/usr/bin/env python
122+# Copyright 2011 Canonical Ltd. This software is licensed under the
123+# GNU Affero General Public License version 3 (see the file LICENSE).
124+
125+import sys
126+
127+from devportalbinary.stubs import ImageBackend
128+
129+
130+if __name__ == '__main__':
131+ sys.exit(ImageBackend.want_script())
132
133=== added directory 'devportalbinary/backends/shell'
134=== added file 'devportalbinary/backends/shell/all_info'
135--- devportalbinary/backends/shell/all_info 1970-01-01 00:00:00 +0000
136+++ devportalbinary/backends/shell/all_info 2012-09-19 16:09:21 +0000
137@@ -0,0 +1,11 @@
138+#!/usr/bin/env python
139+# Copyright 2012 Canonical Ltd. This software is licensed under the
140+# GNU Affero General Public License version 3 (see the file LICENSE).
141+
142+import sys
143+
144+from devportalbinary.stubs import ShellScriptBackend
145+
146+
147+if __name__ == '__main__':
148+ sys.exit(ShellScriptBackend.all_info_script())
149
150=== added file 'devportalbinary/backends/shell/want'
151--- devportalbinary/backends/shell/want 1970-01-01 00:00:00 +0000
152+++ devportalbinary/backends/shell/want 2012-09-19 16:09:21 +0000
153@@ -0,0 +1,11 @@
154+#!/usr/bin/env python
155+# Copyright 2011 Canonical Ltd. This software is licensed under the
156+# GNU Affero General Public License version 3 (see the file LICENSE).
157+
158+import sys
159+
160+from devportalbinary.stubs import ShellScriptBackend
161+
162+
163+if __name__ == '__main__':
164+ sys.exit(ShellScriptBackend.want_script())
165
166=== modified file 'devportalbinary/metadata.py'
167--- devportalbinary/metadata.py 2012-09-18 14:34:23 +0000
168+++ devportalbinary/metadata.py 2012-09-19 16:09:21 +0000
169@@ -266,6 +266,47 @@
170 return metadata, None
171
172
173+def want_single_file_with_metadata(backend, path, metadata, predicate,
174+ match_score,
175+ mismatch_message, too_many_message):
176+ excluded_files = get_excluded_package_files(backend, metadata)
177+ files = list(get_package_files(path, excluded_files))
178+ # By default, we don't want it and give no reason. Sane default in
179+ # case of buggy code below.
180+ score = 0
181+ reason = None
182+ if len(files) == 0:
183+ reason = 'No files found, just metadata'
184+ elif len(files) == 1:
185+ filename = files[0]
186+ if predicate(filename):
187+ score = match_score
188+ else:
189+ reason = '%s: %r' % (mismatch_message, filename)
190+ else:
191+ reason = '%s: %r' % (too_many_message, sorted(files))
192+ return {'score': score, 'reason': reason}
193+
194+
195+def _has_extensions(extensions):
196+ def _filename_has_extensions(filename):
197+ for ext in extensions:
198+ if filename.endswith(ext):
199+ return True
200+ return False
201+ return _filename_has_extensions
202+
203+
204+def want_single_file_by_extension(cls, path, metadata, extensions, name, score=20):
205+ return want_single_file_with_metadata(
206+ cls, path, metadata,
207+ _has_extensions(extensions),
208+ score,
209+ "File is not %s" % (name,),
210+ "More files than just %s" % (name,),
211+ )
212+
213+
214 class MetadataBackend(object):
215 """A backend that is mostly powered by metadata from MyApps."""
216
217
218=== modified file 'devportalbinary/pdf.py'
219--- devportalbinary/pdf.py 2012-09-12 15:04:26 +0000
220+++ devportalbinary/pdf.py 2012-09-19 16:09:21 +0000
221@@ -4,11 +4,10 @@
222 import os
223
224 from devportalbinary.metadata import (
225- get_excluded_package_files,
226- get_package_files,
227 make_all_info_fn,
228 make_want_fn,
229 MetadataBackend,
230+ want_single_file_with_metadata,
231 )
232
233
234@@ -50,23 +49,10 @@
235
236 @classmethod
237 def want_with_metadata(cls, path, metadata):
238- excluded_files = get_excluded_package_files(cls, metadata)
239- files = list(get_package_files(path, excluded_files))
240- # By default, we don't want it and give no reason. Sane default in
241- # case of buggy code below.
242- score = 0
243- reason = None
244- if len(files) == 0:
245- reason = 'No files found, just metadata'
246- elif len(files) == 1:
247- filename = files[0]
248- if filename.endswith(".pdf"):
249- score = 20
250- else:
251- reason = 'File is not a PDF: %r' % (filename,)
252- else:
253- reason = 'More files than just a PDF: %r' % (sorted(files),)
254- return {'score': score, 'reason': reason}
255+ return want_single_file_with_metadata(
256+ cls, path, metadata,
257+ lambda filename: filename.endswith('.pdf'),
258+ 20, "File is not a PDF", "More files than just a PDF")
259
260
261 want = make_want_fn(PdfBackend)
262
263=== modified file 'devportalbinary/stubs.py'
264--- devportalbinary/stubs.py 2012-09-12 17:27:24 +0000
265+++ devportalbinary/stubs.py 2012-09-19 16:09:21 +0000
266@@ -22,6 +22,7 @@
267 make_all_info_fn,
268 make_want_fn,
269 MetadataBackend,
270+ want_single_file_by_extension,
271 )
272
273
274@@ -33,14 +34,14 @@
275 "%s not implemented yet" % (backend_name,))
276
277
278-class UnimplementableBackend(PkgmeError):
279+class UnimplementableBackendError(PkgmeError):
280 """Raised by backends we'll never implement."""
281
282 def __init__(self, backend_name, reason=None):
283 msg = '%s will never be implemented' % (backend_name,)
284 if reason:
285 msg = ': '.join([msg, reason])
286- super(UnimplementableBackend, self).__init__(msg)
287+ super(UnimplementableBackendError, self).__init__(msg)
288
289
290 class StubBackend(MetadataBackend):
291@@ -68,6 +69,14 @@
292 return make_all_info_fn(cls)(path)
293
294
295+class UnimplementableBackend(StubBackend):
296+
297+ reason = None
298+
299+ def get_info(self):
300+ raise UnimplementableBackendError(self.name, self.reason)
301+
302+
303 class JarBackend(StubBackend):
304
305 name = 'Java binary'
306@@ -131,31 +140,35 @@
307 return {'reason': reason, 'score': score}
308
309
310-class TextBackend(StubBackend):
311+class TextBackend(UnimplementableBackend):
312
313 name = 'Text'
314-
315- @classmethod
316- def want_with_metadata(cls, path, metadata):
317- excluded_files = get_excluded_package_files(cls, metadata)
318- files = list(get_package_files(path, excluded_files))
319- score = 0
320- reason = None
321- if len(files) == 0:
322- reason = 'No files found, just metadata'
323- elif len(files) == 1:
324- filename = files[0]
325- if filename.endswith(".txt"):
326- score = 20
327- else:
328- reason = 'File is not a .txt: %r' % (filename,)
329- else:
330- reason = 'More files than just a .txt: %r' % (sorted(files),)
331- return {'score': score, 'reason': reason}
332-
333- def get_info(self):
334- raise UnimplementableBackend(
335- self.name, "You can't package a text file")
336+ reason = "You can't package a text file"
337+
338+ @classmethod
339+ def want_with_metadata(cls, path, metadata):
340+ return want_single_file_by_extension(
341+ cls, path, metadata, ['.txt'], "a .txt")
342+
343+
344+class ImageBackend(UnimplementableBackend):
345+
346+ name = 'Image'
347+ reason = "You can't package an image"
348+
349+ EXTENSIONS = [
350+ '.bmp',
351+ '.ico',
352+ '.jpeg',
353+ '.jpg',
354+ '.png',
355+ '.svg',
356+ ]
357+
358+ @classmethod
359+ def want_with_metadata(cls, path, metadata):
360+ return want_single_file_by_extension(
361+ cls, path, metadata, cls.EXTENSIONS, "an image")
362
363
364 class PythonBackend(StubBackend):
365@@ -179,3 +192,45 @@
366 else:
367 reason = "No setup.py found"
368 return {'reason': reason, 'score': score}
369+
370+
371+class ShellScriptBackend(StubBackend):
372+
373+ name = 'Shell script'
374+
375+ @classmethod
376+ def want_with_metadata(cls, path, metadata):
377+ return want_single_file_by_extension(
378+ cls, path, metadata, ['.sh'], "a .sh", score=5)
379+
380+
381+class DesktopBackend(UnimplementableBackend):
382+
383+ name = 'Desktop file'
384+ reason = "Can't package a .desktop file"
385+
386+ @classmethod
387+ def want_with_metadata(cls, path, metadata):
388+ return want_single_file_by_extension(
389+ cls, path, metadata, ['.desktop'], "a desktop file")
390+
391+
392+class DiffBackend(UnimplementableBackend):
393+
394+ name = 'Diff'
395+ reason = "Can't package a .diff"
396+
397+ @classmethod
398+ def want_with_metadata(cls, path, metadata):
399+ return want_single_file_by_extension(
400+ cls, path, metadata, ['.diff'], "a diff")
401+
402+
403+class ExeBackend(StubBackend):
404+
405+ name = 'EXE'
406+
407+ @classmethod
408+ def want_with_metadata(cls, path, metadata):
409+ return want_single_file_by_extension(
410+ cls, path, metadata, ['.exe'], "a .exe", score=5)
411
412=== modified file 'devportalbinary/tests/test_stubs.py'
413--- devportalbinary/tests/test_stubs.py 2012-09-12 17:49:31 +0000
414+++ devportalbinary/tests/test_stubs.py 2012-09-19 16:09:21 +0000
415@@ -9,11 +9,13 @@
416 BackendNotImplemented,
417 DebianBinaryBackend,
418 DebianSourceBackend,
419+ ImageBackend,
420 JarBackend,
421 StubBackend,
422 PythonBackend,
423+ ShellScriptBackend,
424 TextBackend,
425- UnimplementableBackend,
426+ UnimplementableBackendError,
427 )
428 from ..testing import BackendTests
429
430@@ -35,11 +37,11 @@
431 class UnimplementableTests(TestCase):
432
433 def test_str(self):
434- e = UnimplementableBackend('backend_name')
435+ e = UnimplementableBackendError('backend_name')
436 self.assertEqual('backend_name will never be implemented', str(e))
437
438 def test_str_with_reason(self):
439- e = UnimplementableBackend(
440+ e = UnimplementableBackendError(
441 'backend_name', "You can't package a text file!")
442 self.assertEqual(
443 ("backend_name will never be implemented: You can't package "
444@@ -47,7 +49,7 @@
445 str(e))
446
447 def test_user_friendly(self):
448- e = UnimplementableBackend('backend_name')
449+ e = UnimplementableBackendError('backend_name')
450 self.assertIsInstance(e, PkgmeError)
451
452
453@@ -158,7 +160,72 @@
454
455 def test_get_info(self):
456 backend = self.make_backend()
457- self.assertRaises(UnimplementableBackend, backend.get_info)
458+ self.assertRaises(UnimplementableBackendError, backend.get_info)
459+
460+
461+class ShellScriptTests(BackendTests):
462+
463+ BACKEND = ShellScriptBackend
464+
465+ def test_want_with_only_metadata(self):
466+ self.assertEqual(
467+ {'score': 0, 'reason': 'No files found, just metadata'},
468+ self.want([VALID_METADATA_FILE]))
469+
470+ def test_want_with_only_shell(self):
471+ filename = self.getUniqueString() + '.sh'
472+ self.assertEqual(
473+ {'score': 5, 'reason': None},
474+ self.want([VALID_METADATA_FILE, filename]))
475+
476+ def test_want_with_only_non_shell(self):
477+ filename = self.getUniqueString() + '.txt'
478+ self.assertEqual(
479+ {'score': 0, 'reason': "File is not a .sh: %r" % (filename,)},
480+ self.want([VALID_METADATA_FILE, filename]))
481+
482+
483+class ImageTests(BackendTests):
484+
485+ BACKEND = ImageBackend
486+
487+ def test_want_with_only_metadata(self):
488+ self.assertEqual(
489+ {'score': 0, 'reason': 'No files found, just metadata'},
490+ self.want([VALID_METADATA_FILE]))
491+
492+ def test_want_with_random_files(self):
493+ filename = self.getUniqueString() + '.data'
494+ self.assertEqual(
495+ {'score': 0, 'reason': "File is not an image: %r" % (filename,)},
496+ self.want([VALID_METADATA_FILE, filename]))
497+
498+ def test_want_with_only_jpg(self):
499+ # Do all image types
500+ filename = self.getUniqueString() + '.jpg'
501+ self.assertEqual(
502+ {'score': 20, 'reason': None},
503+ self.want([VALID_METADATA_FILE, filename]))
504+
505+ def test_want_with_only_jpeg(self):
506+ # Do all image types
507+ filename = self.getUniqueString() + '.jpeg'
508+ self.assertEqual(
509+ {'score': 20, 'reason': None},
510+ self.want([VALID_METADATA_FILE, filename]))
511+
512+ def test_want_with_more_than_image(self):
513+ img_file = self.getUniqueString() + '.jpg'
514+ other_file = self.getUniqueString() + '.exe'
515+ self.assertEqual(
516+ {'score': 0,
517+ 'reason': ('More files than just an image: %r' %
518+ (sorted([img_file, other_file]),))},
519+ self.want([VALID_METADATA_FILE, img_file, other_file]))
520+
521+ def test_get_info(self):
522+ backend = self.make_backend()
523+ self.assertRaises(UnimplementableBackendError, backend.get_info)
524
525
526 class PythonTests(BackendTests):
527
528=== modified file 'devportalbinary/unity_webapps.py'
529--- devportalbinary/unity_webapps.py 2012-09-17 20:37:06 +0000
530+++ devportalbinary/unity_webapps.py 2012-09-19 16:09:21 +0000
531@@ -2,13 +2,12 @@
532 # GNU Affero General Public License version 3 (see the file LICENSE).
533
534 from .metadata import (
535- get_excluded_package_files,
536 get_icon_install_map,
537 get_install_file,
538- get_package_files,
539 make_all_info_fn,
540 make_want_fn,
541 MetadataBackend,
542+ want_single_file_by_extension,
543 )
544
545
546@@ -99,23 +98,8 @@
547
548 @classmethod
549 def want_with_metadata(cls, path, metadata):
550- excluded_files = get_excluded_package_files(cls, metadata)
551- files = list(get_package_files(path, excluded_files))
552- # By default, we don't want it and give no reason. Sane default in
553- # case of buggy code below.
554- score = 0
555- reason = None
556- if len(files) == 0:
557- reason = 'No files found, just metadata'
558- elif len(files) == 1:
559- filename = files[0]
560- if filename.endswith(".user.js"):
561- score = 20
562- else:
563- reason = 'File is not *.user.js: %r' % (filename,)
564- else:
565- reason = 'More files than just one *.user.js: %r' % (sorted(files),)
566- return {'score': score, 'reason': reason}
567+ return want_single_file_by_extension(
568+ cls, path, metadata, ['.user.js'], '*.user.js')
569
570
571 want = make_want_fn(UnityWebappsBackend)

Subscribers

People subscribed via source and target branches