2 # Copyright (C) 2008, Thomas Leonard
3 # See the COPYING file for details, or visit http://0install.net.
6 from optparse
import OptionParser
7 import tempfile
, shutil
, os
8 from xml
.dom
import minidom
11 manifest_algorithm
= 'sha1new'
13 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
15 deb_category_to_freedesktop
= {
16 'devel' : 'Development',
18 'graphics' : 'Graphics',
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()
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
])
55 child
= subprocess
.Popen(cmd
, stdout
= subprocess
.PIPE
)
56 output
, unused
= child
.communicate()
58 print >>sys
.stderr
, output
59 print >>sys
.stderr
, "%s: code = %d" % (' '.join(cmd
), child
.returncode
)
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]
68 for x
in description_and_summary
[1:]:
77 description
+= x
[1:].replace('. ', '. ') + '\n'
78 description
= description
.strip()
80 pkg_name
= '(unknown)'
84 for line
in details
.split('\n'):
86 assert line
.startswith(' ')
89 key
, value
= line
.split(':', 1)
92 category
= deb_category_to_freedesktop
.get(value
)
94 print >>sys
.stderr
, "Warning: no mapping for Debian category '%s'" % value
95 elif key
== 'Package':
97 elif key
== 'Version':
99 elif key
== 'Architecture':
102 pkg_arch
= 'Linux-' + value
104 template
= '''<interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface">
106 doc
= minidom
.parseString(template
)
107 root
= doc
.documentElement
110 tmp
= tempfile
.mkdtemp(prefix
= 'deb2zero-')
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(';'):
121 if cat
in valid_categories
:
124 elif f
.startswith('./usr/bin'):
127 manifest
= read_child(['0store', 'manifest', tmp
, manifest_algorithm
])
128 digest
= manifest
.rsplit('\n', 2)[1]
129 subprocess
.check_call(['0store', 'add', digest
, 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
)
139 new
.appendChild(doc
.createTextNode(text
))
140 parent
.appendChild(doc
.createTextNode(after
))
143 add_node(root
, 'name', pkg_name
)
144 add_node(root
, 'summary', summary
)
145 add_node(root
, 'description', description
)
147 add_node(root
, 'category', category
)
148 group
= add_node(root
, 'group', '')
150 group
.setAttribute('arch', pkg_arch
)
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'))
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
)