root/trunk/umit/plugin/Containers.py @ 4746

Revision 4746, 12.5 kB (checked in by nopper, 4 years ago)

Merged revisions 4741-4743 via svnmerge from
http://svn.umitproject.org/svnroot/umit/branch/UmitPlugins

........

r4741 | nopper | 2009-05-02 20:16:37 +0200 (sab, 02 mag 2009) | 1 line


Adding testcase plugin

........

r4742 | nopper | 2009-05-02 20:17:20 +0200 (sab, 02 mag 2009) | 1 line


Adding extract_dir function and fixing extract_file in PluginReader?

........

r4743 | nopper | 2009-05-02 21:40:17 +0200 (sab, 02 mag 2009) | 3 lines


Added documentation for PluginReader?.
Added a second tutorial for localize.

........

Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (C) 2008 Adriano Monteiro Marques
4#
5# Author: Francesco Piccinno <stack.box@gmail.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21import os
22import os.path
23
24from fnmatch import fnmatch
25from zipfile import ZipFile, BadZipfile, ZIP_DEFLATED
26from xml.dom.minidom import parseString, getDOMImplementation
27from tempfile import mktemp
28
29from umit.plugin.Parser import Parser
30from umit.plugin.Atoms import StringFile
31
32from umit.core.Paths import Path
33from umit.core.UmitLogging import log
34
35# For setup functionality
36from distutils.dist import Distribution
37
38from distutils.core import setup as dist_setup
39from distutils.core import setup_keywords, extension_keywords
40
41from distutils.command.install import install as installcmd
42from distutils.command.install_lib import install_lib as install_libcmd
43
44try:
45    from distutils.command.install_egg_info import install_egg_info as install_egginfocmd
46
47    class PlugEggInstaller(install_egginfocmd):
48        def run(self):
49            pass
50
51except ImportError:
52    pass
53
54# For removing directory trees we need
55import shutil
56
57# FIXME: add others fields
58FIELDS = (
59    "url",
60    "conflicts",
61    "provides",
62    "needs",
63    "type",
64    "start_file",
65    "name",
66    "version", # only a convenient field
67    "description",
68    "author",
69    "license",
70    "artist",
71    "copyright",
72    "update"
73)
74
75SIGNATURE = "UmitPlugin"
76
77class BadPlugin(Exception):
78    "Used to track exceptions while loading Plugin"
79    pass
80
81class PluginReader(object):
82    def __init__(self, file):
83        self.path = file
84        self.enabled = False
85        self.hasprefs = False
86        self.parser = None
87
88        try:
89            self.file = ZipFile(file, "r")
90        except BadZipfile:
91            raise BadPlugin("Not a valid umit plugin format")
92       
93        if not self.parse_manifest():
94            raise BadPlugin("Not a valid umit plugin manifest")
95       
96        if not self.check_validity():
97            raise BadPlugin("Validation phase not passed")
98
99        # Needs some testing
100        self.parse_preferences()
101
102    def parse_manifest(self):
103        """
104        Parse the Manifest.xml inside the zip file and set the fields
105       
106        @return
107                False if the Manifest is not in the proper format
108                True if everything is ok
109        """
110       
111        try:
112            data = self.file.read("Manifest.xml")
113            doc = parseString(data)
114        except Exception:
115            return False
116       
117        if doc.documentElement.tagName != SIGNATURE:
118            return False
119       
120        for field in FIELDS:
121            setattr(self, field, "")
122       
123        for node in doc.documentElement.childNodes:
124            if node.nodeName in FIELDS and node.firstChild:
125                if node.nodeName in ('needs', 'provides', 'conflicts'):
126                    # Convert to list
127                    data = node.firstChild.data
128                    setattr(self, node.nodeName, \
129                            data.replace(" ", "").split(","))
130                else:
131                    setattr(self, node.nodeName, node.firstChild.data)
132       
133        return True
134
135    def parse_preferences(self):
136        try:
137            data = self.file.read('data/preferences.xml')
138
139            self.parser = Parser()
140            self.parser.parse_string(data)
141        except Exception, err:
142            return
143   
144    def check_validity(self):
145        """
146        Checks the fields presents and validity
147       
148        @return True if it's ok
149        """
150        # TODO: implement me!
151       
152        return True
153   
154    def __repr__(self):
155        #FIXME: that
156        return "[%s::Plugin]" % self.name
157
158    def get_logo(self, w=64, h=64):
159        "@return a gtk.dk.Pixbuf"
160
161        try:
162            # TODO: eliminate the mktemp workaround
163           
164            name = mktemp('.png')
165            f = open(name, 'wb')
166            f.write(self.file.read('data/logo.png'))
167            f.close()
168
169            import gtk
170
171            p = gtk.gdk.pixbuf_new_from_file_at_size(name, w, h)
172           
173            os.remove(name)
174
175            return p
176        except Exception, err:
177
178            from umit.gui.Icons import get_pixbuf
179
180            return get_pixbuf('extension_normal', w, h)
181
182    def get_path(self):
183        return self.path
184
185    def extract_dir(self, zip_path, maxdepth=0):
186        """
187        Extract a dir full recursive.
188        @param zip_path the directory to extract (for example data/test/)
189        @param maxdepth the max depth. Set 0 for fully recursive extraction.
190        @return a list containing extracted files or []
191        """
192        ret = []
193        if zip_path[-1] != '/':
194            zip_path += '/'
195        if zip_path[0] == '/':
196            zip_path = zip_path[1:]
197
198        sep_len = zip_path.count('/')
199
200        log.debug("Extracting files contained in %s" % zip_path)
201
202        for i in self.file.namelist():
203            if i.startswith(zip_path):
204                if maxdepth > 0 and \
205                   i.count('/') - sep_len - maxdepth + 1 != 0:
206
207                   log.debug("Skipping %s for maxdepth %d" % (i, maxdepth))
208                   continue
209
210                p = self.extract_file(i, keep_path=True)
211
212                if p: ret.append(p)
213
214        return ret
215       
216    def extract_file(self, zip_path, keep_path=False):
217        if zip_path not in self.file.namelist():
218            log.debug("The file %s seems to not exists in the zip file" % zip_path)
219            return None
220
221        plug_subdir = os.path.join(Path.config_dir, 'plugins-temp', self.name)
222
223        if not os.path.exists(plug_subdir):
224            os.mkdir(plug_subdir)
225
226        if keep_path:
227            # Recursive reconstruct the entire path
228            full_path = os.path.join(plug_subdir, os.path.dirname(zip_path))
229            if not os.path.isdir(full_path):
230                os.makedirs(full_path)
231            plug_subdir = full_path
232
233        log.debug("Extracting %s into %s " % (zip_path, plug_subdir))
234
235        name = os.path.join(plug_subdir,
236                            os.path.basename(zip_path))
237
238        f = open(name, 'wb+')
239        f.write(self.file.read(zip_path))
240        f.close()
241
242        return name
243
244    # Code ripped from gettext
245    def expand_lang(self, locale):
246        from locale import normalize
247        locale = normalize(locale)
248        COMPONENT_CODESET   = 1 << 0
249        COMPONENT_TERRITORY = 1 << 1
250        COMPONENT_MODIFIER  = 1 << 2
251        # split up the locale into its base components
252        mask = 0
253        pos = locale.find('@')
254        if pos >= 0:
255            modifier = locale[pos:]
256            locale = locale[:pos]
257            mask |= COMPONENT_MODIFIER
258        else:
259            modifier = ''
260        pos = locale.find('.')
261        if pos >= 0:
262            codeset = locale[pos:]
263            locale = locale[:pos]
264            mask |= COMPONENT_CODESET
265        else:
266            codeset = ''
267        pos = locale.find('_')
268        if pos >= 0:
269            territory = locale[pos:]
270            locale = locale[:pos]
271            mask |= COMPONENT_TERRITORY
272        else:
273            territory = ''
274        language = locale
275        ret = []
276        for i in range(mask+1):
277            if not (i & ~mask):  # if all components for this combo exist ...
278                val = language
279                if i & COMPONENT_TERRITORY: val += territory
280                if i & COMPONENT_CODESET:   val += codeset
281                if i & COMPONENT_MODIFIER:  val += modifier
282                ret.append(val)
283        ret.reverse()
284        return ret
285
286    def bind_translation(self, mofile):
287        """
288        @return a catalog on success or None
289        """
290       
291        # We foreach inside locale dir and find a proper dir
292       
293        try:
294            import gettext
295            import locale
296            LC_ALL = locale.setlocale(locale.LC_ALL, '')
297        except locale.Error:
298            return None
299
300        LANG, ENC = locale.getdefaultlocale()
301
302        if ENC is None:
303            ENC = "utf8"
304        if LANG is None:
305            LANG = "en_US"
306       
307        # FIXME: is the '/' os indipendent?
308        dir_lst = filter( \
309            lambda x: x.startswith("locale/") and x.endswith("%s.mo" % mofile), \
310            self.file.namelist() \
311        )
312        dir_lst.sort()
313
314        avaiable_langs = []
315       
316        for dirname in dir_lst:
317            t = dirname.split("/")
318
319            if len(t) < 3:
320                continue
321           
322            avaiable_langs.append(t[-2])
323
324        request = self.expand_lang(".".join([LANG, ENC]))
325
326        for req in request:
327            if req in avaiable_langs:
328                # Ok getted! Lucky day :)
329
330                return gettext.GNUTranslations(StringFile( \
331                    self.file.read("locale/%s/%s.mo" % (req, mofile)) \
332                ))
333           
334        return None
335
336class PluginWriter(object):
337    def __init__(self, **fields):
338        # Set to None and filter out the unused fields
339       
340        for i in FIELDS:
341            setattr(self, i, "")
342       
343        for i in fields:
344            if i in FIELDS:
345                setattr(self, i, fields[i])
346       
347        for i in FIELDS:
348            print "Field %s setted to %s" % (i, getattr(self, i))
349       
350        dirs = {
351            'bin'  : '*',
352            'data' : '*',
353            'lib'  : '*',
354            'locale' : '*'
355        }
356       
357        self.file = ZipFile(fields['output'], "w", ZIP_DEFLATED)
358
359        os.chdir("output")
360
361        for i in dirs:
362            self.dir_foreach(i, dirs[i])
363
364        os.chdir("..")
365       
366        self.file.writestr("Manifest.xml", self.create_manifest())
367        self.file.close()
368       
369        print ">> Plugin %s created." % fields['output']
370   
371    def dir_foreach(self, dir, pattern):
372        "Add files contained in dir and that pass the pattern validation phase."
373
374        for path, dirs, files in os.walk(dir):
375            if not files:
376                continue
377           
378            for file in files:
379                if not fnmatch(file, pattern):
380                    continue
381
382                print "Adding file %s %s %s" % (path, file, dir)
383               
384                self.file.write(os.path.join(path, file),
385                                os.path.join(path, file))
386   
387    def create_manifest(self):
388        """
389        Create a Manifest.xml file
390       
391        @return an xml manifest as string
392        """
393        doc = getDOMImplementation().createDocument(None, SIGNATURE, None)
394       
395        for field in FIELDS:
396            node = doc.createElement(field)
397            node.appendChild(doc.createTextNode(getattr(self, field)))
398            doc.documentElement.appendChild(node)
399       
400        print "Manifest.xml created"
401        return doc.toxml()
402
403
404#
405# distutils related class
406
407class PlugLibInstaller(install_libcmd):
408    def finalize_options(self):
409        self.install_dir = 'output/lib'
410        install_libcmd.finalize_options(self)
411
412class PlugInstaller(installcmd):
413    def finalize_options(self):
414        self.home = 'output'
415        installcmd.finalize_options(self)
416
417    def run(self):
418        installcmd.run(self)
419
420class PlugDistribution(Distribution):
421    def __init__(self, *attrs):
422        Distribution.__init__(self, *attrs)
423        self.cmdclass['install'] = PlugInstaller
424        self.cmdclass['install_lib'] = PlugLibInstaller
425        self.cmdclass['install_egg_info'] = PlugEggInstaller
426
427
428def setup(**attrs):
429    "Called to create a plugin like the dist-tools setup function"
430
431    setup_d = {}
432
433    # We need to filter out some fields
434    # to avoid
435    for attr in attrs:
436        if attr not in setup_keywords and \
437           attr not in extension_keywords:
438            setup_d[attr] = attrs[attr]
439
440    print ">> Running setup()"
441
442    import warnings
443    warnings.filterwarnings('ignore', r".*", UserWarning)
444
445    setup_d['distclass'] = PlugDistribution
446    dist_setup(**setup_d)
447
448    print ">> Creating plugin"
449    PluginWriter(**attrs)
450
451    print ">> Cleaning up"
452    shutil.rmtree('build')
453    shutil.rmtree('output')
Note: See TracBrowser for help on using the browser.