PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/home/mykeywordtracker.com/www/keyword-tracker/node_modules/svgo/lib/svgo/
Upload File :
Current File : //home/mykeywordtracker.com/www/keyword-tracker/node_modules/svgo/lib/svgo/coa.js
/* jshint quotmark: false */
'use strict';

var FS = require('fs'),
    PATH = require('path'),
    chalk = require('chalk'),
    mkdirp = require('mkdirp'),
    promisify = require('util.promisify'),
    readdir = promisify(FS.readdir),
    readFile = promisify(FS.readFile),
    writeFile = promisify(FS.writeFile),
    SVGO = require('../svgo.js'),
    YAML = require('js-yaml'),
    PKG = require('../../package.json'),
    encodeSVGDatauri = require('./tools.js').encodeSVGDatauri,
    decodeSVGDatauri = require('./tools.js').decodeSVGDatauri,
    checkIsDir = require('./tools.js').checkIsDir,
    regSVGFile = /\.svg$/,
    noop = () => {},
    svgo;

/**
 * Command-Option-Argument.
 *
 * @see https://github.com/veged/coa
 */
module.exports = require('coa').Cmd()
    .helpful()
    .name(PKG.name)
    .title(PKG.description)
    .opt()
        .name('version').title('Version')
        .short('v').long('version')
        .only()
        .flag()
        .act(function() {
            // output the version to stdout instead of stderr if returned
            process.stdout.write(PKG.version + '\n');
            // coa will run `.toString` on the returned value and send it to stderr
            return '';
        })
        .end()
    .opt()
        .name('input').title('Input file, "-" for STDIN')
        .short('i').long('input')
        .arr()
        .val(function(val) {
            return val || this.reject("Option '--input' must have a value.");
        })
        .end()
    .opt()
        .name('string').title('Input SVG data string')
        .short('s').long('string')
        .end()
    .opt()
        .name('folder').title('Input folder, optimize and rewrite all *.svg files')
        .short('f').long('folder')
        .val(function(val) {
            return val || this.reject("Option '--folder' must have a value.");
        })
        .end()
    .opt()
        .name('output').title('Output file or folder (by default the same as the input), "-" for STDOUT')
        .short('o').long('output')
        .arr()
        .val(function(val) {
            return val || this.reject("Option '--output' must have a value.");
        })
        .end()
    .opt()
        .name('precision').title('Set number of digits in the fractional part, overrides plugins params')
        .short('p').long('precision')
        .val(function(val) {
            return !isNaN(val) ? val : this.reject("Option '--precision' must be an integer number");
        })
        .end()
    .opt()
        .name('config').title('Config file or JSON string to extend or replace default')
        .long('config')
        .val(function(val) {
            return val || this.reject("Option '--config' must have a value.");
        })
        .end()
    .opt()
        .name('disable').title('Disable plugin by name, "--disable={PLUGIN1,PLUGIN2}" for multiple plugins (*nix)')
        .long('disable')
        .arr()
        .val(function(val) {
            return val || this.reject("Option '--disable' must have a value.");
        })
        .end()
    .opt()
        .name('enable').title('Enable plugin by name, "--enable={PLUGIN3,PLUGIN4}" for multiple plugins (*nix)')
        .long('enable')
        .arr()
        .val(function(val) {
            return val || this.reject("Option '--enable' must have a value.");
        })
        .end()
    .opt()
        .name('datauri').title('Output as Data URI string (base64, URI encoded or unencoded)')
        .long('datauri')
        .val(function(val) {
            return val || this.reject("Option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'");
        })
        .end()
    .opt()
        .name('multipass').title('Pass over SVGs multiple times to ensure all optimizations are applied')
        .long('multipass')
        .flag()
        .end()
    .opt()
        .name('pretty').title('Make SVG pretty printed')
        .long('pretty')
        .flag()
        .end()
    .opt()
        .name('indent').title('Indent number when pretty printing SVGs')
        .long('indent')
        .val(function(val) {
            return !isNaN(val) ? val : this.reject("Option '--indent' must be an integer number");
        })
        .end()
    .opt()
        .name('recursive').title('Use with \'-f\'. Optimizes *.svg files in folders recursively.')
        .short('r').long('recursive')
        .flag()
        .end()
    .opt()
        .name('quiet').title('Only output error messages, not regular status messages')
        .short('q').long('quiet')
        .flag()
        .end()
    .opt()
        .name('show-plugins').title('Show available plugins and exit')
        .long('show-plugins')
        .flag()
        .end()
    .arg()
        .name('input').title('Alias to --input')
        .arr()
        .end()
    .act(function(opts, args) {
        var input = opts.input || args.input,
            output = opts.output,
            config = {};

        // --show-plugins
        if (opts['show-plugins']) {
            showAvailablePlugins();
            return;
        }

        // w/o anything
        if (
            (!input || input[0] === '-') &&
            !opts.string &&
            !opts.stdin &&
            !opts.folder &&
            process.stdin.isTTY === true
        ) return this.usage();

        if (typeof process == 'object' && process.versions && process.versions.node && PKG && PKG.engines.node) {
            var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
            if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {
                return printErrorAndExit(`Error: ${PKG.name} requires Node.js version ${nodeVersion} or higher.`);
            }
        }

        // --config
        if (opts.config) {
            // string
            if (opts.config.charAt(0) === '{') {
                try {
                    config = JSON.parse(opts.config);
                } catch (e) {
                    return printErrorAndExit(`Error: Couldn't parse config JSON.\n${String(e)}`);
                }
            // external file
            } else {
                var configPath = PATH.resolve(opts.config),
                    configData;
                try {
                    // require() adds some weird output on YML files
                    configData = FS.readFileSync(configPath, 'utf8');
                    config = JSON.parse(configData);
                } catch (err) {
                    if (err.code === 'ENOENT') {
                        return printErrorAndExit(`Error: couldn't find config file '${opts.config}'.`);
                    } else if (err.code === 'EISDIR') {
                        return printErrorAndExit(`Error: directory '${opts.config}' is not a config file.`);
                    }
                    config = YAML.safeLoad(configData);
                    config.__DIR = PATH.dirname(configPath); // will use it to resolve custom plugins defined via path

                    if (!config || Array.isArray(config)) {
                        return printErrorAndExit(`Error: invalid config file '${opts.config}'.`);
                    }
                }
            }
        }

        // --quiet
        if (opts.quiet) {
            config.quiet = opts.quiet;
        }

        // --recursive
        if (opts.recursive) {
            config.recursive = opts.recursive;
        }

        // --precision
        if (opts.precision) {
            var precision = Math.min(Math.max(0, parseInt(opts.precision)), 20);
            if (!isNaN(precision)) {
                config.floatPrecision = precision;
            }
        }

        // --disable
        if (opts.disable) {
            changePluginsState(opts.disable, false, config);
        }

        // --enable
        if (opts.enable) {
            changePluginsState(opts.enable, true, config);
        }

        // --multipass
        if (opts.multipass) {
            config.multipass = true;
        }

        // --pretty
        if (opts.pretty) {
            config.js2svg = config.js2svg || {};
            config.js2svg.pretty = true;
            var indent;
            if (opts.indent && !isNaN(indent = parseInt(opts.indent))) {
                config.js2svg.indent = indent;
            }
        }

        svgo = new SVGO(config);

        // --output
        if (output) {
            if (input && input[0] != '-') {
                if (output.length == 1 && checkIsDir(output[0])) {
                    var dir = output[0];
                    for (var i = 0; i < input.length; i++) {
                        output[i] = checkIsDir(input[i]) ? input[i] : PATH.resolve(dir, PATH.basename(input[i]));
                    }
                } else if (output.length < input.length) {
                    output = output.concat(input.slice(output.length));
                }
            }
        } else if (input) {
            output = input;
        } else if (opts.string) {
            output = '-';
        }

        if (opts.datauri) {
            config.datauri = opts.datauri;
        }

        // --folder
        if (opts.folder) {
            var ouputFolder = output && output[0] || opts.folder;
            return optimizeFolder(config, opts.folder, ouputFolder).then(noop, printErrorAndExit);
        }

        // --input
        if (input) {
            // STDIN
            if (input[0] === '-') {
                return new Promise((resolve, reject) => {
                    var data = '',
                        file = output[0];

                    process.stdin
                        .on('data', chunk => data += chunk)
                        .once('end', () => processSVGData(config, {input: 'string'}, data, file).then(resolve, reject));
                });
            // file
            } else {
                return Promise.all(input.map((file, n) => optimizeFile(config, file, output[n])))
                    .then(noop, printErrorAndExit);
            }

        // --string
        } else if (opts.string) {
            var data = decodeSVGDatauri(opts.string);

            return processSVGData(config, {input: 'string'}, data, output[0]);
        }
    });

/**
 * Change plugins state by names array.
 *
 * @param {Array} names plugins names
 * @param {Boolean} state active state
 * @param {Object} config original config
 * @return {Object} changed config
 */
function changePluginsState(names, state, config) {
    names.forEach(flattenPluginsCbk);

    // extend config
    if (config.plugins) {
        for (var name of names) {
            var matched = false,
                key;

            for (var plugin of config.plugins) {
                // get plugin name
                if (typeof plugin === 'object') {
                    key = Object.keys(plugin)[0];
                } else {
                    key = plugin;
                }

                // if there is such a plugin name
                if (key === name) {
                    // don't replace plugin's params with true
                    if (typeof plugin[key] !== 'object' || !state) {
                        plugin[key] = state;
                    }
                    // mark it as matched
                    matched = true;
                }
            }

            // if not matched and current config is not full
            if (!matched && !config.full) {
                // push new plugin Object
                config.plugins.push({ [name]: state });
                matched = true;
            }
        }
    // just push
    } else {
        config.plugins = names.map(name => ({ [name]: state }));
    }
    return config;
}

/**
 * Flatten an array of plugins by invoking this callback on each element
 * whose value may be a comma separated list of plugins.
 *
 * @param {String} name Plugin name
 * @param {Number} index Plugin index
 * @param {Array} names Plugins being traversed
 */
