root/branch/QuickScan/umit/plugin/Containers.py @ 4946

Revision 4946, 18.4 kB (checked in by cassiano, 4 years ago)

Merged revisions 4909,4930-4932,4934-4935 via svnmerge from
http://svn.umitproject.org/svnroot/umit/trunk

................

r4909 | nopper | 2009-06-22 11:55:24 -0300 (Mon, 22 Jun 2009) | 21 lines


Merged revisions 4781-4783,4908 via svnmerge from
http://svn.umitproject.org/svnroot/umit/branch/UmitPlugins


........

r4781 | nopper | 2009-05-08 21:26:32 +0200 (ven, 08 mag 2009) | 1 line


Switching to new schema see UmitPlugins.xsd in PM branch. Switched also from xml.minidom to sax to speed up things.

........

r4782 | nopper | 2009-05-09 16:11:38 +0200 (sab, 09 mag 2009) | 1 line


Switched update process to new xml schema file.

........

r4783 | nopper | 2009-05-09 17:02:06 +0200 (sab, 09 mag 2009) | 1 line


Updating documentation to the new schema

........

r4908 | nopper | 2009-06-22 16:43:59 +0200 (lun, 22 giu 2009) | 1 line


Fixing a typo

........

................

r4930 | luis | 2009-06-27 11:33:59 -0300 (Sat, 27 Jun 2009) | 1 line


Fixing #334 - import error when umit is installed from package

................

r4931 | luis | 2009-06-27 22:38:54 -0300 (Sat, 27 Jun 2009) | 1 line


Fixed #331 - Permission denied writing umit config file

................

r4932 | luis | 2009-06-27 23:09:05 -0300 (Sat, 27 Jun 2009) | 1 line


Typo in Flow Analyzer plugin

................

r4934 | luis | 2009-06-27 23:30:31 -0300 (Sat, 27 Jun 2009) | 3 lines


Initialized merge tracking via "svnmerge" with revisions "1-4699" from
http://svn.umitproject.org/svnroot/umit/branch/radialnet

................

r4935 | ignotus | 2009-06-27 23:54:28 -0300 (Sat, 27 Jun 2009) | 3 lines


Fix accents problem in header.

................

