PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/share/texlive/tlpkg/TeXLive
Viewing File: /usr/share/texlive/tlpkg/TeXLive/TLConfFile.pm
# $Id: TLConfFile.pm 21770 2011-03-20 18:34:02Z karl $ # TeXLive::TLConfFile.pm - reading and writing conf files # Copyright 2010, 2011 Norbert Preining # This file is licensed under the GNU General Public License version 2 # or any later version. package TeXLive::TLConfFile; use TeXLive::TLUtils; use File::Temp qw/tempfile/; my $svnrev = '$Revision: 21770 $'; my $_modulerevision; if ($svnrev =~ m/: ([0-9]+) /) { $_modulerevision = $1; } else { $_modulerevision = "unknown"; } sub module_revision { return $_modulerevision; } sub new { my $class = shift; my ($fn, $cc, $sep) = @_; my $self = {} ; $self{'file'} = $fn; $self{'cc'} = $cc; $self{'sep'} = $sep; bless $self, $class; return $self->reparse; } sub reparse { my $self = shift; my %config = parse_config_file($self->file, $self->cc, $self->sep); my $lastkey = undef; $self{'keyvalue'} = (); $self{'confdata'} = \%config; $self{'changed'} = 0; my $in_postcomment = 0; for my $i (0..$config{'lines'}) { if ($config{$i}{'type'} eq 'comment') { $lastkey = undef; $is_postcomment = 0; } elsif ($config{$i}{'type'} eq 'data') { $lastkey = $config{$i}{'key'}; $self{'keyvalue'}{$lastkey}{'value'} = $config{$i}{'value'}; $self{'keyvalue'}{$lastkey}{'line'} = $i; $self{'keyvalue'}{$lastkey}{'status'} = 'unchanged'; if (defined($config{$i}{'postcomment'})) { $in_postcomment = 1; } else { $in_postcomment = 0; } } elsif ($config{$i}{'type'} eq 'empty') { $lastkey = undef; $is_postcomment = 0; } elsif ($config{$i}{'type'} eq 'continuation') { if (defined($lastkey)) { if (!$in_postcomment) { $self{'keyvalue'}{$lastkey}{'value'} .= $config{$i}{'value'}; } } # otherwise we are in a continuation of a comment!!! so nothing to do } else { print "-- UNKNOWN TYPE\n"; } } return $self; } sub file { my $self = shift; return($self{'file'}); } sub cc { my $self = shift; return($self{'cc'}); } sub sep { my $self = shift; return($self{'sep'}); } sub key_present { my ($self, $key) = @_; return defined($self{'keyvalue'}{$key}); } sub keys { my $self = shift; return keys(%{$self{'keyvalue'}}); } sub value { my ($self, $key, $value) = @_; if (defined($value)) { if (defined($self{'keyvalue'}{$key})) { if ($self{'keyvalue'}{$key}{'value'} ne $value) { $self{'keyvalue'}{$key}{'value'} = $value; # as long as the key/value pair is not new, we set its status to changed if ($self{'keyvalue'}{$key}{'status'} ne 'new') { $self{'keyvalue'}{$key}{'status'} = 'changed'; } $self{'changed'} = 1; } } else { $self{'keyvalue'}{$key}{'value'} = $value; $self{'keyvalue'}{$key}{'status'} = 'new'; $self{'changed'} = 1; } } if (defined($self{'keyvalue'}{$key})) { return $self{'keyvalue'}{$key}{'value'}; } return; } sub delete_key { my ($self, $key) = @_; %config = %{$self{'confdata'}}; if (defined($self{'keyvalue'}{$key})) { $self{'keyvalue'}{$key}{'status'} = 'deleted'; $self{'changed'} = 1; } } sub rename_key { my ($self, $oldkey, $newkey) = @_; %config = %{$self{'confdata'}}; for my $i (0..$config{'lines'}) { if (($config{$i}{'type'} eq 'data') && ($config{$i}{'key'} eq $oldkey)) { $config{$i}{'key'} = $newkey; $self{'changed'} = 1; } } if (defined($self{'keyvalue'}{$oldkey})) { $self{'keyvalue'}{$newkey} = $self{'keyvalue'}{$oldkey}; delete $self{'keyvalue'}{$oldkey}; $self{'keyvalue'}{$newkey}{'status'} = 'changed'; $self{'changed'} = 1; } } sub is_changed { my $self = shift; return $self{'changed'}; } sub save { my $self = shift; my $outarg = shift; my $closeit = 0; # unless $outarg is defined or we are changed, return immediately return if (! ( defined($outarg) || $self->is_changed)); # %config = %{$self{'confdata'}}; # # determine where to write to my $out = $outarg; my $fhout; if (!defined($out)) { $out = $config{'file'}; my $dn = TeXLive::TLUtils::dirname($out); TeXLive::TLUtils::mkdirhier($dn); if (!open(CFG, ">$out")) { tlwarn("Cannot write to $out: $!\n"); return 0; } $closeit = 1; $fhout = \*CFG; } else { # check what we got there for $out if (ref($out) eq 'SCALAR') { # that is a file name my $dn = TeXLive::TLUtils::dirname($out); TeXLive::TLUtils::mkdirhier($dn); if (!open(CFG, ">$out")) { tlwarn("Cannot write to $out: $!\n"); return 0; } $fhout = \*CFG; $closeit = 1; } elsif (ref($out) eq 'GLOB') { # that hopefully is a fh $fhout = $out; } else { tlwarn("Unknown out argument $out\n"); return 0; } } # # first we write the config file as close as possible to orginal layout, # and after that we add new key/value pairs for my $i (0..$config{'lines'}) { my $is_changed = 0; if ($config{$i}{'type'} eq 'comment') { print $fhout "$config{$i}{'value'}"; print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n"); } elsif ($config{$i}{'type'} eq 'empty') { print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n"); } elsif ($config{$i}{'type'} eq 'data') { # we have to check whether the original data has been changed!! if ($self{'keyvalue'}{$config{$i}{'key'}}{'status'} eq 'changed') { $is_changed = 1; print $fhout "$config{$i}{'key'} $config{'sep'} $self{'keyvalue'}{$config{$i}{'key'}}{'value'}"; if (defined($config{$i}{'postcomment'})) { print $fhout $config{$i}{'postcomment'}; } print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n"); } elsif ($self{'keyvalue'}{$config{$i}{'key'}}{'status'} eq 'deleted') { $is_changed = 1; } else { print $fhout "$config{$i}{'original'}"; print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n"); } } elsif ($config{$i}{'type'} eq 'continuation') { if ($is_changed) { # ignore continuation lines } else { print $fhout "$config{$i}{'value'}"; print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n"); } } } # # save new keys for my $k (CORE::keys %{$self{'keyvalue'}}) { if ($self{'keyvalue'}{$k}{'status'} eq 'new') { print $fhout "$k $config{'sep'} $self{'keyvalue'}{$k}{'value'}\n"; } } close $fhout if $closeit; # # reparse myself if (!defined($outarg)) { $self->reparse; } } # # parse/write config file # these functions allow reading and writing of config files # that consists of comments (comment char/string is the second argument) # and pairs # \s* key \s* SEP \s* value \s* # where SEP is the third argument, # and key does not contain neither white space nor SEP # and value can be arbitry # # continuation lines are allowed # Furthermore, at least the separator has to be on the same line as the key!! # Continuations followed by comment lines are invalid! # sub parse_config_file { my ($file, $cc, $sep) = @_; my @data; if (!open(CFG, "<$file")) { @data = (); } else { @data = <CFG>; chomp(@data); close(CFG); } my %config = (); $config{'file'} = $file; $config{'cc'} = $cc; $config{'sep'} = $sep; my $lines = $#data; my $cont_running = 0; for my $l (0..$lines) { $config{$l}{'original'} = $data[$l]; if ($cont_running) { if ($data[$l] =~ m/^(.*)\\$/) { $config{$l}{'type'} = 'continuation'; $config{$l}{'multiline'} = 1; $config{$l}{'value'} = $1; next; } else { # last line of a continuation # do nothing, we will finish here $config{$l}{'type'} = 'continuation'; $config{$l}{'value'} = $data[$l]; $cont_running = 0; next; } } # ignore continuation after comments, that is the behaviour the # kpathsea library is using, so we follow it here if ($data[$l] =~ m/$cc/) { $data[$l] =~ s/\\$//; } # continuation line if ($data[$l] =~ m/^(.*)\\$/) { $cont_running = 1; $config{$l}{'multiline'} = 1; # remove the continuation marker so that we can do everything # as normal below $data[$l] =~ s/\\$//; # we will continue below } # from now on, if $cont_running == 1, then it means that # we are in the FIRST line of a multi line setting, so evaluate # it accordingly to get the key if necessary # empty lines are treated as comments if ($data[$l] =~ m/^\s*$/) { $config{$l}{'type'} = 'empty'; next; } if ($data[$l] =~ m/^\s*$cc/) { # save the full line as is into the config hash $config{$l}{'type'} = 'comment'; $config{$l}{'value'} = $data[$l]; next; } # mind that the .*? is making the .* NOT greedy, ie matching as few as # possible. That way we can get rid of the comments at the end of lines if ($data[$l] =~ m/^\s*([^\s$sep]+)\s*$sep\s*(.*?)(\s*)?($cc.*)?$/) { $config{$l}{'type'} = 'data'; $config{$l}{'key'} = $1; $config{$l}{'value'} = $2; if (defined($3)) { my $postcomment = $3; if (defined($4)) { $postcomment .= $4; } # check that there is actually a comment in the second part of the # line. Otherwise we might add the continuation lines of that # line to the value if ($postcomment =~ m/$cc/) { $config{$l}{'postcomment'} = $postcomment; } } next; } # if we are still here, that means we cannot evaluate the config file # give a BIG FAT WARNING but save the line as comment and continue # anyway warn("WARNING WARNING WARNING\n"); warn("Cannot parse config file $file ($cc, $sep)\n"); warn("The following line (l.$l) seems to be wrong:\n"); warn(">>> $data[$l]\n"); warn("We will treat this line as a comment!\n"); $config{$l}{'type'} = 'comment'; $config{$l}{'value'} = $data[$l]; } # save the number of lines in the config hash $config{'lines'} = $lines; return %config; } sub dump_config_data { my $foo = shift; my %config = %{$foo}; print "config file name: $config{'file'}\n"; print "config comment char: $config{'cc'}\n"; print "config separator: $config{'sep'}\n"; print "config lines: $config{'lines'}\n"; for my $i (0..$config{'lines'}) { print "line ", $i+1, ": $config{$i}{'type'}"; if ($config{$i}{'type'} eq 'comment') { print "\nCOMMNENT = $config{$i}{'value'}\n"; } elsif ($config{$i}{'type'} eq 'data') { print "\nKEY = $config{$i}{'key'}\nVALUE = $config{$i}{'value'}\n"; } elsif ($config{$i}{'type'} eq 'empty') { print "\n"; # do nothing } elsif ($config{$i}{'type'} eq 'continuation') { print "\nVALUE = $config{$i}{'value'}\n"; } else { print "-- UNKNOWN TYPE\n"; } } } sub write_config_file { my $foo = shift; my %config = %{$foo}; for my $i (0..$config{'lines'}) { if ($config{$i}{'type'} eq 'comment') { print "$config{$i}{'value'}"; print ($config{$i}{'multiline'} ? "\\\n" : "\n"); } elsif ($config{$i}{'type'} eq 'data') { print "$config{$i}{'key'} $config{'sep'} $config{$i}{'value'}"; if ($config{$i}{'multiline'}) { print "\\"; } print "\n"; } elsif ($config{$i}{'type'} eq 'empty') { print ($config{$i}{'multiline'} ? "\\\n" : "\n"); } elsif ($config{$i}{'type'} eq 'continuation') { print "$config{$i}{'value'}"; print ($config{$i}{'multiline'} ? "\\\n" : "\n"); } else { print STDERR "-- UNKNOWN TYPE\n"; } } } 1; __END__ =head1 NAME C<TeXLive::TLConfFile> -- TeX Live Config File Access Module =head1 SYNOPSIS use TeXLive::TLConfFile; $conffile = TeXLive::TLConfFile->new($file_name, $comment_char, $separator); $conffile->file; $conffile->cc; $conffile->sep; $conffile->key_present($key); $conffile->keys; $conffile->value($key [, $value]); $conffile->is_changed; $conffile->save; $conffile->reparse; =head1 DESCRIPTION This module allows parsing, changing, saving of configuration files of a general style. The configuration files (henceforth conffiles) can contain comments initiated by the $comment_char defined at instantiation time. Everything after a $comment_char, as well as empty lines, will be ignored. The rest should consists of key/value pairs separated by the separator, defined as well at instantiation time. Whitespace around the separator, and before and after key and value are allowed. Comments can be on the same line as key/value pairs and are also preserved over changes. Continuation lines (i.e., lines with last character being a backslash) are allowed after key/value pairs, but the key and the separator has to be on the same line. Continuations are not possible in comments, so a terminal backslash in a comment will be ignored, and in fact not written out on save. =head2 Methods =over 4 =item B<< $conffile = TeXLive::TLConfFile->new($file_name, $comment_char, $separator) >> instantiates a new TLConfFile and returns the object. The file specified by C<$file_name> does not have to exist, it will be created at save time. The C<$comment_char> can actually be any regular expression, but embedding grouping is a bad idea as it will break parsing. The C<$separator> can also be any regular expression. =item B<< $conffile->file >> Returns the location of the configuration file. Not changeable (at the moment). =item B<< $conffile->cc >> Returns the comment character. =item B<< $conffile->sep >> Returns the separator. =item B<< $conffile->key_present($key) >> Returns true (1) if the given key is present in the config file, otherwise returns false (0). =item B<< $conffile->keys >> Returns the list of keys currently set in the config file. =item B<< $conffile->value($key [, $value]) >> With one argument, returns the current setting of C<$key>, or undefined if the key is not set. With two arguments changes (or adds) the key/value pair to the config file and returns the I<new> value. =item B<< $conffile->rename_key($oldkey, $newkey) >> Renames a key from C<$oldkey> to C<$newkey>. It does not automatically save the new config file. =item B<< $conffile->is_changed >> Returns true (1) if some real change has happened in the configuration file, that is a value has been changed to something different, or a new setting has been added. Note that changing a setting back to the original one will not reset the changed flag. =item B<< $conffile->save >> Saves the config file, preserving as much structure and comments of the original file as possible. =item B<< $conffile->reparse >> Reparses the configuration file. =back =head1 EXAMPLES For parsing a C<texmf.cnf> file you can use $tmfcnf = TeXLive::TLConfFile->new(".../texmf/web2c", "[#%]", "="); since the allowed comment characters for texmf.cnf files are # and %. After that you can query keys: $tmfcnf->value("TEXMFMAIN"); $tmfcnf->value("trie_size", 900000); =head1 AUTHORS AND COPYRIGHT This script and its documentation were written for the TeX Live distribution (L<http://tug.org/texlive>) and both are licensed under the GNU General Public License Version 2 or later. =cut ### Local Variables: ### perl-indent-level: 2 ### tab-width: 2 ### indent-tabs-mode: nil ### End: # vim:set tabstop=2 expandtab: #