function flattenPluginsCbk(name, index, names)
{
    var split = name.split(',');

    if(split.length > 1) {
        names[index] = split.shift();
        names.push.apply(names, split);
    }

}

/**
 * Optimize SVG files in a directory.
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {string} output output directory
 * @return {Promise}
 */
function optimizeFolder(config, dir, output) {
    if (!config.quiet) {
        console.log(`Processing directory '${dir}':\n`);
    }
    return readdir(dir).then(files => processDirectory(config, dir, files, output));
}

/**
 * Process given files, take only SVG.
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {Array} files list of file names in the directory
 * @param {string} output output directory
 * @return {Promise}
 */
function processDirectory(config, dir, files, output) {
    // take only *.svg files, recursively if necessary
    var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);

    return svgFilesDescriptions.length ?
        Promise.all(svgFilesDescriptions.map(fileDescription => optimizeFile(config, fileDescription.inputPath, fileDescription.outputPath))) :
        Promise.reject(new Error(`No SVG files have been found in '${dir}' directory.`));
}

/**
 * Get svg files descriptions
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {Array} files list of file names in the directory
 * @param {string} output output directory
 * @return {Array}
 */
function getFilesDescriptions(config, dir, files, output) {
    const filesInThisFolder = files
        .filter(name => regSVGFile.test(name))
        .map(name => ({
            inputPath: PATH.resolve(dir, name),
            outputPath: PATH.resolve(output, name),
        }));

    return config.recursive ?
        [].concat(
            filesInThisFolder,
            files
                .filter(name => checkIsDir(PATH.resolve(dir, name)))
                .map(subFolderName => {
                    const subFolderPath = PATH.resolve(dir, subFolderName);
                    const subFolderFiles = FS.readdirSync(subFolderPath);
                    const subFolderOutput = PATH.resolve(output, subFolderName);
                    return getFilesDescriptions(config, subFolderPath, subFolderFiles, subFolderOutput);
                })
                .reduce((a, b) => [].concat(a, b), [])
        ) :
        filesInThisFolder;
}

/**
 * Read SVG file and pass to processing.
 * @param {Object} config options
 * @param {string} file
 * @param {string} output
 * @return {Promise}
 */
