#!/usr/bin/python3
#
# Copyright (C) 2007 Enrico Zini <enrico@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Given a file, search Debian packages that can somehow handle it
import sys
# Requires python-extractor, python-magic and python-debtags
import extractor
import magic
from debian import debtags
import re
from optparse import OptionParser
import apt
VERSION="0.1"
mime_map = (
( r'text/html\b', ("works-with::text","works-with-format::html") ),
( r'text/plain\b', ("works-with::text","works-with-format::plaintext") ),
( r'text/troff\b', ("works-with::text", "works-with-format::man") ),
( r'image/', ("works-with::image",) ),
( r'image/jpeg\b', ("works-with::image:raster","works-with-format::jpg") ),
( r'image/png\b', ("works-with::image:raster","works-with-format::png") ),
( r'application/pdf\b', ("works-with::text","works-with-format::pdf")),
( r'application/postscript\b', ("works-with::text","works-with-format::postscript")),
( r'application/x-iso9660\b', ('works-with-format::iso9660',)),
( r'application/zip\b', ('works-with::archive', 'works-with-format::zip')),
( r'application/x-tar\b', ('works-with::archive', 'works-with-format::tar')),
( r'audio/', ("works-with::audio",) ),
( r'audio/mpeg\b', ("works-with-format::mp3",) ),
( r'audio/x-wav\b', ("works-with-format::wav",) ),
( r'message/rfc822\b', ("works-with::mail",) ),
( r'video/', ("works-with::video",)),
( r'application/x-debian-package\b', ("works-with::software:package",)),
( r'application/vnd.oasis.opendocument.text\b', ("works-with::text",)),
( r'application/vnd.oasis.opendocument.graphics\b', ("works-with::image:vector",)),
( r'application/vnd.oasis.opendocument.spreadsheet\b', ("works-with::spreadsheet",)),
( r'application/vnd.sun.xml.base\b', ("works-with::db",)),
( r'application/rtf\b', ("works-with::text",)),
( r'application/x-dbm\b', ("works-with::db",)),
# How do we tell these two apart?
# ( 'application/ogg', ('works-with-format::oggvorbis',)),
# ( 'application/ogg', ('works-with-format::oggtheora',)),
# Missing tags
# ( 'application/vnd.oasis.opendocument.presentation', ),
# Still unhandled/unhandlable:
# works-with::3dmodel - 3D Model
# works-with::dictionary - Dictionaries
# works-with::dtp - Desktop Publishing (DTP)
# works-with::fax - Faxes
# works-with::file - Files
# works-with::font - Fonts
# works-with::logfile - System Logs
# works-with::music-notation - Music Notation
# works-with::people - People
# works-with::pim - Personal Information
# works-with::software:source - Source code
# works-with::spreadsheet - Spreadsheet
# works-with::text - Text
# works-with::unicode - Unicode
# works-with-format::docbook - Docbook
# works-with-format::info - Documentation in Info format
# works-with-format::ldif - LDIF
# works-with-format::sgml - SGML, Standard Generalized Markup Language
# works-with-format::svg - SVG, Scalable Vector Graphics
# works-with-format::tex - TeX, LaTeX and DVI
# works-with-format::vrml - VRML 3D Model
# works-with-format::xml - XML
# works-with-format::xml:rss - RSS Rich Site Summary
# works-with-format::xml:xslt - XSL Transformations (XSLT)
)
extractor = extractor.Extractor()
magic = magic.open(magic.MAGIC_MIME)
magic.load()
def mimetype(fname):
keys = extractor.extract(fname)
xkeys = {}
for k, v in keys:
if k in xkeys:
xkeys[k].append(v)
else:
xkeys[k] = [v]
namemagic = magic.file(fname)
contentmagic = magic.buffer(open(fname, "r").read(4096))
return "mimetype" in xkeys and xkeys['mimetype'][0] or contentmagic or namemagic
class Parser(OptionParser):
def __init__(self, *args, **kwargs):
OptionParser.__init__(self, *args, **kwargs)
def error(self, msg):
sys.stderr.write("%s: error: %s\n\n" % (self._get_prog_name(), msg))
self.print_help(sys.stderr)
sys.exit(2)
if __name__ == '__main__':
parser = Parser(usage="usage: %prog [options] filename",
version="%prog "+ VERSION,
description="search Debian packages that can handle a given file")
parser.add_option("--tagdb", default="/var/lib/debtags/package-tags", help="Tag database to use (default: %default)")
parser.add_option("--action", default=None, help="Show the packages that allow the given action on the file (default: %default)")
(options, args) = parser.parse_args()
if len(args) == 0:
parser.error("Please provide the name of a file to scan")
# Read full database
fullcoll = debtags.DB()
tag_filter = re.compile(r"^special::.+$|^.+::TODO$")
fullcoll.read(open(options.tagdb, "r"), lambda x: not tag_filter.match(x))
type = mimetype(args[0])
#print("Mime type:", type, file=sys.stderr)
found = set()
for match, tags in mime_map:
match = re.compile(match)
if match.match(type):
for t in tags:
found.add(t)
if len(found) == 0:
print("Unhandled mime type:", type, file=sys.stderr)
else:
if options.action != None:
apt_cache = apt.Cache()
query = found.copy()
query.add("role::program")
query.add("use::"+options.action)
print("Debtags query:", " && ".join(query))
subcoll = fullcoll.filter_packages_tags(lambda pt: query.issubset(pt[1]))
for i in subcoll.iter_packages():
aptpkg = apt_cache[i]
desc = aptpkg.raw_description.split("\n")[0]
print(i, "-", desc)
else:
print("Debtags query:", " && ".join(found))
query = found.copy()
query.add("role::program")
subcoll = fullcoll.filter_packages_tags(lambda pt: query.issubset(pt[1]))
uses = map(lambda x:x[5:], filter(lambda x:x.startswith("use::"), subcoll.iter_tags()))
print("Available actions:", ", ".join(uses))
# vim:set ts=4 sw=4: