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: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils/stringutils.py
""" Functions for manipulating or otherwise processing strings """ import base64 import difflib import errno import fnmatch import logging import os import re import shlex import time import unicodedata from salt.utils.decorators.jinja import jinja_filter log = logging.getLogger(__name__) @jinja_filter("to_bytes") def to_bytes(s, encoding=None, errors="strict"): """ Given bytes, bytearray, str, or unicode (python 2), return bytes (str for python 2) """ if encoding is None: # Try utf-8 first, and fall back to detected encoding encoding = ("utf-8", __salt_system_encoding__) if not isinstance(encoding, (tuple, list)): encoding = (encoding,) if not encoding: raise ValueError("encoding cannot be empty") exc = None if isinstance(s, bytes): return s if isinstance(s, bytearray): return bytes(s) if isinstance(s, str): for enc in encoding: try: return s.encode(enc, errors) except UnicodeEncodeError as err: exc = err continue # The only way we get this far is if a UnicodeEncodeError was # raised, otherwise we would have already returned (or raised some # other exception). raise exc # pylint: disable=raising-bad-type raise TypeError(f"expected str, bytes, or bytearray not {type(s)}") def to_str(s, encoding=None, errors="strict", normalize=False): """ Given str, bytes, bytearray, or unicode (py2), return str """ def _normalize(s): try: return unicodedata.normalize("NFC", s) if normalize else s except TypeError: return s if encoding is None: # Try utf-8 first, and fall back to detected encoding encoding = ("utf-8", __salt_system_encoding__) if not isinstance(encoding, (tuple, list)): encoding = (encoding,) if not encoding: raise ValueError("encoding cannot be empty") if isinstance(s, str): return _normalize(s) exc = None if isinstance(s, (bytes, bytearray)): for enc in encoding: try: return _normalize(s.decode(enc, errors)) except UnicodeDecodeError as err: exc = err continue # The only way we get this far is if a UnicodeDecodeError was # raised, otherwise we would have already returned (or raised some # other exception). raise exc # pylint: disable=raising-bad-type raise TypeError(f"expected str, bytes, or bytearray not {type(s)}") def to_unicode(s, encoding=None, errors="strict", normalize=False): """ Given str or unicode, return unicode (str for python 3) """ def _normalize(s): return unicodedata.normalize("NFC", s) if normalize else s if encoding is None: # Try utf-8 first, and fall back to detected encoding encoding = ("utf-8", __salt_system_encoding__) if not isinstance(encoding, (tuple, list)): encoding = (encoding,) if not encoding: raise ValueError("encoding cannot be empty") if isinstance(s, str): return _normalize(s) elif isinstance(s, (bytes, bytearray)): return _normalize(to_str(s, encoding, errors)) raise TypeError(f"expected str, bytes, or bytearray not {type(s)}") @jinja_filter("str_to_num") @jinja_filter("to_num") def to_num(text): """ Convert a string to a number. Returns an integer if the string represents an integer, a floating point number if the string is a real number, or the string unchanged otherwise. """ try: return int(text) except ValueError: try: return float(text) except ValueError: return text def to_none(text): """ Convert a string to None if the string is empty or contains only spaces. """ if str(text).strip(): return text return None def is_quoted(value): """ Return a single or double quote, if a string is wrapped in extra quotes. Otherwise return an empty string. """ ret = "" if ( isinstance(value, str) and value[0] == value[-1] and value.startswith(("'", '"')) ): ret = value[0] return ret def dequote(value): """ Remove extra quotes around a string. """ if is_quoted(value): return value[1:-1] return value @jinja_filter("is_hex") def is_hex(value): """ Returns True if value is a hexadecimal string, otherwise returns False """ try: int(value, 16) return True except (TypeError, ValueError): return False def is_binary(data): """ Detects if the passed string of data is binary or text """ if not data or not isinstance(data, ((str,), bytes)): return False if isinstance(data, bytes): if b"\0" in data: return True elif "\0" in data: return True text_characters = "".join([chr(x) for x in range(32, 127)] + list("\n\r\t\b")) # Get the non-text characters (map each character to itself then use the # 'remove' option to get rid of the text characters.) if isinstance(data, bytes): import salt.utils.data nontext = data.translate(None, salt.utils.data.encode(text_characters)) else: trans = "".maketrans("", "", text_characters) nontext = data.translate(trans) # If more than 30% non-text characters, then # this is considered binary data if float(len(nontext)) / len(data) > 0.30: return True return False @jinja_filter("random_str") def random(size=32): key = os.urandom(size) return to_unicode(base64.b64encode(key).replace(b"\n", b"")[:size]) @jinja_filter("contains_whitespace") def contains_whitespace(text): """ Returns True if there are any whitespace characters in the string """ return any(x.isspace() for x in text) @jinja_filter("human_to_bytes") def human_to_bytes(size, default_unit="B", handle_metric=False): """ Given a human-readable byte string (e.g. 2G, 30MB, 64KiB), return the number of bytes. Will return 0 if the argument has unexpected form. .. versionadded:: 2018.3.0 .. versionchanged:: 3005 """ m = re.match(r"(?P<value>[0-9.]*)\s*(?P<unit>.*)$", str(size).strip()) value = m.group("value") # default unit unit = m.group("unit").lower() or default_unit.lower() try: value = int(value) except ValueError: try: value = float(value) except ValueError: return 0 # flag for base ten dec = False if re.match(r"[kmgtpezy]b$", unit): dec = True if handle_metric else False elif not re.match(r"(b|[kmgtpezy](ib)?)$", unit): return 0 p = "bkmgtpezy".index(unit[0]) value *= 10 ** (p * 3) if dec else 2 ** (p * 10) return int(value) def build_whitespace_split_regex(text): ''' Create a regular expression at runtime which should match ignoring the addition or deletion of white space or line breaks, unless between commas Example: .. code-block:: python >>> import re >>> import salt.utils.stringutils >>> regex = salt.utils.stringutils.build_whitespace_split_regex( ... """if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then""" ... ) >>> regex '(?:[\\s]+)?if(?:[\\s]+)?\\[(?:[\\s]+)?\\-z(?:[\\s]+)?\\"\\$debian' '\\_chroot\\"(?:[\\s]+)?\\](?:[\\s]+)?\\&\\&(?:[\\s]+)?\\[(?:[\\s]+)?' '\\-r(?:[\\s]+)?\\/etc\\/debian\\_chroot(?:[\\s]+)?\\]\\;(?:[\\s]+)?' 'then(?:[\\s]+)?' >>> re.search( ... regex, ... """if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then""" ... ) <_sre.SRE_Match object at 0xb70639c0> >>> ''' def __build_parts(text): lexer = shlex.shlex(text) lexer.whitespace_split = True lexer.commenters = "" if r"'\"" in text: lexer.quotes = "" elif "'" in text: lexer.quotes = '"' elif '"' in text: lexer.quotes = "'" return list(lexer) regex = r"" for line in text.splitlines(): parts = [re.escape(s) for s in __build_parts(line)] regex += r"(?:[\s]+)?{}(?:[\s]+)?".format(r"(?:[\s]+)?".join(parts)) return rf"(?m)^{regex}$" def expr_match(line, expr): """ Checks whether or not the passed value matches the specified expression. Tries to match expr first as a glob using fnmatch.fnmatch(), and then tries to match expr as a regular expression. Originally designed to match minion IDs for whitelists/blacklists. Note that this also does exact matches, as fnmatch.fnmatch() will return ``True`` when no glob characters are used and the string is an exact match: .. code-block:: python >>> fnmatch.fnmatch('foo', 'foo') True """ try: if fnmatch.fnmatch(line, expr): return True try: if re.match(rf"\A{expr}\Z", line): return True except re.error: pass except TypeError: log.exception("Value %r or expression %r is not a string", line, expr) return False @jinja_filter("check_whitelist_blacklist") def check_whitelist_blacklist(value, whitelist=None, blacklist=None): """ Check a whitelist and/or blacklist to see if the value matches it. value The item to check the whitelist and/or blacklist against. whitelist The list of items that are white-listed. If ``value`` is found in the whitelist, then the function returns ``True``. Otherwise, it returns ``False``. blacklist The list of items that are black-listed. If ``value`` is found in the blacklist, then the function returns ``False``. Otherwise, it returns ``True``. If both a whitelist and a blacklist are provided, value membership in the blacklist will be examined first. If the value is not found in the blacklist, then the whitelist is checked. If the value isn't found in the whitelist, the function returns ``False``. """ # Normalize the input so that we have a list if blacklist: if isinstance(blacklist, str): blacklist = [blacklist] if not hasattr(blacklist, "__iter__"): raise TypeError( "Expecting iterable blacklist, but got {} ({})".format( type(blacklist).__name__, blacklist ) ) else: blacklist = [] if whitelist: if isinstance(whitelist, str): whitelist = [whitelist] if not hasattr(whitelist, "__iter__"): raise TypeError( "Expecting iterable whitelist, but got {} ({})".format( type(whitelist).__name__, whitelist ) ) else: whitelist = [] _blacklist_match = any(expr_match(value, expr) for expr in blacklist) _whitelist_match = any(expr_match(value, expr) for expr in whitelist) if blacklist and not whitelist: # Blacklist but no whitelist return not _blacklist_match elif whitelist and not blacklist: # Whitelist but no blacklist return _whitelist_match elif blacklist and whitelist: # Both whitelist and blacklist return not _blacklist_match and _whitelist_match else: # No blacklist or whitelist passed return True def check_include_exclude(path_str, include_pat=None, exclude_pat=None): """ Check for glob or regexp patterns for include_pat and exclude_pat in the 'path_str' string and return True/False conditions as follows. - Default: return 'True' if no include_pat or exclude_pat patterns are supplied - If only include_pat or exclude_pat is supplied: return 'True' if string passes the include_pat test or fails exclude_pat test respectively - If both include_pat and exclude_pat are supplied: return 'True' if include_pat matches AND exclude_pat does not match """ def _pat_check(path_str, check_pat): if re.match("E@", check_pat): return True if re.search(check_pat[2:], path_str) else False else: return True if fnmatch.fnmatch(path_str, check_pat) else False ret = True # -- default true # Before pattern match, check if it is regexp (E@'') or glob(default) if include_pat: if isinstance(include_pat, list): for include_line in include_pat: retchk_include = _pat_check(path_str, include_line) if retchk_include: break else: retchk_include = _pat_check(path_str, include_pat) if exclude_pat: if isinstance(exclude_pat, list): for exclude_line in exclude_pat: retchk_exclude = not _pat_check(path_str, exclude_line) if not retchk_exclude: break else: retchk_exclude = not _pat_check(path_str, exclude_pat) # Now apply include/exclude conditions if include_pat and not exclude_pat: ret = retchk_include elif exclude_pat and not include_pat: ret = retchk_exclude elif include_pat and exclude_pat: ret = retchk_include and retchk_exclude else: ret = True return ret def print_cli(msg, retries=10, step=0.01): """ Wrapper around print() that suppresses tracebacks on broken pipes (i.e. when salt output is piped to less and less is stopped prematurely). """ while retries: try: try: print(msg) except UnicodeEncodeError: print(msg.encode("utf-8")) except OSError as exc: err = f"{exc}" if exc.errno != errno.EPIPE: if ( "temporarily unavailable" in err or exc.errno in (errno.EAGAIN,) ) and retries: time.sleep(step) retries -= 1 continue else: raise break def get_context(template, line, num_lines=5, marker=None): """ Returns debugging context around a line in a given string Returns:: string """ template_lines = template.splitlines() num_template_lines = len(template_lines) # In test mode, a single line template would return a crazy line number like, # 357. Do this sanity check and if the given line is obviously wrong, just # return the entire template if line > num_template_lines: return template context_start = max(0, line - num_lines - 1) # subt 1 for 0-based indexing context_end = min(num_template_lines, line + num_lines) error_line_in_context = line - context_start - 1 # subtr 1 for 0-based idx buf = [] if context_start > 0: buf.append("[...]") error_line_in_context += 1 buf.extend(template_lines[context_start:context_end]) if context_end < num_template_lines: buf.append("[...]") if marker: buf[error_line_in_context] += marker return "---\n{}\n---".format("\n".join(buf)) def get_diff(a, b, *args, **kwargs): """ Perform diff on two iterables containing lines from two files, and return the diff as as string. Lines are normalized to str types to avoid issues with unicode on PY2. """ encoding = ("utf-8", "latin-1", __salt_system_encoding__) # Late import to avoid circular import import salt.utils.data return "".join( difflib.unified_diff( salt.utils.data.decode_list(a, encoding=encoding), salt.utils.data.decode_list(b, encoding=encoding), *args, **kwargs, ) ) @jinja_filter("to_snake_case") def camel_to_snake_case(camel_input): """ Converts camelCase (or CamelCase) to snake_case. From https://codereview.stackexchange.com/questions/185966/functions-to-convert-camelcase-strings-to-snake-case :param str camel_input: The camelcase or CamelCase string to convert to snake_case :return str """ res = camel_input[0].lower() for i, letter in enumerate(camel_input[1:], 1): if letter.isupper(): if camel_input[i - 1].islower() or ( i != len(camel_input) - 1 and camel_input[i + 1].islower() ): res += "_" res += letter.lower() return res @jinja_filter("to_camelcase") def snake_to_camel_case(snake_input, uppercamel=False): """ Converts snake_case to camelCase (or CamelCase if uppercamel is ``True``). Inspired by https://codereview.stackexchange.com/questions/85311/transform-snake-case-to-camelcase :param str snake_input: The input snake_case string to convert to camelCase :param bool uppercamel: Whether or not to convert to CamelCase instead :return str """ words = snake_input.split("_") if uppercamel: words[0] = words[0].capitalize() return words[0] + "".join(word.capitalize() for word in words[1:])