#--------------------------------------------------------------- # Project : Mandrake Linux # Module : msec2 # File : ConfigFile.py # Version : $Id$ # Author : Frederic Lepied # Created On : Wed Dec 5 21:42:49 2001 # Purpose : class abstraction to handle configuration # files. #--------------------------------------------------------------- import re import string import os import stat import Config import commands from Log import * import gettext try: cat = gettext.Catalog('msec') _ = cat.gettext except IOError: _ = str BEFORE=0 INSIDE=1 AFTER=2 space = re.compile('\s') class ConfigFiles: def __init__(self): self.files = {} self.modified_files = [] self.action_assoc = [] def add(self, file, path): self.files[path] = file def modified(self, path): if not path in self.modified_files: self.modified_files.append(path) def get_config_file(self, path, suffix): try: return self.files[path] except KeyError: return ConfigFile(path, suffix, self) def add_config_assoc(self, regex, action): self.action_assoc.append((re.compile(regex), action)) all_files=ConfigFiles() def move(old, new): try: os.unlink(new) except OSError: pass try: os.rename(old, new) except: error('rename %s %s: %s' % (old, new, str(sys.exc_value))) class ConfigFile: def __init__(self, path, suffix=None, meta=all_files): self.meta=meta self.path = Config.get_config('root', '') + path self.is_modified = 0 self.is_touched = 0 self.is_deleted = 0 self.is_moved = 0 self.suffix = suffix self.lines = None self.sym_link = None self.meta.add(self, path) def get_lines(self): if self.lines == None: file=None try: file = open(self.path, 'r') except IOError: if self.suffix: try: moved = self.path + self.suffix file = open(moved, 'r') move(moved, self.path) self.meta.modified(self.path) except IOError: self.lines = [] else: self.lines = [] if file: self.lines = string.split(file.read(), "\n") file.close() return self.lines def append(self, value): lines = self.lines l = len(lines) if l > 0 and lines[l - 1] == '': lines.insert(l - 1, value) else: lines.append(value) lines.append('') def modified(self): self.is_modified = 1 return self def touch(self): self.is_touched = 1 return self def symlink(self, link): self.sym_link = link return self def exists(self, really=0): return os.path.exists(self.path) or (not really and self.suffix and os.path.exists(self.path + self.suffix)) def move(self, suffix): self.suffix = suffix self.is_moved = 1 def unlink(self): self.is_deleted = 1 self.lines=[] return self def write(self): if self.is_deleted: if self.exists(): try: os.unlink(self.path) except: error('unlink %s: %s' % (self.path, str(sys.exc_value))) log(_('deleted %s') % (self.path,)) elif self.is_modified: content = string.join(self.lines, "\n") file = open(self.path, 'w') file.write(content) file.close() self.meta.modified(self.path) elif self.is_touched: if os.path.exists(self.path): try: os.utime(self.path, None) except: error('utime %s: %s' % (self.path, str(sys.exc_value))) elif self.suffix and os.path.exists(self.path + self.suffix): move(self.path + self.suffix, self.path) try: os.utime(self.path, None) except: error('utime %s: %s' % (self.path, str(sys.exc_value))) else: self.lines = [] self.is_modified = 1 file = open(self.path, 'w') file.close() log(_('touched file %s') % (self.path,)) elif self.sym_link: done = 0 if self.exists(): full = os.lstat(self.path) if stat.S_ISLNK(full[stat.ST_MODE]): link = os.readlink(self.path) # to be fixed: resolv relative symlink done = (link == self.sym_link) if not done: try: os.unlink(self.path) except: error('unlink %s: %s' % (self.path, str(sys.exc_value))) log(_('deleted %s') % (self.path,)) if not done: try: os.symlink(self.sym_link, self.path) except: error('symlink %s %s: %s' % (self.sym_link, self.path, str(sys.exc_value))) log(_('made symbolic link from %s to %s') % (self.sym_link, self.path)) if self.is_moved: move(self.path, self.path + self.suffix) log(_('moved file %s to %s') % (self.path, self.path + self.suffix)) self.meta.modified(self.path) self.is_touched = 0 self.is_modified = 0 self.is_deleted = 0 self.is_moved = 0 def set_shell_variable(self, var, value, start=None, end=None): regex = re.compile('^' + var + '="?([^#"]+)"?(.*)') lines = self.get_lines() idx=0 value=str(value) if start: status = BEFORE start = re.compile(start) else: status = INSIDE if end: end = re.compile(end) idx = None for idx in range(0, len(lines)): line = lines[idx] if status == BEFORE: if start.search(line): status = INSIDE else: continue elif end and end.search(line): break res = regex.search(line) if res: if res.group(1) != value: if space.search(value): lines[idx] = var + '="' + value + '"' + res.group(2) else: lines[idx] = var + '=' + value + res.group(2) self.modified() log(_('set variable %s to %s in %s') % (var, value, self.path,)) return self if space.search(value): s = var + '="' + value + '"' else: s = var + '=' + value if idx == None or idx == len(lines): self.append(s) else: lines.insert(idx, s) self.modified() log(_('set variable %s to %s in %s') % (var, value, self.path,)) return self def get_shell_variable(self, var): regex = re.compile('^' + var + '="?([^#"]+)"?(.*)') lines = self.get_lines() for idx in range(len(lines) - 1, -1, -1): res = regex.search(lines[idx]) if res: return res.group(1) return None def get_match(self, regex, replace=None): r=re.compile(regex) lines = self.get_lines() for idx in range(0, len(lines)): res = r.search(lines[idx]) if res: if replace: s = substitute_re_result(res, replace) return s else: return lines[idx] return None def replace_line_matching(self, regex, value, at_end_if_not_found=0, all=0, start=None, end=None): r=re.compile(regex) lines = self.get_lines() matches = 0 if start: status = BEFORE start = re.compile(start) else: status = INSIDE if end: end = re.compile(end) idx = None for idx in range(0, len(lines)): line = lines[idx] if status == BEFORE: if start.search(line): status = INSIDE else: continue elif end and end.search(line): break res = r.search(line) if res: s = substitute_re_result(res, value) matches = matches + 1 if s != line: log(_("replaced in %s the line %d:\n%s\nwith the line:\n%s") % (self.path, idx, line, s)) lines[idx] = s self.modified() if not all: return matches if matches == 0 and at_end_if_not_found: log(_("appended in %s the line:\n%s") % (self.path, value)) if idx == None or idx == len(lines): self.append(value) else: lines.insert(idx, value) self.modified() matches = matches + 1 return matches def insert_after(self, regex, value, at_end_if_not_found=0, all=0): matches = 0 r=re.compile(regex) lines = self.get_lines() for idx in range(0, len(lines)): res = r.search(lines[idx]) if res: s = substitute_re_result(res, value) log(_("inserted in %s after the line %d:\n%s\nthe line:\n%s") % (self.path, idx, lines[idx], s)) lines.insert(idx+1, s) self.modified() matches = matches + 1 if not all: return matches if matches == 0 and at_end_if_not_found: log(_("appended in %s the line:\n%s") % (self.path, value)) self.append(value) self.modified() matches = matches + 1 return matches def insert_before(self, regex, value, at_top_if_not_found=0, all=0): matches = 0 r=re.compile(regex) lines = self.get_lines() for idx in range(0, len(lines)): res = r.search(lines[idx]) if res: s = substitute_re_result(res, value) log(_("inserted in %s before the line %d:\n%s\nthe line:\n%s") % (self.path, idx, lines[idx], s)) lines.insert(idx, s) self.modified() matches = matches + 1 if not all: return matches if matches == 0 and at_top_if_not_found: log(_("inserted at the top of %s the line:\n%s") % (self.path, value)) lines.insert(0, value) self.modified() matches = matches + 1 return matches def insert_at(self, idx, value): lines = self.get_lines() try: lines.insert(idx, value) log(_("inserted in %s at the line %d:\n%s") % (self.path, idx, value)) self.modified() return 1 except KeyError: return 0 def remove_line_matching(self, regex, all=0): matches = 0 r=re.compile(regex) lines = self.get_lines() for idx in range(len(lines) - 1, -1, -1): res = r.search(lines[idx]) if res: log(_("removing in %s the line %d:\n%s") % (self.path, idx, lines[idx])) lines.pop(idx) self.modified() matches = matches + 1 if not all: return matches return matches # utility funtions def substitute_re_result(res, s): for idx in range(0, (res.lastindex or 0) + 1): subst = res.group(idx) or '' s = string.replace(s, '@' + str(idx), subst) return s def write_files(): global all_files run_commands = Config.get_config('run_commands', 0) for f in all_files.files.values(): f.write() for f in all_files.modified_files: for a in all_files.action_assoc: res = a[0].search(f) if res: s = substitute_re_result(res, a[1]) if run_commands != '0': log(_('%s modified so launched command: %s') % (f, s)) cmd = commands.getstatusoutput(s) if cmd[0] == 0: log(cmd[1]) else: error(cmd[1]) else: log(_('%s modified so should have run command: %s') % (f, s)) def get_config_file(path, suffix=None): global all_files return all_files.get_config_file(path, suffix) def add_config_assoc(regex, action): global all_files return all_files.add_config_assoc(regex, action) # ConfigFile.py ends here |~J|Qmqq. پMU'o?Gv(=W0o3ı@5´{m}TI`ۡPaޫYYb=b: aC)<'Ej?7 I_ _ꑗD0Ky9OIZOo=(j'Crx;/SnCk){4xĻ"q%[:( ImmXY8PU]*/Q7}2OO5OOc h|~mb{"*8p0lu2||1c)բٔŷ&wRk0@K' t6\cљ݂i !Py ?1!S_}}}{Ǯd \Q _pjy ޫbDR`!IɣDG1;C"*}<R7A_۫27'K?_t e G'*:$`]]D%(e#TS^m?\AT$F ڐ 㜏h:\k/FDm~ 2BggiR8gI[i/qoeA[9z9c@<-UruV r*!7,$,Lj}7Wnp*5ȷ͕5r8(U=lY 3U垷SB(?CtN5@WTʏ|U̷ 2۟d B2.ie0HC0WDwivVmMD"0Cj(78[h鼓GbFir׭/hM&eRrq,mH]?fgQ2ѽ Cb_"/դIwG㇔O!'zYTo%/IPŵ㇙>%je4l* 8&gLӳ9‎bʪ{eJ jdb YR!V˿*8zl'{DѰd⟽''Q$z{lZ1nc_[gD#x- ȗ.-|J.޵Dg+])#%]RMI&+w๚< ZavPϬ@$yŠz" M=Uo'JKTXlm\7R"3N};GP),Q J*=bs_ ݡKF^Vc䎔nTdYob~jʀJROQxAmՀeüРn )w.2#XesDw%  P|:2"Gf5o8w:G6SJåOAI\X#sO ꜀ -g\ùhmǪW=ONAW3Ǯ T#JĪc'znP2W8*sA`0DKR-H?v@]<69˫r*}-щl\uxnZYV-?t1 &bbR@J7UDOݐ_=ؚQj}^U"8SU~P9Q:M ZEotPd '3N_Hd~$ݟ)gWBaV̀vI^-)aH*lxmU7S'tzjчh/KZ=.-O0U99VK?B>&[+m-O!T/i=xzws>5'9!rf&8A91ހj Z4!'O@gmK/p*9*` w&~K (m.Hf:"`f$ Ǡ;|\ɟ8H ,ZVB\^E! [ISM}A~՟Y?@7nf=G|Gutu`-WJKMTG=,ۮ 4lLlÓ#- y"-_s1a~QP{ab~~5\OԬ2 /_F8Y֛z Ծlc`B!rj o52t lC%v7dAE8pP~ 8wl.1Rpy3 ]͊z3udŶ|oAqh_ܶhTEg#)nj\P340JpMt.^s-rɆwұ?TR}"xtjpj\`QưfNnMPu !/e)݂_I2O\mqs06’}p"c ȅmGtq4$,@|cp'ר|OAJQ9$+ze[w2Es^' Z`Y?F}Q\D4eАǺ2B6m[k6bcj.gj0/ `S<|HmF9"(g /S\Rlx<;pqlFSKpn\a<(l^X^V?iǘ5ߤ4 yaq{6Ԭ3mpͩHCFВʥ <&=G"`V7lR?Uf+TWCZh-:FDl"!GCٲJƙ_}! di(jq p6"rۇeXkսFfbO1ZrIsL.e. [~vknA/;ZRL,RF/|k6Ln*E@ =dω\_Q{;Ds{JS]ũv}+I7٭]͸2^0K)/M||WͣbL]QF[xMʎُ7#\gAvU^2&ZC9Vm?j$NQ:$(]Lȭ ȴ%ZU,H;59`8G rLVPʃ>rrC =;AN`urp85ui`b=k9ܡE8sqX)ͽ]`D)J`5Z B̉=-PZM,4a۴LaLrʔ  pG闃$ D/'pMi}y{HIut$V{ws<}}g]9F1_Th :/̉ʑj&g&~ & 9X /F,J[N>0ʪ4*)D0f9S˚D *9)T(K]]hI [Õ^m<E iqqk?hݪa$uS<44̣iI1?s TD2GRl•|"Ae'e*MXLxf>+(.f-(Az_$us}io¤ga$eKM`}wC쩀xo|mԕBlOՂfZ5@2 (J:EF?3ADܠ uԇYW4 o i M9$gH9g(DT|5Ӈg٦wU|O.Lϫ )JZX]y˜plTQE݈LQy݇bِŧ]S5$ݗYv6ȔX0z *[旐~wB}(JUG] p7 Q5Z_Ĝh&y/j=BZ,2PC%۫pmp 7ya&crLHȵ(Y4#:"/wF&L3Tړn=ͬ#gFo1@vNY֪'&i1D0PœhР{5,B\wHB0V;QbQ#֎}`!։ lr,uxL5i! yqDdV7R$۲9uR5f[d]/1HOあQ)tkZ?şƙ9rsu uN oRdL:㛟8j# EU @XVm+Vp3pw m+ uM/"p@B ~Jey^5yf_dC2'ΫdOk}N  #bmJz6%YQHGve=퀮-C:굆~WPa"8S]W2I:&eh6<68#e7rft\5{YJ7Hu{,AStoN %8l{gc$+u7M#R[.՞XEă:=j|)Ec貿 ZPLReXMHԸĮ"1v =y0ȿK8ڠQHoW*7QM`x-|6:GHpY55s5v#"@\[NV>jf˝-k08;C[HVkeZFyo/'ʃ🛾5}cXC-W+RsWĘf9iMhr[[~Q%>6L9;A=/32 qꆝW5wyw }SerNsYb YUZT"}2}#k)%X=ER&{:9-Q/MMl2KWL%b!ٕI\\}z+}Q+pm&**^zʟlc%3 (IFi[Y_qPSrGE5í/=Kz@ģKH6>Y߯e[c h2x`TbQjTn0ymnzŇbWnΕИفS4q筥! ZKm-ipYSYJPCr!dzp7l !`cH=* ,4m }Ac=oյDx: n xeMU9Ӳ|/)ڃ]шr)_s{ XIx7W^RryV}tºlxH@ferPo۷۠*=7<=~$UzN$m@47IgLwB6- C-SCyE D|Zc͗&}@,gw ۷!`UNfC7.FfQ;ÐrWkC^ve:3}TO}"E'k_]%U /5xk S ?5{^9,&q⡺7dYJy(H;@fk- Mp yŤuu]~;V]]i B-, (^IGdCwTo#oIS˒W67q!'I ?_k1a_g/D*g℀֑yE=OhHpYr/HYVFŊɗxqxճr~jρ̂84vQۏ~]7ý` l?_-F#GU|Ry%#Rğ(k@Gg*ƃbv}-нy*}SY]c 9xnh]DeD?I`GD W/uV|ցJMb8UCN̊52z,=(\M?7QDNSuI y@޸gRuՒFÛA*d{dƍ&>͍V(]2p~;W 4gꪚQ?▓@;`20 SOOe8ƈ&\~MV12/y?!~`h.6Ife]KS[+PflܜnqZ{r!Ea?+@kԆ42.6fIwuo>h5sEi_Cuػ;t=P$B^]M!(x|D+8 ͿMՍ| u8юt_2,0?;g1\E}I>i~l Zhx45 wa&G+@HT E'aͣtkJS{䒐59wsl>H6N\=U0 C~rG`)VCui⯀|x(4AX?c]:[&g jJdQVDT)T\5^D;t>'jй/1՗=6H{D-ΗKOf12I9DŎƭ]ZnUV~^htV#-> נ՜Ĕs#Hprb$R?׍aۆ7Mr0UO%i-2&)Ž‚^4~:,u R^_ն,2B8HhYsƌ An#X ׏G׸X7L/Oc‡z3ZEJA",2bKع H?2=r|8o߱,tB&~mC 5Ul}(]4]g*^πX79_0m0 &'"CB_ӹ!g+kw?lHLaե [WdK~${qRqF>9Mnc%__7-OO ^7i9e>ZX73LIU %#3 .%{q4+KT:8*-N_m/}.Щu 8W+t=]_?%X#:v&. M:OD3R=K.{9}i^.b$|l`uCqH2DŽU Mɧm_uv.ߦ1uWy./}ݬRS˺^;5"<(̱C<ƅ(Xc C%M0Nj d#( 9Zbdi6p쮺'J `VX?dpL%|'tYǾC7 hPg|5gyJ/32qm= +BRh.S!`%F9fjV{&G1k1!ҽ;h⠵;:pdn*(/]ۑD]Ywl]dŨ*cVpxK:Z} )<=]:{ PK=#ԃPr3I-3J,[6&G&Y +rRXj<6#S  j_;ː5MP #OAa݈1'Lo7IaP0u PȜ7rdD$ch:!KV(Ps n{&U7ж1 [q2,Ef`Ot1t&4\HOH@yLL,vIIr#V_# IoR;rL l֫dIi9YIr1 k}I!vc>Qh"Y.Cz h$ۓǑu!|H&0)7;SeYO0O7 jtLŠDhfo}~Qɬ%WI%;:6yp48d θSYqNGU,OxݟA-?5<ј<",flH_FyG^-DT:DHY+0b|Ùr~V!"5^PlEtc-p 5!إLai{Q"9T~Ef`%^(VQY,m6^Opf5YxqPYob ,N%UI_ak"TJ^YT#f=i`lb8D$D!*LuQ[>,ㆈoH9k5TLs^AgL x1M_~dKg!&4+]R|j[ʜ5Wasg|P{pq=ײ%gb^hDjr.|6-C&M@i tRtmWT3y,m ݔ ٷELwA /ng$ϻ el!`u5]ӓ@ޫӼSG"vIcpI\-=D=ޜֺ}^H{r bT1ۅww1Dtx0O ᓟ'10Ug? t`',9QaiRF+HOꦝ"5p0 3/ d Hq\Ճ5EZ@?/O1nQzS}4Bo.\"#MpFSպz/Hyi3z_V:=9hoNKYdQ GάsQl(D;AM XWGbn|>|w` jiz+ aмփ;V8Q|㢗I=b Q %v#GkưDx]=aru>R-gHu0&4@G-v"4n~87MRHazMS8iHx{QL Nma|IT`at-y3gW0Q԰D/ ? ilw Dk,3;l5rF|+oڽK3\fa$]^Ho0EH,Fh `'EuGiIAhn?8G1AT7#0bՕZl[R MWz|eį54 aڿ''F*XkǎTdbD֘L{P3JHxuXK{&|e[.Jk> ;]vuVI-PwNtHVc>EcE!մRe [k8;Է_yLe3t+65/랁2́uk\hqT15jsi~>%T1l 2^@ӊvHsI(R B y;"jM8q_)Q@KQG y (T1džG %Ic`#1S4 Y]V1X{AS DaK8 0#61Ju{lPV'UZ9CnRl'=:tڙ]ƫЩˮn S)tT=׭owSTȶĢ}!g`/VۦɔtʃܰTZ8[aKZP-c؟/]ulxLa_ekUUSp~i0“Ք*W:%0GxG Kȳ=vqqErwT ib/+"Eh˯ađd.3'KbAvHŵR%8;1|R,[­'B/ANyd7l t*D#/ԛAEe_^̥6fT3J^_*.Oޥ[C\ fzΟtB{j-z*;N]wplAmŸJvb"4ؼh<ú2IZzVPğ½ CYL"5}P7BX4*xqja˚PwMP̵DfcԇP)/g\Q{u*S%.rhY*f]OމYZ@(?EUK̈Bmr]r),"},ʍ_$DEeoBìG~0~7{VbpCcB-a{|b Q2"f KEiyW6LvNI:RpJ}]|q6tX֎5!>@ͧ<?{+xKn$hE D<9nԈ >o( 9mq)+VG& 3^#AoU1(#Kм{M)%q ]ER}'q 4@_<̋Ud_13ȸɉ+f_ќWUyu˛&X2fȽ.kD̞XV?u _4ډr/98o v̞x:`hg Д cjcy#t;=GVz 9ksy; R7+;DXrt[)Dz?#~V?D9(齹ӛ.P:)D?L2GU%:lEY_دM5wgO3>>Grw\42cɞ"o YyZ5VdFسp-aN) ϋ@G,V1 K 1j661Xr\QgiO6pZ7 j/>o闃3;8,R #kKb'q7̗<~Gu8ez ΢mޓ+]BIh-`xS38C)0w )3dxΏhnK1}&( 5e_yݛy%}$fXPBq4еm ZERP5JAzC/Z 򰿬Sj.TQY-cwQSHnH\!Q˵ƂqH˰ ؂g*`,Qنxc-uwd p;&gΎAy,hu-Ã>/25pxDm ^78(}>V6~ *];=?l<y^|ĚnkBIOSY/^K :ۿv/=]㛑뿨""3hl`u.G~O<:b4(TB˼pXgV~3E ǟ"IݫS;Sfj &8<"t)@R7#dD OmACͻ#]ǻ[ x?~<d~es z>a N;9ޞ|7e>@*¸RO>Jb*cӭԎ) r.W_zGP*T*CC'\G 5]kL9ĉ*H&7)$U.b _3EaILN.};g ֕_ojnnWpw]Hi=u')jjՎ da.a"Ҵ"Ԋg3)oijtzh_+Yصɔ/ %5X7Ďɴz}ZDO'AwD',c^0kbM7ӱ~^[VlsR1?{P&C xiAoUpZQnCMO.|JQ$.5U3,~I9#IRg鴤" #߀C>,w\pPqKKcuU:ӹnnH?;%ljD?J@RG⽍bc.Ou^Z$|h1@PtCi‘]%}Q`}`Ch˙}z{{P)˰\Wٟ|CtϊI>]4eЪi0.MaravD s0vD|~@=5vVB(l/Zɔv]K#& ëʬ?͔L$S jue$@@>X %тL'cE _f3U5I ŔaS=->=^O;Q hSf*rsj4nh4Ob`\,PVqkWxeM15[L9]awW|6ݓ \(k$ fdRx$v>*_2 ?[kbp]!s]$_S=^:|HM].<(/Gmөe.څga+x4Yp;N쨴 \oԣrntz)UQ9(:#^\XՃ]NznT TПP (`! X6 =Sɕ]JL5yQKGX"Ryq˨0jJQi7 H "!cޒkE@9*u  TbɥvTۍ7xZlM0R[v1ZySUШHylhFm-+i;21C,Udj_-uR"ܜg?w/f?wHvcepC9ddc]_5JD1uq@A24Gkx5t>JPY$uïrycAvwmb vEMes:R^LdV>J 7 i#͈ sLC|]G0en $ _ZRhu_}NCF2W(9iwq0h䧈%2f3 ֚1U>L9?W#RK%X/h!q))'a]*GwY>C-jg)a7Dp/Ct_Dk)5˜{­iJI\Nu 33G]Z s9"fh͊,S[H+kQ8_moa.{eSXMOJgґAIi&>uDAQә{\Tcx0KODQX&=z<ԁmp:eg{>I87y@~Uy!FW;\{ - K0}ѬUf8G PjQbp*\OHZ +X@qҘL)zJ& 0[3Q_ {]m_&L7YL'Nڂx!<"Ե{#N⭅8e-`_08(m|G(ihkQ{4ph'o^_ζF>*qKnHAi0m jrĹZ)ql 1L!L{v0 rكK&6^` !}Ta ŽhPa8CzA%|::Xv1TNEY06q<%U+:'D$AMTM Wg J49E38hx2Jy>/Y-G֫= Rk$pfUg4#G|/0|zؗPֈy }LiJrtcuxBP\*OGڼ& 8b55|C7+dX m8AB͑GswSo ?TY6d[t 5CH7Qb#x&wci40#uHgA? <8@P6w@f۩kD<`sm}ezzѭgr@op;PKCΥ]QDϮjVA$2t1-+h̹`qϑk5|i(P/í?G5Nt?~W]!l6 >O|I3S=6y1h'!P#8&=IƔ mqy:Q= b-;x_^Yl҇h@ĊreP $C05d 8&:I!FYĈAL<#ǢԲۢ[e=GF7(b=2o.Rd^[!%r?zA>HfMH =TR[dΝMzi5X$gfZ`,o1T9ulPH60xb'~ѩ<9S:>*;g="=(yHxJ ql7r Xk3?[iљϟVL ! 읙yYN T]!*{!Yʎ- .鴼3]M; vhRinO`LA)T}~^T m>9< JB"*bك" ){mɶy1Xz}ZEldy$9bIT;vjbV# };3k u9QW nƊ;,yR OEesP{2ZVQʤoyɰ) UQ|՛6XpW6LỊ #kГ 12a %Tc%pSAQoF9.5@LP߷E+;0-/- $3B=\ lU]yɷX_ZKfnkM~vL jd>V+A).. "UKCW˃?B}NZ۔4Z2 >DŘY#M}C1ַ)m8b@#O`BÈ٘?y6]/DT^~ϣLkJtpύ Xu9xycux.y99à QH 0 A&8*8Q4klrkRg-iUҺVG)Ǖʜ/A43h^6y~&)`1&QOHTXwP[sp*A1AИQZZ;隗̵sBJ}%D&K:gJ9JzS5Ÿ$>"[][⬚Jd{3R 1=A1 =3,rA`讟LDx p's w C70$?eH =,'!v,+ Nئ?3xh=~5H8ߞlOlgPӇ+ʓK"Ly}g[{pAǔNU]i܏QȉF|-lY䔮 [e ig* ~΄Yd"c7̪uA^G:hV%N,.'O+U,̔ Dw)a٧7Ϭ 5mG|8䕩-p30{E$ H-du F>g̭UV0< oڟomРuNF4>k'8"> TX0I<-b(&NGP {n\JHZ't .8Cph=ʟ0NbYvY{ͤCGQf5ZCTumk"@W"s N9?p[ܛ$vƺh*6P%4L\FQV/vߒ:.D]=j \7X@&b!^S͎/ўAX ,8|Y.Mе F/HPx 1O?vlBqBpqȢб '-АϛI?l]J4sAo!6b-xCm2s1(|`EyۗORs?[۫de6(=\ۆ>7Yus "(e0jBt_ ,M1] '4~^j^䰽"哿2M*ZS? Y`mrA/k ]:a*#E-%mLAtf M8!s:r$G}*PI~ts)jæYڢ-qpЎ[zOTPFZGҔa։p~">8(`^xS PWz(5_Ez/^ʹp^P701L/Cкm[5%f36M@c ƎD_ s6Q-[wrHaC԰f/H@^KitL;R:)i:>,t|ao6lKv^QgY:B5jGIbG:`إ.󝔭p@&Iġܝғ /,[,$?{}\Dhϭ~C7\ ƪ y9H^"]5pԬ8E ݖ$ `nojK$;TQl^Bݴ!)2퀕ھj&]Y -z*(wX@p!ŎClw6(CṊӲ,KR<ީVK ['[nJg+\o#0pe>W\<5} C46U-ouOFy67OC[wg"3ϛ.q((Nu2мs#Hv~+V⳺͵NY@Pxo"*-z,k$Sf\'$r1,*BAt_d¤eT*k+5-۞b8*I %LbD/ o(NsYlVOݖJ:QHnbVU1ШŝIޅ̬8.^v5bևS Vuѡ9UpK}X*lS&{G҄4N963(yZסCo2onLK<_)~l:VN=B+R hC&RS ^KZgW3d w4>1YZq#ON@b}KUCBH"t[#y|⋣ /ʤ8w8uH6 SU[p/GT:G 7~nf 1,p˺Tx)9O萃w ~T2i󙂑.)qqxh )pI!}8mR302ULZϊ~)}(uXvQ])v; f)|CVG˃Wg oeΧn0YZ$ΪpU yE%5?Buj$}C.s 6!T)Mj91#v:+?B >W7ЕAzVOu2t՘|PN[vTkחl<|.g?-#A2&wQyrAUG8!Y*R2G_Pąøv]?c%-;!u[һ/@_{dIhݮl{B2l%}'"C= 0Y$B)mt4bd,q46]^S7ζkO%qFⱈ6!qo+U+IR/:ap8G-- LܾW֑RD a7/y.k@J#㒭q KO'}eT`MZ뜤,h|$Gw1MppyH.{<_ο6|F;:SKWD+d.{Y(lឤ+/jVPƓX( XSD|/$4MĢ!vc rڎCw05ڼ:Ϣ~VR +ys߭r1vyAaΎO<) l6^1TOT, H do/jF'VsیG;qZgz]Yi:0:G,kjq>*ē4,31`+X[MGx%DF5U]k==W9? b/u 6R^[auCѮ O?F7b'$.} >鵺pw 6sяgrfYD26O(ڌcbT%okbݠTs,|{QU,Wuʸ i)#䑚eS KaŪ[^yz4mE ~[1l (8@ey9@hX=r_x>YUtJ;z8'g iHiz,Z}CIN}Y"Iz>.gple_&D\(#y"aTk!R$9j&\ &s24ג p%+,gZxuګ3Ƿێ9[(+qG6)=c}_ܙs \l/8?kHzde7W suiYWAy/87 HZg0Bw1_z cstd>YFf[;˯rS7ɩD(O80x~R5zd2[}Ԯ;`󂣞L!y;?r#,% h,DU* k>sk-bO٤`;PBiOJMJP|5QϤ6wاeϓwmjsgkgXsEPgv~rK.q |uݾ3U5iT.i{gFO%čWe xz&+&C`dSCKy[&%@'^ee\L0ɣI0>??i0+SyYv2S}VDtEnGYްEVq>^[[Fq-i=~7HRYl3VrX";\?gqEXhL~_rlt:&J"z_WJaQiH6k(?3 z2u&H?өg˾!?]6P2`fsQUC >\C$@\<:}Qn)PZC-?'%Ys=bh[ gSSy$4bOD {)l@ߑeҊr;2'd IԋR0?ߝܾ*ܽθakdf@w.KpV2%dA:·.ᣨյ+\~UQHP=#ܛU(PDz`"dG0멡qZ!ùX\})9~A.%6 N/?&Vx.h S[ 5;[6.uS֚H_%vb#~":WC7sipUoU(דoSJ4ɩ{7WjN7~._o@ ɍ;4,bTXbC* _پ˕Fcٷ xg@d1fXqںnJwZ3[;a~c?c/j, lzkb8a S\0T)u7B`wX=]jOϑlnb >~徦l5s&fptD]!YVmӥ扷KXprZnG>A¥I C)Z{ȩ'Œ,Zsx9K\SWO[ԼO&B"]r˵h8C]ø?WF<#pD;кpPSz?ЩOqB;cXm~C,pVg E 9ơrPX2fF"p/'ަB7D}`(kǏ C(XȦ!LTõ]ʺ:gF}g wmc+knHIg^"B1q^`H"9[!3`|14Qt6zRǂ@.@;~! 0x!qRwو덴4zǵ9 0@c(L!OϬqffe2:?ƐHh#yqt Bpۊs݂Z/@,h̛bhb{["oܙ>Aaz v&CmKMMwgʟ #񀙒,V=j*U3j]TuEk!ؕz.gym㻹q"HuX(/yVٱT,C570+v}z}\`= u͕m!@ulRWa/sL<#l;r}c]jܘpc=#\jM=jX6%H} 8P轢&୒,>3kv+)|=_PR]86.J! 6b ]،KЍ;vr(86C q?Y~baWS:}SqkoBװ!'aXi_I^+i6"YׅDN(v~,n|lIā\ˑ^f B P۰!a׻DȎcC a3jjp2Pmörp~Y-cߨzf-ZqJ惀+aB~H̛\އI+}e.P8[;04Q# 㝷ܨqpv(利g$5gYWK2Cmg9FoG1x7T6sf\LzQ<I/&[l lyY:ق(z }ܨהvȯ F^S58D t.I&k%`(|BЯ.Sh 8&o筟nKak:*Sh06De'wƗ×Z}YLH Qd$,ϓ (b d =pK[iRޙrZ{o?Rއ0U5-cm¨geqB$c¿v#Sf`rmDH? D*xLMN^Rjܜ߈ fY $ټH֔T(UoDC_:~',)LJTHbCh(hh^d,z'c?cMnvq-!`zx6G+fg(@{N-D(|1nSEo2Y}YS,1Q o.uS?)7P.cŇB= Qf MvPEO(@ĩU]^o߾t'T*˺ B!kvOƬ]wK5"Y4GGN"-]/FlO24#ڭpb5$,!j塵eg^ߓYI}ƈbn۳toȢf^>#u1fʋQ GًyHCwA}nrrH'CRկ?oى T5HB0a8ŷ_ q*X]]-mT!PLGsY:ZҼa&i_#mq-qhk_</< s=[e?D\ag$5}'zWSx]ZL%Q#]LvThkX^ >I,U}ѫf;R "4W5 1|KW|\ۜ쌙=÷˪vӗg?iZH6ڹxͮJ72é"ʪ8HX`}8`fu@c |wa`|5 j+۱p vzRq#ꃧ؋E hgQw_~8dR:! 4J i1?,ߞ}.^g{ W.tmK4'Zy"Pv,,Qp*>6x&gH ' |4ro+/G6Ú~v4GB;tr0Ȧ rR-?:x=;p}oqМӞ큉ԞY`EN8DY0-GC(#Ɏ w-3nZ>uTmqC&Fr;t'O\uJ.z};@3DzBNxj0>kό?isJ}p9\kb"/SA1ujԆ[/Du,{UΊs@8B{4k(r36PC`8>ĊjJЃmο ")tY:1da9f-Ild4HV#D%I=-}ՃZMpI9)7Fzw16LJftC:5^le@{8XLSt2(( o`j;Ia ޑyB1[r!,ЖT˞)'q<#bT/wgrQfxW$*C6im:c#^`)5CvuAD>]4DبZ;[G2#n1DiXNo/'`mO`)[֏B ;M31¿B Np lDBħ iD1z/G:+ 06O<7q<$tOa}*)> ,(Eiq>kzd0gZ¢ |rJk$.{޵њx=׮o9̃ưԈ[LӘ-Nx]-wuGGjH]מKꓖN_JYÝٸe}f^[*zy0DN ATʏg ?Um UOpݢ/:k4_ZX_En_Y?4~\,R:!k~Դ hP*iȑOiaX⢕VJCV랞CwF%C[2J,mo-P!$=D\.Ӈ-Or}7cmA7yQ(HBR+Ӛ=>;IGfk[3\Z) 9/.=Ξ(6Bc/xwJo6ߎbD&p=hjHO[y Ձb1i]>!/)YoCUI(eރ}Xw:֬zd5A=)%EPp{4jRKkDslϴRӬ6 u6]5^pQޔfYhm@a~TR4}ں܀ss=F݊`A ^*oC]$eDMIk˵/` 6&Qn֏(ALA.Q 8! Tw4ME";ǯ8njBXY,DøSzOIJ4w˰9)1 t.@M]< Ny*dRaݢeci+tIj_гA擼_+t7+qk`?=-^i"?bc뿌KA(87Le%DbƷ/R:g_*9 ZiD/)5G+a+jЅ{5cYƚ+^r@"Њ.)c@i7|17I$t6(2& @ M5m߿kbJ쥊ߩ.QޜM#JcU`aKoh*cۓڦ nuu`{$15eNS8L١mt £DjImᩀ`䃷ҡhǑ[H{cbÌXЇBLt:.XJ3W -4әk#¾-;O '&Pm腠ߔdrȯϿOan@]ޚfBQj93WZ#܄g^0aP6TF]EU[=7͡ ڔ4f,,"85uvH){ԾJT#cJB0; 1Vu09ox\{aqw`wLq0yjI)xB< ұ/ 06.5*_lDaf 'B5XjNRLy79-k^b'+ux$( LWAGg]]8g*tחD2|-46=`{q`e9]J/!>y;vT6=,b[\b(г3+SEG ՖTYWT(N<3]=wԠUPˣ) @ciOvo@_4u[[4G)NS; QPӸFH#W%*cMΓ°]U73PܦN H8[ي8r"ommiyeBnW R/d! бV3߆_A_fLȆc {{ •ֈ ŨZ;!2X@sA e :,)dU|A{yw.IFYQ˨ @HA@|M\n<חl #3AE[NI5lS/!Β9kL<Ϳc`ܝ6n9:e7ݴmD zjG/|]2-xU({36)f%`kF]#,Ut~ӶeyN avHZR 2Ƞn cz >kq44bݯ*Rf8g`/ko0nNL{=]zE2.ڝ,޵L*) F'.x׾@mɻTiWRs0b"ӫ.+ثg_ϩv?CIMȦ@ZR$ m+{ei4Dg&`j&ky3ycȰ0ݐr aX C4l %nKw`/e^U٣Q uðщoZMyM#Uہqs,HmP: hVl6`X=.B`ܳ8cە;[Z+Ufʸ:w 9n%ZK<ͭQ5<aY<]g"9@NVsH.y*ka>95PF\U{usoI$6yԑbbdӘQ1nyApҿ̃\=Z~BQ4SJP*Fz+?|Wu+LoL -m Z "G?~a mV6 |qUsӚ&F+dz|4iT7M!428s%$C -ħ4"A-w0-2zP0jP-/}U56Jk9A;$;x l TǼܶgzVù8 .vA]vlmyJYTR7|k),5X:B\nt(M-`؝J[&|B9J5xЦuO̝ad^ӚSJAL,O1"QsC'|YGBWhDtXKzhFcT.V*;Dajt(2ao/ʑK }G'.ɇZOhi: 6 I.LleG)ߙ"囻HRדp%oN%"h!=JzM%E ӽcCZhd0 <%[ok1?7 $QX~PY=%{Dt<\\ &NJ RR6|5y`p"#]WyiHl mIK`svXĶ=$h ZY7:DxDg9B$٪If2Ff}%;ߴ &z-kDM"D Da 72FGV_8ʣgsb .NR' Iu1}VȎ^Y& uj ]`8: M!$JIP |VI;[\!j0.wdb}b\XV+TK=o4T.^GűpO#mvۺQ7qԐj+`90-8@VvFF{D"·Z, mf|T=#ϦH1KسPnN tV-K2ndxZ6MMӖyo0|$dD:+|}ÀFZ-s̯[aՒ 9/N#=Xu͙O~ŐVѓ ԶA3J& M)(n^t&hߏ돴dh>0QTMHk `ld% rKl-s2=F٪R;tFS<@u[`3 g $ARa6:S$ahtwˀ<@"*)mZྙwçL//M:aO!( k}W"x YG?*۝+4r'4Ywk4+Y jkxUR[ J>q(:@.ٙXu܌6@igj):o7!t8:ho˔h **1 Y+17l}giK{rBM2:g J{in$@E뭀|dXQv`vu`1Am8ަ^?W4)͝J٭#PC],gVv SAJ(rXHmODދY敨ǗNJkW凭!ϻu3[tO揗`kD< P`ᮊ36`9l{Z(,~Wghtz:@y9‹-vM@kAOѶ=Ck'6r5XT"oAbvj [fg<*a^u.z.r^ @w6 f,XЕ6 ^76 b,$QP=Q?K;X1ʪF/ӎt9d8&[p`h!F|\Ehm 䢽HwKk%.v7phCl3eMKl\OEA^Q07Rb[s _ 臧l{%æ?ym R=`|*L4r>2A^EOcQj&]-ĺ&,^y(t88|a/r>nicˡ民imX[b9=.y7j /;ӏJJ F{ |qӫ)e.I…M9{g cfk %ZaH_w@m'.z5fN|= tGj>9*r{?'s)4]b|}2?[2jia:?~8nk[fE̒hlT~i1uKߠ+xuɎtu#{Iq9& `2sWXb}[QxnRRR k(Wiޙ])gY*\lA7!m$%SqiʽS릏oJqx!-tOlR9 ytWoPJ >^ @XޓBgOKZZTc[/\3’TcFH^r<**,O}hp\dq)ۊ0{½uy+qK%]&[ ;tKћmD ?Y -8mHo{T1۷6VE3Bzk<1`r5eroV