pyppd-1.1.1/0000775000175000017500000000000014755402564011356 5ustar tilltillpyppd-1.1.1/MANIFEST.in0000664000175000017500000000006014755402564013110 0ustar tilltillinclude *.txt include pyppd/*.in include ISSUES pyppd-1.1.1/ISSUES0000664000175000017500000000006514755402564012315 0ustar tilltillIssues ------ * Add unit tests. * Generate manpage. pyppd-1.1.1/LICENSE.txt0000664000175000017500000000204214755402564013177 0ustar tilltillCopyright (c) 2012 Vitor Baptista Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyppd-1.1.1/README.md0000777000175000017500000000000014755402564013510 2READMEustar tilltillpyppd-1.1.1/README0000664000175000017500000000415114755402564012237 0ustar tilltillpyppd ===== ``pyppd`` is a CUPS PPD generator. It holds an compressed archive of PPDs, which can be listed and retrieved only when needed by CUPS, saving disk space. Installation ------------ To install ``pyppd``, you can use: # pip install pyppd Or download the source package, uncompress, and run as root: # python3 setup.py install It depends on Python 2.x or 3.x (http://www.python.org) and XZ Utils (http://tukaani.org/xz/). Usage ----- At first, you have to create a PPD archive. For such, put all PPDs (they might be gzipped) you want to add in the archive inside a single folder (which can have subfolders), then run: $ pyppd /path/to/your/ppd/folder It'll create ``pyppd-ppdfile`` in your current folder. This executable only works with the same Python version that you used to generate it. You can test it by running: $ ./pyppd-ppdfile list And, for reading a PPD from the archive, simply do: $ ./pyppd-ppdfile cat pyppd-ppdfile:MY-PPD-FILE.PPD For CUPS to be able to use your newly-created archive, copy ``pyppd-ppdfile`` to ``/usr/lib/cups/driver/`` and you're done. The generated ``pyppd-ppdfile`` can be arbitrarily renamed, so that more than one packed repository can be installed on one system. This can be useful if you need a better performance, be it in time or memory usage. Note that also the PPD URIs will follow the new name: $ ./pyppd-ppdfile list pyppd-ppdfile:LasterStar/LaserStar-XX100.ppd $ mv pyppd-ppdfile laserstar $ ./laserstar list laserstar:LaserStar/LaserStar-XX100.ppd Contributors ------------ * **Till Kamppeter** - Original idea, mentoring and feedback. User #0. * **Hin-Tak Leung** - Lots of technical suggestions. * **Martin Pitt** - Python 3 port. * **Flávio Ribeiro** and **Diógenes Fernandes** - Refactorings and general Python's best practices tips. * **Didier Raboud** - Make archives reproducible, by sorting the list of found PPDs and using JSON dumps instead of Pickle dumps. * **Sambhav Dusad** - Streaming decompression, to not need to hold the whole decompressed archive in memory. * **Google's OSPO** - Initial funding at GSoC 2010. pyppd-1.1.1/.git/0000775000175000017500000000000014755402564012217 5ustar tilltillpyppd-1.1.1/.git/objects/0000775000175000017500000000000014755402564013650 5ustar tilltillpyppd-1.1.1/.git/objects/pack/0000775000175000017500000000000014755402564014566 5ustar tilltillpyppd-1.1.1/.git/objects/pack/pack-a6fb528e05bedf061d5fd41f60ef70dd206ba153.idx0000444000175000017500000004671414755402564024061 0ustar tilltilltOc ""%)+.0458:<=?ACGMOTX^^`abglqvwxz{}   "$%(()*,02258:?AFGNSXY[_adknuv|}  "#$(+-/34557:;<>BGGIMRRUY\`acejlmrrvxz{}\vP+]a6*\WfΔ;#wN*'@VKԄĢt;[F MZ_nV(Yy},2 BZ`+m^&l_v.LNƈe2S\I4y}vӃWQ-RkiVD~9)(]3Z@tM߂;3Q "tYg?kg Qu(݌Tꪅ {9[-A*ny6t##QfBٱt;+P'=pSrnMҿV="ZPj0lk=K@17FAl+SQLfxgcFpL̵&ޡ ⥖' k8wCJf̿ w|- >&; ?kX7TZ E `D (|CR9ݼ W En, U,t : [Ip Эp|oijL ^08: z{H @Jr'=( /^Oq\ݛ OJN Y,S!~ I̠һ=W- UG[ƢkaoL e!5}pNBqx" Ʈl<LBWHT܃+u!bY@]uyY%pbBH z0ٱ%'JDCpͽI L"Vsb:cFQo0@fLD5.:hfL_!k%aEEU"(4 R9f&U^]%MO%), yۙu.uLUV> 8c,↦FN\Ⅱ:% aPF5[DBsdR헟-wx!h6M7@ ˮL@rἐ;+mG 'FqocVjGƏqN4^^VJM?ڹᐒ%NojwP8;i.L,Y.ؖH1~Vn@W_0 =;ҍ|qIнCob< NEOe2%Q},> )KjiiVtxc<n4A]|C-kPD'G|?x*T6^RkX}b8n)_Xґ14BI(5ῪtG$XIۖg ^Э @ʌ4KG_&+)TP tQ2s B,*yZxڟ|tOyW8d@-@Ύf^98@\} Q\ pa_=m5V/ ̧TQJ]o\ }.#ZҾUyi5ۭ+S.z8|N0f}فB _>oニgwz&\dWnZPM0n 32onHd i= A7-"wU8v<r) b8B 7xdJ h Ʌ2yŪ[Wb S yV6v㨤n<γN *T).h& ̗x`wE}~(te!@DcXt0)!Wnj'ޚ+zp]R!jryjۂ/$jt! !Z+QNwaWD"zKlxE`p41M"$"==v~q"':-+KGF"gaVTX" *1 :]H"4;zkgŊT) $QZd=kxg$*-Мs0l`&CH}\%_Rr@}b &9Rdyn ς}x /'4욥1~t8@+'3ǿ^]uaםn'kO 1[m &'wg|*jG'en"i*<C (,_-ِw"nP(,ÊUMQ|tϪ0r(tX%ԩgZ(;epS򝺗,/(yo+/J⎆)3LozjK^)υxwNtXW)Ϲt9'4ȸ϶I){p!K_d5Lhj)i+[bUX{3*~,uv_ѲWY*:*Dͯ|O*%!a6N/7cJ*oo$xKR891.*wf*C >.+w+z0 *h?G,- C5 HU-0!! h\?-GzWVS+wz.dӿX x%~.m/.}/C(%|ag/1}2k$=uM[0h'd•dGWXz#0,wNpHS_̫_PĢ(0a6}*/ 18DBr0sJ< Rѵݕ0x^6@_{q=b1<~D{{(C4Z`Z.pH1̏2j[hF\2d϶í>%kMyv2C)ځ`sNX_{W34fXDu%{WS5ݭOM@Ũ枸6j4pda#v^9623? 8Z5gmZ>}75Hሡi78oB`\_t[8t7E66pӯ8Γ)ucU768hZpqPj&D$9IjJGvw+/9eݾKM{gb y9b)Rr!28Ε:](RY`Elh:ιRozaJl}-S}:>c귩"A{wPo:Q~R&c_S,;odqS6Ȉ.NE# |; S %㻭0>]=S}}H]u=1Z~4c=\ZSA^Qk]wA;="iLH t>(hPԹ@fH>,@Fiytc]>L[@$j7sFW>M͛vMHtW>btB/y>ZCkսէ<ǯ>\(5"TO -><e[{t? 4ע~y2 OY.?M]y%rSڀ{+A?bcvjp0pl8nb@T#7V۾. N@NuFB,A.]/ir HߜA$2P>f1L6 A=yI%2B:+ɤ?{УB7v}B9NBy饆@12BsYbD񷶼cngiiC1?B*Dz.C`N(x^d~&?CGI@膘|o"YD&q<^k;DD@ZxH9N~(DDm<\ɺ $u0O!DpQ(ܧAD^EE 9*S_vCVFLzblfT>jiCFk,}H5zj9FK+3̓oFX~VQ^u;G&ąUl *j G_~%o׻ ~xHGٟ[a(7{3W0HxٗH,W|HPV6 fNI=O/b3C]Z};4I_ssv9C%+;0[eIؕS2 zE^eI6%RR-٨/ANoJα{9lUZ {J<=o`Y=J 'p"@xXK| pRE5(7K?.'qÓ=0#?3}KKn-Hckg_Ly K%)F?\LR.liJLؑDi=GL;`ժ/hk؊M3g'WcbM7o`(aMqmhhM翬?.d~)b.ۡOox}!]O3yQX΄#$6O>AaT71SgPfVYq82mlmOSj@DI` iQ鏕i 폡)xS I,Zᣘ)P"bSY%`]Hho0S0ȸ\ZYas4TXĵŴ΄gO'T"N_մ##GST'p5X{TR/Ty(`ڞ*dUݷB>W::U 7GF(Q9VZG)wFh^ҝVrX!fYÎV&TUy=utWRǢw W(GnR3^|CW;j?|r#PNmoXa:}=;8>"BRrY 5''ĬYZRyLkw[m?9Zw+C0y湨{Z.6%25˒zHZ̙沚)+G.)Z`dv'bsl[F;iNS|%~([T'N3 y[yG6Q۬s;[ AL_)z fEf_[4EW?͖[fet5D>y!.W\ 鿇wb<]\2܊2:)p<[r3\ imm8e1\…;GR&l.z<]<}0KʰH].6(1 ݿ+]ɺjՆ@*VA]oclG2|\hc^f/q($]#o{^}-ѾiCQ[^?NjBj 1 H^ Z6_cn!ԝ?^dǠ,,44`;vMq M*U`E<ϡSjʂ `ڌ1 sZ,Gazl D5aEo_eJgs(qw9axZ}iHH67cJd#/adf\+hWYejߐ_[)DVpfH=t::N=IfLm jE'4K_g1xVf v ťv֨tf:c8x| m0"ig Mۇi^ [g}9U`&H 8XegAc-)xUS+H)4g%i |hUvb謊TPiLF&0%ʔ:*fs+iQMgLwU:qsi{ ~_k#_Ri̜@#kJfIɇv>W6+]kܺ HAhGr (l $MQ<nlzp:9^\A8{lA[ f,Tɓĉmq/ק (#Sm\28,ޞ1nSUe! [SoMh<` K*_ +H}roWq? : 6Toלiُ3} )|TqïB_:=rrl򒣱5e9s0d!uSI3>qsm/( *B%VCxS'DA#DZFCnZIx`۵r7BV-yT&'F/yGCDPq<5z:ء ^mn6Pz<(4R5сpz@%дY|/\zR5v] 2/CxC(zk0/N,6{,,_~5${Hig7|3(b.k'&9rJ|UXҰQ<<+/xt|ѓN%$F ګ|Uǘ7=b.Si|#|S>y*Q}/ 5M /~ ;{ũdp~ViQE=a|n~V{ۗUau웷&~ax.;"zs=dK?F6~(ϚbBKץ~:A!ѣVx#~ u>_J#|ݍq׻{&F] h:Q5Ö7 ko`\9HuzyMA}4?e/{} Q~\ Jh^tE$t:* $%|KހnpnX]gaS 1S5*0&r ܥXb 냣S61w`+ЂQ`oح?pBCCM/[9=qV64) 8!;7f[hhQسb_˸:T3QO:izdp@@B08R9ۗ%f1+'3!*(c(ըqZk~Rqs\X~ЃfKN:'+v&ZS !t繁Zr±wBmz$k Ůa` \sF9(rf T*u$.L/VxϞ9izUW Dפ* !$l/g4M]8A/i2'1]@֪qt.J`Y֋ߠ:ʆGO"ZO6"uj᥋+ϙLYz6TE `w& $$^3ȈA˘J*_钥H X2pNHLmD~v6Ζ+ #n;Xdbw|slRv4K&K 3Mh@Ky{a%!3HvHC9Z}ض=FH˪M>U wZaQS YpC`9Ê3 l`L߾cY(WCRk 2=7Aù?rʎ| 8tnU5OV/ϑio;8N|׽1Zޞ!>?Zh**%uCR݇C54*'AoE'xUԱR{H8‡t㓒CoVﺡг3mxwi)MjU8鐢ByĕXO? ]FnbBJUpu "2yw1i (Q5qHkO>gK$ m_HqEcX Ub\rrw6 /bוn(D ʕNn}?h.de8>p4-aS*ZbJ(x7g:A8 y r4kr 6#Jm EetU^bHp;dCy@XƘ둶\@"ݺ ɡ՘j-kM!rKFՙc]x5S3M;^ل}hhYe6UnQ^HL6vn왮jHZMVln[p,Ys`RȔNo3nMy!FH_0S=$5a>NQ:D6'(}G\9<`ESsVEP@r? T@Ed?O$hwx (--;0seV|_'vi.[xC+^ ӝ̝jc1s)ʬ`t'@ 6?fˮhaB4ӢscP QO̧i(NV1ҝwȠW5ؿ|lV+FS8)1#ڠI%yΨ4~{8bTyzHz>BޡCFwVq . HD'/ +[,}oh \-*`e0V+@$l1fp\ԕpS},"&/*vnB RTrws!6s Ra5_kEĖ#:AN &]3QYka!*PƔsA~?g0h*-LZSwx+jPs>_#TA4/ưש>$bMOCݧOp*tzFB2X3ON;! y~JeAzL>1"QMn[U{զٳ ɪ!6Oڳb(iN 0f{QtsE/USTߣZIb-.:vC~L;LGg6Q~쌴>ҍWͪ!WI@ͮ! `oņ o桯<@ ߻M~֎րRi:iR ^Qr2X#S}]2֣ba1X gN@/&gQ2s~Ĩ`i`0/+ݯgm-ZRx>uy 2zv_Bp=(P@{+ ̇Q:V%!سK٧3Č;0v`2>=~u_9y q..Mvh^cO!WW*nKEd!68Z:(>g]o#K`J.`S0T|JܡvŭZ_J^|}*Elztcy[I͵GC1#{02nQsS<q0/ԍ(i˹AEPV*Y#Vh S{\F4f)i-x1 JmFQqnq[9;gw_6ֻ!8.TN;a1Bk5[KjJ3VE2qS%`pKioѵti)-kݘt$,Psqx/ g^0j!8".A1_xcy$2JI%qĽ'!7f@gp)̋%&*ҏ,zLQ`'4|؞%_61fIE>Ww6g>Ȕe6]%.|'0~I9P tp]! 3?_fO-Ȣ;|D# ݺ,$]~WpK:z'FH$1"*D@h ;j:#cs*΢-_ఋJ6KZ}6Uj>)Qa$c$ŝSeW۵BT;?B$sxf2WKV ~D߾nV`Qu0sv"\ ,77ǟA07@x 3ޫcW.?V:OW تrz lO~U«0$u/&VOPMY !lS n)|LFv&b!"o{iQ3稯6eI~uS(o2ƀ1qq,>č-=hU0|!tLW!uy.-&/\: nɶ.Yl>!omS Gm4L" Y{۵vydkk3v'{ 4%OˉN71L}, Ç@J5Ò5(̶{U_af^ <3]rDINpC[Q6VzvL$+zg,a6ݍ%,b槮)YCS6C,JbsDlJc 1nٚulHe ٩1k6y6,\p+?/MZڶvnFsiK0b@pMٴO,O?8̼4b?r3 H">1 _!*o;K=@Kg,sGM4V<' h2i+jD;-r0J@wKS4Q*16]=( =,#&tǨqz M#b7J.e5nײ;ߙkϡ. 8߷¹Ws<UMġ+ A ]C\BP==w~y qぢ0@\Q.d/#/)mk cƏ0|QFOOOܢcPJ,(5ҨisL|*Fζ+@N0$PEO8loglx]RZYuhس4x#]ldxPw,K4^dBq3wz8i gԆKteUePzFJ MC/VSWAhKZ3;fb=1l/4'#{.LA"ci\\=pMݣ"שYq9^H/ls \d&2p}ʷ\*2aJ|)`FօdbJ(Gy1 T.`љJnE2vYUb!9"ag5`B~iўsɕ3`F0`7A"JWJaұF7ԺpsK"`zYjCX7#a/Q,eg;u]ˑ?o- gr&UߢtRxq'C!lEL#8znkP>(([ iBvD͟\⾣C=;XᳩюRα@V=VS]s\ƲfiAlDlix?ewA"δQDdYn}T&R?1$rYB?g(u߰L ]Cj"y"WV5dW1= !L[,/HLKT֪ZWxɲk)s.ߋS\rr,T xmc cjZTX >-Ӛ[z.S 3*~ h[pJN"J&d%¼^t4tS F1uY]/;&o"P_Q/s@Klgdۨv6Yy]n3EouFq|3@;"uӯ4ъ,dvj\.j|NAºf?<*Pph8qJhW扠E0Gȅ%M]>FvTl,y/(mf.D(nw7V|2EߑIqR fd-񾂴LK8zl~R4]I i ?_|寡!*ػ V?Dj7u|0]0Xd?{j{1Q^$ \6K:(x1@20Z ֳQBu wДaL-DSVFg9>甙Iro!V!$vbu_<, [+svHi '\‡VW>j2>;zz2g=`Nlٯ ?b+@$N;=֮]rkH{*nC@XrcQPt!/Co#~#XfNd WζWs=-yW;>JM,W։>i.+#D&hE8/ssC$gT'>(Bovա}rQlQc3LBN$k\ÊD+8k~N:h mifʉ) 'z3i@u`0)US9tZxZ5a\rգl]bw^1NjrPQP"cB_? G\_.2w[Г@%y\$9|hf*!i5jZZrQB}(řxլYWZ=&V3N6M|4GwԂ4яmf%Yy@W*`@Ӻkt>7W93T3]nORo 3T$9*k041jg / nE2jwө 7صN,od/']Q4 ~I#.*c>2. NOqcb wX:Ʃ(Q|1R*ӣƫjː@\*鹀jaNY;BPf[ۖ|c- nRku̗JmUf4('n dl%3q?}tdYٶxɅLt7H*L9C}9 .(4wZk})E# %{M3`77G2"+(z*n~j.j ieڐ5@ mx]\w7MN$%FCK} Тu TFuC)0^XO .le5h0Pz1bWlߔɯ2F"n#k_/Ts, @ 9edeCw%EMc`YipSK\^g|x*SVB3uઉ4c0n3%l`PͮsZ&j`Hlv'<@~Ze)}˞BVK1yM@GDbC7/YI^sYs,?Id}=HjoV2ń>`gR™z] I:Wr`vD=̦3riDtJ,vqbj)p! Ij;^vj5, h: uz]gX IZ'ۛY\TMuGjg s9U`QMyq:F:d$d+v\ҴSXoܑ՛9L[ 7 d ȋy'g B~>[saC/ e*MA'&Q,gNbQZT DFlGxe f?40.M_5|wHǾFt n?4+_P|I&A?SK:HYxo=]( _efm1 =] VT/F.|?&%s1^2ogQN@)A`47I)y (75` kHkXO7 17joeiI!Ma@oJ&di^(' HJ|k.O?N5.^?.pY D.PWvk(c ( \4^X@s˶AG>&am,%>Nn!Tt-JBW\QOeiuUB+k6-c.AP_'\;BH~ ?V a83;^b,w0mdnq1 0E K P5`c kP'e">;{FYV8B)5uy*%wn`3Kd(Y`$Rfѣ"6[MX;?^<(ʪM1QzKmrhN"ktgQvP}BE%dj!hPW8 Ilek4ubRdr v}$Dф, /Cj"G&U18B b<iKW]~?qvCR]'mW_MwJ^3q0liDj# w,p'Y;$ZBnmEiZCNyL>-E|fBC%L F R+SNĒHEM"g|.K7H_(5-@_XjGOC8]Aia#LC S6ˈR>IVi0v801e|Ϧ0AE8\N&:4_7BF_L W0yRJ!0o4auR!O b'j!BPh^xem5if 4 'W5Y/'8&>)I+X7vbQ20)gl &6v/se<8 %Y9K 6QBi lR=Ś2W`)Üҍ^A\'3 uVG+BAul\V "j|V4t/l3xȦtbe(?,t_`%L$]?_Dq bPl\mdEEQ:7?Q0!NZp!/Eud%-(yG Ξ+39 9 h~fCkgg 13K{P',Kh%ɀeg8+4ACuf$& /ykuD-l*%@Y]PvR_`p kS7WNd,I122SIpyppd-1.1.1/.git/objects/pack/pack-a6fb528e05bedf061d5fd41f60ef70dd206ba153.rev0000444000175000017500000000527014755402564024061 0ustar tilltillRIDXm x=7gS _6(t1 c#os{UP&6Op@'Aw=Z%P [~0@p@Ol`SXqgYIQ7TZ"^h.I+:9BjkywVKTHeE.%;i#F WYEH+?uI'=j22 a/vC&X\Y- s~WN,|E"{Qc#F~Om1>n*o|?Jyh37 wR)P\A>G:z!dd  DFGfX/6t43nbzbxV(D\ni-r(8sie80{J8 NW<hJqfuK}UT uNt'C5>l dj^v ]B"_&QVb$`-R xRM[kr+!.;z,_)) ^/r5U3*f?*5<k<` C%4ag[$qKLMa}]DG9mLyHA}M$]2;|v:9SBop cZ10le4,L!٦R_`p kSشEP)#p~Ɉ9FX pyppd-1.1.1/.git/objects/pack/pack-a6fb528e05bedf061d5fd41f60ef70dd206ba153.pack0000444000175000017500000031313014755402564024200 0ustar tilltillPACKxn0 w})[ A]4M9Bw]-f9QAYi:(jӐxiԱ;VA)PZ䖣Aq4I0זвW}yYSj?UހB. !ӮVS9o3XujAO3xK 09 4?ED7E-%޺.g`L`Y$Ih*&#ѐK6v$F?jƖ AŘ$j Ou[ךR^r&t}GǑ O.3Wow}NZK%a(fs۟G(q׹L7xMj0:CFSJh^lJ.0F.zМ=o"}ZlAELtK jMn5)ϙtI@@TRK,\i7Ulw>~:82}2}V{;Ljz^^#ALlǮ[2Ϥ5B=|iU:M㑎%) B!Xsk"!z}GDb%́ v)>mM+·?~;g^C*!w tXĜvt\67,wGxWݜ.WYxNj1+\C?Y(%^c{n9H ..PNE K11lH5el] S3l9!eg]5[~um}dZ<8jZtuhqdJej믾 TI=WxMJ1F9E!I'4(8+q#zJf 5]xt@ɶd_rR!cq4dUH,Z.%L!y)9QU99g覟ۀ;в2^>}uOu[&ZsK_'cgRa۪Q}+Oϯg.T2xuK0TҚ {A BHegw3wwNSs`a8F6g&"@(2bUTBfcB2@2b9-F8"vl| b0KnHC; 6t6*)%XY&4*_4{ae @61C]מnVaעT%Mڱ::F3`5 fz| h=湮sݥ풡^Mpr2W$G%Gfdw2Ls MPBO1!g'jO,k1ᶽλ 7lgMf7Z pm_nDAxcAk+Dd5ϡ|wHp18(}_ rNjZr'ۙO Bg=t61ܴ ҿH-I챴&܅d/1lNBYmUIHm9yК]JÔ5UUU[+ y,6NxKj0D:E~0 Y9A,Ȳ!EVE=G 918C"1|@7Iimvѥ2E8 D ߔyi=BuX"9f>AUf"GX;)Ѣ(zRBJMKty A(Mf:zq0FF ۇ-Ϸ'. zqN#A%N5OVgi#%Rh]*͑apнWT=xKj0@: < r4J -d9'G.@ID Bd,i*N5H~FJdH;N}!G>xq:\?9C\+/S4yvH5e OCbv\aHmwfeMS[5xuRɒ0I"LjcW>7!e X^>LR%.ݲ@Mؠ;PPuʘji2p (%"jͲnt1a, kn}tLE'ab fAqjt6$*NMhQ B?dǿg+/hg4 ]w4TMUN-RrETPWS'K^}nͼzm%QBoâ *f `o,~x7l@ 2|N2k H}"}iOڏ3V|$ㆅ{>ǫp/ z1ǭTt61eT6ݢ*6PK@J(i^e%+Ez(n%eu G$O7D+vJ[@i>m- Q=PSS ^xA Ea41ƅ7mi_GpkYzp@C1Q9*"qL,v*63i7 .c`۷94=^ 6oE>(~TJRx2-sUjhg˓FnI&p˶ MTxJ1yyZ&XD֫z$=9l2ۛKQQ"X!bIY\%lܒ,i5DqPe6F:g$2B'#j561 KQx;N_ާv}u Z:|4RI!.X7A7=77ӼtcRذ*~'m^~x1n0 wAdEPNIT!vyӢ?rp~A_SCyiD-̭Тjb#94QaJ95bĵkke O^?_O@gsdnyn籝a}44oAOxN0 y ߸*itbHC.9E*ux{ 'lDuqU|P&ZY*qF٭0SbƠ7 U#M5F))ucPքZ9m8yG8p_n,u*ZJ1La]ph۷} o_C ֖7og : sFn'6)H< -\&b&O* 5} %-{e`xU%h^](<;eheb~Fuxl.DOLv PI[Iݐ$SP X6khqca޾}[ڲ*urʃ 7<ޓ a9hڬiBwwQ25-gk5z%yc/sņQXEfxx8N0㊻0Á<4v7ռ(x:HN@e@Ϙϡp|l'z`ͱAᏎIz>tPƄDG:: b-MxY4O@}ad\&Vx{/Gx0mPeެ`:0@$  숉pƪ't+鬆{/k@z$mcόFe˞ 92n,I~p$iibvxOk0 i+tci`]ˍik鷟3 ;"Г~QF~p±MTÈr` W[+q$1tZ 7=sػqTA%;έJlPF4 ߞj}Չ|! *x|.f{gGOPL ۤ·0P޲%% ea.hUm:Ӳ[Q-Й|@ҴUܬ e|^g"nRjo`>cT/ V_xN0D{vTDvػBhbqĜI+4eV"#u]k؈溉NBmyI$lLK$}uH!RK%**C^=-y.i- w}.]axZޡ!Vk1"tFkP;MrN9|7?^+%uAt^D/!cex; 1@"PLI@,l]dFbWYG~`>1ᄡ&sJUJ-a*xm=3 td jt~q Uuy=y~>aq{`~ o#zX/}H@w4_=bl]7L`G#'FxA1E9E]@IRI*aܺItgawӖvn>?OVf@mk,Dvѣoe[666VK^y&oFNdJk6w.]΋d8<^?|=ALDiFF]?wo:y4|{=sa2^RxAJ@E}k+ :,&i2aQn>mQ**A}CD* QIBz-CdPxERQҦ>*W; |3W/p?))bG#ym<lt̀e^ }\ZqrXx[1EhJn``<*;M[-w0?ref\Z MՔJɣY5z& j3;]ASqĺ;&P/.ըȵpn'iwË70 ZޚCqsjet{Y>n%ި_ tUxJ0yY I \Dn\>I'Mf*ܷo9G6fB,ؑ!8(t=8L7ZhISjI߶{ڣưe"~R'?2&.#h"!=:TD?9y,y QKY` m]a+T2V 8a/xj0D=5lY6$C*ؒ$:%C/.̾V4lЪE% k˻ ;ik!ڼ\׺2lR=MZƈZ+.d[Fa)*y9e=0bl+Y29a honug8x5^&ƅԣ4Ot87>׶!h5 cnvUQG#aBwJ@G.p/BocZ7 tBr%X}v{^ʧ}ޡ6xQn0+uUH@QhC>$gcE,) }_{";3; *ܫlP&D= R6Mۗ)pPm#td4j/ʮZUuWQ^YVԑj;k'tA8ۜ )j6ϛ>e i1nˢnl}n-\|>8mN0Q_xvUsNx#:9 т XHZE_(f;D(8/. ]2DR@Ng=1`ނ"uC]:D ̅mȸD!l.s\BBl"Es!l),.HaxuQdɾ$F|uKU[n{orv"ubFA#{{}BmG wM*mzA;da~IkRxAn0E>\'Ɩ*T!u؏1$2CCq .Ul{'ܲO]4vRׇ6! #OΨ jR՞{c#{5hMPta*{>ޚfg!͕6EN+hu>ZU{"VDmr~6ߥZ~e:5w2yQݔ xj0za/=qePJChPE_Z<@ao'"h F;JSW{F[j$`Sh^ݨѝT1\k\gF˗ڗf='hэCJR=yfz;WC_?|x)Ŕk!FL.. }&G G5RrSqxZfWކ9"yYU9 b`wU`hSp' V2vxHq 5ޕ@,_D*/!p‘,Gʵ81<~qxM 09EL YIBEiu',{KR@>bQ7*1"Dhw^dDT6vldmZQ̸2)tt]H*iPJ\p\q;~pJ{챭<23u"4L[r-RLeHcDUx;N1Ds3"Fc#!@"wGfRa7 * dg;!qilxF،4"L w^|\ 1E$lҤLiSZ& \_8]Z+&`\C߫E5y^\,Gx?ab( =<=ܿt!~aZȵS`xMj@ >ȶ8Ȏ=PB馔B^$Cl` 9TOыuZ =x}IFfPX\[Eд5kQAVQS*4 "֎`c*rMJUuM:RE,0‹:x>Fa#/~w67PMQkYJ{/燤['!ؼ5?្J鲀W${#4 r I°mK,q'Bz4 B~91p'v;x}ϲgܜkxA = 2$ƨp?0TY6Sx1nJJb@e`̝,9dب{ɲc?^`gd6=ΤRN2 zZ~^gZ`}~8u9Ҩ2QJuɭNĉ’k+l=Pxm0 E Ԡ(K"rU!v0*SdE7?wB`e\1{"9@ޓ!PȨrt7t1!&y ?*DQ"ʐ$`ZC fvy~َܬMv?^)/ nf:qY-E'[Ba wdfn5bXR=U"X/T(K+JYxb)CIp}k3/:lpj>}#Xڀ=tT[L <-| y*RAxAN0 D9/@i"!H,UEi4JSp1\Gͼp!=ؙ#;XEF+Z"m,qMNkIe[#[̄Zh]?ѣo/'r+{<ل^:؃Ti RK)vwH7nh3BTC{?FbinTa$xI-[te"=g8ngLm7p_qx=0 =;m !ʞfZí8# nIϲ2k";B1<#>@HWC 6MVDQ[ӺFL wU_^Oq M?J?l<A`y; Uu,"u.S&KI: a֨7PxMN0F> lORbW$*x SnXq\l-^WP)&VV92^D3 C{Qy`HFй~L3-u=j*`wI[a:ހVK%[`#QJcA5"B(pHsʛ}64._45𘖟w\rÔy`xm0 Ủ ^P@P"wPl[u.%ë8D& rA טdRC(lnqYsB+YI=ec5[e߯9śM#Z?7p 1YagkMkSGp0"櫔 Ahsi9#ut&\keZxKN1DsޱJ/!2=UNP.JR&(eDN2&'<%NlF?ldQH76HPxXg]9ao@^w=6] ^no %g Ɛd1A*X>${)u PKIӗ6hU*:Q[u3/kmHx0TsOx 0%I1tE4F@tgwg۪H2& { .h ]^gìF'iՕL "#vl:v~>fY+\ztm'iЦƠAXETKk׉:DcN3VkY7tR#xP;n0y7$_axW"BIٹSNe8\JCofg6G"8tZڶJ[e8Pb۷ZR= =c{+FZ2RZТnҚN( n:k<_]wwx+V&̟@knt.g/(eH V`<}>UB| p&=1 f&\(K?L` N2s$,hQXoZs9D\b1f*$/#lOar1QbDJ_PmMn1~i3׊[wVB'!\T[x 0b Zb%ݟM%]Q!:F3' 3ޒ͔|4+TGǘJ;R$વ9Cq0&PA}5ި tm~nŮ̷hCڡ`QmDuil+/6O 3`:>0P_x;n0{bs H]%X@o|+i`xE '[GȢg!!( Hf.-k[5b!ӘsO! XCujɛ7exϻnҖw ȣs+:DeVsn/pjAx$p>s V^xAj0E:\f4,JHr҈ X@Sb57?[|Dpf,H9"Q<ƄY7אʛ"k]c(X:ʅw1I{I {g^ue|ݏ;!/XJ#M6:DkSS){oAuX\R/x !TAnobAJ.*lLbL23"RNi) KigB2dN0ER\ W:$D@$Gs<k 830Fs9\hVF[{D>Pe$xPKn0 ܵbö;. tOY# IvCHI0>`?FDҴoV{ԛNʊĂ\Zv Q[Ttwh;~b*D"fSv},_nu[5MCQɪ m_$n@dQNF^qj̞IS)4d&GL`D`|$x,1 * 4+h+xu9W`&wy0M\I &ٵg|>J:M)ާ$E&ɮ!Ŷ!6PS'3)T(]iҗxQJ0EЖ6-AG(&mmJ0U1]? 瀍LCVDȀ:TC8r%ؘMKF%"'s- aJ{*J@XZSn}r~SGCY/G (_n/]}tb~fYLhۭ 8@)PT#xQAN0{R"'mFBUs{-%v8 ӓ4LxKN0 9566MM%G`ib0bAG;.bG SR~Őx 0Db ZO$}5I #BteftЕZcCE(g_]9J(6y>:s0 1qı$q$z8_2lmo?ٕ]oeؠG45׈9^yϺ4Nuh>O*Q-!xMN0>*DsHA]cbK4P1M ClͳG"֕u]`"ZyU6kDu!#ٺ+[^*:2DҔ2T]7$&7E[ bGx|<1Ne~T˦hn2e"шv`Ğ`Tuְ'R!^ܓ`)tՆNVv8.wL:£83:, GER'P=ƫ .Xps?lc 8/#5nїL~+xN0 UY_wnxK0=\2mCGp~6G/)nǷ*OC26G<;M !W5M$-Qe4cCwˑ )m^~\'^R|$?~!D5hUcLIL< q E ;7)KPpx 0Db ƛ8B?(YUь;l1B5֒Odmӄ`8"5[RyvLb\DgtJUq~Iwa8OV~.uAK'mVNY$u#C8eY@Wo QV'xPKn0l -(I dp=?CD JSb:nn34#۶Zi4JJ7][iw1M[ab3^mZo7c+mWݵ]#k!O ?~$r"HTWqUj>MY fGO!꫈xN* |H@pa D!ē;CGY$v0ZlŲt $ȡH_q 49@;m8eνCH02|](_|8^_)Հ@foqY0@ 89Mg\q|ZG4\Q"0Rx8 z%Mvxb\%3)g|o74G'׶xA0 E9/rI$9[*4s(NŨ6B']́ZBy@>qV|sĦx֋AAM!%BER -7͐CR-6[OSXVbLuo|M#jU`kU8x 0Db$%pN6` ]QaP*2o̪zGOI\{DBd֩5 z^)u #yIBkS6( TVl/"uq lZDSKN/'栓y:Aj C|@Oџ)xͮ0~Qw\k BR_[J?x(c[,X#wΌ&"Эeu{Y mIS+r P6HU3u [uT]5qG](G\ vw+J^JJbS{ d,yNve "X ;pY/v.J.>=?bh=h?E3G71zUtwT LrKM\m2L N1g@lr~*)Or[y&>UʁlI}{ t#>H ~jax1n!S-,`("WI?`#y OysTO+ވRASR#FX""³n*KV R0D9$5&6 ~) ~'֞m[\q͑/z;iZa<;\|;ZZ1Ͽ;HP_}᜽OX8xQ 0s%٤I"wf5mISx1sa19{ldc G&#7ޑZY $7li@fS 61Z{4G-6ZlBpK2uo^S (j*">h{[YftwKӒ#xKn0 :URL ˯q }lҵ,Q#I0$w)zғ]Q$~ ] QdZ;adM;̂aak7M]mǡmumwvTfx%|7#SlNU/S5-Q7Mn붮cduPߜ#0x!>U >HR?y1q=9ބPNۄ( Zq"@"t`hC#۝|'03 c_&)>Fb .#m)&1|ԕ[D l0 z@yBk,H=ehϕV38#ņ\vJtk( nVFxm0b!<_˄uUHcV ~ Lom$Ѻ)CzLdE9IӥPH#S9-I:5k~S^.p;9jeKCjw 'L<"YuV8UIEf Nxm0 @Ủ1HECD'Po)XENE(P4[(d %ϓO!E$K!Kuɏy&?v_z'oἷyoux4L #1cZDSʿk[Ku]|zKAxAn EeMA*wH30njoSb Y'}is<ȈL&Uƫh4LO@3$v[ؚI+V)Pôn=.\`~o<`NMxAn "]r}Y-[xm+ =e1b8KBbbr2$`h>[ z(a8Y }1({n|ȁ .RWA}џ#t۰.ml4֚{@>"21:md?HLSE#5 L2ڜ2b#q}t~'YFm&ke܏`#DŽ>=)ync?Y>sֻ n7*2GzUawO5sQxM1D`Wm !D޵ǖwQ#DJOU<@YuA,E1z&{QRTZ#LKrS쳱g ]*X7 _|[\w|~{[ H獆."*3C Ը?x>/a]1&OWwFG[CWxAN!D.n1;辡AISx1YxJR/)hdSm̘LM &pDEiMtNP-!c2<'.`UE|C|˴7!|o3r֞.ih D7ɠ1*D?3 {g}j.G9G{>cV~èRxm!;U@V`"r >jG]-;=4i Fр4#!EP(YF[(h! >I% R4k YTH3ϫ8ayy9NTii. g48߄w 1δᚷO~^R[yO+b9̉֠Ժu]vF7Œtb^ԊИKPW}}G\9dFۃ۰¦{0e Z8ρ0ڜ˜8, 8 ) WD9 _TvJJi[Jz@6aDr+A:CD)H0Pi_5&oB}᠖cxm0Dfl DX~]`r_M nU)!yZD́XMy$f3'C#ޠڹZD"@ѠDŽb @Ї^Q[_?{m_|>uSdq@mYroW^)噷U25tk HNx=j0F{bTkF!MG8eczOSbR}y hk ZDUyn"y9Z}Y|קƷ f|8?AgW$xώ0 y ߸ UNB+46n&URމpv$cϖHt釆М  Ǯk #ye]]?քyU`꬏9cwC~^wcAxss[l[-%R: a]kBpL 109%LZ Jόy-rO4av00LbVbTp!Nb+8a%-Z\'ur,.'c!Tht((C%YQ7/~K'TM7bATT]c'e]:nI8p k;Ӹ/jlǶYLexAJ09;$m ⯈\(M^@Ky /f[00\ZoBTJS#0 %jI;E#Fu)dMj9T*ӛAs.qc#|}،rfuJk>h[ß <)R4SߦB!%OWA0A2gX1&ٓ*[ \N\҈xxA 0y~i?x$жW1O00R!0rGƷDjKٻFqjʓ@dG\Ș8}2ӌ{ޢMs#nHY༗ï/KnaMhMNZ."@ݟ4ea纖yA7mgMTxMKJ1o-Nߤqqϋ Sx1[xUTQU"BE K-șv"y+f4=H7Rg{T:j# Y{B6zLnײ_K\Ci5wv\qt>";4C5\uem;Nח儿xiu+a2[ʊfxPN0)Vd7Y K[T.⺚Ep"{ީO'Np$U.Sz:e,uVzݴTF)0)ƨ.۵!lTi m7umQ)Z "<pds}[9m]4hUSJ 3<=yf6>2xc$cϯtHBa1RJ_C& ‚bJLVxh~ozLisP{s\-%ܰnypf)~@vG',t*}}ݯ7hf`&xAN0=G@3qu?0SlKy /;?CXB#&ZnE(h:}!B7!ohsiN8xPѩ_vME on3c=O` \"VS^פ*TW@9YU P6 CZ|XJxM 0F9E.`5DĽ[dAۆ8=b $mz CM,Ƀ'@J#X4٨Z#J|\z _&8b`gYpg2ŶJ. q,8L ^jWߒ^x'N;xm0 D廒 Ġ% wҢ7d]447xMYxK#$_)D! Ɛl6j(XYɜ-'ޥa$셽-cС?k^M] z_:ki>ဘ}7tf\繪櫔܁>zcޞ)m2];yU{xm0 @Ủ5H@QHlCaUb 9=|CHbqe1bj"ԄJ\hȢ$! %PȞ2(5zup#?i(huzmg},a{җ 2}]@WyIMxm0b<$E1Y"]HcRB^3̐EdH|#o8|_ox2u)m/t.xxFJeiKW[wa'>4+\N#Sx1n0 wc;4L9(2 ] Re(L~Wcu QT2J8O!ONa 1.2<4%ay*rМ7;߿pu3kUv?S vT3}~lրK\^^e's[?EbU xKN0@>\hKb ! hl[K9ӊKq .F'=S[бEff6ΣT\s(4&R&ppyIGxQ̔8f\PcmQt[v*u{B[Yh3:hUZ*Cεxx};%@-"ei[u/MZOX xQMO0 WXsaFJ'B!q8M\6U0?juY,s lӬ Ua2޿ 5]z'5)Oc0ܣATi*87Po<-;Cux`^^ 7W[YVxMN0>۱O:BPBKcĕCq .FrVo44!hC¨I֌J4qbVoT0]ƅ6޶A. i3JK۠msxqg<\O'wϏ B!s$ ء? \T3K _۠ sMS-rGsӂ#\ʔ)\# &Ďa}\Ҍ՜svƓxMj0:ŐM[H\9bJP趌Q"-!i;XCǼ1#H "TR$Iv[7- i`%aPZ4|:7z#t#т>g.Cv)#\{<8SM6tUo^+.kV\pδ38ܸcUH)AK{(fkX#nL3k8`VgKx#0~lJ 7 `E~9 Tc,^cF(tڛ }Ӯ %h`Qo܊•xON0.,mdIH[%```EDJlvڦSb <t}?)lDJajdYu\ֲB%#2lrS@%*-[kʘ;ZZkK\ﯔ34Ƅp4ix]bJ4.#G{:~-i%R`vٹݞ{]`zH`Lw,T7D9b-%tϴ=;_U|s0 KCj)3 .i^xMN0>,[)DN`[B!P xB,'݆Sp1V]{s7Kd]Fj%R +؄3Jֆ*J(֥hjN( g8gLBD9xp6߷| Ex]\s- Y3m|Kh)n`B:r^ amNjn9<!9Olҧҹ[5غs6p?Ęs-qβS,кa~oIZxMN0 9w4TߴB,{;qh4mS%PqV=?KdֆmmiH$HƔnhԊ֠3l@uvkځZ+<"}}J.Opwy~mqp+zݖ pk E)k` a #n6@RH~Q(:'E_ CgYdU6-زs9E.r,œ :?pdѣcI9bo5Zr!{vبs(MɘSpX)2eϓ@O9F]^o3=jeدNYD/h.K =ھ@ +%ng~rRvs~/VxKj1D:E/>dA!CV Ghz }!*x+1Y_j t6Yt8Kr1:^*;.h `>GdɲwX5eQ.uwGb Z9S`lL!spNkE<7_}]~6hr|m0JL2rxZzfl˫euXxAN0 E9wV6M@b[g&U4,\,l;E"Ј Eд BW5VIbQJTACH]+p`oI0!Dx..Cr1&ƓMVXvaz}jYo SYV'Ep)͜ܒ /~}K7˅P7?{s_n< Ɵ o&b} vc-.ԃ͙@I~R7w~ExxAN0E>/Ȏ;*TXF gZ$[p.F*zIVdo=јT@3j0yB{QS6Go [cv.U>O?\kFʵ͐#OX{ѽUrRbsn|mK=gyH݅W}xY^>$ ~Pl2/gI|Z( ax=n0 @]Ȧ)Q@zE^ٮ )uSƪ2FFbCB6u1S,1i|<\șiug/٬&$Vng-wB>.ynf}_w[~V`:r70IFxAn Eb.` EQEJ04H0,Xwޯ:aȕ2!W$y7D`Dٞjb'zA2=x>LG{*v*'єwZaiɥ2g Mqݰo9duxQN0+Jk !@Qk{Xrv8$\V}ΌS T5U(eeSjdNuSCٌT:] emi\6J9U +bx)g ^.3B(^t-;^sΔGkgOn[%enI3ً 4>`̓Z"$E::qlq5HDi;ZFJ`P:4huU~Rx۩fc$ e r-qv7ɇoƆ :2.lfY/ xj!~E3EG!ܻeqp{\ d0yp]rSu`*Yv{v x )Fw;`jrbo a,%&?|]C篿lrԝ.4^!%E[UD䵟1LOV`zŏˑmc r8QxMj0N1hЯ%C dMݏF#"bq{=YA碱D]ߡ'M٥A[]rdq@'r6c0GC$hC%3Zܧ~U1is> ƔqP+ƱϫkF c­#ȝF|@aHyW"xQ;S0 +u)$n^wKNJ,IJ z@OC@i˳ c m-vi3MXW;5ꈁ&h4%*]MbӔm]V8s:"#ܟggۤ U]|WTp4UGLw^=wg nbH=o"p 1i~IΑB Ɛ8;p20x4j҂ ~F>pqWȀXX?OS-Mo[r\m%+ZS6 =JF&C[pĕ4˯9Db^ {YaI]} sdBob|xj1D>&- BHtxl!KAUFct4(E1C$ {4`r.a"/FdLlDXuu>^~k%״M ^VJË4R|<1_Q⃨úRˁ*y_9*_ߐ ]YxJ0}f3IQ |I`&vpsr9pLiMmZ/\H2B T\ g (>Ӷ$oţt*h9Vgn--p*1z7=1Tyrn1ֶ+.8g~L)BPɰ0/i||x=k3|g Co7bPC8C2)-3^~,Oenx1k0 w m]ڐqC)RR˱|1$N+߯sA =x%[*vV"Y%J;z1 IoĂ+EMYBYʶ%M[j"KV׭ypq?/TZ)]C)R4fMxqƝ9& /{11_"ki9~; w c:9сKCJ[8A>iJ7oh{H_ zxPAN0 ~mR !$66)IXc{fC35p/5McVi }Pp%̦i ҿW=(T|K%t<`#1`ى5%;DS߰a !x F'F*7*osNf( }ahPm5m*xMn >Ż@#~lRU]?a#p_fFS (ѸYIYǏZ:9|vpjX(U(xK#hGyǥLq?upHv\"|yl/\ m3S$c۷-Ji)D,RR:+ֵKU |m=d=xMK@ +<ێeoċȒN3ہvL3[; xBH y7aOZwֵu'aˌ,IV^cbFOAmjUȰ+v_Zl7HȦ)$0<g6 #<^yċ铠.EӶyQ2˄rdA0AǑ:(:vu}l} Ēc|Wf`(EX,2F*ǁ,Žnb`%)Wr" 9'O(;Z$~+qG6ЇvsA«BtSYLoj+W2L!1`>܎/nDz//V^MxAj0E: #˲%-t@gbRΟtu7ރn"05֒ EvB=4sllkZi\!^aHo|dtZ ~R}5xZ\!l'ChT%*;s3(ύf!2C}"p|5VxJ1yyr&?XDԋ'餳v&Cg;lʨȠL:d:LdSk01 U\&/QJ]IqK\nr{e'8+]^yxZJ=MFkicxN0{?hݬHۊ~bs(=x{bFg"ޛ`S:Q Md,&i`JJ]NVvQZnVqy?qa~Lx8-ʃ,Uc`'+)>28SW t/0`Opwt%wbl@aaBc.G,Url]A?0p쩀[ϴ7~s"n][t4Ôr[~&bB|xPJ0)[DDxEP8M@ t׾Ӫ/!_eOyԽLT&kJ") ]h,ﻦP=M ƺ&oYH.Ңit*oKFݲ:n/~ċwm;H3F:C"gLGO,H[x @o{{#qtBR/5_yI(-`UW]2r Mirs]o)-y줓xYfU`:b0OQ 3 'b)YʖxKj1D:E/텍4 #dmZ c#_!UJ:3sɹhm7-kF$LsՊo`|%չ9Yi2(L‡\MFp _l+bi:ahD{hv>JƓ_~QixAj0zr2dK%<"9f֖8_ȥ!!ITXc1XW ̑4dWX9;>d-#9r^%G>z \>z;+_Ko`0M6FxEhJ۶UU/n.  tMw] 1QfxMj0>5ȖeEPJ)=@.~j$CO|C "01r!GŨ ^ȣJ6Y/ hC+=ƪ lcFŨ 蕦G:S(Y_kWMkgz)q.@:@kv]n_≯ vaϯs7EzbHE/g 1!![Rfl#|NMū*̀Ĥ.$嵷Tx%#fک*C1.k4uK)DC>MFC.z{+{S o ┷ۭ?cmOg,د C6 νgxky-Y{xKjC1 >/Р'W(YtZC^qI,9ʑC.%WזWI%BĜ$Qu0RPE5x8W+f1|֟mگ~l.O8YFn!Dm]joM<~}T{|s;+zN-"xn1 E{}4녴L`:p5h qH{8IF {%Fv0i}{H=04qZęa@k;LƍSgktHn^1+\ |#[ww 5-n-ƴZÕnV>O1W%" OOOXp0@d.k6gXkb U /p&>dYN9mӔ"ʭrt_y4W 'bWv'*ncw@ǣGV"}ԊNj \4et=[p%@(BnZdnI(fX++0<1R$V˄3.R3Nu+3٥Dgoa^V19R6#,k\  5C21;捝@ӤP q HprBEM&=N1@OE,fT0Lq1b z, =>}tz*8oA.4fLQZKM'u"}(نt.!h+:ܪ{U[ۏj>O`l0Z|Vߛ, s}VA}JܝxM 0F9E.`$m:.Oڙ?ı7^0[&BdaqcJ}˓ <SUO0}p؅!R 5EG[uok~+닎<5S:`O̸-KV{se;:ۙJԶI:xK 0yV X~8w&ݽq 3hE*'/*ibqF[]A CbV|ByP2cFfL2辬hڋ??`oejOSZO\*ﴓJK~ZynDg>}ᵽ(Ho xM0@= L(&w^Nc" _',SH4)]1zcxr H&!v{i*rAMnԞרbA[z2ESPƕic}S:x340031QK,L/Je`6H'.p~ѵg;6+BTy3wOlm7_wnP>ή~z%% 'd`$R)LSi2_G?O7<OD6xbtxÊoʂ\]|]P=`[՞r/fhd z) ܓ)o9^aa$%Ij)@1 y%EI Nn0}}C' !* * R6ݫyrߨ㍲vinjPZRZWPɠ$i7cNk§S&K<㪈Pxw+ A ]CʑsXIؕS2 zE^e40000 pyppd! 3?_fO100755 setup.py4M]8Au3xx4Q:V%!ؑH\J<=o`Y=/3 H">1.8m5V/ ̧TQJ]100755 setup.pyƏqN4^^VJM?6x@)89[-A*ny6100755 setup.pyն-_[ Q+$|zm]xK*IJ,.KMOnf^Z>YM1 =]J*J|<\C{+x,..M-..-ǔҼb={j^jQbIBnb^Abz Bx]ROo0)8uRm=fS8rLG)(6C{еܟ_'w#<_'xqO0B4Ym \Nv Ɍv&ktDf|>7Z#Mx5Ludh"`<¼#_Hgn{Dlk#åvtzp'wW x0;wMΗBAz8 4lH(k v]!3%kO`E4MceIm#Mh_)ZQc6xeMYn;Voz A{{/ u^!D~Jz|DF.)ZQ<+рUzr OQ_MRXץ8U^n Q=qPH%J\+<(e"%2"ߔLAQl8H[jPyQg_͊ebt\;%WV,8EoR*/XgP5{ %E튧1ZȊb Tt+SBJ"=ՉHW U ^Wi\~RtxK)MIU+(ʄ * R2B\Jnx}TnF}߯ l @ N`K'-!0]Ja&OȏRnm ș33;Z[?{R$6Oټ5;h5]ϚJ[sEҕ:0:&%[UDT)bqpet7"5i /1U\!oeəkdPF՗?!Tѹ%fX||#+ʞS*sԝɛL"^ҠO.Mғ3&@M`3; @[ 7S؛]!؛*kJGCUE'1b :`GIȼHeבO[<]7)@0+תP{tT12z?+R\7˿JZ諝Tq+p ͛?ޯ[A(E-p e%i$n\MW_*>;U`}^9$,E[1:qfg뼲*8fg |fmW8 cކ#;gmq_ L]ZDS v I/u#`H>T=Xlџ&ԝQ(q$3$&8:^y8=___gD@AHN/Uonΰ^hbCb]_XЊj`baܰE Hu6j+_${|Vx~hϟУ*X9Xcx>鎝; kYN:yo兇X`JeOCB[kAOݓ7ׯ1?RP:x51JCAy+XD t$&d_vKJ#x%<ػ)213?GQ{ΣEZ4Ư MkVl瀥a քAd,Z1Qz;ȒWL0c*SX$;KKtU$I:hެz*VҙnN\vtBfQ\7}~<anq{C<R#mxM1N@E!@׫E b'X{'xd("HPFM(ff\׺8߼"v /'v| E1-7-ќ70&{J ,l+\&nV+nŢ70eV{PHWH,a$̝ 7q"'O sxOϷOS,Y<Wx{.R`:Ē<.ݍ[6T6x rutux340075U(,(Ha,=tƏfc xe0C|šPDsja2<6*!\A^E|;j5.=^ _+SRك(f5˽8JI+Oe%>k xɸq(D%Zx340075UH,1JHKO-+d?S٠Z5-wס "axWnF}WLhjۇBT4$@ZIےr)G@?3;˛(K3_Js8كV+x "6[RXYWw_FA"TVlR[aXa=|eg&d2O2p P_ m4CYm Sd m,1d/`2D!HMqxƃPrWTpCi%Dv2W+.acDϨQʡdP̔<>{;Z/\Pvں:yCvlcRɚĚS߲bԹB3b;_b# L2aCn7ߜ}?}/Hp ^C_SQX1STx9VǮcҦnD2׋(L)B?k539TgU:fAl~1 Ȉ"KR &8%}92B&utl[ǐŒZG+XX=nBORfɘtii3p۟巹!yPx1rMھ&.tD mhV/m9Fʨs[>(_xB=4v5L1\VNo"5^j!p?n px1MgkrkXPthx/0Ի{ac~".hpK fLr@Qq `eIŚ:+ugꖎJo+ARkXOV-=q샩TzqM/<1'`|<f/J>e|Ok\R4?̈~R-*dDp4P9>Jt;AmדmKhƱJr8oЩK+?>v-\ {x{*\zCjQ^qjbQrFzZfNjBrFb^zjBzbQRbzb"n׎x340031Q,+dx6M9{wk+qIODCĢ̲"& So}An};:'܂|Bʹ{3Gmˠ R@FEɈ_0[T~ӓx쀩̜T<;j')nt=__-*˃H-VɆ[޸,^HUx6: pa_=‘Nc2C)ځ`sNX_{W%Ex@9g Mۇi^ [100644 runner.pyoלiُ3} )|TR0xxWmo6_Ah,Z`E"k|iv/P$*IQI@b=$Җ=~&Tï<:VT_E~7jriud5|-|JԱJ֫UwA|),m-4}vbI3X Ij>͙VZ.k.+A@t(V|emhc۔΀C=x_N4Jõ5⨟l0R5A#b@;}\N55,0IflIZoqcRٹC?]QIZҿ}x l xpacj![v9F/έ!JSr ,mK{H%Nz¶Gxh<+&oGI>5vga{sTZP'3x`VWLnj!߇c°S|􈃬;Z6 EȅH'4eub TP $#3p`7;fTTquI8l:O@%hK07Hul*-Z+d)X&;o09:44<@idڮ k)#HY%6p !=d bU J vX,c2"T1g:MM؁6U>_ox#aKZGKݞݱe 3kT "*[8d#,MPI`fecB -6p?KiZ4uH(dNQlK[HM$~`8J-ԃ4*0ǻü1IWN;m_ԑ-RmF]n?P_@ \졇zL‘q%YX>y4 UH}1<"i;.ˋ5:;y}Ɣe޼ɦ˟dTa?F=O0K†sݑkQl3^O3qG"@7gf #l+'EX}Ltu ZtQam>,ROOzaP]gz{}v0LlA/\"UH;\E֢F?חlIb ǖHʍ.h95;=6;vlؕAV')e7c 'n ظd X0;9-;(%/?9R=98ه 9u) 8`prFДv,Bz d,2p5ɸR!f~fAs6AY>D8ѰijY]#Z)"@t7걞N޳׿Wcc͐&c:px2_/klmQM3z~8lu-k4SHH=,Ha1-U!nRF+>D2͚˼.ĕ/5 x>x+wVnD܂7؋ɟt6˱gQHKOIU(WHJ,N53Q(.)K|KA"dfQ1/^J*>Y[_ dBJin&ǃD'M1@`̼ ťEə!E: 'gVzPvijrM/J-)-CXy;w3 Te*xMk0 —=m;{ M [_?n6C:˒Wjv=(,14֡yk^:ņSNOB<уϤ|)G e$bc~6Pq^ r6XPU-s mR#@f}:I6/?+Ӭ%X:3I;t%alKE (|KLxx[:u/ReIBbQQbByfIdCFL"Km6d` ռxXmܶz[\`i1 !qqq$J2Iz3Cq_tr ܭD gmoV-tjLY\,|/߿zx!BF$R&OJx)ZRlȶVg V{db }+{?(J ;͋ZLj͞DGWsMCS·MZ&oIoZͨѮ-jW]) & SGsŭLLzl&X9+*݌KvqᖠXρXxs2Ke۠+pT`ruJdJ5d 7:š+++;oX(p*G'Hǁ ?& Ő GB*kF ^aU+}0? n]fTM`sSDl$& @.rxgK :B *,[q5XqJFRW3(J۪z;lجNMĢג9kNE 5ǧ3F#=S#LJVEzGZ$3&a13>ox v?t81uj1Bx{3#3; .Ņ鼕m&`򥷉KSWMS"ksve \.RF))?gV^*pFQ[\)+b#75RmZP Pv#:FÈGI=EQ)| IphNGgH x]cx9:gRW^ 0 //bt 9oG`;,R\90E q(&oy? yhW#\/ ֩Uĕ 3~I8ERH{'rJ90ۀ㏝:mouY۳M[ԱN(W71@ʭP2_-R~؝5 uY[1 2tX(qeE5vbM\CJ+`Ė8@2'2":%mV5|p׫ktM@JeDMy&Lnd0{W\6 tᐐHQ Q-.-_=_jg @e-_$V dE~6fjOFFA QM5G\5^AEpو|k^ݥG/ >$/qFnAX^/V\yok`F'8_tF6nPr^y7~{f}^v{<ІOAб%}rsf?|1)^D1#YJg?/dc.4}j8J4| APMk/bw$beȄ=KRGh0E.?IL,u mxЙU)M}ë+-(B__ YtwQ~BfahէL/g8%^*% YwgR}ޝ!aڪ1/|\Ee"+.drgת%jSjiq گWOA)Lfv&Xj!URh#V.m)TЫma!|2z7>f}.I*[ %4q!VȠrdqfg\u&.]7B>Rbo7N Hr%Ҩ*̛ ɔÏ^2Q!Ĝ|u]kC|n1Gbbk(StA|JiFυ9:b~ |.ޥt/wyzqff5lj4Rw4{; tX!6vݾ%e!_Ya9X2a3:L댺.{>Gk<>;䤆^B{\RJVZla%k?ڵ$cE kN쐪lYYRoPwKesȬUT({JR X>~-IhCFpZdFz$•w&CMukr&U;Nw:7DkLpnsFmtn+XVہaؿC߲%Qx.wAgp"hn 7JAq 4 %b8;L3BXa]JY(Hy9m (;{G|e6"+yXe@4@_ B~uN4N4K.*w:[-EI^/97{9'n[jPK |M(UDOp@Vx/3'gZv'S[1/Rdl&7?һh:L~z?lzq"9Ҩ!aECmW.ʔ>7]J6/C1Q^lBz3=< uHlU(vSm\cO5DDXlyD3p-:CK X o\kKoW3x\z -.!֐9A}vj!К.թe Ojĉ"78o;# \yDz]74 0'XW'Wa ⹻/yc>=$O:.w K˄DWmԡ?1$Z]@WeUؗIU9JM71-9axQOKAG=ة a6rKdѥnԱef!}43~|$6\";R1ȠZ GBrp|ZȅFK#UӉ">\\̱m$hc` Q2*T< ]7 # /\Jp8w8Jf KJRQ٬$L5=lQZ)XH|;q,ki LQn9d{aD=\lz݌şXo.nr-;68J^i-v8SܕAFѧlyKZlX:")ډDi4Z j ux-P~(D.܂%$RRSR5RTf^cg~FN\ @PY``d\`b`N~b&\$Z13U=vr _lMͱv\1x;(P~7'dE.ɜqlD%xVmo8ίHhԢU>mP[4qKI,Qp۽%=xi0.5_B}k%sZͰ$\t2*`lb.Xi#)Tw1O v'%hesqN O\B,g 4M*)$o6‘};aK=w+;'>,1VB~G}H _l"5Xrff)ymk{Nk z:PAzM+OB㙉94<g 2^*}jTn,CM )n&@63/XX]ni8'* k% !P;Qe(T 3Gĸ(|s*HZa%f Kےhn8KC ]`lo b+6V)[UT`EUD츒y` ح,uYIvPLH/݁h/,YƓ&v̖ʽݸ>qE~]nl zA`7-^Qo\%aWÙJpۜ82Uj#^>#kHT{W=a2;a:h[]oFzY..'dݫ]im}bテV %3Z9b>AwE B3lMޒ|p4& XԞ(b;gKέ?3Fnork[𽑤pG9͉Ԝ\IZde$fMN]r<:ns ˚:XW4f9"L l:?+U!x;'KdB1[AeAATVC==E *23J45 702'((*%gTNN`ʹ4O"S ԛP\ZP[SYPZP5y#Rf^rQjbqT"&q"lg0$HNjYjBN~zzf^dvv@t WPw. H)N@]]c;XAZ~QnbIIjP"/sB`325fقI^[-8= J2ޘ)?74'ssmfLڼKnCx%KdfC=9+Nx}SQo0~ϯ8ʃS%"mT[EVM_SĶl[N5$>/_銋n50nlcyeBj^+-PT62|EQQQcpƆb݈`B~Y{o3'MBBA!|5]~1)Q.o<ϖ[=qbr :tB:"Nm/=@eaN8Z~rEH]rP,PƌRRdmr|살qέpAu-i/Ě*#[a-~Rw#}ŗZr#'o.e ͕ނvk(MKOec˴w(NSqL]aKDb\(QÕNbNi4)\ %ޓHQ.N6_}썠Wdh,& 0&g?N` pEj|btkT,^N'uxw:_sg@KpT5 1093'``0@wb cVsḂFV1躝Ϲr~ziPyrӅ=:vʥ=t",Ax9Axh8m̊2xP`gL0 x{i;荷T8,}$T|-\J~⎕ 7x64ˉN71L}, H\UePzȮEx{ifV]a֫ cPl9֍ TpxM1n0 EBҵ['w)ĨO@[LM QJErޠ{Cks{Y}Vީo40(uؐxJK'[,gbzQK@3[氇/a2Hn>\ mO'T螡ɉ),W1.8ON;! y~JeAzL100755 setup.pyƏqN4^^VJM?W$3fxUePz 6xx6: pa_=‘NcVZG)wFh^ҝ% lx-@~|܂%$RRSR5RTf^cg~FN\ @PY``d\`b`N~b&\$Z13U=vr _lMͱv\e0`x )KT֪ZWxɲk)=$1sux6: pa_=‘Nc!h6M7@ ˮL%ix-Mn|܂%$RRSR5RTf^cg[y x{i&r*~i^ieڳoZl- G 8x{qZV}ֳG/[vyfى~s' Fmx;+wTn6{t6bK?x{i&~EcVLkgK8ٖQ\ A-x{i/quk'<5P>oݸ laxys(FC,'x340075U(,(Ha\ueRojVg1]Gx` XIؕS2 zE^e40000 pyppd>,@Fiytc]100755 setup.pyT'p5X{TR/>)+oxW+ A ]Cʑ8HxٗH,W|100755 setup.py" *1 :]HG(ex{i/H7 }'}iE#Ky)- \ x@9oWq? : 6T100644 runner.pyazl D5H1Ox@95:iy9x, :jtb3100644 runner.py}*tE4 bE;6x-[b3le7|G)x-[b;le?{x{ys[FVC==ɟwg&%N¤2S0f/ |9HTAW!(?4$3?O?$1)'u,K7POfrj^q,J~nfxysGFF͟20`"nxysGFF͟20f#>x{i&a%6[`Xwtd[F xK)MIU+(ʄ * R2 `x340075UH,1JHKO-+dh;Z |7U/J?=jǎ3y =x;'KdB1[AeAATVC==E *23J45 602'((*%gTNN`ʹ4O"S ԛP\ZP[SYPZP5y#Rf^rQjbqT"&q"lg0$HNjYjBN~zzf^dvv@t WPw. H)N@]]c;XAZ~QnbIIjP"/sB`325fقI^[-8= J2ޘ)?74'ssmfLڼKAx+ A ]Cʑ6_px%Kd#FÉ7s1pD x{i'ȁ7LhЉE|q3aɲ.: Lxd3.txt3p.txt|LFv&b!L80&r ܥ100755 setup.pyݍq׻{&F]-txY:|LFv&b!120000 README.md L"Vsb:cp/1}2k$=uM[e%gx637q%0I%o}w%ҟk#_Ri̜@#VZx{q5^ә(m>k>5&3 0x;'KdB1[AeAALFE7~gO/+QQHI-.U*K-J/,$93iQiD45RA#91O!)U '35E!4$ 'U$37X$_AI!kFi̼T/2E0M6fUDؔaI6Բ[̼t=O?7J.N\ @Sjfp ?O?w.Ē" E&_P1H'%g&;ekͲ:PP#&[pz d!1S~Noh O̘y#-Ix{sM&f_ϐEMtSJK2KrR'˲4q(d&NϢ \Fx{ys*&$[V|xys/@f/ ߼YOx{iFs)Flnl8sfòwnxo/C7;0:3Nne:792Ì* z)@Y\5UkVVyq.Ԣ"oY;QlIxys/@>F!Xcx;tiӒ_wgdzIٶ, Bx3 /^Oq\ݛ OJNpB7v}B9NLBx;tiO_\սy/8xM*Rոs 9x{ys0 SjId ]B_/ x3_.A1_xcys=@Kg,sGM4V<' hˀQx]rG}Ɉm3xc# C\F tUدO :y$|Q[4, \m_;9@'GX_6m!9y 7DsW;;b}H?$"h(}L}`HRvOYְv3^5,}]J//5y$bױDZ ݋a:#AgkݼqcU?ڻu/Rfݨp׺g<"\Y=1EЙw_#ΡmR-G`@;` 6Ŭ]ۓn)R*f(Rʥ ӗH }Qn ?߰T"F;ow- a5X09m7NHwyGՠ% X yE;B58g&=VJgNH\XUPouc~*0ޒtTQC{{uLHa'ۿtnݜ>'!׳ 6}9L *y,U{ݎ},A{*(a1kVϫ&;LZdK`܍߉։{&—YuW$~byR&htPN EwFI. Y%g[lz+7%Lإ:Zpyv2&ʍʂe,!@Jt]V> DVf#$( y `$N$~e[f=\ah 3bX=cނ1)i@HX/"Wص5sZش8qwq3g0D|D&Rl1ڐzyRi,Qimiݗe;(S ༀXn U4s镕ob̀ a"$ *L *}ulF@y)c ;| wOPÐ6%jJ ~lJX650 g^vC>I N> Mө/vMYHG#BÞ z!Aak,wd[i!ha.B W4GdoPɴ SM HA\#g{Q'L8Rǚ1=8 =,>[{ysv?\;۔3x觠G'Q ʙiU߽Jڂ*Q.&V#LM ۖ`&@p kFҩTvSo" ccgrgL_p݌dA͌(CRGR$f3q4&3O"߱]~!!9ϻ'l8L."8ȔZ Xq!T=T^U8dJW1f+ &NbflX 6SVR &Ü,&%dSߚ< @6 ia8A4;ЯU҆ة7 &QӚ{D@fw6ǭ^sy㍤ Ϲ t٭@;bFvK2mjT(SȟbRkTXjM##D͂?}TUU ѳ;:(!,m1m .Qi*Q qcؕ3x}/K<8r= vݜ0'8#2[yL ` #hw_UWׯ@nf4&bt 'n^.3֥4жk`$6CP",0wqi$S.}ԫ%=SUH/ lo`;6UǍٻC6rG{э hN~} O 0i%=ċ$ 4]qޱY&7C&p6y11<9ba[1X0^_P%BOgI<:P95G "GoGv[J)ݬ۱Q=dfGP5o L'}\gav1BLA̡# N)h!.B,MCrcv,ee>.Xb8t}慂G'+3L༔& 9?w8& -$TP(E-Qug4 (׽5o)-"@B5IOMu DF0RO(mX"j$)!.!)%e5h5qH9OF^F0^i ]`RzѠMW9 Ib]ȱDN6R4)'H%xV)67Ir;9D"$5$2>ኸp_@MU_ON\ɲzɎPg_z"u@FDeC+G{K G "#w.`G|=$(}[dƨWhJIDVӄu|6cT7UgBeeȣ$)Djo7CE޼b?v~]Mb8z$îZιo o:e:28Eċ98bz Val+X,i8 _Cӌxf|v2a1z10^ *v]I>YC1SWu VM4,vd>p`T3NէaDžϮu L:w qdRSmEHkSf76&_..%z I _b|- XHYa{Lݎ~)r}8P z3fWչv Y\J\Å @p AÓP)4~P\M/!T|_uvCW&㑕·}xj|6{ )={ Wu?E a"`Oݮkc06Ğ&ᯩ}N>4TmLc!.)^܋ORM3"122 oh:K.?g*Y?851l$ e!V0u$gXQt?v~?4 'D3RO- *x_[qVWH}ZG0"Eix54MKBFHsmf#uSYċﻤJ]Rn| U;Y"F9;mO6+G&,Wk+ JO5lǶ?\F$Pb3b?ϩO&m _:NI(Dؑez&-)&+4B;L64"OOR/|d: {0VӾc m^zj}rS@ФPn#X RmFdZNjysy+y8%_ @M%»#qmCrʉݰmjZ3Qh(!{$8E4Qj * ]Bv1¤ xa3&%; [ 2N'(iDft+mAMWK-y Lj}89Wm +N\%]sz.H "HrZPjh)aoXuig&nmGOۤTtCtOܫX!lĨQ_;Ɗtёx6"uw]r#%|xg&[Y+H+bQ0J";XwR t1a0 0('JArD\^ @*dBPЌOh2IMI9m`kNHdK?TŒ--) v ӎu텽b87Erԉ/:n~@&?x_΄v*bc_PUPq6Z[IHmuQTKZsrA\6.7"Z}`*T7a:)]9=M+a`}9EY(b;T&j&͎C?FN|\v_sPKDܪrv49cHm8v u۞J\45S:#ҌvpNPpPc^׍Ho|LB{T&2y8΍{,M;P|͇жҟQ~ء^|Hz=BF |h.T{44(Erc˓P0TڄF _,`˜4CbsKb}DDsT|7TҲg857cW]nKoX0'ҺQ呓Irȉ%=|Ia3mؕg#iV_@01P,…w$(A#-/v2'Tg_cZ@Ƶ_ 鏝AfGNT!@7iw{va!3ZZo/w0$k =#r-7&,if45g$3OjcOHw=n=k CXO2ޠ#IƖr.JI9hA8 +8#ɪc1XFQ2FeOyG`8ტpY섔(|,jyQ2=qzg/~NXl9A>Lkǚi\q_ޣ}q6걍|ŷ yyiA.Mpc!JvO (p(҄-~oqo!I~j^|L "|;OL:lL_un*;*a- %zc{]= i!$/ť9xrېUb}V~>uD=q"w6Jb mUR0I\Fت "KpHy7཭Gmuq%&~43뙿x:w3vys.Z(wmqتfr>z>SZ@j](r&i l'45oB+(]5_# 1% zw/^\ ıAS=opBV?+mt>ɀ,_Q-%cB9^yRO&w+xՈ۸Hai{@7b^kB^h`K3e2B,Ф9tTqBeqe#0!+џfQTڲx6ix ֿwID4kr5@N PZiG 'F]PLaG`:Ha _J;s#Ly [\$T~)^FhƧ lJ<\8= $ÂVNFf뵤>'Ma (Wr}"[k@9"/H|nf9FJuGNUՅȅ?=,06k[|C77h5Ky6 ںp^wʁM;1~ضa!)&ɚKbx-x)8aSl).OcgCA qߟR rv!p$>=бq"'VcAɐ98. a^(Vsޯy,ȅ mӭN$ K~⸴ 7e|AJ0ˎf]poGi({969.{']6n,]JO:y&K+w-吳 OA$d)=|6܀\-]=6 RFۏy9tz["Gp'}; 䐌 wCaLWÊ<8{nH}# 6/$Ѹ7K|9U<'Z*#T2Z\sv";bX=ɹ̒Jk!lYLv,X O+B$QM*K Z?}\)]2gCM[>}};F Fڪ]D\riҵ'[4Asnx};PhyH}X)JIp2҅W3qn3"\ KO)NU,ԥKz&3z/z٠6k s$|MH>\>{?GWChPhvݶVʚNNoEZt[iM@f&jF\Xz% e#H|xu}8"VA2JdVk&|{^[nҬWz)才Oz {믵WՒulڙo@FT#aI'Ӎn7p16q;:&L]ǏQK ﹐㝑ö*pV:ΜQȝA #WZp_R]YE/r+7 ̮ ?%?-F/O"H{1씔^oz /R-Nµs -չ/k4/OwO˵3Y} "\wKˋFz=4FS`T9v95 q1L+ Fߤ?]${s{I&G.<$eL1>TMZ_k5 ۑuبbwJ )ʢ D I&\vS!K-鰍 ܏6U6( 0df.r+OLD-(I˲9Ho i&475m(q܌pEZ5: g4$+P& պ enȥ!K`3-vkR\qS1hmmi̱:Hk g3_1;İ v. -\-}maPZmitMGxC/I [DYS>+^O!\W(`OCSpH[Ki==vE?Z]=$Y񥛆/xWoAA t&Iq;(btMJ39 ^c 3J5 4w<!0-953nЫi|~U/ Kc}|ӱeJ`cU,hOf|,)I$Ȥ&Zm9-BzIǟyAtGicS !MB hs#(NhL8X:K,f7!oiiW&ipR/kPp:i <y@CHӌm"u9 i[[݇1w\Emrߟ( ̞f)g$1{n Em~c%ߎ wyCn~6xC3v]e]>#z7sRR ^z|MA^>M(3d*6^uU 1 G0It"M\b):H.F\ !86-X3}Xz.Q&bS^RU+2yBS~7ɷzL0rĻ^&_gc s)>m@:݁5Z?̄Ov%+XDP|q9]% {hO.Izl:]٪:~*iØ_o,d=C9ӿ|!x4ևm]nHڕmNjbzqp*/8QRwיKIɋ'FnFeihD=beBsz4Z ̄W3MDzu;2I͹$s+w4 UB(R%!&JGrc6.|sC"ORA83Kڻƒ $}/߰H ` _Ye̾!B}??dʸk\TlJ[>wwݿcԍ\z>j$l؝kҾnFo$@apjY#. Ċp "؄^FB)5\D6{[彽ww/>w7?d_aq`ow3Z^\-39韗`||X^^~[zofao)Mv6'gLJ@yG|X^b-y{'hxA./>%Z2FyWKZ=p̦A aqw~Y^-_Zwˇky7/?^ǻۛŅ ?,@_a .a~}\ɚ mk|u_1ZطwˇE'i̯}OKv.o0͵\ WkY,5$h [GZ+Ď[00Dv/ E˽IB|";sƼ!BLQ.a?['9~vfoK@<\ iZb)h>"@[JYP(=$м?-SLY51ul~y O #iZve_޽5^Xn͗W廬o0 y<3||GS]m#UbVYc󷟖:TxH/"J TRU^8 pɇtoS5@߬VR"l ݋$@\"T)ј1FN` ANk5?Jh?3I0Xl$  ƌǝz~fKۏu_9֜ox;tiH|/}(Ukb :x{ysC8^QjbRL6Ffɓ١R1k1F `x;tiG3Oh4֤ * Nx340031Q,+dx6M9{wk+qIODCĢ̲"ZJ;\%t(O?TYr~nAQjqq>Xg|qq3}MP) xXplìZ&>3?O̜T%x.Y|C9j|Fɯt4`r!BbB鱹y#BfBAAJfGVmV Ssr83PN}&Ģax%Sd=V==ˉ 6s1sl`Nx@ 8},2 BZ`+m100755 setup.py]/;&o"P_QnTx340031Q,+dx6M9{wk+qIODCĢ̲"2G}luڽ<ĬpQx;ιsjɛ]-7d<G\8_x;tiv.~w%nm;²OV`T- =xX:A$2P>f1L6 Nc9DpQ(ܧAD^E100644 runner.pyzk0/N,6l.&0x)YvC|ؼ5RsRS2RK*ҊsJ2R2sRsS&ײKK$dD45 b7b;¸Yk#+"hx,Nv7'siQ`V2gx.~TlBfnA~QBr@frvN*2%l~xHx;'QdFE7~gO/+QQHI-.U*K-J/,$93iQiD45RA#91O!)U '35E!4$ 'U$37X$_AI!kFi̼T/2E0M6fUDؔaI6Բ[̼t=O?7J.N\ @Sjfp ?O?w.Ē" E&_P1H'%g&;ekͲ:PP#&[pz dy.!Xw/x(VdFE7~e)ٌ7# Kx;tiȯV#rWX+9k x{qFO[yI8F jmxQUA Cx;*vUtՍo/32ZMd ;S+2K4 47fb,y .eNx7vYUb!9"aؑ. : [Ipߓ $,͡x340075U(,(Ha|5IS2  xe@D1Bb Ű{[D^"tN2$oC3em iAعʸD :1:jeS2*z•NTdF0{60EH,lP#?a{|" [ߔ| Hvx{qF9]U+䗅,vLx|ģQ 7x;*ApՍo '3o>,YPWPP2렌kQQ~wjeR~bQg^IjQQiA{\Ry%E Pu yEr."3x@ 8=7Aù?rʎ|100755 setup.py\⾣C=;wSx@9{,,_~5$100644 runner.py}فB _>oニ~x ApC $V=,),(100644 runner.py[U{զٳ ! Hx;)xRpC $V=,,76GNx;ιsjɛ]7d<GP6Yx7\…;GR&l.z<.t$,Psqx/ $ fx340075U(,(HaOxl)ţtR/N?xSV/-.OO+S(,J+r R2s JJR %|=x{qF?RKBKJϣmXGU ox;)xX` {nbf&+wx@ 8hQسb_˸:T3QO100755 setup.py (Q5qHkO>gK@Jx@9,"&/*100644 runner.pyL[,/HL x;)xX`C $V=,s344oDox(QdFE7~9rlDx;ιsj͛E"6swx;ti^ N9d/y {)x].6(1 ݿ+D _x340075UH,1JHKO-+dphϫFF{yC~q 7""x{*'+3,yXγl6ix;tiLST"=;ֽ * ex340031Q,+dx6M9{wk+qIODCĢ̲"2*{]I[d8g|3܂|B2KYg6UM S R@*>6glb~izܨv0@5@P`]= )=OTmQi^mξ֑ߪ ]r>G/[FxZw~D Rl\42sRsS'+.),Ԝ':$cB ;n89VI.P~X x;) "f$FFI{X60&fihr x8/ԍ(i˹ALUi0x{qDBBx@ 8FK+3̓o100755 setup.py̶{U_af^ <3]YHnx@9n4A]|C-kPD'G100644 runner.py/s@Klgdۨv6,{ x;) "f$FFI{X60&fihrgx(QdFE7~9qn?ɶ*fx{xq9 x@ 8[ AL_)z fEf_100755 setup.py}/ 5M /*rx@98wzҤd(t100644 runner.py߷¹Ws<UMġ{x;) "f$FFI{X60&fihrokx(QdFE7~8o@x;Ϲ#柌1k2Lx  75Hሡi $4x340031Q,+dx6M9{wk+qIODCĢ̲"2*{]I[d8g|3܂|B2KYg6UM S R@*XSgCN2kl}`**jt8-3'U/3A.?/O-'/:i TmQi^m#x5mBO=\Yx[PwC\&?pܬ!Ƽ@ xkx?aī$ˢ bR rS5u5'_d|Q )x@ 8;dCy@X100755 setup.pyMy!FH_0S=$uLHx@9d@4퓼100644 runner.py$q/oE{`(hPԹ@fHN7Q,eg;u]ԑQ8 hxUw̜Լya[xU|jL#\'pTqtD Rڢ\4+.),Ԝ':$cB ;NQ'rqr/Ktj6E:qx;tiȵw/;)Zu *A }x{qȖ?.0@~s|rx@ 8O6"uj᥋+ϙ100755 setup.py9Ê3 l`L߾.zx@9'4욥1~t8@+100644 runner.py=K@17FABnx{ fy3(1 x(QdFE7~8mbx;Ϲ#柌1k2Lsx;tiȿekqvZ+eьUc ]Qx{qod|uV>vD9 &mx)N~{j^r~JjP+ x{~x;tiȫ}XQᙯdXp8l jFxd̤=x;tiK*n;eRތ>?& _i]xa4aī?x;ti#lWG\B3iŚ yx{q5's0mOjw;* x78e6UnQ^L|!tLW!uy $ԑ8xK:8@\} Q\N7oglx]RZYu=(tX%ԩgZ%;xLM\Cppds_compressedP) zg˓  6$ >c #W, x۪S33 D(uf?󌓗 uNv|U؊qrSy Mx(rkD/M܂Ylz1yJNdRR@J Mb& 2ILV`bG{7_e`x2x ĒĢ̲T -Ez0FAAJq|JfQjrI~Q&?ѓgqNp2exuk.6<%Lg_Q0K(Mb4.y|f^4x;5k[|))E%EX? )x5k2f=ÉD7a,[\'Fx@ 8tja.`*90@/100755 setup.pyqïB_:=r!Ox@9pNHLmD~v100644 runner.pyL;LGg6F&M.8wtwx;ti,ӑVvCr6pie올Y GUx{qH =S("IxFD9/1ux.yOb^ɒ"Jź)z)J @7,9XU x;tifg=K0x?ۺ>?f?xa~8Nx;ti9,?V.9W'w+_D3 ԸxMj0 y k`[!4VQW64o?`?~5u5x=woh6Kj=]1Gi]=Ho8CH?M֡"  pP_CRr*1vwy4]!kj.c./M`YS*2?M湄a$OJ(̤(Z> 7 t2<NG˚`slB*/oq`3sM5< SȚCgg"xi=# sm_x@ 8'~?.}100755 setup.pyڶvnFsiK0b@pOx@9A"ci\\=pMݣ100644 runner.pyE 9*S_vCVNr"Kx{ fy30)x5k2fc=D7a,[@%5xdX`O>F&M.B|x;tiHhsǶOnI#\2XLCuVJH 㙾| 7\Eg!X0,e|D&8#mn3.- ޿ւۚGjUG-*5<;lz7ѼX<psKx[yJcC>N̜Լb7 H3n>)x *pRx78*%!a6N/7cJLAoE'xU $+ox{qH]◊wzJ窊m?xu37  x.->wc+6̂b[X.(H/RH/(HQ̛Ů4وM E6P}qIXf,\+[Z |3K&[Sx;tiH庹!lՄYՏm?yU )x6:~ax.;"zs=dK?F6NceFUO=M>%Dx{qF̼Ԋg! L 6˳a asx@ 8ぢ0@\Q.d/100755 setup.py3F&M.B x@ 8xgcFp100755 setup.py3W6+]Q3x/ /m\`<J5x;tiH܊6(+%{qe *Z )x{qBH{2cִ38 lzx[Js6;0%h%x;tiHۖLU2rfy * x6:><e[{tԑN7AKICȃ7Qżx- /m\ ~f )x[9Is^yZsSl&l2y2Pp0#&vx;tiI[Z˺7{n7khT‚OOlIV4Q- w`**jt8-3'U/3aƵ{Yپ[z6UڢҼ<$k~X!~ͥ [} x'JdjfnA~QBr~nAQjqq~WZQ~BAAT* ec]u,Hp)((lâ8-~M6xr[J +>-3'Us2+_q|f^JjE4X<%E`&Ә' / ix[yLy^̜ylK`|⒢̼t=%%%.(%N0|TDcDnb^iZbrIiQj݄'K M AJ*F@qjN<[mz  4`hjjbj|RP]@/'ɯ'L.Pr-*/R(H,*ƁB@ 8&?l7\C4x;|DyCj ?FLh{zx{qF悂g]x 2JhVL6˳a\x;tiO'oN+{D/rҧ=s &xyBAjpiAA~QBzUfAAjB@K^AA^zBRBZfQqBi^r~nAQjqqf^BIFjBb^ SRdf*& s(x@ 8wo"x2HfH100755 setup.py xmc cjZTX lBjNx@9 8tnU5OV100644 runner.py ?kX7TZwUnpx;?fF00F x5k2f#=ÉD7a,[>%xdhw L\8mtx340031QK,L/Je`6H'.p~ѵg;6+BTyT0tx!MŠI)'}3|<]]ps|ls +A:ye18~'SeV<| U 1nOI%[2a®w*/41$1ZXvԱk6*lr~^IQfCl!;viϚt+=) yޛ=jςY]anjPZRZWP{~En2;l_B#ricx}}FRļ| L& C&2 x{qȜu=sU2VL<"F&M.B zx;tiH233m]OSCsU٫ BXx{qHG)IO˛k~)ސ3xК~s3,'x]Q]k1E]g/r; vFUY, %&wL2$~<'% տ? o2ݵ&{NN~~zx13 fqB8ïK4p:ԂT A9nnTY?uK*eaJS hixM iݗOB F܎.dyl :zWkɸODfς:?4bvZBr'bʢZzZ/0E=jpe;TECPqQwy4bxկ%pf4\-aL6!xUj8@L豔TM;m?}3n ؾ1y[;5-Y8"Yg 6,sc&5LZ;ټ4 (ݽL7bC?"% FW+} ^x+rBhtĢ̲TɌ7px JJY[&Wy:h(@i9@nqIFb-3gVf^ x@ 8W;j?|r#PNmo100755 setup.py|ѓN%$F ګ,u}x@9 Ӎn{jT[gN100644 runner.py@T#7V۾. N,4nx;ffC=09l x5kfA.F xdPlO>F&M.B6qx;ti=L.O=.2Xk = "Ox:Ç@J5Ò5(NIKx;ti[f%k^IԚf %U )x6:)3LozjK^Nckܺ HAhGr (%U@x+rMdB7obQrFfYFAAJqdFf፛Vs x;)zNUnb|AQ~E^A%Mt-/*Q(I,J˜8MiDVfEezE9ɩz: w3$(d)bĜb M+.(L'$+B,I I,rV y%yp]Ey% ц ' 1k4iXIY%@`&k\99YhDf^Z^zjInjnRjD#Y&?1uV%&5(4KLɒlg0Utsf<8Hxzr~nAQjqq~^A%Mtx1YqevjBAABf*,I-b M+.(LS*TUPw r sU,4Qw&̺f Y0+j&x;tiȾIk)<^rVU?GAgdFK #[x3:8v<r)N)Ϲt9'4ȸ϶INx;tiȋ?KI * aox{qȡ7Vu1/*V;ǿЉ~sAox+rGdB[6fȲ= f  x;tiBmo^7}+Ta;YQؚ axx{qB\e&p邮ki7Oެtby1 dx  (+D<46 $1 bx340031Q,+dx6M9{wk+qIODCĢ̲"OgdUYm9Pe9UE rLm n}Xa_7m_\s6}sdOuœJBL&6]׾n Y)TaAA H'6O+zQɅ;0@5@pOW*GJ~{ 4/ ۞=N5{seWsK3x![_TP5Arc_ "\Eũ)y9 R-8hAxk˾/3hOexuTMo0 W!ص@Cb A !P%zQkK͆e'-A>>>r]b*7lekg#P~R'YȩN,xLDYkOyݶVqD3 b>D/.#U%7{xK)MIU+(& x70f{QtsE/USTP%_Rr@}b %$qJ#T (bIxM͛vMHtWbjx;>`QiBbBQ~~fKFFfL%`"d9 1hx;ti){&~ֳPnu B4x4Q\qYFMfC=?sx:TL6e6f Cx5n(D ʿl^xiC&#&mx340031QK,L/Je_oͮ{s,95X VKG.N럋AyBU](tȡ[=8p}T_+Xz |P!t[r}:2_G?O7</׼;6:TI9 PHjc}VPDzrl ) }T2LgҊZ%QwZ9>rjxti#&#x[̴ikuʾa{ӳRo*MxF7ԺpsK"ӑ%xf]Ƌ?Rx>7Ʈl<LBWHT܃+u100644 setup.pyBsdR헟-wxIyx340031Q,+dx6M9{wk+qIODCĢ̲"7j_~fmaW)#TYr~nAQjqq>XS ӋVpd~RQsKWw6g>Ȕ$u3xĴi-/̳|y[>ĉ\Ч 7+x[qBĪBZB~*Yx.dӿX x%~.m[AxKo0 V. Γ8m;pҎMuG6~y^c5l1Kdk49Xjj9:2Ϛc+K41@) !bG>>Ԋ'`];7PJ:b6cN!HH}wk*5rPKUBH`n44!3>fJ;t8NȔG ߖzW?~0ܷ'auurmt}Xy. Tb6)Uyj汁vJaƮnCN{-GZ:?ӟk`a^h"p0 5M_%~oLCJL^x[qBĪB94JZwl-Rrj*I3xg%i |D8x[xC%sLK#71_gf2[x!Tzq%f1+'3!$2x6c !lS n)wc<@ ߻M~DbixsgzVԊ[X}'INdur5x{#Br@frvN^N~bdCFǘtsrRRs R5 R=<\c55'1&;E2@ze9V\ PыOI,.WU(I,JZX's15t/L~lb4Ytr pPRŔI!fr 4dV́BNx;Vf&[A7fS+å&wy3 f_&;x[qBĪBAn֍AijbU} S_ R)x_XR7T%{[}ro>100644 runner.pyTy(`ڞ*dUݷB>W::40000 test|_'v*)"x[qBĪB+˥c귩"A{wPo֑Da{x;˿rF+Y Ɍ칉y\A x[qBĪB7 k]jg5\ZWyxElztcy[I͑pɜl!x;OyC0V"0y x[qBĪBfAcѽBߛU?rx_XS8)1#100644 runner.py]ɺjՆ@*VA40000 test|_'v0+{2x;˿rF+?0NdYi9ɇu&d19 @QVS0m;X,&bQúldF< M.,(xȵkB䛌2+ՙt&Od(K-*ϳ^n|Fg@dFF0h9yfU&flRRx[qBĪBgw \_'_Хa:O*JFxĴiH.w/oygrD' +x!Tzq k8wCJf̿Ց$d[xc'FH$1"*D@wʅ 7x;k܌-Ù';FMfč-=hU0pUPx;KyC>ɮReV1Z74Fx!TzqJܡvŭZ_J^|}*$vx6c'FH$1"*D@w7i{ ~_p! gx;|\iC\}4' n% ex!TzqsF9(rf T*$%x6cg ^Э @ʌ4KG_wcFLzblfT>jiCD(sxmJ@M! ApH,J.L͙ "t朗BLj@\3ཱུS-Y7J;Mq'!yFRiU*d88Ghݡx.xE߅< w} p-4y$FeK٢noԟzx7߹#0--5 !QW䝪ش$HPe*mDCۏ3V{ZgICMbׄufc8E6G5+vy+"5xRMo@U+=@5lK !RHU*PD*˱ ,pn\߀Gv,ͼy3>vҷYPV,>]]F㧻uJC +2, IL0x ly2?gn^ųzoR (!$qWEE6|r2s5[D_BI-u`U(YŐpz*7#RF"dSM\uq~גalLVz[}X@T]gG)GPAT'>]iI^& 0M[B6Zн#S/%2J~pr2u <+/S;W(E$iրjjE%0;ꏜWm$;Me5'!90no%CQcKlFnMRX;X~6mc}_.-s:zgՂ/_|kx!TzqٚulHe $!x _!*o;K NgCx±m 3 Z>Zx!Tzq g^0j!8"$/QoxĴiBH~F?2;Aڪ8 }lZxǵa&O"+Kx!Tzqz@%дY|/\$V`x6c*(c(ըqZk~Rwc@NuFB,Dq?fxn暠1td1f1.%J+̉22NfUTSH,I-J)-Ԝ*6U&inxPMK@*/,9m@  e3^zGKA6s4{F{|/Q$S"Kzu銞_hOT?iHT25aE8z<ݯ`9Cp܊[aZ X6e, +igX=9c{5@,ͮHY qߑ`e #zwZg&%:Tmb`',Ї 7YxhL;2h< ߏ-rM+x[qBĪBY9zS1s]\_Z\*>x6cS I,Zᣘ)P"b监wcJbsDlJc 1nDΰWxmTM0W ,H9 8W B2C^bGӏ#Iɵwfdzq2փqNܷ}U2Ȣfiǧ\24YsfSA8uj{aKo}Wr$o{j=HAiXH313G@1]Om38Smv,Gw#{l. '+Os:C\Z+3=wwyV+8V׬zTR Sg9Z[Q$ ;9cւfQB3I0zAVϭwPN :sUޠfZ8,[DԮ~QĂVw$" b'Hnw('M9euJR ׸HH{yl&OU $0^$(BxilH6{xZo012vh!o>]Hnt„Wـ.3GŬ䢬B|׆Zpʒ Cy5m[%C;T@vh693< 8J_JFyUيg_Sxk/BbBNUnxAVJ x{us:̹%EV\ @0Yp+R=SPRsScl4K4B: ̜TMVHHN-(A / f^|AfrvN*Ш0K/4X$ Wf:UK"YZRZ1)9?(85 < xȿw̉cSRs RSSlrrb)\ @PZRZUlEEy% @hLۅzADlafM\y70%x[qBĪBVBӊcD ]М*hzxĴiҽ*gX|~Tl~~ެE-M @$a[7QgSn8xȵkB䛌2+ՙt&Od(K-*ϳ^n|Fg@dFF0h9yfUDFՂ̢Jm%<D+Ե$x!TzqY 5''Ĭ$Jtdx=S}}H]upRsx;|@qCFQn^"v% x!TzqA˘J*_钥H X2$xc! `oņ o桑wո5kWx;ĹsC:}x!TzqcP QO̧i(NV$ vx6c! `oņ o桑w7gwz&\dWnZPpx;xMasnҜRu+<,qnx[qBĪBGn>fy7kwU x6c! `oņ o桑w7gAc-)xUS+H)4̑pS:$x;x33 D(us,O|2c&g l?V 8x[qBĪBLN56SSҚ/kO{5 ֲ /x6cKg]>~y qw7}oh \pxSx;cK5'_bYk=:p  ox;x33 D(uq,rfBZfNj^bnBqjN`TPYPb=OM.'1/41Ofr ?PUnb^iZbrIiQjw|v<@l@)eɩ)V3̈́m$A|){r<xDZcf]&CUO0, x[qBĪB-VG,EҰK3=_Yp TxĴi:^ R n;NgpqBAeAA.ee1 B+:' Μ6 |k>xqcC*'xx3!}b͝-\\)i ɉ%)V70E Nx?8]~WpK:z100644 setup.py[F;iNS|%~(Aǯ x340031Q,+dx6M9{wk+qIODC܂"鱢>} z%ϓ; sr +*A ldd]CzyCT4ZeXPRpAt:k%[*JRs7y]pGBIjq \>.ko8?߂\xc]ZQ~B~^AbIBfnA~QBfqJf[PZ\_ŕPXTX^i5 Ju&8R3l'(3$(W]\XTPYP\XTPYPS ԕPP2#Є< ? $CS(51EC j#HaƔsA~?g0h$  {x<Yl>!oؑDIj6x{ukFoVDxqQ.ƛƾ/;+U]*5 a Dhx<MٴO,O?8D mxc¤mfd(\PRXRRTZZP*> d1Vz P/-)(-QU/H+QQP*OR+JLIK)-@3YGbT)W^Y Q[PZ\#9'8UC 4?x{Ʊ}$F1MaVVQx|UXҰQ<<+/xtБ$a/wx8qgܲ֙ߵ?u\{މ]o x{Ʊ}fɋm.HCxs.ߋS\r$ix8qHUϊ 2=l6k.ʷ !xL;`ժ/hk؊$5>Ax8q̍/YfI']zQm^mC$I5JD#KA\.*XK )[ caYHDJ"/D(AETckZsk!1< I_ jX^CUQж% pibRϔŶٖ))xZn<9994 2 Mo'+Dk]0hƺOleL0s9xcɰRs^/0sBTd -%^MNYpA ][(=&3P;R(~u@-]7ɍ*]>f^᜙V)?7Uǁ8: 5Tryi :Dv#Z7 qMB{wum(m;~뜌Yst}dzf[4-N̚G9M̸zޘn C^ZmӨփEL_JIZDڸQ^o8A>n*jr`zXtG _K]Ye $Yt2C7Vws[CB7?\-k$DuWC!]7ni'}]дZ]tc`C`pPϞw񉜥6Y_Ck# w[tV`')=Gd۵!]@da\`Nl]vVNەuJD!+%h!'8pa߸GMvo&loDp DcKfȾi2F{XM] Co};PzҌ$I(_s 뚔U%-)aK¾{žf둖)h3fm/Ow$ ]}uH3<% K@Aˮz(F 2 #xN0EQW J8 !T*$@ǞӖ=NK +*b2od4%i4-RE"F2TT1"KJ,-$òiB%Ԛ;0c=?o~piQ^Β4IDē&yYh#;E{VRA۸VO!^ l Q q$}A cY[?:wf<#wm .LZרS< ėx0) nH#ܭdImؓ&5ߧDfq}:/ǔ%xOO |լMc4{0t%Rh(o_7/${""gι P ß()UW F'(6tJ57R]t֦RLϩspqĄf?h& ReQ) :ؔaO=zأǨ{`}0OYct] l (m|<1O7oz8CCl Zt:RN}| hvXy ̊e/ſby#6l"l |I T~bSҋv^ѿ x340031QK,L/Je`6H'.p~ѵg;6+BTy3lhh:Ŷo݀tv v+(a='%KJg8v HC:ye1ܿW{.^ٓ5v>UˆvG{{℅OIDD^n &{>bJa[N%|X& hlo~޻ߘCo$1uF8>rK(,(Ha(:t%bj%s>{injPZRZWPpun)\SO?[{*&x340031Q,+dx6M9{wk+qIODCĢ̲"O|=b [^ RaU[PZ\V(3YݿS9[|O~mTaAA Ȩ3e>]TdtX- ĕa**jt8-3'U/3!}ޙng^bb ڢҼ<^Ӟycq-˧C\5!r`$x{i&UN'fg\?VP@hd[F Tx6: pa_=‘N7FN\Ⅱ:Qix@)8$k Ůa` \100755 setup.py(P@{+ ̇WyAx{i&HMf5,S?_aݵɶ*BBx@9Gٟ[a(7{3W0100644 runner.py^23+ I9kuMcx~:A!ѣVx#Q/xW[o6~ 4ZUݰ -/[v ʤ@RMa}^taK$;űWڲׯ6½фGvX^D; :2] i~lԱ>%7X34U+4oc~aIX ^a8kd`Xll1oܐk׭-ˏ}W[8]|I_NtJõ5⤟l0R-oAȧ#:0݁ۧ,T^0ܝZYra2el' 0eeV:.vL*rǏs$ߜ_ݿ~6\%ձ|s&r,dL[[*Q%}mYrDPΖx?ggwó$&)N?'&̲W!rzSj< a =o(`AVDŽau#A{7b"dIWҺ("@@.tJ3^7lW@_uR>b 8>GKvcFLAHLNayڍ%-@_ S'4F dh[$41)) 4Ȯ~$Jṕ554gG6͑.65WF6@=keH B{{A*HT+ܪ5u6FeJƜ rڈ>퇫_xgCRG//HǞ),Ί8|Ϳqx?XV6,aqK!ML$m3,KCIvDc߱(2etazyaJ$&ٲS\gy(Ff 5957F,Kd.)?"JPZ?B;5Ӡ,^.O`OQơ|Ц` Á}"mK~g{ hfrfw{݀q(i), + o*61͓}]9hq\cOm"{@z`/!/.! Ug/4z ިg%(bCqi 6:"@E;ƣ!-+iKXӱL,bψPӕ/8 xYmoF_'#*Jr _@I~H aEȵ]Ҋ|wݥHvҋD"9;/<3;K*M-:WV&.7/^\{*7LTӲ[k1z+eaf8T4:9/0r _[+seKX*H~g;eRB;6jtMt*+Mcw}xQ%EN&oI:Qm&seY\̜|7ddNnzjodq\3e0X SQJFt2. 1'3Q&wbG9&&PԖLL!̫S2K! l:[e_դia&Xn8.BHF߰Lya6F &Gx&lFemE"U؜!1% Ö47S =MecBR],r)e1  NGP&W+J2a;|h ]4 %[YtaG[>$HڨHHq|nF 8AyaHTiod;b+s@-3>e<($j o9mi͖ EA#bۘɖ9UM>탤3ɲN{:m?UFxvf=4D=ɩit朌\F *sT/g#UERVK^f  ~FkϢ5| ٘Pw˸](F/JSp;[Fι+#R8ZEiJD3YrmWMq͓+vH'U#^Tk#P5kLMx 2G  iR |Eč61J ,)`f V{ivhlPXL[&w!Ȣ8XH0bp(t[&sZTf˴YyZ%=ӶZbJ^ˈشqȓ<,sce4dn>Wv7 ˹ʢ]n|]ЎvB\ИjwZ:fSɸ>jZ` aT2O1։l4]7f]opb0f[U[oe2πB_|ms5cW#lFMqhFVh[dM}ߕY]9-p𙩻T{^a><AbFX$W~'Fƺ n6x`K KVk#Wz7IIkŬ\ߨqX䝪 .0JIq]}dtU e%dyM6bB jfUYn%Q ld (BÈq:@%4ib/sQU:UB2"R[gH 8.6]|;8gg_+}Ͼ@ K8lm<^qhL!Gv"YSH0? E74,ו'''_%UWĕ3~5IXD~)|?LBbS.*it~z?eq>-AYO55ӥ)ɑPszPxS^Ǒ'D/%m 2[$°xBB8{&]b\l92|1xr>ęΗ>52M u/{&/j5@kQl\\(_O&Fx3zPLxZ=")9dZm3 ɿoN(7 >v2M` G(ФqYH+fGus%3۹x%cl˦A4#ڟo(#h?\ >sč08`V ?fEY\PEliM~;3KX@A\@)7MOy3qqWx/owk, U[9T v,@w [+{dm]'ߣuVd]a~]~q9!YG\S<o;+>>;= 7NoS6xd4,Á{ dz\|o)9UJ=e5LB8?D[y%FL>cHQ>EsGQ":ف1aT{ vԅAQ92Js7 Ga6D;&k75i&o^N4 ΃W4^TZ0rxb0{)0x2޽gɪ<˙pu=pi]b0Sv.6$m IQ3De܍:YP3tQ_ZC`K=/m2RŃ=Ta>!a``bxH߇bd;48@[rQҼ= A܇AR dГO%p=HP Uj?u@zO(ַ4a  Nx&^%~ccçAځ^$)1ͧC9> 7V myje,nkzBRG#%o[_h7}jxH 㳾Ħ.^X#@>+QP_.hzdo(Z[] {s`1xM13p὇  ̅>ExMJ@ElAJZ.\O2 o]u'#_'(8 w޽]~f>ÊQl ñfmC P2ӧ%'Cx=bGMё uXK' ^SsVp5Nlhn^k9Ά L)1P 4 mlM/ECAAoco 8:fsᤜN>\,65JfXR,9Ny[(˄xWmo6_qHFSE?'K1cmc$i- :jdR )Q%dkNm,{xj=9(>f*<Z0RB1J'"8_O6lV(mAY` Ta ߸,ldtEAM'%P|b(YgjI}hzdB)4إaZE׿X_g"RCDO(R3Xڈ)t:G*xd@;횗&_Cdlezx&J墤[&'{}E A-t@4b+bcξ=[S[+ ؇K _dg+d1AQNjCLb4gw{|WCeb}i3i"#0kf=63~aawy5) ඐ]yv0֬@qD|whe+xLxvwuI*Ry~RSZ>pN_JAj_JZ^m(' Gޯ|$ 4nz0hU % c7fr1Sꠢۨ"Qx_IsA1/=kndžJ !{4Ȯʙ?5ǬKv[[d?΄GԘmꃐ67\Y.=d~!|Z{EC>\BGBu v A Xl2PR (l7|^F:p>:TGU/tW1DN/ၛW|cwYmwPz'қkxsy]wnzvy}TNr7;wV՗p߫P!}x x8)KbOׁC2׹A8̨Akg,gC2@Y\9CiMX3u],'$Dj` 0PVEkcM㍚.]W\)qtޔI'ϳG{&T\LS1st,I{%dkk,!4r/'\}IHKck`N\4GI`M|b 29VL|&qvweZcSM󯛘̭Ƶ7,crQx 56Mc78[4눞B?"K,[u]=R";x< Owl; Xp0sc0+2:QFP{\%,lM܂ߨH,]2AlSz kJ'SCpxV[o8~WHm4uڥ u*dX vd;0Hn%3;>;ϰu%:̭&YS)h6X_mRur) *](xqJ6ծȴ`|hViAJ]S+@`wЭXC-2&iV-'~{~I*4in"=A ڼ` 7I?.̛ZrI0+1D~µVOeowLma\X|N2/Ֆ~4[q{wDcI޿Ey3>yXevT8O3k.;"vQ,䑠X 55c=ZUOYVۚ~^[%(ɦ@y 'aÑnbrK_Rѻ?=q+ t{7nRUݮ[ʾ^E}FH ;f$ 2$K.D~RG۠c a|M/.gEi(p- 7Ѣ[$ڈ$HɍDۣhӂdL[r#hp IcSvB%SJxn / Q1iqb (?IaƵ6ZɽA=/H^CR_`p kSpyppd-1.1.1/.git/objects/info/0000775000175000017500000000000014755402564014603 5ustar tilltillpyppd-1.1.1/.git/config0000664000175000017500000000040614755402564013407 0ustar tilltill[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = git@github.com:OpenPrinting/pyppd.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master pyppd-1.1.1/.git/HEAD0000664000175000017500000000002714755402564012642 0ustar tilltillref: refs/heads/master pyppd-1.1.1/.git/info/0000775000175000017500000000000014755402564013152 5ustar tilltillpyppd-1.1.1/.git/info/exclude0000664000175000017500000000036014755402564014525 0ustar tilltill# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ pyppd-1.1.1/.git/hooks/0000775000175000017500000000000014755402564013342 5ustar tilltillpyppd-1.1.1/.git/hooks/sendemail-validate.sample0000775000175000017500000000440414755402564020302 0ustar tilltill#!/bin/sh # An example hook script to validate a patch (and/or patch series) before # sending it via email. # # The hook should exit with non-zero status after issuing an appropriate # message if it wants to prevent the email(s) from being sent. # # To enable this hook, rename this file to "sendemail-validate". # # By default, it will only check that the patch(es) can be applied on top of # the default upstream branch without conflicts in a secondary worktree. After # validation (successful or not) of the last patch of a series, the worktree # will be deleted. # # The following config variables can be set to change the default remote and # remote ref that are used to apply the patches against: # # sendemail.validateRemote (default: origin) # sendemail.validateRemoteRef (default: HEAD) # # Replace the TODO placeholders with appropriate checks according to your # needs. validate_cover_letter () { file="$1" # TODO: Replace with appropriate checks (e.g. spell checking). true } validate_patch () { file="$1" # Ensure that the patch applies without conflicts. git am -3 "$file" || return # TODO: Replace with appropriate checks for this patch # (e.g. checkpatch.pl). true } validate_series () { # TODO: Replace with appropriate checks for the whole series # (e.g. quick build, coding style checks, etc.). true } # main ------------------------------------------------------------------------- if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1 then remote=$(git config --default origin --get sendemail.validateRemote) && ref=$(git config --default HEAD --get sendemail.validateRemoteRef) && worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) && git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" && git config --replace-all sendemail.validateWorktree "$worktree" else worktree=$(git config --get sendemail.validateWorktree) fi || { echo "sendemail-validate: error: failed to prepare worktree" >&2 exit 1 } unset GIT_DIR GIT_WORK_TREE cd "$worktree" && if grep -q "^diff --git " "$1" then validate_patch "$1" else validate_cover_letter "$1" fi && if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" then git config --unset-all sendemail.validateWorktree && trap 'git worktree remove -ff "$worktree"' EXIT && validate_series fi pyppd-1.1.1/.git/hooks/fsmonitor-watchman.sample0000775000175000017500000001116614755402564020375 0ustar tilltill#!/usr/bin/perl use strict; use warnings; use IPC::Open2; # An example hook script to integrate Watchman # (https://facebook.github.io/watchman/) with git to speed up detecting # new and modified files. # # The hook is passed a version (currently 2) and last update token # formatted as a string and outputs to stdout a new update token and # all files that have been modified since the update token. Paths must # be relative to the root of the working tree and separated by a single NUL. # # To enable this hook, rename this file to "query-watchman" and set # 'git config core.fsmonitor .git/hooks/query-watchman' # my ($version, $last_update_token) = @ARGV; # Uncomment for debugging # print STDERR "$0 $version $last_update_token\n"; # Check the hook interface version if ($version ne 2) { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; } my $git_work_tree = get_working_dir(); my $retry = 1; my $json_pkg; eval { require JSON::XS; $json_pkg = "JSON::XS"; 1; } or do { require JSON::PP; $json_pkg = "JSON::PP"; }; launch_watchman(); sub launch_watchman { my $o = watchman_query(); if (is_work_tree_watched($o)) { output_result($o->{clock}, @{$o->{files}}); } } sub output_result { my ($clockid, @files) = @_; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # binmode $fh, ":utf8"; # print $fh "$clockid\n@files\n"; # close $fh; binmode STDOUT, ":utf8"; print $clockid; print "\0"; local $, = "\0"; print @files; } sub watchman_clock { my $response = qx/watchman clock "$git_work_tree"/; die "Failed to get clock id on '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; return $json_pkg->new->utf8->decode($response); } sub watchman_query { my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') or die "open2() failed: $!\n" . "Falling back to scanning...\n"; # In the query expression below we're asking for names of files that # changed since $last_update_token but not from the .git folder. # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. my $last_update_line = ""; if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; $last_update_line = qq[\n"since": $last_update_token,]; } my $query = <<" END"; ["query", "$git_work_tree", {$last_update_line "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] END # Uncomment for debugging the watchman query # open (my $fh, ">", ".git/watchman-query.json"); # print $fh $query; # close $fh; print CHLD_IN $query; close CHLD_IN; my $response = do {local $/; }; # Uncomment for debugging the watch response # open ($fh, ">", ".git/watchman-response.json"); # print $fh $response; # close $fh; die "Watchman: command returned no output.\n" . "Falling back to scanning...\n" if $response eq ""; die "Watchman: command returned invalid output: $response\n" . "Falling back to scanning...\n" unless $response =~ /^\{/; return $json_pkg->new->utf8->decode($response); } sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { $retry--; my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; $output = $json_pkg->new->utf8->decode($response); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # close $fh; # Watchman will always return all files on the first query so # return the fast "everything is dirty" flag to git and do the # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); $last_update_token = $o->{clock}; eval { launch_watchman() }; return 0; } die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; return 1; } sub get_working_dir { my $working_dir; if ($^O =~ 'msys' || $^O =~ 'cygwin') { $working_dir = Win32::GetCwd(); $working_dir =~ tr/\\/\//; } else { require Cwd; $working_dir = Cwd::cwd(); } return $working_dir; } pyppd-1.1.1/.git/hooks/applypatch-msg.sample0000775000175000017500000000073614755402564017507 0ustar tilltill#!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup commitmsg="$(git rev-parse --git-path hooks/commit-msg)" test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : pyppd-1.1.1/.git/hooks/pre-merge-commit.sample0000775000175000017500000000064014755402564017721 0ustar tilltill#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git merge" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message to # stderr if it wants to stop the merge commit. # # To enable this hook, rename this file to "pre-merge-commit". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" : pyppd-1.1.1/.git/hooks/commit-msg.sample0000775000175000017500000000160014755402564016621 0ustar tilltill#!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } pyppd-1.1.1/.git/hooks/pre-commit.sample0000775000175000017500000000316114755402564016625 0ustar tilltill#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=$(git hash-object -t tree /dev/null) fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --type=bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff-index --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- pyppd-1.1.1/.git/hooks/pre-rebase.sample0000775000175000017500000001144214755402564016577 0ustar tilltill#!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up to date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END pyppd-1.1.1/.git/hooks/pre-receive.sample0000775000175000017500000000104014755402564016751 0ustar tilltill#!/bin/sh # # An example hook script to make use of push options. # The example simply echoes all push options that start with 'echoback=' # and rejects all pushes when the "reject" push option is used. # # To enable this hook, rename this file to "pre-receive". if test -n "$GIT_PUSH_OPTION_COUNT" then i=0 while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" do eval "value=\$GIT_PUSH_OPTION_$i" case "$value" in echoback=*) echo "echo from the pre-receive-hook: ${value#*=}" >&2 ;; reject) exit 1 esac i=$((i + 1)) done fi pyppd-1.1.1/.git/hooks/update.sample0000775000175000017500000000710214755402564016032 0ustar tilltill#!/bin/sh # # An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --type=bool hooks.allowunannotated) allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) denycreatebranch=$(git config --type=bool hooks.denycreatebranch) allowdeletetag=$(git config --type=bool hooks.allowdeletetag) allowmodifytag=$(git config --type=bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero=$(git hash-object --stdin &2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 pyppd-1.1.1/.git/hooks/pre-applypatch.sample0000775000175000017500000000065014755402564017502 0ustar tilltill#!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup precommit="$(git rev-parse --git-path hooks/pre-commit)" test -x "$precommit" && exec "$precommit" ${1+"$@"} : pyppd-1.1.1/.git/hooks/pre-push.sample0000775000175000017500000000253614755402564016321 0ustar tilltill#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 pyppd-1.1.1/.git/hooks/post-update.sample0000775000175000017500000000027514755402564017021 0ustar tilltill#!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info pyppd-1.1.1/.git/hooks/prepare-commit-msg.sample0000775000175000017500000000272414755402564020265 0ustar tilltill#!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first one removes the # "# Please enter the commit message..." help message. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. COMMIT_MSG_FILE=$1 COMMIT_SOURCE=$2 SHA1=$3 /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" # case "$COMMIT_SOURCE,$SHA1" in # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; # *) ;; # esac # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" # if test -z "$COMMIT_SOURCE" # then # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" # fi pyppd-1.1.1/.git/hooks/push-to-checkout.sample0000775000175000017500000000533714755402564017762 0ustar tilltill#!/bin/sh # An example hook script to update a checked-out tree on a git push. # # This hook is invoked by git-receive-pack(1) when it reacts to git # push and updates reference(s) in its repository, and when the push # tries to update the branch that is currently checked out and the # receive.denyCurrentBranch configuration variable is set to # updateInstead. # # By default, such a push is refused if the working tree and the index # of the remote repository has any difference from the currently # checked out commit; when both the working tree and the index match # the current commit, they are updated to match the newly pushed tip # of the branch. This hook is to be used to override the default # behaviour; however the code below reimplements the default behaviour # as a starting point for convenient modification. # # The hook receives the commit with which the tip of the current # branch is going to be updated: commit=$1 # It can exit with a non-zero status to refuse the push (when it does # so, it must not modify the index or the working tree). die () { echo >&2 "$*" exit 1 } # Or it can make any necessary changes to the working tree and to the # index to bring them to the desired state when the tip of the current # branch is updated to the new commit, and exit with a zero status. # # For example, the hook can simply run git read-tree -u -m HEAD "$1" # in order to emulate git fetch that is run in the reverse direction # with git push, as the two-tree form of git read-tree -u -m is # essentially the same as git switch or git checkout that switches # branches while keeping the local changes in the working tree that do # not interfere with the difference between the branches. # The below is a more-or-less exact translation to shell of the C code # for the default behaviour for git's push-to-checkout hook defined in # the push_to_deploy() function in builtin/receive-pack.c. # # Note that the hook will be executed from the repository directory, # not from the working tree, so if you want to perform operations on # the working tree, you will have to adapt your code accordingly, e.g. # by adding "cd .." or using relative paths. if ! git update-index -q --ignore-submodules --refresh then die "Up-to-date check failed" fi if ! git diff-files --quiet --ignore-submodules -- then die "Working directory has unstaged changes" fi # This is a rough translation of: # # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX if git cat-file -e HEAD 2>/dev/null then head=HEAD else head=$(git hash-object -t tree --stdin _pyppd/archiver.pygw{gw{VyV> 8c,↦pyppd/compressor.pygw{gw{Vy#~:A!ѣVx# pyppd/ppd.pygwgwVyGٟ[a(7{3W0pyppd/pyppd-ppdfile.ingwgwVy :^23+ I9kpyppd/runner.pygwgwVy(P@{+ ̇setup.pyTREEt15 3 FօdbJ(Gy1bin1 0 3 H">1pyppd6 0 $k Ůa` \contrib1 0 IؕS2 zE^eq`L!7>T w]pyppd-1.1.1/.git/description0000664000175000017500000000011114755402564014456 0ustar tilltillUnnamed repository; edit this file 'description' to name the repository. pyppd-1.1.1/.git/refs/0000775000175000017500000000000014755402564013156 5ustar tilltillpyppd-1.1.1/.git/refs/tags/0000775000175000017500000000000014755402564014114 5ustar tilltillpyppd-1.1.1/.git/refs/heads/0000775000175000017500000000000014755402564014242 5ustar tilltillpyppd-1.1.1/.git/refs/heads/master0000664000175000017500000000005114755402564015454 0ustar tilltillc0ad5597fe6abd15ed3e29ef51ea6124e863f6ff pyppd-1.1.1/.git/refs/remotes/0000775000175000017500000000000014755402564014634 5ustar tilltillpyppd-1.1.1/.git/refs/remotes/origin/0000775000175000017500000000000014755402564016123 5ustar tilltillpyppd-1.1.1/.git/refs/remotes/origin/HEAD0000664000175000017500000000004014755402564016541 0ustar tilltillref: refs/remotes/origin/master pyppd-1.1.1/.git/logs/0000775000175000017500000000000014755402564013163 5ustar tilltillpyppd-1.1.1/.git/logs/HEAD0000664000175000017500000000027314755402564013611 0ustar tilltill0000000000000000000000000000000000000000 c0ad5597fe6abd15ed3e29ef51ea6124e863f6ff Till Kamppeter 1739981943 +0100 clone: from github.com:OpenPrinting/pyppd.git pyppd-1.1.1/.git/logs/refs/0000775000175000017500000000000014755402564014122 5ustar tilltillpyppd-1.1.1/.git/logs/refs/heads/0000775000175000017500000000000014755402564015206 5ustar tilltillpyppd-1.1.1/.git/logs/refs/heads/master0000664000175000017500000000027314755402564016426 0ustar tilltill0000000000000000000000000000000000000000 c0ad5597fe6abd15ed3e29ef51ea6124e863f6ff Till Kamppeter 1739981943 +0100 clone: from github.com:OpenPrinting/pyppd.git pyppd-1.1.1/.git/logs/refs/remotes/0000775000175000017500000000000014755402564015600 5ustar tilltillpyppd-1.1.1/.git/logs/refs/remotes/origin/0000775000175000017500000000000014755402564017067 5ustar tilltillpyppd-1.1.1/.git/logs/refs/remotes/origin/HEAD0000664000175000017500000000027314755402564017515 0ustar tilltill0000000000000000000000000000000000000000 c0ad5597fe6abd15ed3e29ef51ea6124e863f6ff Till Kamppeter 1739981943 +0100 clone: from github.com:OpenPrinting/pyppd.git pyppd-1.1.1/setup.py0000775000175000017500000000221614755402564013074 0ustar tilltill#!/usr/bin/env python3 from distutils.core import setup from distutils.command.sdist import sdist as _sdist class sdist(_sdist): def run(self): try: import sys sys.path.append("contrib") import git2changes print('generating CHANGES.txt') with open('CHANGES.txt', 'w+') as f: git2changes.run(f) except ImportError: pass _sdist.run(self) setup( name='pyppd', version='1.1.1', author='Vitor Baptista', author_email='vitor@vitorbaptista.com', packages=['pyppd'], package_data={'pyppd': ['*.in']}, scripts=['bin/pyppd'], url='https://github.com/OpenPrinting/pyppd/', license='MIT', description='A CUPS PostScript Printer Driver\'s compressor and generator', long_description=open('README', 'rb').read().decode('UTF-8'), cmdclass={'sdist': sdist}, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: System Administrators', 'Operating System :: POSIX', 'License :: OSI Approved :: MIT License', 'Topic :: Printing', ], ) pyppd-1.1.1/contrib/0000775000175000017500000000000014755402564013016 5ustar tilltillpyppd-1.1.1/contrib/git2changes.py0000775000175000017500000000674714755402564015607 0ustar tilltill#!/usr/bin/python # Copyright 2010 Vitor Baptista # Distributed under the terms of the GNU Lesser General Public License v3 or later from subprocess import Popen, PIPE import re class Commit: def __init__(self, log): log.strip() log_array = log.split('\n') self.commit = log_array.pop(0).split('commit ')[0] self.author = log_array.pop(0).split('Author: ')[1] self.date = log_array.pop(0).split('Date: ')[1] # Remove garbage from array while True: garbage = log_array.pop() if re.search(r'files? changed', garbage): break self.changed_files = [] changed_file = log_array.pop() while changed_file != "": self.changed_files += [changed_file.split('|')[0].strip()] changed_file = log_array.pop() self.message = "" try: while True: commit_line = log_array.pop().strip() if (commit_line == "" or commit_line.find('Signed-off-by') != -1 or commit_line.find('git-svn-id') != -1): commit_line = log_array.pop() continue self.message = commit_line.strip() + " " + self.message except IndexError: pass self.message = self.__break_string(self.message, 80) self.version = None def __break_string(self, string, column): finished_string = "" string = string.replace('\n', ' ') while column < len(string): break_point = string[0:column].rfind(' ') if break_point == -1: break_point = string[column + 1:].find(' ') if break_point != -1: break_point += column finished_string += string[0:break_point] + "\n" string = string[break_point + 1:] if string != "": finished_string += string return finished_string def __str__(self): message_with_files = '* ' + ', '.join(self.changed_files) + ': ' + self.message message_with_files = self.__break_string(message_with_files, 78) message_with_files = ' ' + message_with_files.replace('\n', '\n ') if message_with_files[-1] == ' ': message_with_files = message_with_files[0:-1] return message_with_files def get_version(commit): setup_py = Popen(["git", "show", "%s:setup.py" % commit], stdout=PIPE).communicate()[0] version = setup_py.split("version='")[1].split("'")[0] return version def run(output): log = Popen(["git", "log", "--summary", "--stat", "--no-merges", "--date=short"], stdout=PIPE).communicate()[0] log = "\n%s" % log log_array = re.split('\ncommit ', log) log_array.pop(0) commits = [] for commit in log_array: commits.insert(0, Commit(commit)) prevVersion = "" for commit in commits: version = get_version(commit.commit) if prevVersion == "" or version != prevVersion: commit.version = version prevVersion = version commits.reverse() if not commits[0].version: commits[0].version = "HEAD" for commit in commits: if commit.version: output_format = "v%s (%s)\n\n" if commit.version == "HEAD": output_format = "%s (%s)\n\n" output.write(output_format % (commit.version, commit.date)) output.write("%s\n\n" % commit) if __name__ == "__main__": from sys import stdout run(stdout) stdout.close() pyppd-1.1.1/.gitignore0000664000175000017500000000011014755402564013336 0ustar tilltillbuild dist *.egg *.pyc *.egg-info *.so *.pyd *.swp CHANGES.txt MANIFEST pyppd-1.1.1/bin/0000775000175000017500000000000014755402564012126 5ustar tilltillpyppd-1.1.1/bin/pyppd0000775000175000017500000000027214755402564013211 0ustar tilltill#!/usr/bin/env python3 from pyppd import runner try: runner.run() except KeyboardInterrupt: # We don't want a KeyboardInterrupt throwing a traceback # into stdout. pass pyppd-1.1.1/pyppd/0000775000175000017500000000000014755402564012512 5ustar tilltillpyppd-1.1.1/pyppd/pyppd-ppdfile.in0000664000175000017500000000770114755402564015624 0ustar tilltill#!/usr/bin/env python3 # compressor.py @compressor@ # compressor.py import os import sys from optparse import OptionParser from sys import argv import base64 import json from io import BytesIO from os.path import basename from errno import EPIPE import lzma def load(): ppds_compressed = base64.b64decode(ppds_compressed_b64) ppds_decompressed = decompress(ppds_compressed) ppds = json.loads(ppds_decompressed.decode(encoding='ASCII')) return ppds def ls(): binary_name = basename(argv[0]) ppds = load() for key, value in ppds.items(): if key == 'ARCHIVE': continue for ppd in value[2]: try: print(ppd.replace('"', '"' + binary_name + ':', 1)) except IOError as e: # Errors like broken pipes (program which takes the standard # output terminates before this program terminates) should not # generate a traceback. if e.errno == EPIPE: exit(0) raise def cat(ppd): # Ignore driver's name, take only PPD's ppd = ppd.split(":")[-1] # Remove also the index ppd = "0/" + ppd[ppd.find("/")+1:] # Object for streaming decompression decompressor = lzma.LZMADecompressor() # size for one decompression i.e. ~20MB size = 20000000 ppds = load() ppds['ARCHIVE'] = base64.b64decode(ppds['ARCHIVE'].encode('ASCII')) ppdtext=bytearray() if ppd in ppds: start = ppds[ppd][0] length = ppds[ppd][1] text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) for i in range(int(start/size)): text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) text.seek(start%size) if((size-(start%size)) < length): ppdtext.extend(text.read()) length = length - (size-(start%size)) text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) while(size < length): ppdtext.extend(text.read()) length = length - size text = BytesIO(decompressor.decompress(ppds['ARCHIVE'],size)) ppdtext.extend(text.read(length)) else: ppdtext.extend(text.read(length)) return ppdtext def main(): usage = "usage: %prog list\n" \ " %prog cat URI" version = "%prog 1.1.1\n" \ "Copyright (c) 2013 Vitor Baptista.\n" \ "This is free software; see the source for copying conditions.\n" \ "There is NO warranty; not even for MERCHANTABILITY or\n" \ "FITNESS FOR A PARTICULAR PURPOSE." parser = OptionParser(usage=usage, version=version) (options, args) = parser.parse_args() if len(args) == 0 or len(args) > 2: parser.error("incorrect number of arguments") if args[0].lower() == 'list': ls() elif args[0].lower() == 'cat': if not len(args) == 2: parser.error("incorrect number of arguments") ppd = cat(args[1]) if not ppd: parser.error("Printer '%s' does not have default driver!" % args[1]) try: # avoid any assumption of encoding or system locale; just print the # bytes of the PPD as they are if sys.version_info.major < 3: sys.stdout.write(ppd) else: sys.stdout.buffer.write(ppd) except IOError as e: # Errors like broken pipes (program which takes the standard output # terminates before this program terminates) should not generate a # traceback. if e.errno == EPIPE: exit(0) raise else: parser.error("argument " + args[0] + " invalid") # PPDs Archive ppds_compressed_b64 = b"@ppds_compressed_b64@" if __name__ == "__main__": try: main() except KeyboardInterrupt: # We don't want a KeyboardInterrupt throwing a # traceback into stdout. pass pyppd-1.1.1/pyppd/ppd.py0000664000175000017500000002177514755402564013663 0ustar tilltillimport re import logging LANGUAGES = {'afar': 'aa', 'abkhazian': 'ab', 'afrikaans': 'af', 'amharic': 'am', 'arabic': 'ar', 'assamese': 'as', 'aymara': 'ay', 'azerbaijani': 'az', 'bashkir': 'ba', 'byelorussian': 'be', 'bulgarian': 'bg', 'bihari': 'bh', 'bislama': 'bi', 'bengali': 'bn', 'bangla': 'bn', 'tibetan': 'bo', 'breton': 'br', 'catalan': 'ca', 'corsican': 'co', 'czech': 'cs', 'welsh': 'cy', 'danish': 'da', 'german': 'de', 'bhutani': 'dz', 'greek': 'el', 'english': 'en', 'esperanto': 'eo', 'spanish': 'es', 'estonian': 'et', 'basque': 'eu', 'persian': 'fa', 'finnish': 'fi', 'fiji': 'fj', 'faeroese': 'fo', 'french': 'fr', 'frisian': 'fy', 'irish': 'ga', 'scots gaelic': 'gd', 'galician': 'gl', 'guarani': 'gn', 'gujarati': 'gu', 'hausa': 'ha', 'hindi': 'hi', 'croatian': 'hr', 'hungarian': 'hu', 'armenian': 'hy', 'interlingua': 'ia', 'interlingue': 'ie', 'inupiak': 'ik', 'indonesian': 'in', 'icelandic': 'is', 'italian': 'it', 'hebrew': 'iw', 'japanese': 'ja', 'yiddish': 'ji', 'javanese': 'jw', 'georgian': 'ka', 'kazakh': 'kk', 'greenlandic': 'kl', 'cambodian': 'km', 'kannada': 'kn', 'korean': 'ko', 'kashmiri': 'ks', 'kurdish': 'ku', 'kirghiz': 'ky', 'latin': 'la', 'lingala': 'ln', 'laothian': 'lo', 'lithuanian': 'lt', 'latvian': 'lv','lettish': 'lv', 'malagasy': 'mg', 'maori': 'mi', 'macedonian': 'mk', 'malayalam': 'ml', 'mongolian': 'mn', 'moldavian': 'mo', 'marathi': 'mr', 'malay': 'ms', 'maltese': 'mt', 'burmese': 'my', 'nauru': 'na', 'nepali': 'ne', 'dutch': 'nl', 'norwegian': 'no', 'occitan': 'oc', '(afan) oromo': 'om', 'oriya': 'or', 'punjabi': 'pa', 'polish': 'pl', 'pashto': 'ps', 'pushto': 'ps', 'portuguese': 'pt', 'quechua': 'qu', 'rhaeto-romance': 'rm', 'kirundi': 'rn', 'romanian': 'ro', 'russian': 'ru', 'kinyarwanda': 'rw', 'sanskrit': 'sa', 'sindhi': 'sd', 'sangro': 'sg', 'serbo-croatian': 'sh', 'singhalese': 'si', 'slovak': 'sk', 'slovenian': 'sl', 'samoan': 'sm', 'shona': 'sn', 'somali': 'so', 'albanian': 'sq', 'serbian': 'sr', 'siswati': 'ss', 'sesotho': 'st', 'sundanese': 'su', 'swedish': 'sv', 'swahili': 'sw', 'tamil': 'ta', 'tegulu': 'te', 'tajik': 'tg', 'thai': 'th', 'tigrinya': 'ti', 'turkmen': 'tk', 'tagalog': 'tl', 'setswana': 'tn', 'tonga': 'to', 'turkish': 'tr', 'tsonga': 'ts', 'tatar': 'tt', 'twi': 'tw', 'ukrainian': 'uk', 'urdu': 'ur', 'uzbek': 'uz', 'vietnamese': 'vi', 'volapuk': 'vo', 'wolof': 'wo', 'xhosa': 'xh', 'yoruba': 'yo', 'chinese': 'zh', 'simplified chinese': 'zh_TW', 'traditional chinese': 'zh_CN', 'zulu': 'zu', 'portuguese_brazil': 'pt_BR'} class PPD(object): """Represents a PostScript Description file.""" def __init__(self, uri, language, manufacturer, nickname, deviceid): """Initializes a PPD object with the information passed.""" self.uri = uri self.language = language self.manufacturer = manufacturer self.nickname = nickname self.deviceid = deviceid def __str__(self): return '"%s" %s "%s" "%s" "%s"' % (self.uri, self.language, self.manufacturer, self.nickname, self.deviceid) def parse(ppd_file, filename): """Parses ppd_file and returns an array with the PPDs it found. One ppd_file might result in more than one PPD. The rules are: return an PPD for each "1284DeviceID" entry, and one for each "Product" line, if it creates an unique (Manufacturer, Product) DeviceID. """ def standardize(model_name): # Consider it the same model if the product name differs only by # upper/lower case and by the presence/absence of the manufacturer # name return model_name.lower().replace("Hewlett-Packard ".lower(), "").replace("%s " % manufacturer.lower(), "").strip() logging.debug('Parsing %s.' % filename) language_re = re.search(b'\*LanguageVersion:\s*(.+)', ppd_file) manufacturer_re = re.search(b'\*Manufacturer:\s*"(.+)"', ppd_file) nickname_re = re.search(b'\*NickName:\s*"(.+)"', ppd_file) modelname_re = re.search(b'\*ModelName:\s*"(.+)"', ppd_file) deviceids = re.findall(b'\*1284DeviceID:\s*"(.+)"', ppd_file) try: language = LANGUAGES[language_re.group(1).decode('UTF-8', errors='replace').strip().lower()] manufacturer = manufacturer_re.group(1).strip().decode('UTF-8', errors='replace') nickname = nickname_re.group(1).strip().decode('UTF-8', errors='replace') if modelname_re != None: modelname = modelname_re.group(1).strip().decode('UTF-8', errors='replace') else: modelname = None logging.debug('Language: "%s", Manufacturer: "%s", Nickname: "%s".' % (language, manufacturer, nickname)) ppds = [] models = [] drventry = None line = 0 num_device_ids = 0 num_products = 0 product_added = False if deviceids: for deviceid in deviceids: deviceid = deviceid.decode('UTF-8', errors='replace') logging.debug('1284DeviceID: "%s".' % deviceid) if (not deviceid.endswith(";")): deviceid += ";" uri = "%d/%s" % (line, filename) # Save a DRV field (from Foomatic) and use it for all entries # of this PPD newdrventry = re.findall(".*DRV:\s*(.*?)\s*;.*", deviceid, re.I) if (len(newdrventry) > 0): drventry = newdrventry[0] elif (drventry != None): deviceid += "DRV:%s;" % drventry newmodels = re.findall(".*(?:MODEL|MDL):\s*(.*?)\s*;.*", deviceid, re.I) if (newmodels): newmodels = list(map(standardize, newmodels)) if (len(newmodels) > 0): # Consider only IDs with a MODEL/MDL field ppds += [PPD(uri, language, manufacturer, nickname, deviceid.strip())] models += newmodels num_device_ids += 1 line += 1 for product in re.findall(b'\*Product:\s*"\(\s*(.+?)\s*\)"', ppd_file): num_products += 1 product = product.strip().decode('UTF-8', errors='replace') # Don't add a new entry if there's already one for the same # product/model product_standardized = standardize(product) logging.debug('Product: "%s"' % product) if product_standardized in models: logging.debug('Ignoring already found *Product: "%s".' % product) continue deviceid = "MFG:%s;MDL:%s;" % (manufacturer, product) if (drventry != None): deviceid += "DRV:%s;" % drventry uri = "%d/%s" % (line, filename) ppds += [PPD(uri, language, manufacturer, nickname, deviceid)] line += 1 product_added = True models += [product_standardized] # Note that we do not consider the ModelName if it contains # "BR-Script" here, as in PPD files for Brother BR-Script printers # we want to have the Product entry, as this is most probably # what the printer reports as device ID (there is no explicit device # ID entry in the PPD file). if (num_products == 1 and product_added and (num_device_ids > 0 or (modelname != None and ("br-script" not in modelname.lower())))): # If there is only one Product line, it either contains the # model described by the PPD's ModelName or NickName or something # weird. So we will not add it. And if we have no device ID # from the PPD, we prefer the info from the the ModelName. ppds.pop() logging.debug('Single Product line, entry removed') if (num_device_ids == 0 and modelname != None): modelname_standardized = standardize(modelname) logging.debug('ModelName: "%s"' % modelname) deviceid = "MFG:%s;MDL:%s;" % (manufacturer, modelname) if drventry != None: deviceid += "DRV:%s;" % drventry ppds += [PPD(uri, language, manufacturer, nickname, deviceid)] if len(ppds) == 0: logging.info('WARNING: No index entry generated for %s' % filename) return ppds except: raise Exception("Error parsing PPD file '%s'" % filename) pyppd-1.1.1/pyppd/__init__.py0000664000175000017500000000000014755402564014611 0ustar tilltillpyppd-1.1.1/pyppd/archiver.py0000664000175000017500000000752514755402564014700 0ustar tilltillimport base64 import sys import os import fnmatch import gzip import logging from random import randint import pyppd.compressor import pyppd.ppd import json def archive(ppds_directory): """Returns a string with the decompressor, its dependencies and the archive. It reads the template at pyppd/pyppd-ppdfile.in, inserts the dependencies and the archive encoded in base64, and returns as a string. """ logging.info('Compressing folder "%s".' % ppds_directory) ppds_compressed = compress(ppds_directory) if not ppds_compressed: return None ppds_compressed_b64 = base64.b64encode(ppds_compressed) logging.info('Populating template.') template = read_file_in_syspath("pyppd/pyppd-ppdfile.in") compressor_py = read_file_in_syspath("pyppd/compressor.py") template = template.replace(b"@compressor@", compressor_py) template = template.replace(b"@ppds_compressed_b64@", ppds_compressed_b64) return template def compress(directory): """Compresses and indexes *.ppd and *.ppd.gz in directory returning a string. The directory is walked recursively, concatenating all ppds found in a string. For each, it tests if its filename ends in *.gz. If so, opens with gzip. If not, opens directly. Then, it parses and saves its name, description (in the format CUPS needs (which can be more than one)) and it's position in the ppds string (start position and length) into a dictionary, used as an index. Then, it compresses the string, adds into the dictionary as key ARCHIVE and returns a compressed pickle dump of it. """ ppds = bytearray() ppds_index = {} abs_directory = os.path.abspath(directory) for ppd_path in sorted(find_files(directory, ("*.ppd", "*.ppd.gz"))): # Remove 'directory/' from the filename ppd_filename = ppd_path[len(abs_directory)+1:] if ppd_path.lower().endswith(".gz"): ppd_file = gzip.open(ppd_path).read() # We don't want the .gz extension in our filename ppd_filename = ppd_filename[:-3] else: ppd_file = open(ppd_path, 'rb').read() start = len(ppds) length = len(ppd_file) logging.debug('Found %s (%d bytes).' % (ppd_path, length)) ppd_parsed = pyppd.ppd.parse(ppd_file, ppd_filename) ppd_descriptions = [p.__str__() for p in ppd_parsed] ppds_index[ppd_parsed[0].uri] = (start, length, ppd_descriptions) logging.debug('Adding %d entry(ies): %s.' % (len(ppd_descriptions), ppd_descriptions)) ppds += ppd_file if not ppds: logging.error('No PPDs found in folder "%s".' % directory) return None logging.info('Compressing archive, encode to base64 string.') ppds_index['ARCHIVE'] = base64.b64encode(pyppd.compressor.compress(ppds)).decode('ASCII') logging.info('Generating and compressing json dump.') ppds_json = pyppd.compressor.compress(json.dumps(ppds_index, ensure_ascii=True, sort_keys=True).encode('ASCII')) return ppds_json def read_file_in_syspath(filename): """Reads the file in filename in each sys.path. If we couldn't find, throws the last IOError caught. """ last_exception = None for path in sys.path: try: return open(path + "/" + filename, 'rb').read() except IOError as ex: last_exception = ex continue raise last_exception def find_files(directory, patterns): """Yields each file that matches any of patterns in directory.""" logging.debug('Searching for "%s" files in folder "%s".' % (", ".join(patterns), directory)) abs_directory = os.path.abspath(directory) for root, dirnames, filenames in os.walk(abs_directory): for pattern in patterns: for filename in fnmatch.filter(filenames, pattern): yield os.path.join(root, filename) pyppd-1.1.1/pyppd/compressor.py0000664000175000017500000000123314755402564015257 0ustar tilltillfrom subprocess import Popen, PIPE def compress(value): """Compresses a byte array with the xz binary""" process = Popen(["xz", "--compress", "--force"], stdin=PIPE, stdout=PIPE) return process.communicate(value)[0] def decompress(value): """Decompresses a byte array with the xz binary""" process = Popen(["xz", "--decompress", "--stdout", "--force"], stdin=PIPE, stdout=PIPE) return process.communicate(value)[0] def compress_file(path): """Compress the file at 'path' with the xz binary""" process = Popen(["xz", "--compress", "--force", "--stdout", path], stdout=PIPE) return process.communicate()[0] pyppd-1.1.1/pyppd/runner.py0000664000175000017500000000507214755402564014401 0ustar tilltillimport os import stat import errno import logging import logging.handlers from optparse import OptionParser import pyppd.archiver def parse_args(): usage = "usage: %prog [options] ppds_directory" version = "%prog 1.1.1\n" \ "Copyright (c) 2013 Vitor Baptista.\n" \ "This is free software; see the source for copying conditions.\n" \ "There is NO warranty; not even for MERCHANTABILITY or\n" \ "FITNESS FOR A PARTICULAR PURPOSE." parser = OptionParser(usage=usage, version=version) parser.add_option("-v", "--verbose", action="count", dest="verbosity", help="run verbosely (can be supplied multiple times to " \ "increase verbosity)") parser.add_option("-d", "--debug", action="store_const", const=2, dest="verbose", help="print debug messages") parser.add_option("-o", "--output", default="pyppd-ppdfile", metavar="FILE", help="write archive to FILE [default %default]") (options, args) = parser.parse_args() if len(args) != 1: parser.error("incorrect number of arguments") if not os.path.isdir(args[0]): parser.error("'%s' isn't a directory" % args[0]) return (options, args) def configure_logging(verbosity): """Configures logging verbosity To stdout, we only WARNING of worse messages in a simpler format. To the file, we save every log message with its time, level, module and method. We also rotate the log_file, removing old entries when it reaches 2 MB. """ if verbosity == 1: level = logging.INFO elif verbosity == 2: level = logging.DEBUG else: level = logging.WARNING formatter = '[%(levelname)s] %(module)s.%(funcName)s(): %(message)s' logging.basicConfig(level=level, format=formatter) def run(): (options, args) = parse_args() configure_logging(options.verbosity) ppds_directory = args[0] logging.info('Archiving folder "%s".' % ppds_directory) archive = pyppd.archiver.archive(ppds_directory) if not archive: exit(errno.ENOENT) logging.info('Writing archive to "%s".' % options.output) output = open(options.output, "wb+") output.write(archive) output.close() logging.info('Setting "%s" executable flag.' % options.output) execute_mode = stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH mode = os.stat(options.output).st_mode | execute_mode os.chmod(options.output, mode)