...
[iramuteq] / functions.py
1 #!/bin/env python
2 # -*- coding: utf-8 -*-
3 #Author: Pierre Ratinaud
4 #Copyright (c) 2008-2012 Pierre Ratinaud
5 #Lisense: GNU/GPL
6
7 import wx
8 import re
9 from ConfigParser import ConfigParser
10 from subprocess import Popen, call, PIPE
11 import thread
12 import os
13 import ast
14 import sys
15 import csv
16 import platform
17 import traceback
18 import codecs
19 import locale
20 import datetime
21 from copy import copy
22 from shutil import copyfile
23 #from dialog import BugDialog
24 print 'TEST LOGGING funcion'
25 import logging
26
27 log = logging.getLogger('iramuteq')
28
29
30 indices_simi = [u'cooccurrence' ,'pourcentage de cooccurrence',u'Russel',u'Jaccard', 'Kulczynski1', 'Kulczynski2', 'Mountford', 'Fager', 'simple matching', 'Hamman', 'Faith', 'Tanimoto', 'Dice', 'Phi', 'Stiles', 'Michael', 'Mozley', 'Yule', 'Yule2', 'Ochiai', 'Simpson', 'Braun-Blanquet','Chi-squared', 'Phi-squared', 'Tschuprow', 'Cramer', 'Pearson', 'binomial']
31
32
33 class History :
34     def __init__(self, filein, syscoding = 'utf8') :
35         self.filein = filein
36         self.syscoding = syscoding
37         self.corpora = {}
38         self.openedcorpus = {}
39         self.analyses = {}
40         self.history = {}
41         self.opened = {}
42         self.read()
43
44     def read(self) :
45         self.conf = DoConf(self.filein)
46         self.order = {}
47         self.ordera = {}
48         for i, section in enumerate(self.conf.conf.sections()) :
49             if self.conf.conf.has_option(section, 'corpus_name') :
50                 self.corpora[section] = self.conf.getoptions(section)
51                 self.order[len(self.order)] = section
52             else :
53                 self.analyses[section] = self.conf.getoptions(section)
54                 self.ordera[len(self.ordera)] = section
55         todel = []
56         for corpus in self.corpora :
57             self.history[corpus] = copy(self.corpora[corpus])
58         for analyse in self.analyses :
59             if self.analyses[analyse]['corpus'] in self.corpora :
60                 if 'analyses' in self.history[self.analyses[analyse]['corpus']] :
61                     self.history[self.analyses[analyse]['corpus']]['analyses'].append(self.analyses[analyse])
62                     todel.append(analyse)
63                 else :
64                     self.history[self.analyses[analyse]['corpus']]['analyses'] = [self.analyses[analyse]]
65                     todel.append(analyse)
66             else :
67                  self.history[analyse] = self.analyses[analyse]
68         #for analyse in todel :
69         #    del self.analyses[analyse]
70     
71     def write(self) :
72         sections = self.corpora.keys() + self.analyses.keys()
73         parametres = [self.corpora[key] for key in self.corpora.keys()] + [self.analyses[key] for key in self.analyses.keys()]
74         self.conf.makeoptions(sections, parametres)
75         log.info('write history')
76
77     def add(self, analyse) :
78         if 'corpus' in analyse :
79             if analyse['corpus'] in self.corpora :
80                 if 'analyses' in self.history[analyse['corpus']] :
81                     self.history[analyse['corpus']]['analyses'].append(analyse)
82                 else :
83                     self.history[analyse['corpus']]['analyses'] = [analyse]
84                 self.analyses[analyse['uuid']] = analyse
85             else :
86                 self.analyses[analyse['uuid']] = analyse
87         elif 'corpus_name' in analyse :
88             self.history[analyse['uuid']] = analyse
89             self.corpora[analyse['uuid']] = analyse
90         self.write()
91
92     def delete(self, uuid, corpus = False) :
93         if corpus :
94             del self.corpora[uuid]
95             self.conf.conf.remove_section(uuid)
96             for analyse in self.history[uuid].get('analyses', [False]) :
97                 if analyse :
98                     del self.analyses[analyse['uuid']]
99                     self.conf.conf.remove_section(analyse['uuid'])
100         else :
101             del self.analyses[uuid]
102             self.conf.conf.remove_section(uuid)
103         self.write()
104
105     def addtab(self, analyse) :
106         self.opened[analyse['uuid']] = analyse
107
108     def rmtab(self, analyse) :
109         del self.opened[analyse['uuid']]
110
111 class DoConf :
112     def __init__(self, configfile=None, diff = None, parametres = None) :
113         self.configfile = configfile
114         self.conf = ConfigParser()
115         if configfile is not None :
116             self.conf.readfp(codecs.open(configfile, 'r', 'utf8'))
117         self.parametres = {}
118         if parametres is not None :
119             self.doparametres(parametres)
120
121     def doparametres(self, parametres) :
122         return parametres
123
124     def getsections(self) :
125         return self.conf.sections()
126
127     def getoptions(self, section = None, diff = None):
128         parametres = {}
129         if section is None :
130             section = self.conf.sections()[0]
131         for option in self.conf.options(section) :
132             if self.conf.get(section, option).isdigit() :
133                 parametres[option] = int(self.conf.get(section, option))
134             elif self.conf.get(section, option) == 'False' :
135                 parametres[option] = False
136             elif self.conf.get(section, option) == 'True' :
137                 parametres[option] = True
138             elif self.conf.get(section, option).startswith('(') and self.conf.get(section, option).endswith(')') :
139                 parametres[option] = ast.literal_eval(self.conf.get(section, option))
140             else :
141                 parametres[option] = self.conf.get(section, option)
142         if 'type' not in parametres :
143             parametres['type'] = section
144         return parametres
145             
146     def makeoptions(self, sections, parametres, outfile = None) :
147         for i, section in enumerate(sections) :
148             if not self.conf.has_section(section) :
149                 self.conf.add_section(section)
150             for option in parametres[i] :
151                 if isinstance(parametres[i][option], int) :
152                     self.conf.set(section, option, `parametres[i][option]`)
153                 elif isinstance(parametres[i][option], basestring) :
154                     self.conf.set(section, option, parametres[i][option].encode('utf8'))
155                 elif isinstance(parametres[i][option], wx.Colour) :
156                     self.conf.set(section, option, str(parametres[i][option]))
157                 else :
158                     self.conf.set(section, option, `parametres[i][option]`)
159         if outfile is None :
160             outfile = self.configfile
161         print outfile
162         with codecs.open(outfile, 'w', 'utf8') as f :
163             self.conf.write(f)
164
165     def totext(self, parametres) :
166         txt = ['Corpus']
167         for val in parametres :
168             if isinstance(parametres[val], int) :
169                 txt.append(' \t\t: '.join([val, `parametres[val]`]))
170             else :
171                 txt.append(' \t\t: '.join([val, parametres[val]]))
172         return '\n'.join(txt)
173
174
175 def write_tab(tab, fileout) :
176         writer = csv.writer(open(fileout, 'wb'), delimiter=';', quoting = csv.QUOTE_NONNUMERIC)
177         writer.writerows(tab)
178
179 class BugDialog(wx.Dialog):
180     def __init__(self, *args, **kwds):
181         # begin wxGlade: MyDialog.__init__
182         kwds["style"] = wx.DEFAULT_DIALOG_STYLE
183         kwds["size"] = wx.Size(500, 200)
184         wx.Dialog.__init__(self, *args, **kwds)
185         self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
186         self.button_1 = wx.Button(self, wx.ID_OK, "")
187
188         self.__set_properties()
189         self.__do_layout()
190         # end wxGlade
191
192     def __set_properties(self):
193         # begin wxGlade: MyDialog.__set_properties
194         self.SetTitle("Bug")
195         self.SetMinSize(wx.Size(500, 200))
196         self.text_ctrl_1.SetMinSize(wx.Size(500, 200))
197         
198         # end wxGlade
199
200     def __do_layout(self):
201         # begin wxGlade: MyDialog.__do_layout
202         sizer_1 = wx.BoxSizer(wx.VERTICAL)
203         sizer_1.Add(self.text_ctrl_1, 1, wx.EXPAND, 0)
204         sizer_1.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
205         self.SetSizer(sizer_1)
206         sizer_1.Fit(self)
207         self.Layout()
208
209
210 def CreateIraFile(DictPathOut, clusternb, corpname='corpus_name', section = 'analyse'):
211     AnalyseConf = ConfigParser()
212     AnalyseConf.read(DictPathOut['ira'])
213     AnalyseConf.add_section(section)
214     date = datetime.datetime.now().ctime()
215     AnalyseConf.set(section, 'date', str(date))
216     AnalyseConf.set(section, 'clusternb', clusternb)
217     AnalyseConf.set(section, 'corpus_name', corpname)
218
219     fileout = open(DictPathOut['ira'], 'w')
220     AnalyseConf.write(fileout)
221     fileout.close()
222
223 def sortedby(list, direct, *indices):
224
225     """
226         sortedby: sort a list of lists (e.g. a table) by one or more indices
227                   (columns of the table) and return the sorted list
228
229         e.g.
230          for list = [[2,3],[1,2],[3,1]]:
231          sortedby(list,1) will return [[3, 1], [1, 2], [2, 3]],
232          sortedby(list,0) will return [[1, 2], [2, 3], [3, 1]]
233     """
234
235     nlist = map(lambda x, indices=indices: 
236                  map(lambda i, x=x: x[i], indices) + [x],
237                  list)
238     if direct == 1:
239         nlist.sort()
240     elif direct == 2:
241         nlist.sort(reverse=True)
242     return map(lambda l: l[-1], nlist)
243
244 def add_type(line, dictlem):
245     if line[4] in dictlem:
246         line.append(dictlem[line[4]])
247     else :
248         line.append('')
249     return line
250
251 def treat_line_alceste(i, line) :
252     if line[0] == '*' or line[0] == '*****' :
253         return line + ['']
254     if line[5] == 'NA':
255         print 'NA', line[5]
256         pass
257     elif float(line[5].replace(',', '.')) < 0.0001:
258         line[5] = '< 0,0001'
259     elif float(line[5].replace(',', '.')) > 0.05:
260         line[5] = 'NS (%s)' % str(float(line[5].replace(',', '.')))[0:7]
261     else:
262         line[5] = str(float(line[5].replace(',', '.')))[0:7]
263     return [i, int(line[0]), int(line[1]), float(line[2]), float(line[3]), line[6], line[4], line[5]]
264
265 def ReadProfileAsDico(parent, File, Alceste=False, encoding = sys.getdefaultencoding()):
266     #print 'lecture des profils : ReadProfileAsDico'
267     #if Alceste :
268     #    print 'lecture du dictionnaire de type'
269     #    dictlem = {}
270     #    for line in parent.corpus.lem_type_list :
271     #        dictlem[line[0]] = line[1]
272     dictlem = {}
273     print 'lecture des profiles'
274     #encoding = sys.getdefaultencoding()
275     print encoding
276     FileReader = codecs.open(File, 'r', encoding)
277     Filecontent = FileReader.readlines()
278     FileReader.close()
279     DictProfile = {}
280     count = 0
281     rows = [row.replace('\n', '').replace("'", '').replace('\"', '').replace(',', '.').replace('\r','').split(';') for row in Filecontent]
282     rows.pop(0)
283     ClusterNb = rows[0][2]
284     rows.pop(0)
285     clusters = [row[2] for row in rows if row[0] == u'**']
286     valclusters = [row[1:4] for row in rows if row[0] == u'****']
287     lp = [i for i, line in enumerate(rows) if line[0] == u'****']
288     prof = [rows[lp[i] + 1:lp[i+1] - 1] for i in range(0, len(lp)-1)] + [rows[lp[-1] + 1:len(rows)]] 
289     if Alceste :
290         prof = [[add_type(row, dictlem) for row in pr] for pr in prof]
291         prof = [[treat_line_alceste(i,line) for i, line in enumerate(pr)] for pr in prof] 
292     else :
293         prof = [[line + [''] for line in pr] for pr in prof]
294         prof = [[treat_line_alceste(i,line) for i, line in enumerate(pr)] for pr in prof]
295     for i, cluster in enumerate(clusters):
296         DictProfile[cluster] = [valclusters[i]] + prof[i]
297     return DictProfile
298
299 def GetTxtProfile(dictprofile) :
300     proflist = []
301     for classe in range(0, len(dictprofile)) :
302         prof = dictprofile[str(classe + 1)]
303         clinfo = prof[0]
304         proflist.append('\n'.join([' '.join(['classe %i' % (classe + 1), '-', '%s uce sur %s - %s%%' % (clinfo[0], clinfo[1], clinfo[2])]), '\n'.join(['%5s|%5s|%6s|%6s|%8s|%8s|%20s\t%10s' % tuple([str(val) for val in line]) for line in prof if len(line)==8])]))
305     return '\n\n'.join(proflist)
306
307 def formatExceptionInfo(maxTBlevel=5):
308          cla, exc, trbk = sys.exc_info()
309          excName = cla.__name__
310          try:
311              excArgs = exc.__dict__["args"]
312          except KeyError:
313              excArgs = "<no args>"
314          excTb = traceback.format_tb(trbk, maxTBlevel)
315          return (excName, excArgs, excTb)
316
317
318 #fonction des etudiants de l'iut
319 def decoupercharact(chaine, longueur, longueurOptimale, separateurs = None) :
320     """
321         on part du dernier caractère, et on recule jusqu'au début de la chaîne.
322         Si on trouve un '$', c'est fini.
323         Sinon, on cherche le meilleur candidat. C'est-à-dire le rapport poids/distance le plus important.
324     """
325     separateurs = [[u'.', 60.0], [u'?', 60.0], [u'!', 60.0], [u'£', 60], [u':', 50.0], [u';', 40.0], [u',', 10.0], [u' ', 0.1]]
326     trouve = False                 # si on a trouvé un bon séparateur
327     iDecoupe = 0                # indice du caractere ou il faut decouper
328     
329     # on découpe la chaine pour avoir au maximum 240 caractères
330     longueur = min(longueur, len(chaine) - 1)
331     chaineTravail = chaine[:longueur + 1]
332     nbCar = longueur
333     meilleur = ['', 0, 0]        # type, poids et position du meilleur separateur
334     
335     # on vérifie si on ne trouve pas un '$'
336     indice = chaineTravail.find(u'$')
337     if indice > -1:
338         trouve = True
339         iDecoupe = indice
340
341     # si on ne trouve rien, on cherche le meilleur séparateur
342     if not trouve:
343         while nbCar >= 0:
344             caractere = chaineTravail[nbCar]
345             distance = abs(longueurOptimale - nbCar) + 1
346             meilleureDistance = abs(longueurOptimale - meilleur[2]) + 1
347
348             # on vérifie si le caractére courant est une marque de ponctuation
349             for s in separateurs:
350                 if caractere == s[0]:
351                     # si c'est une ponctuation 
352                     
353                     if s[1] / distance > float(meilleur[1]) / meilleureDistance:
354                         # print nbCar, s[0]
355                         meilleur[0] = s[0]
356                         meilleur[1] = s[1]
357                         meilleur[2] = nbCar
358                         trouve = True
359                         iDecoupe = nbCar
360                         
361                     # et on termine la recherche
362                     break
363
364             # on passe au caractère précédant
365             nbCar = nbCar - 1
366     
367     # si on a trouvé
368     if trouve:
369         fin = chaine[iDecoupe + 1:]
370         retour = chaineTravail[:iDecoupe]
371         return len(retour) > 0, retour.split(), fin
372     # si on a rien trouvé
373     return False, chaine.split(), ''
374
375 def BugReport(parent):
376     for ch in parent.GetChildren():
377         if "<class 'wx._windows.ProgressDialog'>" == str(type(ch)):
378             ch.Destroy()   
379     dial = BugDialog(parent)
380     txt = u'            !== BUG ==!       \n'
381     txt += u'*************************************\n'
382     for line in formatExceptionInfo():
383         if type(line) == type([]):
384             for don in line:
385                 txt += don.replace('    ', ' ')
386         else:
387             txt += line + '\n'
388     if 'Rerror' in dir(parent) :
389         txt += parent.Rerror
390         parent.Rerror = ''
391     print formatExceptionInfo()
392     log.error(txt)
393     dial.text_ctrl_1.write(txt)
394     dial.CenterOnParent()
395     dial.ShowModal()
396     raise NameError('Bug')
397     
398 def PlaySound(parent):
399     if parent.pref.getboolean('iramuteq', 'sound') :
400         try:
401             if "gtk2" in wx.PlatformInfo:
402                 error = Popen(['aplay','-q',os.path.join(parent.AppliPath,'son_fin.wav')])
403             else :    
404                 sound = wx.Sound(os.path.join(parent.AppliPath, 'son_fin.wav'))
405                 sound.Play(wx.SOUND_SYNC)
406         except :
407             print 'pas de son'
408
409 def ReadDicoAsDico(dicopath):
410     with codecs.open(dicopath, 'r', 'UTF8') as f:
411         content = f.readlines()
412     dico = {}
413     for line in content :
414         if line[0] != u'':
415             line = line.replace(u'\n', '').replace('"', '').split('\t')
416             dico[line[0]] = line[1:]
417     return dico
418
419 def ReadLexique(parent, lang = 'french'):
420     parent.lexique = ReadDicoAsDico(parent.DictPath.get(lang, 'french'))
421
422 def ReadList(filein, encoding = sys.getdefaultencoding()):
423     #file = open(filein)
424     file = codecs.open(filein, 'r', encoding)
425     content = file.readlines()
426     file.close()
427     first = content.pop(0)
428     first = first.replace('\n', '').replace('\r','').replace('\"', '').split(';')
429     dict = {}
430     i = 0
431     for line in content:
432         line = line.replace('\n', '').replace('\r','').replace('\"', '').replace(',', '.')
433         line = line.split(';')
434         nline = [line[0]]
435         for val in line[1:]:
436             if val == u'NA' :
437                 don = ''
438             else: 
439                 try:
440                     don = int(val)
441                 except:
442                     don = float('%.5f' % float(val))
443             nline.append(don)
444         dict[i] = nline
445         i += 1
446     return dict, first
447
448 def exec_rcode(rpath, rcode, wait = True, graph = False):
449     print rpath, rcode
450     needX11 = False
451     if sys.platform == 'darwin' :
452         try :
453             macversion = platform.mac_ver()[0].split('.')
454             print macversion
455             if int(macversion[1]) < 5 :
456                 needX11 = True
457             else :
458                 needX11 = False
459         except :
460             needX11 = False
461
462     rpath = rpath.replace('\\','\\\\')
463     if not graph :
464         if wait :
465             if sys.platform == 'win32':
466                 error = call(["%s" % rpath, "--vanilla","--slave","-f", "%s" % rcode])
467             else :
468                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
469             return error
470         else :
471             if sys.platform == 'win32':
472                 pid = Popen(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
473             else :
474                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
475             return pid
476     else :
477         if wait :
478             if sys.platform == 'win32':
479                 error = call(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
480             elif sys.platform == 'darwin' and needX11:
481                 os.environ['DISPLAY'] = ':0.0'
482                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
483             else :
484                 error = call([rpath, '--vanilla','--slave',"-f %s" % rcode])
485             return error
486         else :
487             if sys.platform == 'win32':
488                 pid = Popen(["%s" % rpath, '--vanilla','--slave','-f', "%s" % rcode])
489             elif sys.platform == 'darwin' and needX11:
490                 os.environ['DISPLAY'] = ':0.0'
491                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
492             else :
493                 pid = Popen([rpath, '--vanilla','--slave',"-f %s" % rcode], stderr = PIPE)
494             return pid
495
496 def check_Rresult(parent, pid) :
497     if isinstance(pid, Popen) :
498         if pid.returncode != 0 :
499             error = pid.communicate()
500             error = [str(error[0]), error[1]]
501             if error[1] is None :
502                 error[1] = 'None'
503             parent.Rerror = '\n'.join([str(pid.returncode), '\n'.join(error)])
504             try :
505                 raise Exception('\n'.join(u'Erreur R', '\n'.join(error[1:])))
506             except :
507                 BugReport(parent)
508     else :
509         if pid !=0 :
510             try :
511                 raise Exception(u'Erreur R')
512             except :
513                 BugReport(parent)
514
515 def print_liste(filename,liste):
516     with open(filename,'w') as f :
517         for graph in liste :
518             f.write(';'.join(graph)+'\n')
519
520 def read_list_file(filename, encoding = sys.getdefaultencoding()):
521     with codecs.open(filename,'rU', encoding) as f :
522         content=f.readlines()
523         ncontent=[line.replace('\n','').split(';') for line in content if line.strip() != '']
524     return ncontent
525         
526 class MessageImage(wx.Frame):
527     def __init__(self, *args, **kwds):
528         # begin wxGlade: MyFrame.__init__
529         kwds["style"] = wx.DEFAULT_FRAME_STYLE
530         wx.Frame.__init__(self, *args, **kwds)
531         #self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
532         self.imageFile = False
533         self.imagename = u"chi_classe.png"
534         self.HtmlPage = wx.html.HtmlWindow(self, -1)
535         if "gtk2" in wx.PlatformInfo:
536             self.HtmlPage.SetStandardFonts()
537         self.HtmlPage.SetFonts('Courier', 'Courier')
538         
539         self.button_1 = wx.Button(self, -1, u"Fermer")
540         self.Bind(wx.EVT_BUTTON, self.OnCloseMe, self.button_1)
541         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
542         self.__do_layout()
543         # end wxGlade
544
545     def __do_layout(self):
546         # begin wxGlade: MyFrame.__do_layout
547         sizer_1 = wx.BoxSizer(wx.VERTICAL)
548         self.sizer_2 = wx.BoxSizer(wx.VERTICAL)
549         self.sizer_2.Add(self.HtmlPage, 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
550         self.sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0)
551         sizer_1.Add(self.sizer_2, 1, wx.EXPAND, 0)
552         self.SetAutoLayout(True)
553         self.SetSizer(sizer_1)
554         # end wxGlade
555
556     def addsaveimage(self, imageFile) :
557         self.imageFile = imageFile
558         self.button_2 = wx.Button(self, -1, u"Enregistrer l'image...")
559         self.Bind(wx.EVT_BUTTON, self.OnSaveImage, self.button_2)
560         self.sizer_2.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0)
561         self.Layout()
562
563     def OnCloseMe(self, event):
564         self.Close(True)
565
566     def OnCloseWindow(self, event):
567         self.Destroy()
568
569     def OnSaveImage(self, event) :
570         dlg = wx.FileDialog(
571             self, message="Enregistrer sous...", defaultDir=os.getcwd(),
572             defaultFile= self.imagename, wildcard="png|*.png", style=wx.SAVE | wx.OVERWRITE_PROMPT
573             )
574         dlg.SetFilterIndex(2)
575         dlg.CenterOnParent()
576         if dlg.ShowModal() == wx.ID_OK:
577             path = dlg.GetPath()
578             copyfile(self.imageFile, path)
579             
580
581 def progressbar(self, maxi) :
582     if 'parent' in dir(self) :
583         parent = self.parent
584     else :
585         parent = self
586     return wx.ProgressDialog("Traitements",
587                              "Veuillez patienter...",
588                              maximum=maxi,
589                              parent=parent,
590                              style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_ELAPSED_TIME | wx.PD_CAN_ABORT
591                              )
592
593
594 def treat_var_mod(variables) :
595     var_mod = {}
596     for variable in variables :
597         if u'_' in variable :
598             forme = variable.split(u'_')
599             var = forme[0]
600             mod = forme[1]
601             if not var in var_mod :
602                 var_mod[var] = [variable]
603             else :
604                 if not mod in var_mod[var] :
605                     var_mod[var].append(variable)
606     return var_mod