RevLine 
[3001]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
[4946]23import sys
[3001]24
25from fnmatch import fnmatch
[3790]26from zipfile import ZipFile, BadZipfile, ZIP_DEFLATED
[4946]27
28from StringIO import StringIO
29
30from xml.sax import handler, make_parser
31from xml.sax.saxutils import XMLGenerator
32from xml.sax.xmlreader import AttributesImpl
33
[3101]34from tempfile import mktemp
[3001]35
[4240]36from umit.plugin.Parser import Parser
37from umit.plugin.Atoms import StringFile
[3102]38
[4240]39from umit.core.Paths import Path
40from umit.core.UmitLogging import log
[3094]41
[3017]42# For setup functionality
43from distutils.dist import Distribution
[3018]44
[3017]45from distutils.core import setup as dist_setup
[3018]46from distutils.core import setup_keywords, extension_keywords
47
[3017]48from distutils.command.install import install as installcmd
49from distutils.command.install_lib import install_lib as install_libcmd
50
[3948]51try:
52    from distutils.command.install_egg_info import install_egg_info as install_egginfocmd
53
54    class PlugEggInstaller(install_egginfocmd):
55        def run(self):
56            pass
57
58except ImportError:
59    pass
60
[3017]61# For removing directory trees we need
62import shutil
63
[3001]64SIGNATURE = "UmitPlugin"
65
[4946]66class ManifestObject(object):
67    def __init__(self):
68
69        # Ok here we're using list object because py2.5 seems to not support
70        # index() for tuple. Stupid 2.5 :)
71
72        self.elements = [
73            ['name', 'version', 'description', 'url'],
74            ['start_file', 'update'],
75            ['provide', 'need', 'conflict'],
76            ['license', 'copyright', 'author',
77             'contributor', 'translator', 'artist']
78        ]
79
80        self.containers = [SIGNATURE, 'runtime', 'deptree', 'credits']
81
82        self.name = ''
83        self.version = ''
84        self.description = ''
85        self.url = ''
86
87        self.start_file = ''
88        self.update = []
89
90        self.provide = []
91        self.need = []
92        self.conflict = []
93
94        self.license = []
95        self.copyright = []
96        self.author = []
97        self.contributor = []
98        self.translator = []
99        self.artist = []
100
101        self.attr_type = ''
102
103    def check_validity(self, use_print=False):
104        """
105        Checks the fields presents and validity
106
107        @return True if it's ok
108        """
109
110        # This fields should be present and not null
111        fields = ('name', 'version', 'description', 'url', 'start_file',
112                  'license', 'copyright', 'author')
113
114        for element in fields:
115            if not getattr(self, element, None):
116                txt = 'Element named %s should not be null.' % (element)
117
118                if use_print:
119                    print txt
120                else:
121                    log.warning(txt)
122
123                return False
124
125        return True
126
127    def get_provides(self): return self.provide
128    def get_conflicts(self): return self.conflict
129    def get_needs(self): return self.need
130
131    provides = property(get_provides)
132    conflicts = property(get_conflicts)
133    needs = property(get_needs)
134
135class ManifestLoader(handler.ContentHandler, ManifestObject):
136    def __init__(self):
137        ManifestObject.__init__(self)
138
139        self.element_idx = 0
140        self.parsing_pass = -1
141        self.current_element = None
142        self.data = None
143
144    def startElement(self, name, attrs):
145        try:
146            self.element_idx = self.elements[self.parsing_pass].index(name)
147            self.current_element = \
148                self.elements[self.parsing_pass][self.element_idx]
149
150        except IndexError:
151            log.debug('Element named `%s` is not in %s' % \
152                      (name, self.elements[self.parsing_pass]))
153
154        except ValueError:
155            try:
156                idx = self.containers.index(name)
157
158                if self.parsing_pass < idx:
159                    self.parsing_pass = idx
160                else:
161                    log.warning('Element `%s` is not valid at this point. ' \
162                                'Should compare before %s' % (name,
163                                            self.containers[self.parsing_pass]))
164
165                if self.parsing_pass == 0:
166                    if type in attrs.keys():
167                        self.attr_type = attrs.get('type')
168                    else:
169                        self.attr_type = 'ui'
170
171            except ValueError:
172                log.debug('Element named `%s` not excepted.' % name)
173
174    def characters(self, ch):
175        if not self.current_element:
176            return
177
178        if not self.data:
179            self.data = ch
180        else:
181            self.data += ch
182
183    def endElement(self, name):
184        if self.current_element == name:
185            try:
186                attr = getattr(self, name)
187
188                if isinstance(attr, basestring):
189                    setattr(self, name, self.data)
190                elif isinstance(attr, list):
191                    attr.append(self.data)
192            finally:
193                self.current_element = None
194                self.data = None
195
196class ManifestWriter(object):
197    def startElement(self, names, attrs):
198        self.depth_idx += 1
199        self.writer.characters('  ' * self.depth_idx)
200        self.writer.startElement(names, attrs)
201
202    def endElement(self, name):
203        self.writer.endElement(name)
204        self.writer.characters('\n')
205        self.depth_idx -= 1
206
207    def __init__(self, manifest):
208        assert isinstance(manifest, ManifestObject)
209
210        self.output = StringIO()
211        self.depth_idx = -1
212        self.manifest = manifest
213        self.writer = XMLGenerator(self.output, 'utf-8')
214        self.writer.startDocument()
215
216        attr_vals = {
217            'xmlns' : 'http://www.umitproject.org',
218            'xsi:schemaLocation' : 'http://www.umitproject.org UmitPlugins.xsd',
219            'xmlns:xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
220            'type' : manifest.attr_type or 'ui'
221        }
222
223        self.startElement('UmitPlugin', AttributesImpl(attr_vals)),
224        self.writer.characters('\n')
225
226        # First phase saving
227        for elem in manifest.elements[0]:
228            self.add_element(elem)
229
230        # Runtime block
231        self.startElement('runtime', {})
232        self.writer.characters('\n')
233
234        self.add_element('start_file')
235        self.add_element('update')
236
237        self.writer.characters('  ' * self.depth_idx)
238        self.endElement('runtime')
239
240        # Deptree block
241        if manifest.provide or manifest.need or manifest.conflict:
242            self.startElement('deptree', {})
243            self.writer.characters('\n')
244
245            self.add_element('provide')
246            self.add_element('need')
247            self.add_element('conflict')
248
249            self.writer.characters('  ' * self.depth_idx)
250            self.endElement('deptree')
251
252        # Credits block
253        self.startElement('credits', {})
254        self.writer.characters('\n')
255
256        for elem in manifest.elements[3]:
257            self.add_element(elem)
258
259        self.writer.characters('  ' * self.depth_idx)
260        self.endElement('credits')
261
262        self.endElement('UmitPlugin')
263        self.writer.endDocument()
264
265    def add_element(self, name):
266        value = getattr(self.manifest, name, None)
267
268        if not value:
269            return
270
271        if isinstance(value, basestring):
272            self.startElement(name, {})
273            self.writer.characters(value)
274            self.endElement(name)
275        elif isinstance(value, list):
276            for item in value:
277                self.startElement(name, {})
278                self.writer.characters(item)
279                self.endElement(name)
280
281    def get_output(self):
282        return self.output.getvalue()
283
[3001]284class BadPlugin(Exception):
285    "Used to track exceptions while loading Plugin"
286    pass
287
[4946]288class PluginReader(ManifestLoader):
[3001]289    def __init__(self, file):
[4946]290        ManifestLoader.__init__(self)
291
[3001]292        self.path = file
293        self.enabled = False
294        self.hasprefs = False
295        self.parser = None
296
297        try:
298            self.file = ZipFile(file, "r")
[4946]299        except:
[3001]300            raise BadPlugin("Not a valid umit plugin format")
[4946]301
[3001]302        if not self.parse_manifest():
303            raise BadPlugin("Not a valid umit plugin manifest")
[4946]304
[3001]305        if not self.check_validity():
306            raise BadPlugin("Validation phase not passed")
307
[3021]308        # Needs some testing
309        self.parse_preferences()
[3001]310
311    def parse_manifest(self):
312        """
313        Parse the Manifest.xml inside the zip file and set the fields
[4946]314
[3001]315        @return
316                False if the Manifest is not in the proper format
317                True if everything is ok
318        """
[4946]319
[3001]320        try:
[4946]321            # TODO: add validation of the manifest
322
323            # Py2.5 doesn't have open on ZipFile object
324            fileobj = StringIO(self.file.read('Manifest.xml'))
325
326            parser = make_parser()
327            parser.setContentHandler(self)
328
329            parser.parse(fileobj)
330        except Exception, err:
331            log.debug('Exception in parse_manifest(): %s' % str(err))
[3001]332            return False
[4946]333
[3001]334        return True
335
336    def parse_preferences(self):
337        try:
[3018]338            data = self.file.read('data/preferences.xml')
[3001]339
340            self.parser = Parser()
341            self.parser.parse_string(data)
342        except Exception, err:
343            return
[4946]344
[3001]345    def __repr__(self):
346        #FIXME: that
347        return "[%s::Plugin]" % self.name
348
349    def get_logo(self, w=64, h=64):
350        "@return a gtk.dk.Pixbuf"
351
352        try:
[3101]353            # TODO: eliminate the mktemp workaround
[4946]354
[3101]355            name = mktemp('.png')
356            f = open(name, 'wb')
[3018]357            f.write(self.file.read('data/logo.png'))
[3001]358            f.close()
359
[3794]360            import gtk
361
[3001]362            p = gtk.gdk.pixbuf_new_from_file_at_size(name, w, h)
[4946]363
[3001]364            os.remove(name)
365
366            return p
[3101]367        except Exception, err:
[3794]368
[4240]369            from umit.gui.Icons import get_pixbuf
[3794]370
[3790]371            return get_pixbuf('extension_normal', w, h)
[3001]372
373    def get_path(self):
374        return self.path
375
[4746]376    def extract_dir(self, zip_path, maxdepth=0):
377        """
378        Extract a dir full recursive.
379        @param zip_path the directory to extract (for example data/test/)
380        @param maxdepth the max depth. Set 0 for fully recursive extraction.
381        @return a list containing extracted files or []
382        """
383        ret = []
384        if zip_path[-1] != '/':
385            zip_path += '/'
386        if zip_path[0] == '/':
387            zip_path = zip_path[1:]
388
389        sep_len = zip_path.count('/')
390
391        log.debug("Extracting files contained in %s" % zip_path)
392
393        for i in self.file.namelist():
394            if i.startswith(zip_path):
395                if maxdepth > 0 and \
396                   i.count('/') - sep_len - maxdepth + 1 != 0:
397
398                   log.debug("Skipping %s for maxdepth %d" % (i, maxdepth))
399                   continue
400
401                p = self.extract_file(i, keep_path=True)
402
403                if p: ret.append(p)
404
405        return ret
[4946]406
[4746]407    def extract_file(self, zip_path, keep_path=False):
408        if zip_path not in self.file.namelist():
409            log.debug("The file %s seems to not exists in the zip file" % zip_path)
410            return None
411
[4450]412        plug_subdir = os.path.join(Path.config_dir, 'plugins-temp', self.name)
[3790]413
414        if not os.path.exists(plug_subdir):
415            os.mkdir(plug_subdir)
416
[4746]417        if keep_path:
418            # Recursive reconstruct the entire path
419            full_path = os.path.join(plug_subdir, os.path.dirname(zip_path))
420            if not os.path.isdir(full_path):
421                os.makedirs(full_path)
422            plug_subdir = full_path
423
424        log.debug("Extracting %s into %s " % (zip_path, plug_subdir))
425
[3790]426        name = os.path.join(plug_subdir,
427                            os.path.basename(zip_path))
428
[4746]429        f = open(name, 'wb+')
[3790]430        f.write(self.file.read(zip_path))
431        f.close()
432
433        return name
434
[3094]435    # Code ripped from gettext
436    def expand_lang(self, locale):
437        from locale import normalize
438        locale = normalize(locale)
439        COMPONENT_CODESET   = 1 << 0
440        COMPONENT_TERRITORY = 1 << 1
441        COMPONENT_MODIFIER  = 1 << 2
442        # split up the locale into its base components
443        mask = 0
444        pos = locale.find('@')
445        if pos >= 0:
446            modifier = locale[pos:]
447            locale = locale[:pos]
448            mask |= COMPONENT_MODIFIER
449        else:
450            modifier = ''
451        pos = locale.find('.')
452        if pos >= 0:
453            codeset = locale[pos:]
454            locale = locale[:pos]
455            mask |= COMPONENT_CODESET
456        else:
457            codeset = ''
458        pos = locale.find('_')
459        if pos >= 0:
460            territory = locale[pos:]
461            locale = locale[:pos]
462            mask |= COMPONENT_TERRITORY
463        else:
464            territory = ''
465        language = locale
466        ret = []
467        for i in range(mask+1):
468            if not (i & ~mask):  # if all components for this combo exist ...
469                val = language
470                if i & COMPONENT_TERRITORY: val += territory
471                if i & COMPONENT_CODESET:   val += codeset
472                if i & COMPONENT_MODIFIER:  val += modifier
473                ret.append(val)
474        ret.reverse()
475        return ret
476
477    def bind_translation(self, mofile):
478        """
479        @return a catalog on success or None
480        """
[4946]481
[3094]482        # We foreach inside locale dir and find a proper dir
[4946]483
[3094]484        try:
485            import gettext
486            import locale
487            LC_ALL = locale.setlocale(locale.LC_ALL, '')
488        except locale.Error:
489            return None
490
491        LANG, ENC = locale.getdefaultlocale()
492
[3932]493        if ENC is None:
[3094]494            ENC = "utf8"
[3932]495        if LANG is None:
[3094]496            LANG = "en_US"
[4946]497
[3094]498        dir_lst = filter( \
499            lambda x: x.startswith("locale/") and x.endswith("%s.mo" % mofile), \
500            self.file.namelist() \
501        )
502        dir_lst.sort()
503
504        avaiable_langs = []
[4946]505
[3094]506        for dirname in dir_lst:
507            t = dirname.split("/")
508
509            if len(t) < 3:
510                continue
[4946]511
[3094]512            avaiable_langs.append(t[-2])
513
514        request = self.expand_lang(".".join([LANG, ENC]))
515
516        for req in request:
517            if req in avaiable_langs:
518                # Ok getted! Lucky day :)
519
520                return gettext.GNUTranslations(StringFile( \
521                    self.file.read("locale/%s/%s.mo" % (req, mofile)) \
522                ))
[4946]523
[3094]524        return None
525
[4946]526class PluginWriter(ManifestObject):
[3017]527    def __init__(self, **fields):
[4946]528        ManifestObject.__init__(self)
529
[3001]530        # Set to None and filter out the unused fields
[4946]531
532        FIELDS = ('name', 'version', 'description', 'url', 'start_file',
533                  'update', 'provide', 'need', 'conflict', 'license',
534                  'copyright', 'author', 'contributor', 'translator', 'artist')
535
536        # Filter out fields that are not related to the schema
537
[3001]538        for i in fields:
539            if i in FIELDS:
540                setattr(self, i, fields[i])
[4946]541
542        if not self.check_validity(use_print=True):
543            print "!! Manifest could not be created."
544            sys.exit(-1)
545
[3001]546        dirs = {
[3017]547            'bin'  : '*',
548            'data' : '*',
[3094]549            'lib'  : '*',
550            'locale' : '*'
[3001]551        }
[4946]552
[3790]553        self.file = ZipFile(fields['output'], "w", ZIP_DEFLATED)
[3023]554
555        os.chdir("output")
556
[3001]557        for i in dirs:
[3023]558            self.dir_foreach(i, dirs[i])
559
560        os.chdir("..")
[4946]561
562        writer = ManifestWriter(self)
563        self.file.writestr('Manifest.xml', writer.get_output())
[3001]564        self.file.close()
[4946]565
[3017]566        print ">> Plugin %s created." % fields['output']
[4946]567
[3001]568    def dir_foreach(self, dir, pattern):
569        "Add files contained in dir and that pass the pattern validation phase."
[3023]570
[3001]571        for path, dirs, files in os.walk(dir):
572            if not files:
573                continue
[4946]574
[3001]575            for file in files:
576                if not fnmatch(file, pattern):
577                    continue
[3023]578
579                print "Adding file %s %s %s" % (path, file, dir)
[4946]580
[3001]581                self.file.write(os.path.join(path, file),
[3023]582                                os.path.join(path, file))
[4946]583
[3001]584    def create_manifest(self):
585        """
586        Create a Manifest.xml file
[4946]587
[3001]588        @return an xml manifest as string
589        """
590        doc = getDOMImplementation().createDocument(None, SIGNATURE, None)
[4946]591
[3001]592        for field in FIELDS:
593            node = doc.createElement(field)
594            node.appendChild(doc.createTextNode(getattr(self, field)))
595            doc.documentElement.appendChild(node)
[4946]596
[3001]597        print "Manifest.xml created"
598        return doc.toxml()
599
[3017]600
601#
602# distutils related class
603
604class PlugLibInstaller(install_libcmd):
605    def finalize_options(self):
606        self.install_dir = 'output/lib'
607        install_libcmd.finalize_options(self)
608
609class PlugInstaller(installcmd):
610    def finalize_options(self):
611        self.home = 'output'
612        installcmd.finalize_options(self)
613
614    def run(self):
615        installcmd.run(self)
616
617class PlugDistribution(Distribution):
618    def __init__(self, *attrs):
619        Distribution.__init__(self, *attrs)
620        self.cmdclass['install'] = PlugInstaller
621        self.cmdclass['install_lib'] = PlugLibInstaller
622        self.cmdclass['install_egg_info'] = PlugEggInstaller
623
624
625def setup(**attrs):
[3001]626    "Called to create a plugin like the dist-tools setup function"
[3018]627
628    setup_d = {}
629
630    # We need to filter out some fields
631    # to avoid
632    for attr in attrs:
633        if attr not in setup_keywords and \
634           attr not in extension_keywords:
635            setup_d[attr] = attrs[attr]
636
[3017]637    print ">> Running setup()"
[3001]638
[3023]639    import warnings
640    warnings.filterwarnings('ignore', r".*", UserWarning)
641
[3018]642    setup_d['distclass'] = PlugDistribution
643    dist_setup(**setup_d)
644
[3017]645    print ">> Creating plugin"
646    PluginWriter(**attrs)
647
648    print ">> Cleaning up"
649    shutil.rmtree('build')
650    shutil.rmtree('output')
[4946]651
652if __name__ == "__main__":
653    parser = make_parser()
654    loader = ManifestLoader()
655    parser.setContentHandler(loader)
656    parser.parse(open('test.xml'))
657    loader.dump()
Note: See TracBrowser for help on using the browser.