PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/share/guile/2.0/texinfo
Viewing File: /usr/share/guile/2.0/texinfo/string-utils.scm
;;;; (texinfo string-utils) -- text filling and wrapping ;;;; ;;;; Copyright (C) 2009, 2013 Free Software Foundation, Inc. ;;;; Copyright (C) 2003 Richard Todd ;;;; ;;;; This library is free software; you can redistribute it and/or ;;;; modify it under the terms of the GNU Lesser General Public ;;;; License as published by the Free Software Foundation; either ;;;; version 3 of the License, or (at your option) any later version. ;;;; ;;;; This library is distributed in the hope that it will be useful, ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;;; Lesser General Public License for more details. ;;;; ;;;; You should have received a copy of the GNU Lesser General Public ;;;; License along with this library; if not, write to the Free Software ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ;;;; ;;; Commentary: ;; Module @samp{(texinfo string-utils)} provides various string-related ;; functions useful to Guile's texinfo support. ;;; Code: (define-module (texinfo string-utils) #:use-module (srfi srfi-13) #:use-module (srfi srfi-14) #:export (escape-special-chars transform-string expand-tabs center-string left-justify-string right-justify-string collapse-repeated-chars make-text-wrapper fill-string string->wrapped-lines)) (define* (transform-string str match? replace #:optional (start #f) (end #f)) "Uses @var{match?} against each character in @var{str}, and performs a replacement on each character for which matches are found. @var{match?} may either be a function, a character, a string, or @code{#t}. If @var{match?} is a function, then it takes a single character as input, and should return @samp{#t} for matches. @var{match?} is a character, it is compared to each string character using @code{char=?}. If @var{match?} is a string, then any character in that string will be considered a match. @code{#t} will cause every character to be a match. If @var{replace} is a function, it is called with the matched character as an argument, and the returned value is sent to the output string via @samp{display}. If @var{replace} is anything else, it is sent through the output string via @samp{display}. Note that te replacement for the matched characters does not need to be a single character. That is what differentiates this function from @samp{string-map}, and what makes it useful for applications such as converting @samp{#\\&} to @samp{\"&amp;\"} in web page text. Some other functions in this module are just wrappers around common uses of @samp{transform-string}. Transformations not possible with this function should probably be done with regular expressions. If @var{start} and @var{end} are given, they control which portion of the string undergoes transformation. The entire input string is still output, though. So, if @var{start} is @samp{5}, then the first five characters of @var{str} will still appear in the returned string. @lisp ; these two are equivalent... (transform-string str #\\space #\\-) ; change all spaces to -'s (transform-string str (lambda (c) (char=? #\\space c)) #\\-) @end lisp" ;; I had implemented this with string-fold, but it was ;; slower... (let* ((os (open-output-string)) (matcher (cond ((char? match?) (lambda (c) (char=? match? c))) ((procedure? match?) match?) ((string? match?) (lambda (c) (string-index match? c))) ((boolean? match?) (lambda (c) match?)) (else (throw 'bad-type "expected #t, char, string, or procedure")))) (replacer (if (procedure? replace) (lambda (c) (display (replace c) os)) (lambda (c) (display replace os))))) ;; put the first part in, un-transformed if they asked for it... (if (and start (<= start (string-length str))) (display (substring str 0 start) os)) ;; process the portion they want processed.... (string-for-each (lambda (c) (if (matcher c) ;; we have a match! replace the char as directed... (replacer c) ;; not a match, just insert the character itself... (write-char c os))) str (or start 0) (or end (string-length str))) ;; if there was any at the end, tack it on... (if (and end (< end (string-length str))) (display (substring str end) os)) (get-output-string os))) (define* (expand-tabs str #:optional (tab-size 8)) "Returns a copy of @var{str} with all tabs expanded to spaces. @var{tab-size} defaults to 8. Assuming tab size of 8, this is equivalent to: @lisp (transform-string str #\\tab \" \") @end lisp" (transform-string str #\tab (make-string tab-size #\space))) (define (escape-special-chars str special-chars escape-char) "Returns a copy of @var{str} with all given special characters preceded by the given @var{escape-char}. @var{special-chars} can either be a single character, or a string consisting of all the special characters. @lisp ;; make a string regexp-safe... (escape-special-chars \"***(Example String)***\" \"[]()/*.\" #\\\\) => \"\\\\*\\\\*\\\\*\\\\(Example String\\\\)\\\\*\\\\*\\\\*\" ;; also can escape a singe char... (escape-special-chars \"richardt@@vzavenue.net\" #\\@@ #\\@@) => \"richardt@@@@vzavenue.net\" @end lisp" (transform-string str (if (char? special-chars) ;; if they gave us a char, use char=? (lambda (c) (char=? c special-chars)) ;; if they gave us a string, see if our character is in it (lambda (c) (string-index special-chars c))) ;; replace matches with the character preceded by the escape character (lambda (c) (string escape-char c)))) (define* (center-string str #:optional (width 80) (chr #\space) (rchr #f)) "Returns a copy of @var{str} centered in a field of @var{width} characters. Any needed padding is done by character @var{chr}, which defaults to @samp{#\\space}. If @var{rchr} is provided, then the padding to the right will use it instead. See the examples below. left and @var{rchr} on the right. The default @var{width} is 80. The default @var{chr} and @var{rchr} is @samp{#\\space}. The string is never truncated. @lisp (center-string \"Richard Todd\" 24) => \" Richard Todd \" (center-string \" Richard Todd \" 24 #\\=) => \"===== Richard Todd =====\" (center-string \" Richard Todd \" 24 #\\< #\\>) => \"<<<<< Richard Todd >>>>>\" @end lisp" (let* ((len (string-length str)) (lpad (make-string (max (quotient (- width len) 2) 0) chr)) ;; right-char == char unless it has been provided by the user (right-chr (or rchr chr)) (rpad (if (char=? right-chr chr) lpad (make-string (max (quotient (- width len) 2) 0) right-chr)))) (if (>= len width) str (string-append lpad str rpad (if (odd? (- width len)) (string right-chr) ""))))) (define* (left-justify-string str #:optional (width 80) (chr #\space)) "@code{left-justify-string str [width chr]}. Returns a copy of @var{str} padded with @var{chr} such that it is left justified in a field of @var{width} characters. The default @var{width} is 80. Unlike @samp{string-pad} from srfi-13, the string is never truncated." (let* ((len (string-length str)) (pad (make-string (max (- width len) 0) chr))) (if (>= len width) str (string-append str pad)))) (define* (right-justify-string str #:optional (width 80) (chr #\space)) "Returns a copy of @var{str} padded with @var{chr} such that it is right justified in a field of @var{width} characters. The default @var{width} is 80. The default @var{chr} is @samp{#\\space}. Unlike @samp{string-pad} from srfi-13, the string is never truncated." (let* ((len (string-length str)) (pad (make-string (max (- width len) 0) chr))) (if (>= len width) str (string-append pad str)))) (define* (collapse-repeated-chars str #:optional (chr #\space) (num 1)) "Returns a copy of @var{str} with all repeated instances of @var{chr} collapsed down to at most @var{num} instances. The default value for @var{chr} is @samp{#\\space}, and the default value for @var{num} is 1. @lisp (collapse-repeated-chars \"H e l l o\") => \"H e l l o\" (collapse-repeated-chars \"H--e--l--l--o\" #\\-) => \"H-e-l-l-o\" (collapse-repeated-chars \"H-e--l---l----o\" #\\- 2) => \"H-e--l--l--o\" @end lisp" ;; define repeat-locator as a stateful match? function which remembers ;; the last character it had seen. (let ((repeat-locator ;; initialize prev-chr to something other than what we're seeking... (let ((prev-chr (if (char=? chr #\space) #\A #\space)) (match-count 0)) (lambda (c) (if (and (char=? c prev-chr) (char=? prev-chr chr)) ;; found enough duplicates if the match-count is high enough (begin (set! match-count (+ 1 match-count)) (>= match-count num)) ;; did not find a duplicate (begin (set! match-count 0) (set! prev-chr c) #f)))))) ;; transform the string with our stateful matcher... ;; deleting matches... (transform-string str repeat-locator ""))) ;; split a text string into segments that have the form... ;; <ws non-ws> <ws non-ws> etc.. (define (split-by-single-words str) (let ((non-wschars (char-set-complement char-set:whitespace))) (let loop ((ans '()) (index 0)) (let ((next-non-ws (string-index str non-wschars index))) (if next-non-ws ;; found non-ws...look for ws following... (let ((next-ws (string-index str char-set:whitespace next-non-ws))) (if next-ws ;; found the ws following... (loop (cons (substring str index next-ws) ans) next-ws) ;; did not find ws...must be the end... (reverse (cons (substring str index) ans)))) ;; did not find non-ws... only ws at end of the string... (reverse ans)))))) (define (end-of-sentence? str) "Return #t when STR likely denotes the end of sentence." (let ((len (string-length str))) (and (> len 1) (eqv? #\. (string-ref str (- len 1))) (not (eqv? #\. (string-ref str (- len 2))))))) (define* (make-text-wrapper #:key (line-width 80) (expand-tabs? #t) (tab-width 8) (collapse-whitespace? #t) (subsequent-indent "") (initial-indent "") (break-long-words? #t)) "Returns a procedure that will split a string into lines according to the given parameters. @table @code @item #:line-width This is the target length used when deciding where to wrap lines. Default is 80. @item #:expand-tabs? Boolean describing whether tabs in the input should be expanded. Default is #t. @item #:tab-width If tabs are expanded, this will be the number of spaces to which they expand. Default is 8. @item #:collapse-whitespace? Boolean describing whether the whitespace inside the existing text should be removed or not. Default is #t. If text is already well-formatted, and is just being wrapped to fit in a different width, then set this to @samp{#f}. This way, many common text conventions (such as two spaces between sentences) can be preserved if in the original text. If the input text spacing cannot be trusted, then leave this setting at the default, and all repeated whitespace will be collapsed down to a single space. @item #:initial-indent Defines a string that will be put in front of the first line of wrapped text. Default is the empty string, ``''. @item #:subsequent-indent Defines a string that will be put in front of all lines of wrapped text, except the first one. Default is the empty string, ``''. @item #:break-long-words? If a single word is too big to fit on a line, this setting tells the wrapper what to do. Defaults to #t, which will break up long words. When set to #f, the line will be allowed, even though it is longer than the defined @code{#:line-width}. @end table The return value is a procedure of one argument, the input string, which returns a list of strings, where each element of the list is one line." (lambda (str) ;; replace newlines with spaces (set! str (transform-string str (lambda (c) (char=? c #\nl)) #\space)) ;; expand tabs if they wanted us to... (if expand-tabs? (set! str (expand-tabs str tab-width))) ;; collapse whitespace if they wanted us to... (if collapse-whitespace? (set! str (collapse-repeated-chars str))) ;; drop any whitespace from the front... (set! str (string-trim str)) ;; now start breaking the text into lines... (let loop ((ans '()) (words (split-by-single-words str)) (line initial-indent) (count 0)) (if (null? words) ;; out of words? ...done! (reverse (if (> count 0) (cons line ans) ans)) ;; not out of words...keep going... (let ((length-left (- line-width (string-length line))) (next-word (if (= count 0) (string-trim (car words)) (car words)))) (cond ;; does the next entry fit? ((<= (string-length next-word) length-left) (loop ans (cdr words) (if (and collapse-whitespace? (end-of-sentence? line)) ;; Add an extra space after the period. (string-append line " " next-word) (string-append line next-word)) (+ count 1))) ;; ok, it didn't fit...is there already at least one word on the line? ((> count 0) ;; try to use it for the next line, then... (loop (cons line ans) words subsequent-indent 0)) ;; ok, it didn't fit...and it's the first word. ;; were we told to break up long words? (break-long-words? ;; break the like at the limit, since the user wants us to... (loop (cons (string-append line (substring next-word 0 length-left)) ans) (cons (substring next-word length-left) (cdr words)) subsequent-indent 0)) ;; well, then is it the first word and we *shouldn't* break long words, then... (else (loop (cons (string-append line next-word) ans) (cdr words) subsequent-indent 0)))))))) (define (string->wrapped-lines str . kwargs) "@code{string->wrapped-lines str keywds ...}. Wraps the text given in string @var{str} according to the parameters provided in @var{keywds}, or the default setting if they are not given. Returns a list of strings representing the formatted lines. Valid keyword arguments are discussed in @code{make-text-wrapper}." ((apply make-text-wrapper kwargs) str)) (define (fill-string str . kwargs) "Wraps the text given in string @var{str} according to the parameters provided in @var{kwargs}, or the default setting if they are not given. Returns a single string with the wrapped text. Valid keyword arguments are discussed in @code{make-text-wrapper}." (string-join (apply string->wrapped-lines str kwargs) "\n" 'infix))