function optimizeFile(config, file, output) {
    return readFile(file, 'utf8').then(
        data => processSVGData(config, {input: 'file', path: file}, data, output, file),
        error => checkOptimizeFileError(config, file, output, error)
    );
}

/**
 * Optimize SVG data.
 * @param {Object} config options
 * @param {string} data SVG content to optimize
 * @param {string} output where to write optimized file
 * @param {string} [input] input file name (being used if output is a directory)
 * @return {Promise}
 */
function processSVGData(config, info, data, output, input) {
    var startTime = Date.now(),
        prevFileSize = Buffer.byteLength(data, 'utf8');

    return svgo.optimize(data, info).then(function(result) {
        if (config.datauri) {
            result.data = encodeSVGDatauri(result.data, config.datauri);
        }
        var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
            processingTime = Date.now() - startTime;

        return writeOutput(input, output, result.data).then(function() {
            if (!config.quiet && output != '-') {
                if (input) {
                    console.log(`\n${PATH.basename(input)}:`);
                }
                printTimeInfo(processingTime);
                printProfitInfo(prevFileSize, resultFileSize);
            }
        },
        error => Promise.reject(new Error(error.code === 'ENOTDIR' ? `Error: output '${output}' is not a directory.` : error)));
    });
}

/**
 * Write result of an optimization.
 * @param {string} input
 * @param {string} output output file name. '-' for stdout
 * @param {string} data data to write
 * @return {Promise}
 */
function writeOutput(input, output, data) {
    if (output == '-') {
        console.log(data);
        return Promise.resolve();
    }

    mkdirp.sync(PATH.dirname(output));

    return writeFile(output, data, 'utf8').catch(error => checkWriteFileError(input, output, data, error));
}


/**
 * Write a time taken by optimization.
 * @param {number} time time in milliseconds.
 */
function printTimeInfo(time) {
    console.log(`Done in ${time} ms!`);
}

/**
 * Write optimizing information in human readable format.
 * @param {number} inBytes size before optimization.
 * @param {number} outBytes size after optimization.
 */
function printProfitInfo(inBytes, outBytes) {
    var profitPercents = 100 - outBytes * 100 / inBytes;

    console.log(
        (Math.round((inBytes / 1024) * 1000) / 1000) + ' KiB' +
        (profitPercents < 0 ? ' + ' : ' - ') +
        chalk.green(Math.abs((Math.round(profitPercents * 10) / 10)) + '%') + ' = ' +
        (Math.round((outBytes / 1024) * 1000) / 1000) + ' KiB'
    );
}

/**
 * Check for errors, if it's a dir optimize the dir.
 * @param {Object} config
 * @param {string} input
 * @param {string} output
 * @param {Error} error
 * @return {Promise}
 */
function checkOptimizeFileError(config, input, output, error) {
    if (error.code == 'EISDIR') {
        return optimizeFolder(config, input, output);
    } else if (error.code == 'ENOENT') {
        return Promise.reject(new Error(`Error: no such file or directory '${error.path}'.`));
    }
    return Promise.reject(error);
}

/**
 * Check for saving file error. If the output is a dir, then write file there.
 * @param {string} input
 * @param {string} output
 * @param {string} data
 * @param {Error} error
 * @return {Promise}
 */
function checkWriteFileError(input, output, data, error) {
    if (error.code == 'EISDIR' && input) {
        return writeFile(PATH.resolve(output, PATH.basename(input)), data, 'utf8');
    } else {
        return Promise.reject(error);
    }
}

/**
 * Show list of available plugins with short description.
 */
function showAvailablePlugins() {
    console.log('Currently available plugins:');

    // Flatten an array of plugins grouped per type, sort and write output
    var list = [].concat.apply([], new SVGO().config.plugins)
        .sort((a, b) => a.name.localeCompare(b.name))
        .map(plugin => ` [ ${chalk.green(plugin.name)} ] ${plugin.description}`)
        .join('\n');
    console.log(list);
}

/**
 * Write an error and exit.
 * @param {Error} error
 * @return {Promise} a promise for running tests
 */
function printErrorAndExit(error) {
    console.error(chalk.red(error));
    process.exit(1);
    return Promise.reject(error); // for tests
}