JFIFHHC     C   ,, Q  !1"AQa 2#q3BR$CbScrs4%6DTuB!1AQaq"2#BRb$%3Cr4Sc ?NP @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(y{7_l<'v8e@QJ]}疆 )PJvTi:{8ЎqϢCUҠ]w<635>iOzuly1V[iDkR{Uڥ!hZ~$)*8kЩmQҪٴE\[Kz/P @(P @(P @(P @(AaoymN:}(91"+aV</]?fôoz;Uu]vwY8GX⑎kZ}9MBe,}mvtF+Yd 0,-ۖۻD.KXBе$~ccZlJ >3[!]\길)fV|Up^% 漣'9:3lv|M9Z֢^H>@[RJl]rwKe̼WtuޔԪԭ.F%ƣM˒J I$rklYun?U\QgvK7mN5}%B#[ɬ.0m{–x2H6eDd'_C̺7i$ |FBP @(P @(P qŴ@:taBڞ~LmJQ % I>Y/s޲]"+Q^LzO *IcoR#ac;𩚘|p޸,ݾo:Ô^bC+ CP JG >Fl[=c aMGd?FIFRunO6y?YM3!MwO#`jM:o!Ne?CXJveJRݔpJ'fI`=[m(iă$;p_ZekF5a]$]P.gB[mgD fY[Pa &8(_p#!E)* ҵMj /}+wӽR}|͟[5C.)N+xÒ؎d5)(ڔJݲm%*jXmɸ.qZUl1JKu8IIsGH*9riGy5vdgsgАZJPڽ-N}Xk1_Z\h:%_j_4hC3?l>#ъ#{nFnBӢXi_J* VގhpWUaw~];KZʒJΪ{Ϭqߞd,HYc]L5bځ ges ȵݎEw KUYž<[. ;d?.G˙ C>DKH W$ך+ oÌ˹E׳G fdZ%'98f$&ԛtdk~;6Œ(E/~CGK%Vܗ_?5a؞%˘v ǝ[PCi*PRQ(Ip[5C~uXmߢjY~4$'z`9'I kjաA~7{gݾsx^^\-^%eua(B@RMieS|e~%ev3gܑ} hC Rtl l6~5VS+}clVvSXgIBx6@bVSRY+[R4?6:fR?.psmq5NO1x l܁r,zqd?[;ZO褂) A+Okf35@(P @(}[#vZemRA<2I6jMP:o؟4]ѿzzKi]]zj8O66$.ܠ PHs_ QT:SzR=r\?- 1%Fr4֞7;ZzA#^WH )h:^U5[J'}ϸmo+k/jhܭ9k/ox9Sk(uN'a_=$re_3M>Is&Ón:e9KRVܙj$)RIڪFVeJ|zV&cC[PNӕrk~ LxUo;5u ˧*M* Kٗ++պLu/ұU^`$\J~oQ .[z 5||fձ޷R~7?.GR563m0 ҴY7k<$4CWkW8 RCklE)B!GV^SF6eǂU̡gZ>/yo~Diʙ. xm*DdT\mFR [hNW:=i{J Motf>zi'Ic-IvɷL *\n24XR\ujБ`h_UcRipO͝5mZZ%ż4zh),P׹b_rA4:1)s8ߐ}VR4>ŏDT* I_܂꥜zF=8ñ$`y+72\|~,yvr~يR]oVhIEVէ'n9ob哴aR~㷴X z.Qd,SJS2~|I_Foyq*b}y8c5lEdɷ]Ϊ.Tҏ7~UYf@6?ǦI(U{\zJ|F >Xg6Mir/MYkf;1iVٌ7'gY[ eWgɵ TiRe<-Ɨ#7{Ba$[mN @V:m'V\ynmm6OؓR{1X9OC@}˥֕%C,(>dtT=:^r] )?iҧug[k9Y/~vhdw1AahfLr[֔GQ֯Fv7ݒytӋigS/VEP @(P @tVhYu.4'4 0A MwGJ"9ܩVSXsPZZt=߷&yktoᷞWt~CEkoqwq弮-"?}%. ʍi֟Uܠq]ݫ,*uÃkjw(\q,NɸDԨ aو(xVJX}Si{>Mo'SP~҇xϗȍz]DݶIʹ=T4ӡG1ֳFXԔzj]tKio˦Kq>iqԸM,#涛J.~tXT.+<\͂4awƮfxץieGoY(_0o5Mss5W)m\;y}c_j g\n7'pr4z,%P(hg~}>[_UO"6І(QRGTT2K%l)/Iv AshNҠZ5 xpn,HXi!(m %)P !VW=1XܤFLM%U 624L n Zu-Ȝ )E)Hrϰ5P}9 O8Fm voJAڔ@q(Orl+C1er[l5]n"񌧇I|7p'GeyNꛥL)J6V2= 57RNr͊8фipIx"?-@ӹқKV[-I -JA*iKOqQ\g"UQܛ'kq}q:9-m(puAnB\p%E(ZBI7hҕzri,qS݂mJ[\n1x8qmZjĖeIW\!eH4;A@P>ΣONrˑh{KO[R)8ni9`HWznAƙIA$IufĨGCJZVҷm_8T(}U[Q=Ttk|Ww?yE>v[n2А<{a $Uk7Swrhm:O}p.|KKIw#eθ.r2K~G5wXd&J@(P @(P ꏈGK2ε6Sҿ]+Wht޺[95}2T79+|{9QL`NBB_'XX!*uVI]= [;CVNW K]׏2#CQ>d#!E (;־ٌ~ժG |g@}(g[yە߰2 .EXVAv3k q Ғ{VTWW*˯OZ֗(x6z]C";EV2yEZ]6kiP=Ե&WīY9tu;#)?wfȚuֿ6Ze^:LrDvOH$N NJ1 gg()FBUA%ؠ?+y a:ؠ0LceH3vR7)\>1m}1/XLd_O7&)Tip֥ Fd~B̶wX]e k/.6KJ yz) 5kѭk8KuO:j)|Sxwe]]WFnnY\\"2._EJ3!E 6Vږ{> z7ϛ}ߑյr=:^7mO?.I&tǴZPXJOu@&}^7rup^]Kf^ 5&ۓ]X-aMP @(P @(P{kWg--7j:c":JGwgrݓ]yq;?܌\9'Wχ=ɓxN7@*qqNQ-Z{BAҀYr t= 1*hL)"<\aϯƾwRFIczN`˓gZw%Ip*%:TI) J׆aĻ6{8\h5mcn)_mE*$f=$ijPؕEvKDT1 e P- +>c\,҆#!w+RHbވ|cx]b8MCk'6_me}$$JJß6? !W fUCn\.T:Sp?6}ů|W[eh)A/_JfKFGs:]e[_KH :?$WހP;s<~/9)ZOTJhp:rރ8jѧ^XEk+V?2fF-ܕɝ%`Źr}JwFuokrFcf>I˵%6\CuF'd$y*Q>TT$OtL @(P @(uvh9 Gk\P'*=#\d).hw#]΀2 d$Raɷ{Zނ趩1DC#tv^x;R|ZYG챽93hbY5%of%):D'USdbkr^dBƹCbw9Osm['GP菘&Nϊ7ޣ˚̉涜l5(?3$X ))X}4S[Zջ7xh=j>$)YHJ=Kl},k%=3XV>Ei(Y">T@(8$s??5l (Y}n6#kJM@udGq?d97}'wNkOyt|?\Δ0=S㨜Wk%PV).[ڻ=ߪ}i'z ן5|Zv{QyvS8#~:QìضE;;^LHKD]oggf]Gm[z$քw\ދmtCh;_z8G]$j89E1\:# Zږ~kpWXkri]d =QC h~jwZO1f981KՇ,:癬e϶W"iӎ$@j}p<)n/w7}Ks˯ːQJPI1ULyVE^\ @*@DꀊH=crMӒd71JiqV`И\PP )V O.%iϨlXRĮU[SȐ,Rtƈ4T`bAvwʬ"j<5Ӿ5%5KL}kOWz[N5⍤Ki7s=])}%h[Bϴ $$iGhHJZ A B>c=A]~fC+Zm~J- 򴆇 HU?hu5˂o>|c,=Ւߤ <`1۪]qezIqZ OEzR byorݭFe#D.!4dw gPz:61&E$#Ӛ΂k?M +)?zO>n{eCnH$ڲ{) ㌛'k"˜! FQ:/}7A%BBF?T:pYZ$ʼn!1uh~J`o%_UNTKvXxxGIXwqɛQ[g^CpRz4|S XoEg^>&JJ|?2bxYCe>ƁJH~XIA@hmUOU_,g|{=[x7؋ VL"͏ZaX1Stt0cT$?·eP @(rgbÊJ $kGX-{8Ɣi!CjMa_qFYt(a@h @HXH@u#{46[*wz6)yn/}?ft'a|lEy]v|!֗9I:)X>Gή]"Zu: j)51,}עNfb)Ro[aKBҠJ4AYwBZ\2oHi$KR$BN T80?hδ7?vT6NRACj YBZ@Pty`k>9w2>UKmAi%,->GPt@Pf {M埡 GW#dAA* G"?1̺okbOaܮk^8hgn}ZXqZY]~*:'T}җZJ[ ]t*;9%IJ+){GRA{8N2^J@[ظҴodO.θoĿ[\ ?i6G( FU7Y8S9ȸ)Ykɮo@` {Ԕ RTA{ǚC=@uo=8iӹ?'qMO!JJZGR4|H҃>K~D:GQ]k,Ȯw@}8һ.Ůưc3ZUZt0=~'Cd^$\4\R~n=@(P @(8#t>ǎZG[!ڣ\je+uw8 ({~"ۦj3k ɗ!l`o~cB c?݂v53q"Fhi-I$4$Ys e|`eF~{ RT" mUܑf1s^1RoZB|w4'Nӯ6eQ~⓯[G_Ix;Vf2~a{hv{r&eǏԓ9Ole6L?=%)m;մ"UoIԍ$5{s)=Ϳ'bXKy<>aGLѬٖq!u\r6Je*mu ZGiPWVySI5~5$ԧ<ӓ*;=&vycѤ7* \8̈Rjߺdy1{]=(d=Y32DZW&&˲\RR\RI1ێi i %?`5@s@(P @(P @SY? :%t1;}H߷T $ lAq3fD`Ǩ[an:޷@y֞N_I+#MRTe[VFJN] oq nWv!ws umD}$ؠ(GxgWGrh/@HKWrҏ~C^\[gN掱M{3[ˋ~++iSIo\w$!Q܊!^IRڠ~H:]E$R2ip}LGO iM&%0!pCif\e$ $xH46+v1ծ(Aoc5\Oߦ-"G7*+-fw$<^$1 GRg/Uj0nЎ=jo %ټqUJXA_&SCXNhhzV±=$ƒN{tGҽϰ,= 'v9tXOUVk"ՂtϛϷӰt:?j'PD&VxS|2]m=Q; 8 ,MBKOrP]iH #vBJdeΞƗX7K]#I߬p}r?ЂБ@~B$<|A @(P @j]0l~3fp+HNկ$HpzTZb\Kbr~7Cn2M:qTI+JUk%̭/#QTOGM}wiի&JO,n:XBJֹwGQK bk.wi[Փ}_6v?UGP*0,Coz2n֛ aGwp~PC !}X6%VS|䷟RiSKŖӅc=[o>ԹR0B([*'dq=ءl6nl2>7m PhtaB ieV)\lHˉ) O#v>T$h'&g[T[p_C p߭ƄFu{ӕӗ-v\՛0.9$|tx Ucu:VewsXk]>)g'*rR4h09W -+7,ɶJ IA  ׆f.^ˌ'KKphƼ2:49Êr%d}vd2ion&%Gٜߟ+G'_ƘӡbT~MÌxjմ:u?ݏ "Rs_5.[rzdp?S@Tj^T=6v6G9؉^TƜ_kϏ%ǡ}u(s]N1{6<; 3;ڴ<(ijQ*ReDת P?cجU\r, $)@E2#4$z{{|uZAڀNJ>7Fƨ aK:w}~\%V.~'BcW6pl"ke(N`gR&cz/vJT/"ԋM*@m;:A 2U_jFGK*oڋQjQ}\tlJrKq^IŒ?!v!尢^?bS_k,+hT]0/ɯ\1>EMJy~ޠԤɧ:Ӫihedz%#5۵ ?V VG gOq$ӝh{fe܀e[wrI@ /v,mng(4긣˙,6m.6(%I;}A~P O\8w7Kd}e\?$!*QW̤)<%@r.UnCieM;?7 'ҎŕH<4m۽>5^S8˜_V坵hK}.#Ϲ'^+Y53 l v6DGjnhiE9N.^y'r1gՔsѪM>xRm R~Qɯ.Odu= gi/K ip>[_=4g- ~duLW )smnrNA$E Ur.% *Ohc`Iߵm+b/u-yAZtE{c#ؾp)Wrq_rT$$ x}])T4iJRJP @(_hpu̦-A[JKo856սP4?Yfgs2G;ZI-SބX~YԪOeKO/\;0O%wf9")Cu$"~a[AU_CҰ!ye>g^OzLxC cg]e\_e(5nPhJ48곩n/?/&U9u$mjO'~(AuH8W@RjuEujM-l-Oա%5^_WI c7h?%J-uԒ4FH,dp\Ƕ*`'#l.m @_-)ڗ/COZy2 mqS 7}H!bc%E )kW;?ߥt$ρ@Tn|ʺż_ǘE,92 "|h GG--q{lW F`9IIp{{z4ޣ2Ւ\cY47\a3sIP)X.|I r-\pG7_[oDG+=m}~2~b)m .TrV -jQI'-u5tyHS`>t}RsN;: ;'&Džnv]!\r# xMƖNzhVR9*m).+++WKM$O~ P @(P @(!Eo1z'x)Ζҽ~CQjvZU^iǶO;JHaugp7t.Ж}n6ڃI.{nUua6m{j&ۺܛ}vww.fҟE־#l^yB62%8Z#^OJpvHHB>jRFaZɤ4Riv*8t)_Mˆy([oxiZK2c)^GwB3nCߘXǹu[fE!sYlRB4H:ןbnf:ɹS8sk7\̬ )jR?V*pTS)/t#f6J-9Kqr kZIcBmm6M%a2e! y6aCC&j|KHެ|I6#>ѳ/9^pe+KYW-QZ q']ZlOBLZy5vqU!ئL$țylwǶ8r!i]}l,%#evK-%!=̀_eJH?BLPH>e< iRئ:4>S{ }Ǖ?0 mӆK_sЅ(!Mڢ5FݢG{{$c52;i0Jmą%i#D|G}r/ML˰İ׋$"dGoZ pA:-bҟh⽪R5k3ϱGTnxoES; VֽN C\&C T,ƔEy/VyzbE5ϊXiĞ95͊cGO6c򮞖ͯeIgr>HScJ[#eIZbgdz&T}im(AR[KHqP*$GVɏ;4P @(P @as, .lX dΚm'N$ 4<"M9ʘ6鿓"2A#vyw3hmOwO׃xG1koW{34j W$4zvn=}s[D^{$4U˜̓VZJJ1vwI>煇{yq}ߩq8yqBjgTl)q}PH >F }dk@W~9ctO.h߰@"V~AqPv{ n#U%kc1k&_ Zyșz] !a-UR=>%65mO#FTq[RG)$B-;'4]}JMx7kR F}YRNQm($^|@ gә~c7t²^I i&mA(@mG2n4vܷ^PB>\բCʳh};JU(A駣 n<:sy\)rc?A;@HJ@nApܯ7/Mb_rv0uUiBfyZT wV>V z^LBBô1 }VGϷ`}I*P >kj\9 B$RH$|h 1XlƢÆo63\*uZK8|BZӇ~m_ ISj\ju.8U"RMRsnҩ()|+<`.8 Oڥř s$J;>+}vGDun>̼rǂGF@(P @(u..O5R.M'\\ i$oܒX,I1[YKOͼN 洍_HuU55&\}_CN){If]?Y4_jw+NVvFEwF}@ u3/qVJ'El2lDZid $};i~1E;WUk`FPdY Ͻ^g1m@areLm#jZ P5!Mۗ?0m1#*ڴ^t#ҟI̟yn1[E,[dP͗ ŵT#'$${JI&s?ZCŚfkheܞ;'n#ˊ?zu?n;\gCK;6BG Qu$m~q+.9"߮'F#xb9*JQVPjFsx]65'EJN\'ie"+xS͛;wKd"`<›)pزd~y(&rVA_v|@vi^>ZỒ(;$P zmWHl\-Y\y1$i T$) A[4vll+ m!#@}jP @(P #eحdGWw"6mU;v@fڀߤAt27/.d6AO,i'[SI=(?gGaW7.31!L8<);!K/v;q+fŶF2e % (I2 a{ȮЬv~ucmJ l{P+ၭwnwWm?z/XfIlY-¾Z%'K׃$|]#+:mN8$mJQM&3`Xd^l\U_q*9>Ec7vA2Bnqn2KGihJRN SYoSk6tk_ZIBXžI?$|37r&;]^ABt8t{( k3k+ i r4\oP/^D:'WOtv5a_\k\^ {ʎ7>t{d)%c]yv$O<ҹ(o<Ѕqk<|C%)~2)(N+DҡB7w/ԢhjוJ/\廫{=1Yk^YM2 ;yJYIZ ڪ'{XVaqφyg$eJ>Qd"X/-,$bqOuTTmA[IMӡ*Nė>⾆SbZmifz5 =u M%` LeWO{uZV)-=e:mæڒ5Ъ9g-cnLL\KHm- )IQ5jR(7;WwJNSݒYmk g3Ɯ9EXVt`ld!Ďh}h%A?o>M|jurj:6kKׯ`7Ŷn?,_%#zc$<J\m>D?mP[xS \edJf]++>e=B̛@bloR7JFviyiJڏ5?W[*B2?pUsRO_ۈ=+TW:%,c RX}iHuM5z-IeP @(P @(P @(ҽo;;bNK.:HCO䗫gG.)}Znrk\qk>e%b:So-4{O|a)|5n<b{5}ӍN*9kKt~WkӉˏ)#JO}ҜWJFjG*Qrj+=9qs%s>2>K셰% Gޮ I(URSs-Oˋ /^Io5)㾯Mo-c_Sװa/)UI^Τ@p}/_*Pulj,jѤNI~ERqŁ5`*ХS2o赻_dKޜfk'z6 l{NDi,Wt~?Տ.ΌcHݢkw=/r Km)H~cl s*:o1ĸtK4}~޻EkNp؝BOmB=x?,p'Mw>d҇2b%6.(ԓؑysqRꫫU鱲Ѝq[r|6gp"&R?̂!o;^,"Ӝ5&&6>Դ+>4A Տ_VʖaǁGZlCYwKS9ryqw%خ܋~q+s;67vnP Ϧ2:[hsIt+D\ӫ.Qim+(ZQE931<;W6ykyS] Jp^>YmFmҕ: .mxxЬY @(P @(P @(P @pF\qcӎM$@v(|ef3ڝ~l77&nfCF@l!oX3(Km2xL6 Q_h?ހP @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(P @(# frozen_string_literal: true # # = pathname.rb # # Object-Oriented Pathname Class # # Author:: Tanaka Akira # Documentation:: Author and Gavin Sinclair # # For documentation, see class Pathname. # require 'pathname.so' class Pathname # to_path is implemented so Pathname objects are usable with File.open, etc. TO_PATH = :to_path SAME_PATHS = if File::FNM_SYSCASE.nonzero? # Avoid #zero? here because #casecmp can return nil. proc {|a, b| a.casecmp(b) == 0} else proc {|a, b| a == b} end if File::ALT_SEPARATOR SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}" SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/ else SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}" SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/ end # :startdoc: # chop_basename(path) -> [pre-basename, basename] or nil def chop_basename(path) # :nodoc: base = File.basename(path) if /\A#{SEPARATOR_PAT}?\z/o =~ base return nil else return path[0, path.rindex(base)], base end end private :chop_basename # split_names(path) -> prefix, [name, ...] def split_names(path) # :nodoc: names = [] while r = chop_basename(path) path, basename = r names.unshift basename end return path, names end private :split_names def prepend_prefix(prefix, relpath) # :nodoc: if relpath.empty? File.dirname(prefix) elsif /#{SEPARATOR_PAT}/o =~ prefix prefix = File.dirname(prefix) prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a' prefix + relpath else prefix + relpath end end private :prepend_prefix # Returns clean pathname of +self+ with consecutive slashes and useless dots # removed. The filesystem is not accessed. # # If +consider_symlink+ is +true+, then a more conservative algorithm is used # to avoid breaking symbolic linkages. This may retain more +..+ # entries than absolutely necessary, but without accessing the filesystem, # this can't be avoided. # # See Pathname#realpath. # def cleanpath(consider_symlink=false) if consider_symlink cleanpath_conservative else cleanpath_aggressive end end # # Clean the path simply by resolving and removing excess +.+ and +..+ entries. # Nothing more, nothing less. # def cleanpath_aggressive # :nodoc: path = @path names = [] pre = path while r = chop_basename(pre) pre, base = r case base when '.' when '..' names.unshift base else if names[0] == '..' names.shift else names.unshift base end end end pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR if /#{SEPARATOR_PAT}/o =~ File.basename(pre) names.shift while names[0] == '..' end self.class.new(prepend_prefix(pre, File.join(*names))) end private :cleanpath_aggressive # has_trailing_separator?(path) -> bool def has_trailing_separator?(path) # :nodoc: if r = chop_basename(path) pre, basename = r pre.length + basename.length < path.length else false end end private :has_trailing_separator? # add_trailing_separator(path) -> path def add_trailing_separator(path) # :nodoc: if File.basename(path + 'a') == 'a' path else File.join(path, "") # xxx: Is File.join is appropriate to add separator? end end private :add_trailing_separator def del_trailing_separator(path) # :nodoc: if r = chop_basename(path) pre, basename = r pre + basename elsif /#{SEPARATOR_PAT}+\z/o =~ path $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o] else path end end private :del_trailing_separator def cleanpath_conservative # :nodoc: path = @path names = [] pre = path while r = chop_basename(pre) pre, base = r names.unshift base if base != '.' end pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR if /#{SEPARATOR_PAT}/o =~ File.basename(pre) names.shift while names[0] == '..' end if names.empty? self.class.new(File.dirname(pre)) else if names.last != '..' && File.basename(path) == '.' names << '.' end result = prepend_prefix(pre, File.join(*names)) if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path) self.class.new(add_trailing_separator(result)) else self.class.new(result) end end end private :cleanpath_conservative # Returns the parent directory. # # This is same as self + '..'. def parent self + '..' end # Returns +true+ if +self+ points to a mountpoint. def mountpoint? begin stat1 = self.lstat stat2 = self.parent.lstat stat1.dev == stat2.dev && stat1.ino == stat2.ino || stat1.dev != stat2.dev rescue Errno::ENOENT false end end # # Predicate method for root directories. Returns +true+ if the # pathname consists of consecutive slashes. # # It doesn't access the filesystem. So it may return +false+ for some # pathnames which points to roots such as /usr/... # def root? !!(chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o =~ @path) end # Predicate method for testing whether a path is absolute. # # It returns +true+ if the pathname begins with a slash. # # p = Pathname.new('/im/sure') # p.absolute? # #=> true # # p = Pathname.new('not/so/sure') # p.absolute? # #=> false def absolute? !relative? end # The opposite of Pathname#absolute? # # It returns +false+ if the pathname begins with a slash. # # p = Pathname.new('/im/sure') # p.relative? # #=> false # # p = Pathname.new('not/so/sure') # p.relative? # #=> true def relative? path = @path while r = chop_basename(path) path, = r end path == '' end # # Iterates over each component of the path. # # Pathname.new("/usr/bin/ruby").each_filename {|filename| ... } # # yields "usr", "bin", and "ruby". # # Returns an Enumerator if no block was given. # # enum = Pathname.new("/usr/bin/ruby").each_filename # # ... do stuff ... # enum.each { |e| ... } # # yields "usr", "bin", and "ruby". # def each_filename # :yield: filename return to_enum(__method__) unless block_given? _, names = split_names(@path) names.each {|filename| yield filename } nil end # Iterates over and yields a new Pathname object # for each element in the given path in descending order. # # Pathname.new('/path/to/some/file.rb').descend {|v| p v} # # # # # # # # # # # # Pathname.new('path/to/some/file.rb').descend {|v| p v} # # # # # # # # # # Returns an Enumerator if no block was given. # # enum = Pathname.new("/usr/bin/ruby").descend # # ... do stuff ... # enum.each { |e| ... } # # yields Pathnames /, /usr, /usr/bin, and /usr/bin/ruby. # # It doesn't access the filesystem. # def descend return to_enum(__method__) unless block_given? vs = [] ascend {|v| vs << v } vs.reverse_each {|v| yield v } nil end # Iterates over and yields a new Pathname object # for each element in the given path in ascending order. # # Pathname.new('/path/to/some/file.rb').ascend {|v| p v} # # # # # # # # # # # # Pathname.new('path/to/some/file.rb').ascend {|v| p v} # # # # # # # # # # Returns an Enumerator if no block was given. # # enum = Pathname.new("/usr/bin/ruby").ascend # # ... do stuff ... # enum.each { |e| ... } # # yields Pathnames /usr/bin/ruby, /usr/bin, /usr, and /. # # It doesn't access the filesystem. # def ascend return to_enum(__method__) unless block_given? path = @path yield self while r = chop_basename(path) path, = r break if path.empty? yield self.class.new(del_trailing_separator(path)) end end # # Appends a pathname fragment to +self+ to produce a new Pathname object. # # p1 = Pathname.new("/usr") # Pathname:/usr # p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby # p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd # # # / is aliased to +. # p4 = p1 / "bin/ruby" # Pathname:/usr/bin/ruby # p5 = p1 / "/etc/passwd" # Pathname:/etc/passwd # # This method doesn't access the file system; it is pure string manipulation. # def +(other) other = Pathname.new(other) unless Pathname === other Pathname.new(plus(@path, other.to_s)) end alias / + def plus(path1, path2) # -> path # :nodoc: prefix2 = path2 index_list2 = [] basename_list2 = [] while r2 = chop_basename(prefix2) prefix2, basename2 = r2 index_list2.unshift prefix2.length basename_list2.unshift basename2 end return path2 if prefix2 != '' prefix1 = path1 while true while !basename_list2.empty? && basename_list2.first == '.' index_list2.shift basename_list2.shift end break unless r1 = chop_basename(prefix1) prefix1, basename1 = r1 next if basename1 == '.' if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..' prefix1 = prefix1 + basename1 break end index_list2.shift basename_list2.shift end r1 = chop_basename(prefix1) if !r1 && (r1 = /#{SEPARATOR_PAT}/o =~ File.basename(prefix1)) while !basename_list2.empty? && basename_list2.first == '..' index_list2.shift basename_list2.shift end end if !basename_list2.empty? suffix2 = path2[index_list2.first..-1] r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2 else r1 ? prefix1 : File.dirname(prefix1) end end private :plus # # Joins the given pathnames onto +self+ to create a new Pathname object. # # path0 = Pathname.new("/usr") # Pathname:/usr # path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby # # is the same as # path1 = Pathname.new("/usr") + "bin/ruby" # Pathname:/usr/bin/ruby # path0 == path1 # #=> true # def join(*args) return self if args.empty? result = args.pop result = Pathname.new(result) unless Pathname === result return result if result.absolute? args.reverse_each {|arg| arg = Pathname.new(arg) unless Pathname === arg result = arg + result return result if result.absolute? } self + result end # # Returns the children of the directory (files and subdirectories, not # recursive) as an array of Pathname objects. # # By default, the returned pathnames will have enough information to access # the files. If you set +with_directory+ to +false+, then the returned # pathnames will contain the filename only. # # For example: # pn = Pathname("/usr/lib/ruby/1.8") # pn.children # # -> [ Pathname:/usr/lib/ruby/1.8/English.rb, # Pathname:/usr/lib/ruby/1.8/Env.rb, # Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ] # pn.children(false) # # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ] # # Note that the results never contain the entries +.+ and +..+ in # the directory because they are not children. # def children(with_directory=true) with_directory = false if @path == '.' result = [] Dir.foreach(@path) {|e| next if e == '.' || e == '..' if with_directory result << self.class.new(File.join(@path, e)) else result << self.class.new(e) end } result end # Iterates over the children of the directory # (files and subdirectories, not recursive). # # It yields Pathname object for each child. # # By default, the yielded pathnames will have enough information to access # the files. # # If you set +with_directory+ to +false+, then the returned pathnames will # contain the filename only. # # Pathname("/usr/local").each_child {|f| p f } # #=> # # # # # # # # # # # # # # # # # # # # # # # # Pathname("/usr/local").each_child(false) {|f| p f } # #=> # # # # # # # # # # # # # # # # # # # # # # # # Note that the results never contain the entries +.+ and +..+ in # the directory because they are not children. # # See Pathname#children # def each_child(with_directory=true, &b) children(with_directory).each(&b) end # # Returns a relative path from the given +base_directory+ to the receiver. # # If +self+ is absolute, then +base_directory+ must be absolute too. # # If +self+ is relative, then +base_directory+ must be relative too. # # This method doesn't access the filesystem. It assumes no symlinks. # # ArgumentError is raised when it cannot find a relative path. # def relative_path_from(base_directory) dest_directory = self.cleanpath.to_s base_directory = base_directory.cleanpath.to_s dest_prefix = dest_directory dest_names = [] while r = chop_basename(dest_prefix) dest_prefix, basename = r dest_names.unshift basename if basename != '.' end base_prefix = base_directory base_names = [] while r = chop_basename(base_prefix) base_prefix, basename = r base_names.unshift basename if basename != '.' end unless SAME_PATHS[dest_prefix, base_prefix] raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}" end while !dest_names.empty? && !base_names.empty? && SAME_PATHS[dest_names.first, base_names.first] dest_names.shift base_names.shift end if base_names.include? '..' raise ArgumentError, "base_directory has ..: #{base_directory.inspect}" end base_names.fill('..') relpath_names = base_names + dest_names if relpath_names.empty? Pathname.new('.') else Pathname.new(File.join(*relpath_names)) end end end class Pathname # * Find * # # Iterates over the directory tree in a depth first manner, yielding a # Pathname for each file under "this" directory. # # Returns an Enumerator if no block is given. # # Since it is implemented by the standard library module Find, Find.prune can # be used to control the traversal. # # If +self+ is +.+, yielded pathnames begin with a filename in the # current directory, not +./+. # # See Find.find # def find(ignore_error: true) # :yield: pathname return to_enum(__method__, ignore_error: ignore_error) unless block_given? require 'find' if @path == '.' Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.sub(%r{\A\./}, '')) } else Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) } end end end class Pathname # * FileUtils * # Creates a full path, including any intermediate directories that don't yet # exist. # # See FileUtils.mkpath and FileUtils.mkdir_p def mkpath require 'fileutils' FileUtils.mkpath(@path) nil end # Recursively deletes a directory, including all directo