Release 0.2
[deb2zero.git] / deb2zero
blob9b92d73544a0ee69b1848e272fb84f9cf4731bbc
1 #!/usr/bin/env python
2 # Copyright (C) 2008, Thomas Leonard
3 # See the COPYING file for details, or visit http://0install.net.
5 import sys, time
6 from optparse import OptionParser
7 import tempfile, shutil, os
8 from xml.dom import minidom
9 import subprocess
11 manifest_algorithm = 'sha1new'
13 from zeroinstall.injector.namespaces import XMLNS_IFACE
15 deb_category_to_freedesktop = {
16 'devel' : 'Development',
17 'web' : 'Network',
18 'graphics' : 'Graphics',
21 valid_categories = [
22 'AudioVideo',
23 'Audio',
24 'Video',
25 'Development',
26 'Education',
27 'Game',
28 'Graphics',
29 'Network',
30 'Office',
31 'Settings',
32 'System',
33 'Utility',
36 parser = OptionParser('usage: %prog [options] http://.../package.deb\n'
37 'Create a Zero Install feed for a Debian package.')
38 (options, args) = parser.parse_args()
40 if len(args) != 1:
41 parser.print_help()
42 sys.exit(1)
44 pkg_url = args[0]
45 assert pkg_url.startswith('http:') or \
46 pkg_url.startswith('https:') or \
47 pkg_url.startswith('ftp:')
48 deb_file = os.path.abspath(pkg_url.rsplit('/', 1)[1])
50 if not os.path.exists(deb_file):
51 print >>sys.stderr, "File '%s' not found, so downloading from %s..." % (deb_file, pkg_url)
52 subprocess.check_call(['wget', pkg_url])
54 def read_child(cmd):
55 child = subprocess.Popen(cmd, stdout = subprocess.PIPE)
56 output, unused = child.communicate()
57 if child.returncode:
58 print >>sys.stderr, output
59 print >>sys.stderr, "%s: code = %d" % (' '.join(cmd), child.returncode)
60 sys.exit(1)
61 return output
63 details = read_child(['dpkg-deb', '--info', deb_file])
65 description_and_summary = details.split('\n Description: ')[1].split('\n')
66 summary = description_and_summary[0]
67 description = ''
68 for x in description_and_summary[1:]:
69 if not x: continue
70 assert x[0] == ' '
71 x = x[1:]
72 if x[0] != ' ':
73 break
74 if x == ' .':
75 description += '\n'
76 else:
77 description += x[1:].replace('. ', '. ') + '\n'
78 description = description.strip()
80 pkg_name = '(unknown)'
81 pkg_version = None
82 pkg_arch = None
83 category = None
84 for line in details.split('\n'):
85 if not line: continue
86 assert line.startswith(' ')
87 line = line[1:]
88 if ':' in line:
89 key, value = line.split(':', 1)
90 value = value.strip()
91 if key == 'Section':
92 category = deb_category_to_freedesktop.get(value)
93 if not category:
94 print >>sys.stderr, "Warning: no mapping for Debian category '%s'" % value
95 elif key == 'Package':
96 pkg_name = value
97 elif key == 'Version':
98 pkg_version = value
99 elif key == 'Architecture':
100 if value == 'amd64':
101 value = 'x86_64'
102 pkg_arch = 'Linux-' + value
104 template = '''<interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface">
105 </interface>'''
106 doc = minidom.parseString(template)
107 root = doc.documentElement
109 pkg_main = None
110 tmp = tempfile.mkdtemp(prefix = 'deb2zero-')
111 try:
112 files = read_child(['dpkg-deb', '-X', deb_file, tmp])
114 for f in files.split('\n'):
115 if f.endswith('.desktop'):
116 full = os.path.join(tmp, f)
117 for line in file(full):
118 if line.startswith('Categories'):
119 for cat in line.split('=', 1)[1].split(';'):
120 cat = cat.strip()
121 if cat in valid_categories:
122 category = cat
123 break
124 elif f.startswith('./usr/bin'):
125 pkg_main = f[2:]
127 manifest = read_child(['0store', 'manifest', tmp, manifest_algorithm])
128 digest = manifest.rsplit('\n', 2)[1]
129 subprocess.check_call(['0store', 'add', digest, tmp])
130 finally:
131 shutil.rmtree(tmp)
133 def add_node(parent, element, text = None, before = ' ', after = '\n'):
134 doc = parent.ownerDocument
135 parent.appendChild(doc.createTextNode(before))
136 new = doc.createElementNS(XMLNS_IFACE, element)
137 parent.appendChild(new)
138 if text:
139 new.appendChild(doc.createTextNode(text))
140 parent.appendChild(doc.createTextNode(after))
141 return new
143 add_node(root, 'name', pkg_name)
144 add_node(root, 'summary', summary)
145 add_node(root, 'description', description)
146 if category:
147 add_node(root, 'category', category)
148 group = add_node(root, 'group', '')
149 if pkg_arch:
150 group.setAttribute('arch', pkg_arch)
151 else:
152 print >>sys.stderr, "No Architecture: field in .deb."
153 impl = add_node(group, 'implementation', before = '\n ', after = '\n ')
154 impl.setAttribute('id', digest)
155 impl.setAttribute('version', pkg_version)
156 impl.setAttribute('released', time.strftime('%Y-%m-%d'))
157 if pkg_main:
158 impl.setAttribute('main', pkg_main)
160 archive = add_node(impl, 'archive', before = '\n ', after = '\n ')
161 archive.setAttribute('href', pkg_url)
162 archive.setAttribute('size', str(os.path.getsize(deb_file)))
164 print "<?xml version='1.0'?>"
165 root.writexml(sys.stdout)
166 print