1# -*- coding: utf-8 -*-
2
3# ==============================================================================
4# COPYRIGHT (C) 1991 - 2015  EDF R&D                  WWW.CODE-ASTER.ORG
5# THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
6# IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
7# THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
8# (AT YOUR OPTION) ANY LATER VERSION.
9#
10# THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
11# WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
12# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
13# GENERAL PUBLIC LICENSE FOR MORE DETAILS.
14#
15# YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
16# ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
17#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
18# ==============================================================================
19
20"""
21Tools for developpers :
22    - search messages
23"""
24
25
26import os
27import os.path as osp
28import re
29import pickle
30import time
31from glob import glob
32
33from asrun.common.i18n import _
34from asrun.mystring     import ufmt
35from asrun.config       import AsterConfig, build_config_of_version
36from asrun.build        import AsterBuild, unigest2dict
37from asrun.progress     import Progress
38
39
40class CataMessageError(Exception):
41    """Error with a catalog of messages."""
42
43    def __init__(self, fcata, reason):
44        self.reason = reason
45        self.fcata = fcata
46
47    def __str__(self):
48        return ufmt(_("%s: %s, error: %s"),
49            self.__class__.__name__, self.fcata, self.reason)
50
51
52class MessageError(Exception):
53    """Error on a message."""
54
55    def __init__(self, msgid, reason):
56        self.reason = reason
57        self.msgid = msgid
58
59    def __str__(self):
60        return "%s: %s, error: %s" \
61            % (self.__class__.__name__, self.msgid, self.reason)
62
63
64class MESSAGE_MANAGER(object):
65    """Classe pour récupérer des informations sur le catalogue de messages.
66    """
67    def __init__(self, repref,
68                 fort=('bibfor', 'bibf90'),
69                 pyt='bibpyt',
70                 capy='catapy',
71                 cache_dict=None, verbose=True, force=False,
72                 ignore_comments=True, debug=False,
73                 surch_fort=[], surch_pyt=[], unig=None):
74        """Initialisations."""
75        if type(fort) not in (list, tuple):
76            fort = [fort,]
77        if type(surch_fort) not in (list, tuple):
78            surch_fort = [surch_fort,]
79        if type(surch_pyt) not in (list, tuple):
80            surch_pyt = [surch_pyt,]
81        self.repref          = repref
82        self.fort            = [osp.join(repref, rep) for rep in fort]
83        self.pyt             = osp.join(repref, pyt)
84        self.capy            = osp.join(repref, capy)
85        self.surch_fort      = [osp.abspath(d) for d in surch_fort]
86        self.surch_pyt       = [osp.abspath(d) for d in surch_pyt]
87        self.verbose         = verbose
88        self.debug           = debug
89        self.force           = force
90        self.message_dir     = 'Messages'
91        self.cache_dict      = cache_dict or osp.join('/tmp', 'messages_dicts.pick')
92        self.ignore_comments = ignore_comments
93        self.wrk_fort        = osp.join(os.getcwd(), 'F')
94        self.wrk_pyt         = osp.join(os.getcwd(), 'PY')
95
96        self.unig            = unig
97        # init AsterConfig, AsterBuild objects
98        fconf = osp.join(self.repref, 'config.txt')
99        self.conf_obj  = AsterConfig(fconf, run=None)
100        self.build_obj = AsterBuild(run=None, conf=self.conf_obj)
101        if unig:
102            # use with_fordepla=False not to mark moved files as removed
103            self.unig = unigest2dict(unig, self.conf_obj, with_fordepla=True)
104
105        if self.verbose:
106            print('Repertoires :\n   REF=%s\n   FORTRAN=%s\n   PYTHON=%s\n   CAPY=%s' \
107                % (self.repref, self.fort, self.pyt, self.capy))
108
109        self.l_cata            = self._get_list_cata()
110        self.l_src, self.l_pyt = self._get_list_src()
111#      self._build_regexp()
112
113    def read_cata(self):
114        """Read all catalogues"""
115        self._build_dict()
116        self.print_stats()
117
118
119    def _filename_cata(self, cata):
120        """Retourne le nom du fichier associé à un catalogue."""
121        name = osp.join(self.pyt, self.message_dir, cata + '.py')
122        return name
123
124
125    def _id2filename(self, msgid):
126        """Retourne modelisa5 à partir de MODELISA5_46"""
127        mat = re.search('([A-Z]+[0-9]*)_([0-9]+)', msgid)
128        if mat == None:
129            raise MessageError(msgid, _('invalid message id'))
130        cata, num = mat.groups()
131        return self._filename_cata(cata.lower()), int(num)
132
133
134    def _filename2id(self, fcata, num):
135        """Retourne MODELISA5_46 à partir de modelisa5"""
136        return '%s_%d' % (osp.splitext(osp.basename(fcata))[0].upper(), num)
137
138
139    def makedirs(self):
140        """Crée les répertoires pour les fichiers modifiés
141        """
142        for d in (self.wrk_fort, self.wrk_pyt):
143            if not osp.isdir(d):
144                os.makedirs(d)
145
146
147    def _get_list_cata(self):
148        """Retourne la liste des catalogues de messages.
149        """
150        re_mess = re.compile('#@.*Messages')
151        s_cata = set(glob(osp.join(self.pyt, self.message_dir, '*.py')))
152        s_suppr = set()
153        l_surch = []
154        # ajouter la surcharge et retirer le catalogue d'origine
155        for dsrc in self.surch_pyt:
156            for f in glob(osp.join(dsrc, '*.py')) + glob(osp.join(dsrc, '*', '*.py')):
157                with open(f, 'r') as f:
158                    txt = f.read()
159                if re_mess.search(txt):
160                    l_surch.append(osp.abspath(f))
161                    fsup = osp.join(self.pyt, self.message_dir, osp.basename(f))
162                    if osp.exists(fsup):
163                        s_suppr.add(fsup)
164        s_cata.update(l_surch)
165        if len(l_surch) > 0:
166            print('%5d catalogues en surcharge : ' % len(l_surch))
167            print(os.linesep.join(l_surch))
168
169        s_cata = set([osp.abspath(f) for f in s_cata \
170                if not osp.basename(f) in ['__init__.py', 'context_info.py', 'fermetur.py']])
171        l_suppr = []
172        if self.unig and len(self.unig['py']) > 0:
173            l_suppr = [osp.abspath(osp.join(self.repref, f)) \
174                                            for f in self.unig['py'] if f.find(self.message_dir) > -1]
175            s_suppr.update(l_suppr)
176        l_suppr = list(s_suppr)
177        l_suppr.sort()
178        if len(l_suppr) > 0:
179            print('%5d catalogue(s) supprime(s) :' % len(l_suppr))
180            if self.verbose: print(os.linesep.join(l_suppr))
181        l_cata = list(s_cata.difference(s_suppr))
182        l_cata.sort()
183        return l_cata
184
185
186    def check_cata(self):
187        """Vérifie les catalogues.
188        """
189        if self.verbose:
190            print('%5d catalogues dans %s + %s' \
191                % (len(self.l_cata), self.pyt, self.surch_pyt))
192        all_msg = []
193        error_occurs = False
194        for f in self.l_cata:
195            try:
196                cata = CATA(f)
197                cata.check()
198                all_msg.extend([self._filename2id(f, i) for i in cata])
199                if self.verbose:
200                    print(cata)
201            except CataMessageError as msg:
202                error_occurs = True
203                print(msg)
204                self.l_cata.remove(f)
205        if error_occurs:
206            raise CataMessageError(_("global"), _("errors occurred!"))
207        all_msg = set(all_msg)
208
209        # messages jamais appelés
210        used = set(self.d_msg_call.keys())
211        unused =  list(all_msg.difference(used))
212        unused.sort()
213
214        union = used.union(all_msg)
215        not_found = list(used.difference(all_msg))
216        not_found.sort()
217        if self.verbose:
218            print('%6d messages dans les catalogues' % len(all_msg))
219            print('dont %6d messages appeles presents dans les catalogues' % (len(used) - len(not_found)))
220            print('  et %6d messages inutilises' % len(unused))
221            print('   + %6d messages appeles absents des catalogues' % len(not_found))
222        #print '%6d messages total (union pour verif)' % len(union)
223        return unused, not_found
224
225
226    def _build_regexp(self):
227        """Construit la liste des expressions régulières pour la recherche des messages.
228        """
229        l_regexp = []
230        for cata in self.l_cata:
231            cata = osp.splitext(osp.basename(cata))[0]
232            l_regexp.append('%s_[0-9]+' % cata.upper())
233        self.regexp = re.compile('|'.join(l_regexp))
234
235
236    def _get_list_src(self):
237        """Retourne la liste des routines fortran et python.
238        """
239        l_f   = self._get_list_fort()
240        l_pyt = self._get_list_python()
241        if self.verbose:
242            print('%5d routines fortran dans %s + %s' % (len(l_f), self.fort, self.surch_fort))
243            print('%5d modules python   dans %s + %s' % (len(l_pyt), self.pyt, self.surch_pyt))
244        return l_f, l_pyt
245
246
247    def _get_list_fort(self):
248        """Retourne la liste des fichiers sources fortran/fortran90."""
249        s_f = set()
250        for dsrc in self.fort:
251            s_f.update(glob(osp.join(dsrc, '*.f')))
252            s_f.update(glob(osp.join(dsrc, '*', '*.f')))
253            s_f.update(glob(osp.join(dsrc, '*.F')))
254            s_f.update(glob(osp.join(dsrc, '*', '*.F')))
255            s_f.update(glob(osp.join(dsrc, '*.F90')))
256            s_f.update(glob(osp.join(dsrc, '*', '*.F90')))
257        if self.build_obj.support('waf'):
258            l_f = [osp.abspath(f) for f in s_f]
259            l_f.sort()
260            return l_f
261        d_f = {}
262        for f in s_f:
263            assert d_f.get(osp.basename(f)) is None, 'ERROR : %s  (old : %s)' % (f, d_f[osp.basename(f)])
264            d_f[osp.basename(f)] = f
265        # surcharge
266        s_surch = set()
267        for dsrc in self.surch_fort:
268            s_surch.update(glob(osp.join(dsrc, '*.f')))
269            s_surch.update(glob(osp.join(dsrc, '*', '*.f')))
270            s_surch.update(glob(osp.join(dsrc, '*.F')))
271            s_surch.update(glob(osp.join(dsrc, '*', '*.F')))
272        if len(s_surch) > 0:
273            l_surch = list(s_surch)
274            l_surch.sort()
275            print('%5d sources en surcharge : ' % len(l_surch))
276            print(os.linesep.join(l_surch))
277        # retirer des sources originaux ceux de la surcharge...
278        s_suppr = set()
279        for f in s_surch:
280            fexist = d_f.get(osp.basename(f))
281            if fexist:
282                s_suppr.add(fexist)
283                if self.verbose: print('suppression :', fexist)
284        # ... et ceux de l'unigest
285        if self.unig:
286            iunig = 0
287            for f in self.unig['f'] + self.unig['f90']:
288                iunig += 1
289                s_suppr.add(d_f.get(osp.basename(f), ''))
290                if self.verbose: print('suppression :', f)
291            if iunig > 0:
292                print('%5d source(s) supprime(s).' % iunig)
293
294        s_f.difference_update(s_suppr)
295        s_f.update(s_surch)
296        l_f = [osp.abspath(f) for f in s_f]
297        l_f.sort()
298        return l_f
299
300
301    def _get_list_python(self):
302        """Retourne la liste des fichiers python
303        """
304        s_pyt = set()
305        s_pyt.update(glob(osp.join(self.pyt, '*.py')))
306        s_pyt.update(glob(osp.join(self.pyt, '*', '*.py')))
307        s_pyt.update(glob(osp.join(self.capy, '*.capy')))
308        s_pyt.update(glob(osp.join(self.capy, '*', '*.capy')))
309        if self.build_obj.support('waf'):
310            l_pyt = [osp.abspath(f) for f in s_pyt]
311            l_pyt.sort()
312            return l_pyt
313        d_py = {}
314        for f in s_pyt:
315            typ = osp.splitext(f)[-1][1:]
316            key = self.build_obj.GetCModif(typ, f)
317            assert d_py.get(key) is None, 'ERROR : %s  (old : %s)' % (key, d_py[key])
318            d_py[key] = f
319        # surcharge
320        s_surch = set()
321        for dsrc in self.surch_pyt:
322            s_surch.update(glob(osp.join(dsrc, '*.py')))
323            s_surch.update(glob(osp.join(dsrc, '*', '*.py')))
324            s_surch.update(glob(osp.join(dsrc, '*.capy')))
325            s_surch.update(glob(osp.join(dsrc, '*', '*.capy')))
326        if len(s_surch) > 0:
327            l_surch = list(s_surch)
328            l_surch.sort()
329            print('%5d module(s) python en surcharge : ' % len(l_surch))
330            print(os.linesep.join(l_surch))
331        # retirer des sources originaux ceux de la surcharge...
332        s_suppr = set()
333        for f in s_surch:
334            typ = osp.splitext(f)[-1][1:]
335            key = self.build_obj.GetCModif(typ, f)
336            fexist = d_py.get(key)
337            if fexist:
338                s_suppr.add(fexist)
339                if self.verbose: print('suppression :', fexist)
340        # ... et ceux de l'unigest
341        if self.unig:
342            iunig = 0
343            for typ in ('py', 'capy'):
344                for f in self.unig[typ]:
345                    iunig += 1
346                    fabs = osp.abspath(osp.join(self.repref, f))
347                    key = self.build_obj.GetCModif(typ, fabs)
348                    s_suppr.add(d_py.get(key, ''))
349                    if self.verbose: print('suppression :', fabs)
350            if iunig > 0:
351                print('%5d module(s) python supprime(s).' % iunig)
352
353        s_pyt.difference_update(s_suppr)
354        s_pyt.update(s_surch)
355        l_pyt = [osp.abspath(f) for f in s_pyt]
356        l_pyt.sort()
357        return l_pyt
358
359
360    def search_message(self, fich):
361        """Retourne les messages utilisés dans 'fich'.
362        """
363        try:
364            with open(fich, 'r') as f:
365                txt = f.read()
366        except IOError as msg:
367            print(_('Error with file %s : %s') % (fich, msg))
368            return []
369        ext = osp.splitext(fich)[-1]
370        txt = clean_source(txt, ext, ignore_comments=self.ignore_comments, wrap=True)
371
372        if osp.splitext(fich)[-1] not in ('.py', '.capy'):
373            expr = (r'CALL\s+(U2MES.|UTEXC[MP]+|UTPRIN|UTMESS|UTMESS_CORE)\s*'
374                    r'\(([^,]*?), *[\'\"]+(.*?)[\'\"]+ *[,\)]+')
375        else:
376            expr = (r'[\s\.:]+(UTMESS|GetText)\s*\(([^,]*?), *'
377                    r'[\'\"]+(.*?)[\'\"]+ *[,\)]+')
378        all_msg = re.findall(expr, txt, flags=re.I)
379        l_msg = []
380        for found in all_msg:
381            sub, typ, msg = found
382            if msg.startswith('FERMETUR_'):
383                pass
384            elif re.search('^[A-Z0-9]+_[0-9]+$', msg) is None:
385                print("Invalid message id ('%s') in file %s (%s)" % (msg, fich, sub))
386            else:
387                mattyp = re.search(r'[\'\" ]+(.*?)[\+\'\" ]+', typ)
388                # variables and numbers (for exception) are allowed
389                if mattyp is not None and \
390                   mattyp.group(1) not in ('', 'A', 'I', 'E', 'S', 'F', 'M', 'D'):
391                   # may be '' for example STY//'+'
392                    print("Invalid message type (%s) for message '%s' in file %s" \
393                        % (mattyp.group(1), msg, fich))
394                    print("type = %s" % typ)
395                else:
396                    l_msg.append(msg)
397#      l_msg = self.regexp.findall(txt)
398
399        # verif
400        l_res = []
401        for msg in l_msg:
402            spl = msg.split('_')
403            assert len(spl) == 2, 'ERROR invalid : %s' % msg
404            msg = '%s_%d' % (spl[0], int(spl[1]))
405            l_res.append(msg)
406
407        return l_res
408
409
410    def _build_dict(self):
411        """Construit les dictionnaires :
412            fichier source : liste des messsages appelés
413            message : liste des fichiers appelant ce message
414        """
415        # est-ce dans le cache ?
416        if not self.force and osp.isfile(self.cache_dict) \
417                                and os.stat(self.cache_dict).st_size > 0:
418            if self.verbose:
419                print('Load dicts from cache (%s)...' % self.cache_dict)
420            with open(self.cache_dict, 'rb') as pick:
421                self.d_msg_used = pickle.load(pick)
422                self.d_msg_call = pickle.load(pick)
423
424        else:
425            self.d_msg_used = {}
426            self.d_msg_call = {}
427            l_all = self.l_src + self.l_pyt
428            if self.verbose:
429                p = Progress(maxi=len(l_all), format='%5.1f %%',
430                                msg='Analyse des sources... ')
431            for i, f in enumerate(l_all):
432                if self.verbose:
433                    p.Update(i)
434#             key = f.replace(self.repref, '')
435                key = re.sub('^%s/*', '', f)
436                lm = self.search_message(f)
437                if len(lm) > 0:
438                    self.d_msg_used[key] = lm
439                    for msg in lm:
440                        self.d_msg_call[msg] = self.d_msg_call.get(msg, []) + [key,]
441
442            if self.verbose:
443                p.End()
444                #pprint.pprint(self.d_msg_used)
445
446            with open(self.cache_dict, 'wb') as pick:
447                pickle.dump(self.d_msg_used, pick)
448                pickle.dump(self.d_msg_call, pick)
449
450
451    def print_stats(self):
452        """Affiche les stats sur les données lues/construites
453        """
454        if self.verbose:
455            print('%6d messages appelés dans les sources' % len(list(self.d_msg_call.keys())))
456            print('%6d fichiers sources appellent le catalogue de messages' % len(list(self.d_msg_used.keys())))
457
458
459    def move(self, oldid, dest, reuse_hole=True):
460        """Déplace le message 'oldid' dans le catalogue 'dest'.
461        """
462        if self.verbose:
463            print('--- moving "%s" into "%s"' % (oldid, dest))
464        # catalogue objects
465        old_f, old_num = self._id2filename(oldid)
466        new_f = self._filename_cata(dest.lower())
467
468        # have these catalogues been already changed ?
469        fmod = osp.join(self.wrk_pyt, osp.basename(old_f))
470        if osp.isfile(fmod):
471            print('from %s' % fmod)
472            old_f = fmod
473        fmod = osp.join(self.wrk_pyt, osp.basename(new_f))
474        if osp.isfile(fmod):
475            print('from %s' % fmod)
476            new_f = fmod
477
478        old_cata = CATA(old_f)
479        new_cata = CATA(new_f)
480        if self.verbose:
481            print(old_cata)
482            print(new_cata)
483        new_num = new_cata.get_new_id(reuse_hole)
484        if new_num < 0:
485            raise CataMessageError(new_f, _('no message id available in this catalog'))
486        newid = self._filename2id(new_f, new_num)
487
488        # check message existence
489        if old_cata[old_num] == None:
490            raise MessageError(oldid, _('unknown message'))
491
492        new_cata[new_num] = old_cata[old_num]
493        del old_cata[old_num]
494
495        # write modified catalogues
496        self.makedirs()
497        fout = osp.join(self.wrk_pyt, osp.basename(old_f))
498        content = old_cata.get_content()
499        with open(fout, 'w') as f:
500            f.write(content)
501        fout = osp.join(self.wrk_pyt, osp.basename(new_f))
502        content = new_cata.get_content()
503        with open(fout, 'w') as f:
504            f.write(content)
505        print('Nouveau fichier : %s' % fout)
506
507        # modify source using 'oldid' message
508        l_src = self.d_msg_call.get(oldid, [])
509        for f in l_src:
510            ext = osp.splitext(f)[-1]
511            rdest = self.wrk_fort
512            if ext == '.py':
513                rdest = self.wrk_pyt
514            # already changed ?
515            fmod = osp.join(rdest, osp.basename(f))
516            if osp.isfile(fmod):
517                print('from %s' % fmod)
518                f = fmod
519            with open(osp.join(self.repref, f), 'r') as f:
520                txt = f.read()
521            new = re.sub('%s([\'\"]+)' % oldid, newid + r'\1', txt)
522            fout = osp.join(rdest, osp.basename(f))
523            with open(fout, 'w') as f:
524                f.write(new)
525            print('Nouveau fichier : %s' % fout)
526
527
528    def who_use(self, msg):
529        """Qui utilise le message 'msg' ?
530        """
531        return tuple(self.d_msg_call.get(msg.upper(), []))
532
533
534    def get_key(self, sub):
535        """Retourne la clé dans d_msg_used correspondant à 'sub'.
536        Seule la première est retournée si jamais il y en avait plusieurs.
537        """
538        l_allsub = list(self.d_msg_used.keys())
539        l_sub = [f for f in l_allsub if f.split(os.sep)[-1] == sub]
540        if len(l_sub) > 1:
541            print('Plusieurs routines correspondent : %s' % ', '.join(l_sub))
542            print('On utilise la première valeur.')
543        if len(l_sub) == 0:
544            result = None
545        else:
546            result = l_sub[0]
547        return result
548
549
550    def which_msg(self, sub):
551        """Quels sont les messages utilisés par la routine 'sub' ?
552        """
553        key = self.get_key(sub)
554        return tuple(self.d_msg_used.get(key, []))
555
556
557template_cata_header = """# -*- coding: utf-8 -*-
558# ======================================================================
559# COPYRIGHT (C) 1991 - %s  EDF R&D                  WWW.CODE-ASTER.ORG
560# THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
561# IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
562# THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
563# (AT YOUR OPTION) ANY LATER VERSION.
564#
565# THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
566# WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
567# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
568# GENERAL PUBLIC LICENSE FOR MORE DETAILS.
569#
570# YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
571# ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
572#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
573# ======================================================================
574
575def _(x) : return x
576
577cata_msg = {""" % time.strftime('%Y')
578
579template_cata_footer = """
580}
581"""
582
583template_cata_msg = '''
584%(msgid)s : _(u"""%(text)s"""),'''
585
586
587class CATA:
588    """Classe représentant un catalogue de messages.
589    Méthodes attendues :
590        - nombre de messages,
591        - indice du dernier messages,
592        - indice libre,
593        - ajouter/supprimer un message,
594        - ...
595    """
596    def __init__(self, fcata):
597        """Initialisation
598        """
599        self.fcata = fcata
600        self.cata_msg = {}
601        try:
602            d = { '_' : lambda x: x, }
603            with open(fcata) as f:
604                exec(compile(f.read(), fcata, 'exec'), d)
605            self.cata_msg = d['cata_msg']
606        except Exception as msg:
607            print('-'*80+'\n', msg, '\n'+'-'*80)
608            raise CataMessageError(self.fcata, _('unable to import the file'))
609
610
611    def get_nb_msg(self):
612        """Nombre de messages.
613        """
614        return len(self.cata_msg)
615
616
617    def get_last_id(self):
618        """Indice du dernier message.
619        """
620        return max(list(self.cata_msg.keys()) or [0,])
621
622
623    def get_new_id(self, reuse_hole=True):
624        """Indice libre. Si 'end', on prend systématiquement à la fin,
625        sinon on cherche aussi dans les trous.
626        """
627        if not reuse_hole:
628            start = self.get_last_id()
629        else:
630            start = 1
631        all = set(range(start, 100))
632        free = all.difference(list(self.cata_msg.keys()))
633        if len(free) == 0:
634            new = -1
635        else:
636            new = min(free)
637        return new
638
639
640    def __getitem__(self, key):
641        """Retourne le contenu du message ou None.
642        """
643        return self.cata_msg.get(key, None)
644
645
646    def __setitem__(self, key, msg):
647        """Ajoute le message 'msg' à l'indice 'key'.
648        """
649        if self[key] != None:
650            raise MessageError(key, _('message already exists !'))
651        self.cata_msg[key] = msg
652
653
654    def __delitem__(self, key):
655        """Supprime le message d'indice 'key'.
656        """
657        if self[key] == None:
658            raise MessageError(key, _('this message does not exist !'))
659        del self.cata_msg[key]
660
661
662    def __repr__(self):
663        """Résumé du catalogue.
664        """
665        return ufmt(_('%3d messages (last=%3d, next=%3d) in %s'),
666                self.get_nb_msg(), self.get_last_id(), self.get_new_id(), self.fcata)
667
668
669    def __iter__(self):
670        """Itération sur les id des messages.
671        """
672        return iter(self.cata_msg)
673
674
675    def check(self):
676        """Vérifie le texte des messages."""
677        def_args = {}
678        for i in range(1,100):
679            def_args['i%d' % i] = 99999999
680            def_args['r%d' % i] = 1.234e16   # not too big to avoid python issue1742669
681            def_args['k%d' % i] = 'xxxxxx'
682        def_args['ktout'] = "all strings !"
683        error = []
684        for num, msg in list(self.cata_msg.items()):
685            if type(msg) is dict:
686                msg = msg['message']
687            try:
688                txt = msg % def_args
689            except:
690                error.append(ufmt(_('message #%s invalid : %s'), num, msg))
691        if len(error) > 0:
692            raise CataMessageError(self.fcata, os.linesep.join(error))
693
694
695    def get_cmodif(self):
696        """Retourne la carte MODIF.
697        """
698        cmodif = None
699        fobj = open(self.fcata, 'r')
700        for line in fobj:
701            if re.search('#@ +MODIF|#@ +AJOUT', line):
702                cmodif = line.replace(os.linesep, '')
703                break
704        fobj.close()
705        if cmodif == None:
706            raise CataMessageError(self.fcata, _('invalid header "#@ MODIF/AJOUT..."'))
707        return cmodif
708
709
710    def get_content(self):
711        """Contenu du catalogue "reconstruit".
712        """
713        txt = [self.get_cmodif(),]
714        txt.append(template_cata_header)
715        l_ind = list(self.cata_msg.keys())
716        l_ind.sort()
717        for msgid in l_ind:
718            txt.append(template_cata_msg % {'msgid' : msgid, 'text' : self.cata_msg[msgid]})
719        txt.append(template_cata_footer)
720        return os.linesep.join(txt)
721
722
723def clean_source(content, ext, ignore_comments, wrap):
724    """Nettoie un fichier source.
725    """
726    if ignore_comments:
727        assert ext in ('.f', '.F', '.F90', '.py', '.capy'), 'unknown type : %s' % str(ext)
728        if ext in ('.f', '.F'):
729            reg_ign = re.compile('(^[A-Z!#].*$)', re.MULTILINE)
730        elif ext in ('.F90'):
731            reg_ign = re.compile('(^[!#].*$)', re.MULTILINE)
732        elif ext in ('.py', '.capy'):
733            reg_ign = re.compile('(#.*$)', re.MULTILINE)
734        content = reg_ign.sub('', content).expandtabs()
735        if wrap and ext in ('.f', '.F'):
736            content = ''.join([lin[6:] for lin in content.splitlines()])
737    return content
738
739
740def GetMessageInfo(run, *args):
741    """Return info about Code_Aster messages.
742    """
743    if not run.get('aster_vers'):
744        run.parser.error(_("You must define 'default_vers' in 'aster' configuration file or use '--vers' option."))
745    REPREF = run.get_version_path(run['aster_vers'])
746    fconf = run.get('config')
747    if fconf:
748        fconf = osp.abspath(fconf)
749    conf = build_config_of_version(run, run['aster_vers'], fconf)
750
751    if run['nolocal']:
752        run.Mess(_('This operation only works on local source files. "--nolocal" option ignored'))
753
754    bibfor = [conf['SRCFOR'][0],]
755    if conf['SRCF90'][0] != '':
756        bibfor.append(conf['SRCF90'][0])
757
758    args = list(args)
759    named_actions = ('check', 'move',)
760    action = None
761
762    if len(args) > 0 and args[0] in named_actions:
763        action = args.pop(0)
764    elif len(args) == 0:
765        run.Mess(_('You must choose an operation from %s or give a subroutine name or a message ID') \
766                % repr(named_actions), '<F>_INVALID_ARGUMENT')
767
768    # surcharge
769    surch_fort = run.get('surch_fort', [])
770    if surch_fort:
771        surch_fort = surch_fort.split(',')
772    surch_pyt = run.get('surch_pyt', [])
773    if surch_pyt:
774        surch_pyt = surch_pyt.split(',')
775
776    pick_cache = 'messages_dicts.%s.pick' % (run['aster_vers'].replace(os.sep, '_'))
777    msgman = MESSAGE_MANAGER(repref=REPREF,
778                             fort=bibfor,
779                             pyt=conf['SRCPY'][0],
780                             capy=conf['SRCCAPY'][0],
781                             cache_dict=osp.join(run['cache_dir'], pick_cache),
782                             force=run['force'], verbose=run['verbose'],
783                             debug=run['debug'],
784                             surch_fort=surch_fort,
785                             surch_pyt =surch_pyt,
786                             unig      =run.get('unigest', None),
787                             )
788    msgman.read_cata()
789
790    if action == 'check':
791        try:
792            unused, not_found = msgman.check_cata()
793        except CataMessageError as msg:
794            run.Sortie(4)
795        if not run['silent']:
796            print('Messages inutilises :')
797            print(' '.join(unused))
798            print('Messages inexistants :')
799            print(' '.join(not_found))
800        if len(unused) + len(not_found) > 0:
801            run.Sortie(4)
802
803    elif action == 'move':
804        if len(args) != 2:
805            run.parser.error(
806                    _("'--%s %s' requires 2 arguments") % (run.current_action, action))
807        msgman.move(*args)
808
809    else:
810        print()
811        fmt = '%12s : %s'
812        for obj in args:
813            l_msg = list(set(msgman.which_msg(obj)))
814            if len(l_msg) > 0:
815                l_msg.sort()
816                print(fmt % (obj, l_msg))
817            l_sub = list(set(msgman.who_use(obj)))
818            if len(l_sub) > 0:
819                l_sub.sort()
820                print(fmt % (obj, l_sub))
821