From fde76198db785e509f0062dba1d0acac04ca5c52 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 20 Jul 2023 12:39:30 +0100 Subject: [PATCH] Grammar post! --- _drafts/2023-05-01-orthogonal.md | 2 +- _posts/2023-07-20-writing_grammars_is_fun.md | 110 +++++++++++++++++++ assets/blog/parsing/snippet.png | Bin 0 -> 22514 bytes run.sh | 5 +- 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 _posts/2023-07-20-writing_grammars_is_fun.md create mode 100644 assets/blog/parsing/snippet.png diff --git a/_drafts/2023-05-01-orthogonal.md b/_drafts/2023-05-01-orthogonal.md index 13bf13f..9b0857b 100644 --- a/_drafts/2023-05-01-orthogonal.md +++ b/_drafts/2023-05-01-orthogonal.md @@ -1,5 +1,5 @@ --- -title: How imaginary universes can help to understand the real one +title: How Imaginary Universes Can Help us to Understand the Real One excerpt: layout: post image: diff --git a/_posts/2023-07-20-writing_grammars_is_fun.md b/_posts/2023-07-20-writing_grammars_is_fun.md new file mode 100644 index 0000000..d6b066e --- /dev/null +++ b/_posts/2023-07-20-writing_grammars_is_fun.md @@ -0,0 +1,110 @@ +--- +title: Parsing is fun! +excerpt: I came across something I wanted to quickly parse that was too niche to find a ready made parser for. Join me on a quick whip tour of writing a grammar for a PEG parser. +layout: post +image: /assets/blog/parsing/snippet.png +alt: +--- + +Usually when I want to parse something so that I can manipulate it in code, be it JSON, YAML, HTML, XML, whatever, there is a nice existing library to do that for me. The solution is a simple `import json` away. However if the language is a bit more niche, there maybe won't be a good parser for it available or that parser might be missing features. + +Recently I came across a tiny language at work that looks like this: + +```python +[foo, bar, bazz + [more, names, of, things + [even, more]]] + +[another, one, [here, too]] +``` + +I won't get into what this is but it was an interesting excuse to much about with writing a grammar for a parser, something I had never tried before. So I found a library, after a false start, I settled on [pe](https://github.com/goodmami/pe). Don't ask me what the gold standard in this space is, but I like pe. + +To avoid getting too verbose, let's just see some examples. Let's start with an easy version of this problem: "[a, b, c]". + +```python +import pe + +parser = pe.compile( + r''' + List <- "[" String ("," Spacing String)* "]" + String <- ~[a-zA-Z]+ + Spacing <- [\t\n\f\r ]* + ''', +) +parser.match("[a, b, c]").groups() + +>>> ('a', 'b', 'c') +``` + +So what's going on here? Many characters mean the same as they do in regular expressions, so "[a-zA-Z]+" is one or more upper or lowercase letters while "[\t\n\f\r ]*" matches zero or more whitespace characters. The tilde "~" tells pe that we're interested in keeping the string, while we don't really care about the spacing characters. The pattern "String ("," Spacing String)*" seems to be the classic way to express a list like structure or arbitrary length. + +Whitespace turns out to be annoying, "[ a, b, c]" does not parse with this, we'd have to change the grammar to something like this: +```python +import pe + +parser = pe.compile( + r''' + List <- "[" Spacing String (Comma String)* Spacing "]" + Comma <- Spacing "," Spacing + String <- ~[a-zA-Z]+ + Spacing <- [\t\n\f\r ]* + ''', +) +parser.match("[ a, b , c ]").groups() +``` + +NB: there is a [branch of pe](https://github.com/goodmami/pe/blob/fix-6-autoignore/), which hopefully will be merged soon, that includes the ability to auto-ignore whitespace. + +We can now allow nested lists by changing the grammar slightly, we also add a hint to pe for what kind of python object to make from each rule: +```python +import pe +from pe.actions import Pack + +parser = pe.compile( + r''' + List <- "[" Spacing Value (Comma Value)* Spacing "]" + Value <- List / String + Comma <- Spacing "," Spacing + String <- ~[a-zA-Z]+ + Spacing <- [\t\n\f\r ]* + ''', + actions={ + 'List': Pack(list), + }, +) +parser.match("[ a, b , c, [d, e, f]]").value() +>>> ['a', 'b', 'c', ['d', 'e', 'f']] +``` + +I'll wrap up here because this post already feels long but one thing I really like about pe is that you can easily push parts of what you're parsing into named arguments to python functions, in the below I have set it up so that anytime a "Name" rule gets parsed, the parser will call `Name(name = "foo", value = "bar")` and this even works well with optional values too. +```python +import pe +from pe.actions import Pack +from dataclasses import dataclass + +@dataclass +class N: + name: str + value: str | None = None + +parser = pe.compile( + r''' + List <- "[" Spacing Value (Comma Value)* Spacing "]" + Value <- List / Name + Name <- name:String Spacing ("=" Spacing value:String)? + Comma <- Spacing "," Spacing + String <- ~[a-zA-Z]+ + Spacing <- [\t\n\f\r ]* + ''', + actions={ + 'List': Pack(list), + 'Name': N, + }, +) +parser.match("[ a=b, b=g, c, [d, e, f]]").value() +>>>[N(name='a', value='b'), + N(name='b', value='g'), + N(name='c', value=None), + [N(name='d', value=None), N(name='e', value=None), N(name='f', value=None)]] +``` \ No newline at end of file diff --git a/assets/blog/parsing/snippet.png b/assets/blog/parsing/snippet.png new file mode 100644 index 0000000000000000000000000000000000000000..a01c6b174f15e81444127ec0e2d766c8a2c06802 GIT binary patch literal 22514 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

k*lwfU&dKIDUi)8K_BdKFY|671U34yHN$ zs5rlIb4e(teCH<5qED+Tf7h};6Iq1Yu-a1!A$%_+_mfpkXQ;`jd35PqvRJ7uSeh}j zwF%sPXXrwAz}}w5UG+@Z?xlJ>p#B?lH;<6O%H2D$@|RDmaf|=8J75j_!Y_AsGnS(`rMuQn|-UH1lOPI&Wk1sA381B`HLJy zHJV@1!f9@of|H7$bfzuT%?zwg@tjkpdntZ&*tU)wsqcdH2ea;W6*dPy%50iA3X@fM zeR#8p`n@80v}%Ft<&|^Gf10Bm4iu4DxI}-8%b_#?Jo6DTC_u79Tgm%&+<-Jj=D#aa z`?On>MOWj;RPA-^0Is)6N)-ty;WryjSFJUb+w_79sok9@nveJ1$q7+ivY03wn<~?o z(&gQQIOo9Nmk697+5+p5azbQs+6R0w8$=yK3T_h_-p&EsbFvK1Ej_bz_P9kz{LGMz_H37LESKJ+DwH2Opo;zvgXVN;BUh()Nn z+IV-Gm$OtJ?|kO3FIHU9#v4@T2enN8V`7b@0MsNL#EC?cw>cr;n8+3iG~XW1P;K{c z_E-y}ECyuOt@p;A0J5cN2QaK^r;m5f`{xD65o$C~^LeZ3a;5WG?Rm?kHm)clzSO>G zLK(a1z!Kk^lU3txF=R^gwC^d)j>>;qaOx^UWj1UoAv z8fEJA9>?w1k?%FC%rKoMP_gYHg=*PUmb%*N@}FP!Vy+6p!!t8oetg8ni|9x`pFj4U zJSF!id}5Kd%Bu5u>0P%*4t6$8`igmYo)+`ycJ;>g&Ax%5k>hauwLd-UiAHLB?%O-l zbG3O>lhm<%hPZeOB2<67=zZl1B=7OtxOiGwS-nwLNH$~(Nq7bV#mDZ2hp1wFRWb#{ zq5&C--{?qJtcx-_X-OKlQjgxl_I%ybmPx3V{n$EUh0u3%0Bf_$R1FTyhusVb9TKPG zKw)3@C3^VlI79^W;FGqXooZjkZvPXB_BqX14^r5fnv2;GqH{)*Lx98k5g{=LEc@oO z)*9ETboRDAnGFV^Id6=uExk%A!WS#&?6LxWq&>*TG(sd0?6*+oN;NC*LtC5IFElqnyrW+s|#c6`YCgpv<~Jy z;>LT&s8DBpm+OL7U_DtuK|nCR4sNCM#{7>kpWwlWnw_WW&=X?)xc~>yXU`tJME*bW zlN$=amD{(}WhDOK(>6!|WiHtz6~z2OnfieM3$ALO8dv%QGHrjkT^tK0yLo?j@eBz` zh=GsYGh7~0p^Q}s%rc;tEBdC1@@oqceFP! z6M;GwhQwi0s#9}M5cTBVcsI1{FhDVs5J<@5q6nCLyGM(ym6&mDe;QdH5FZw$vfHS2 zo<}%FmoPMJTDLT)=4>T8jj~nO*YyZyg)^DDM;S<)2DVXA+wrp zZrq3|S!CFc1y{08$}p9wLIa>{DR=FvYOJWJx?(y!O6)(gN>E-MFDqp5xE2D|l(M8` zXx_nRhX1v~zrizsU_1!nIfJmFF^cadxUMv+yp<1cSWul(J zYz$Z9WtiXJ*H`+*aeG(|{Ay27FNx7W-93>`D|a~03!s)3Yv01yO^Ot<yxHTtfhE z0F*9Dcc(G00Y4>f&OfE~^b_pH`3ft|_`TWPO3|YDa775CQp73}wQCG<$+#ca#b!XD zI5_Z_Ay3_wPT7J{Zb{VHAVs1c?n#UaTDxr>nGKgU3r=D2U3^yiQIHZ5k3x)2+1Z2Z z0ymh@rI%u843hOOsmQueS>d^KF9ZT){}?S-gM!bl2pCi!OXjo5{@^woK(j^NreGQ{ z%XAOse|~TT15T3n^W$}2LIftYuz}C*8~-sO1h?%BSM%O*EO8HAT>?P+^&^2$Em8j1 zb(7_>N8No(t5G@X2MJv)pTS+{^XSow0EAYn_{Y@{Vsk*Wxp+7qnE;lY64{iu7kgz@ zc)PP2j~bwJnSOB0?8@FxU=*CEi7cJY#)8F$?^c!o7m?@eqZn9I12E(UnTI5=%+_yUts9)w_C~vARt= z7(#Auy@X@MMwrVR^EZo;;mq1+@+0@=;1yGvWwqhs7nirzDD+^TREo~oWbC&2#ORDx zq2uSepM~Lp@My{aWU(3k@z>z-L_d+K1cn}1>H26<%>j^sEKb*Yr5*bb%})S7OIenI zgv1ZP!aIQVIoaZDmA1$2av&)ZjI6YM1hD)1sZ?KCS>)UMd)hv9Gr#B?q^4c=mJs02052M^O)c9^_pi%?-VCEW(2i3Q}bXw{wSJ%0RA zNmihse!O*IVvxg)6u3WNDM&FL$;|M$K9U1O78O&c3Thqi4j5B21;E``a*iSv$o!=v z0@x;dwI_r&2cqx*n8r#q?|KnTb>!MTF3Iv!5Vbo@_qNWd58<)N1`=gs;&^P;h{4DM zvE(6^0zuKh2;^CGDdV<3VzQbz+>d8UZsA%^`H86RUs`a6SA?%hCrL^E;Z;B6oHsRz zX8?NNG9$jp^^kb1tF?U@-si3x=tu7a@y@3%*uWZmtmY=@KGYVkbJPm3<1p1?s~T58 zfe?$!LhVEm1|70E#oQki#Eur~M>Ur+_)Eul7zQOm;M=+#fHCQ%Y;o9tML)j4QXV8a zAK2n@(Lye)KAEQ+HMv3X-rL(d!}2~?ECM0RD}hc+8BubpPa}_|yj!b6 zHkNM|6mIy%S!(;yFfs6}u4>p+cshs(9@vS)vyn9NsMr}Y2e5|3;4ODTw%<*e;++ktCp|*?v827)V5He4cd7eH zOptxGdNX~RQD8~@+~QTOdw6}U7+Z*JZJmCs+xdD`cb)@>c8KpF?E{QVPq`6&@o~`Q zK%Rk_&u7AiOp7EY8-K?H8Z~7DiLP#Fcq!69fa{vv@RpEgtFI>XdtMBGY5GQX!90tV zTd2s-;UvbapA`YFysY|@CEGiPv|-WFg|%iQG=(YYQp+8(9>k2m=7z4e9Q$q@64#>z zOONiS77H7mx|KLy+l+`#a>isH|I? zCvJ@T3*gDL#Dtz~oAtQncm$&JO78vw)vVTu6FwJ*`00jw({m;5YPCn74*q!UV`n{s zFet9e%K63{0<_Kzgy|#zs+2NPfBs3n{Rf>Y$N0T5ww)@)+HF3b)X2D$4oGo51hc8Q zAP*80+AEM~atHmv%H!~mGAdqoy3&?^VA8MMDi`8i&S-TPA7Ur}iOUF3CE|cNRGJeP z@CS zr4`dI!t6%;y|%&tLx)prud!KDv3)a`;`A}EeX~MI#9~p2fV1$-s!EAVe@p#eEkQ7! zCA!F8-DiX%aC3RGFLL{&)1bW)=5p(8oHrYuy~s6hb%~E7@43Cva?M$jVVv9GAkLPx zLFJ80OX9%#x0&I~ODLk|vxTpS7&{~^OYCLpN{T6OwGeIpSFKN#2yO=io|O)pK_x}| z6-j@^?Js0dL_cS1w|~T8ohI`CbH8>0#ry4*(_y{pAGAq=@Lmaq_jAqt&CREnDqHN2 zx-I`H%fNf*6GIX0@+SE#CZzt8%*&(vBrFfK@EvwD{yhE0(9-25iQ$4qw!TVVALUp=ra7FCcn$;o}EC`Dbe~b011M=+W{oWjB~?5 z=-;CsLDi^Hi9R!NseAy0=_;2O8KE7jhd-R=m??)&B8UYv6z~?|2*K)3f6`pQK%@^i(v{9}YQMTC6l)>Q)}kOVIK-DR$-=_}b)WJ>J`f&o(I$ z8Z37jOkv?*80YiUVz^#cdf0!+;*ZkBu21vIR&UwWlCza$(!(fEFfud-2IH#Gq~Dyio9QQymE6YmF?7z zo18!H#Jvg#R@QyUsX7hiDvOJkcl%C{D?&wQsVo^!=L#?GpFGb?=sM9TIs%R7B{Br6 z?plmb47qy-xw-bbZp)%^jb1;WuZB3KAEWF*g-nR)jjp5@sQ5kzAz4l3ZdPcIeHM>0 zS6XZdb~>;ov>3v1KYM-Xv5pd;Mn=A~u-40(l!y7Qi)o@1*agyWRh6Rfrj^)n9ZoER zM}pFH`1PbKBf<#kk^3%XrliAd_3nfkaZ|gpDr!%)YJ_dNtU+`xlyT`ax!=WKxNYFLc@u|J#L(tN;YAT zNOeGO9x$P~rh;?Pcm;24%;0>hc21rji9hn*QNd!;yq|zEukc**h}e@)mlOjx6trqo zjW?V8v_5lN6N;U#*xi{daatcG?KVUWFo97`hQ%~12G~cLbsl`a#g zHT0M6>6GDxt!~iaNC>_d8sV3TEs}q=JKJo(5y~Co^w7Y2oo?h1Vgfnxv!$O%rkXXG z)dkp8Ou=;MM_-N@LMiuUZ0p3U+_3MX2kEH@Enll$&d_BADv2QGrUQ<8In(E_8$us% zZeh44I1=l!({o;PuUU?%>2%!`*SVEX<=|(^$vU@aCbP z@3Y3&WU7PLaWM0P4!(Hb;qE3=F5Rs~?13ZpCUe@4+s!m(P-~c~GwC7BXZngV$?EXk zS@U4}_HTViy+`J4%Y4?J1gU@>$HGMU>wE?i1rf|@?^#JwG9FOyim&uFYx}<2XC5nc zfa{~UY&47PMwB@0=6NjRC$MQ2RCsMu-)Wxrc?Sr>cTbAV)>-UvNj&a*TriD0y|VVd zjp_-pDdZfyMqPgeMp2&N!Jgo!8M_$KSr)^9(XUCPe&tyJm}B59@4~H6W~Q{=9Swsq zE}~GplPiC@wBiRp$Y4{1p`s^%V3iw7T#g$OcRpAPUtk8zVFZ5fzCTid#$yd{W3$X} zc6XmcMjfnUA-FO+a!f3((drnCQz$(cwNzZYr1(?$Y+59Eg0M}(O1r;gsYL0oWaItR{Y^jX*fYZYoT|yoJ<#9e zXKdgelL*q`yoO#s?{Snja#ONr6}!W0wGA_?=c24}6!ssxR<_=&uuwkcI&?gVj%wqai!Fj!T0c<&NT=MeUP2E4JRXUDfEXK3CS`~N{g{PEN z)KbJgwEC{{4wU|Ygq{Ba8vbPv59kOn(_7QM3r0kX6R}d!-|!CJC>l4iic6ua-gGN5 ztH^}}VW*=^_Q%7|M7~x$=sLlwARO%1L&wmpjeB&8b;fcX>_|+>Y4xeD^({d@na-*9 zSue975l}$t*_Z1na1HZxbe!5J?LUOe3VRHEq}*aYBo~q}+Ro*|q)j+(?^*x&(0;2& z_v2W&B1S-cwrjwj8CjjzjGEp!yLQG-gI~Z^Ek5>ggHDZR?q@G0cjrnk+RE2%>%M>@ za-S#nbDG*5AN$W0bkG>t_1)tTa`~7tCs8+>A#gcEtA+?17%5TSwbG~1`v%#5j}~dh z7gGz%%H@!)!k}Q*%}V8uCyt5PIgKm+ry#3t+yfyKkLyC3p5T)Eoc)qP3-?kr6=&l6 zBc5(QSPIQzEtcMVBab#LZ1Lt~3*v1^BH344Ugq(JFY9N~1k>IqB~chA)3U}Nd=$)x zwxL4uYeL=WqRc9`65(w6;@w(`{{#maX&;>{Hl>>}u=7ozj@Ze4U!A5amALNOlDF8( zVUi<*k!}8bu_&2D^>z=FN412SE^Ldd_ziGgbDBdD&EdOUT~zU~eWLreVCn z;vwAhhfSQ)RL+r9uzsZM;A!0YgeW&BV*$R#;6xUKpi>`~(BStg7w`>kuyPa$XRUpirP#fY;52; z3}RuhI|}g2mAV#L=1H&W>h7{2dNz#h)?EE5Y!&^kxA?6Nvwtl4*_Nx4QCcWt_0V_b z(J^m(SC7K?2h{!$Lr3g9QWZWsqzNe6rU^MBHn@_~o5yO}tRo7-#zQLYE#vHgnz@s* zgf;=!opWP73-ac%newA<_|*-?g}82+o|MaRx(Ax-(e>tXxV%ER<<)4W2!#-}9O;i; zqhD|8DQWitrXb|){dt>qzv|6w4@}cF!(%d7d-2&hZ_D>mU64fVHwh;v^^u@{MX^ys z{>BQyvzJCY^JN4{SJ03szW5T7eDEi?jA1qjXL!ne<@WsPg0>Q7+NzMdEELHRVbBX3 z<{#Lg?6Vagk%&ID_xb0%EL*8Ul7ZM2gv6%_{o0wsrzTqntAh7dJ5Fj;AAPk$1Lp`( zo7}KTA1uJvV-f>en6ez9A0y1MX}Uruq(_&GD*Qls+m}j|ebkG#-Y8Kyx6N>H)t?1- zAQRm(rLXr2f}qb%$HY_?Joa0Y$*8pGn13?6B&Y75JS=)2RCvU0kFxWfN4|?^k9K%~ zYPu^?_gKDuX)PVx2SJaN+1xruhg_>AmB~W~?r8Tot*7Q~pZ}bBX`CI51)JGym(9ZB zf;?@w)5MXiQw}g#%YhHF(AB|J?H5MFQ>Lo#5jH%dzrLh+A{dNU5#j%V9F7Z1SS+oyff)PQThArmj2(qL?YgeRA!*sDI z<@2tsdk$jWVd@uncv^9^EJ)fRN)iM9gi8vm9*vaB7w9oK9a8-}zXjr}{LU~koFDhDA8<+94dAGj$SMhy) zXY}kGnwcB$$=};B8!_?~uApSakEL5`rPrNk*@kgl_5npKPeul?n)xU4D?}oingfwU zDnwxJ&&uuGL-@;Li`V5Hn~gtM5K)D;i1`_nz7@K(8Q#6=5sC7)7D^WQ7J-N;(DY91 zjfqGt3kDojviqsV}6PlERBCpo<7Ip@D3 zdw--y0I`lz8ces5x*0y<1cda-C4D7{X@K@z3D_ps)U)pknnI90hSDK8K-@A|LGDCc z&`6HF1jB;@M%ix|vX9;1h6l5%+O`k(rG}Ub*H3sCR!1fkpV3V%D_<6p%MWK((23l= z5+?M}MpA#vWQLHF#BSSAPGkVRa;~+4WPk>`LkS=!8Qe6YBT1$X8hS8umnXi)7@^{? zhwf>>SvaBRDQv5AhH=LHFcttlyLBk9?i%+m>6Nw~vg}>g*T4E{FS_)^z}-z*mhVYt zY%o?ynBwa(8j#2lQ=s=CcyiPG^uc8q(jRdx4cs|PYC=7dB22^Bv*pergb_@Qx{NWh zjYC;*+&r}T)`2)x**#Oj3dM}}iI)=SF%o3mCgPKKOFzJHbW~Ue1i{%gjPGjM2#Qx& zVGR%z-E-|-ZPf#pUJ1usJ_&AW+DpHk|OxEZY9Ag4#w zB^EcPSF#R#TemJZn%ZnJl~HPew)7=2aZ`L^3aXhZu&1s}Jm zqi`6_05q7>!vS;cOg$23Rpq+SLBic{a>$ zHHEKy_cwmLcFxS?P|!m|9OPYdua^4^Pf4bb6>VlS|5h%m&HKrVm&dD@1$w$@p<$C6 zN{!>BW=}$!VMBKLa~bgnmx9Rar1Ig~UXgmP`r~g~kkD&$mPYPTO|zP6uBxIzCU=L9 z+iUj66M>`0$=|+us z8B^`5o~f^cP$Yz6=u_D6jp2>s@Fv;M0rqib3@D}<%Q%v)l?$&N>WFz)qugFf6EwXt_+i*it^4hav>*Ml&Hh84+=Bk^rvOc@`kku_<9#)%;hVUp30vzj@U9vHgc1t?eu zy<)pcsd3%*Np#F~q~u)E#7Xz;>U#Tmg*4JumM_6jydR8Ur`*#xl}vWZ-YpoCrPxRE zX$)EKdLV6COt$|0#cYwi>$N(oiz_+JJp|c{AhfnrnAxVyS(=lFuc;n{crOe zxp1Kq%~&ziAB2%z%&39|q`EzpC^=={>|sa?LRf`D>ky7`ztIIb68JOg7L<8jjUI6< zJJoA`en61oN`kgEyj771vP%lJh$@g_%OM!HfKVaQUp>4c^kkUnFpP-OOEM z{$d9L{&9?X#GPKtdALoqh@15Jw}f{B1;ZO&a}RJ`kbRi3y4AQW5i}oykl?ffoqf4n z6!CO8M?dZKW_g3A;d`vA+sKu-sSW3;!wBzk5NC9H#%i)pD-4aGXqgw0lvm~E*8Gyk zDq#*KWps?Nw1$+nKrDJI}UewF*hJjw-F9B_|T-k8rLbLs7`}M8Tp?faj zftIQsmuf{3?;u}+)IZkdt`p1?UB5ik?`9smj>a<)-QfNf3o+xUh{`L2pXCvL!fW_w z4|6ga;lLKH8+`60a@26fyf{=fFZ`?cH49FvTJR5)$AqiYgW@M0BR2w^uYd0%t9 z7BR%)+e^PRE>Iv#(57w!aD)p3XiYy8_zx8h`gmMTd zI;9>k>|bBDpl~6;3xBm;gZ~^0QE+{DifuU%exl}SN3`v%}`szOwu?qwJt38X5;I{r9 z9cv#qHVZUY$}2G@tss2CWZniM1KpOOTct3GS%nB8aE6-Mh!rHC?kL8o?+#~*Pz`Q` zio7U(o!o)0_LIPe6~7-=VxtnD03R z|9wG(KQA}|>_6*^R*b{t{JK-E|CY6oj6h#$LTftT)s9Mp$;qvKdCOf7Bs*iFXYh(b zLlcgnM!d8Hm*?nqd!!0&!saJ>rmC(*&^zR<4%m-DxUz&eV%FveEb8Bsa?9mgFA2(D zH*#g(?S&92UnaVrNtJeMf8m)?=V!HeiS^zHRd!l!pwEaOotU6oiLZ5b;hz64)G&Ia zADK5a;|%nzmtYVk01<(dUJ1>I1R)}n6fGcI;)a=&wUk%~@jre1L==WMx&}IbZBRA` z-Cs$>YACiCk<8{g6kWB@)wXm%XH;l<+)%i_$eLGjpUQ-Lh{GPDUzY~~T$C!a#pMj* zKMs&^8A9V%?0WuFk>w&2C!LHHJVc6yM}LYSGtqah|d;hb{*y3see-nyWowl7nA zpwDqPVJ;P;#;0BBV`i>jFZA{KB(d*k!BAS16YvE^yl(HW3j%j&yeC|G;#{oUmRb3~ z)oa6;Um7?dG!(llx=9B9NGo}nrsLXq3OBUvZm0N)9o;y3F|(RU&cbC0+$CUsWHVEd zm;OdZ88(x)zj1oC{FN|yu$G#Y`A0XUL~ZMa#}GD`!__FRt2;Ww_pGqkjFsJi-BKv1 zfk$Ls*ESTIqNyykt<{zZXD?21w1;7h$>K#(?#-ENvh{pxzaHHA7 zyr;xI)v*DkX}k2WH=Il7wNwWWI{Ka!3g=97?XwiaYs%ltjU+TwN@7h>&WQ4456I61 z4mG?w8sX3So{s{o(iS}m(*JA(qK|$O+#~Lo!sEFAJOT{BHR8iqq_Zgf>rt#A0QsUp zpPl|6rjcZUy5VKJJKJQPA;&_1O2LBkVu&2^UvFZJapIDfaA^Epp zyEpHaDljNjZ(G}r1a>xTJ4=d#DxIKYh>gXBksFS5VFnV|(^nE$#)1chy9mpS)^JQ~ zm^8&NppO~VDa783OC!r~4(}Jva6*1{dBqrBxUQgCuf^=EUrk%_ct+J1tu_W?!4IsW zpj!@e485X=cSaXXR{h3f@Rb&Y?EN8)KH;#e+e*!+sOV}_c}EvwMojhIp7AfQ#57ag zp!?YcBd<5x@W|9Q3nr>`YGWq1m?Wz}?(=dxKsG4KQ<=OE}~<2gC)sbX#G z7x93}>X-)hv0%_~_ac!3kYEUrzsR`FnObk3Do+);Q*T5T>Q!~h&v5aJnUd0xNAYc5paQ1_a+HpJ3b`a|S_=N&W)0Gg~GtXk8 z7$Zcb_pB4a$GEVmnCivzW)iE#c{8m>p}I`sCxc4et4c-o+VpD$IF>RIFPx2p%Fo_nUpw|0jwNb?;8BcV3O7 zrGa2}`-dOQ2xcA#(HipDV^onX1z0}TlL(SA`g3yjYzle&al+d7X(4Dv&=kQikA3rv zir-%G<(H`#M~(xf71x-XH`RSPw`dXFy?&jFvgx(@s8Kc{ZxLk;5;sD?;+|+iDBj$x z$s*ti2tij6O-&QDcWuJn`oaq;Jts%vYry@`bbRJay0U#c<*iYJ!dO7Si-Rz(<#>I2 zlwHRdYHGsfPaU=uZC%GquT-~g%A9+bHAzGdtiaNuF^ub@pZIAy%vv-|)QC8UaKT8!z=^>HgmAOy4+g40UJe)6tintQ0gXTi zN^^{Z7U6wCo?PY1QB{tG0Q^Chw{26g<*|=K8u`-py2;`RSktO%|M z;Mqps@GRo~BbG!JK1P5hX9#TvCg>9g0hji^`!2QQX9$#*aROsD2xEtT-n?qRBu8}A zgjf)lw`@_^A!rk#WP%V>E1+OOwV&E?0);X51Xrr0#!6sclE;k7{0!-^-p-5@2ptW= zdkOxe3H--={}8qt_+yI5R&3EFQr>!$#*U_9z^cp-=>AVWF$QyU7tm2K253G_IFJh- zl+FG6sVsPzV6v~vp*yjbH})HFxK2E5co_iH+W-7V+MQ-#X*QQ78}Az}Ia3!9K1Hb1{U!im$$^1e6;*9}WU8c%dk3kaA{=k$^|MeFg96w$s z(_y}ssJm46?$m}EO${DIaj!K&Kmc|H>er|1Y%wsCqh|tvk`Ubx45<<`7HAp}o?M}u ziJB9H5;I|7M!Ph_2<3Me%nO8jf@vA6-#(}mHxHENYuWEZ3CL5uI_=^6 zjS#<@iJJ34Ab|i}j>8k<`2)i^_T6_z7F@R;3##py@gq>v9%d>=#6Q-)@GPHSv4W}$ zAFiwd5CkDaYO2J~8QP5J(R?21V6MO(!W5o4C_1ouXo6ybv8*AtrwE&*A^c%{bB6M> z=^9KNftNHLWz(>>BEf2ksFKb3nQYDNNB;HPc4wb=wB1~{e#Z2*(lSmUXcuTBkN@z` z&5OHHBlE`vep5XV_ZO!oXdAepI9vu0{=#2|$SC>O^B z$AF^;Yk&qR6Avo1Iz4dU!Ng01Z6TJ<^;|6^XSNI`sm>;)A>)vLz?7c_ckO36h}!@H zhUl~X0ICub|Aq443=BXASg6E64qU*<(4VR!MyR#y5dh}Y@4qLOa=fX;RuQa$4<-R^ z34=~`W&%YYc)%!({z6)ZNfCX91pz`Tv@l=_bKI50XBXPxH8veh*Y#_XVAoRdm20;POnYL))IIXc9U@1Y{Q#(#T zGj74{hOY?xD?qTWVT{4MqJ=zPz8{!YF+U$Fv*}-I#|cEW)c3R~TRShYz>WU6GEM+f zI3ZdQga;@Runy^G&nlmza~{dq5%U{^4H%t%;B-gua=}ps3_VY^YRWu{G#wjUn<&9- z@B?9RgCwfG5#f1F_%Q)wzeKa9I&qKw!I=m*6`BMFVwhqyQ%95FIcu+R>83)9be6#2 zK>8)s+Fe?3)n2{m1GG|2C0XFO%N7-VPxL^f)AbhpgtmnrJmMi}!OsGNt-TPS%UEqO zU>Qu^DnknAwCNjJpy_LG>#OX%39K|;_L^r3RjMTP9RsBrELjlU&;8oY%%KCz|A**Ksf-=m|dJseXW~S%t_3#|S zlnqOTE7M)DTv(CD%qq>5bGVPNZq#6g3CkYLxUdqMr+4r!YRX-)qIxEQIog15&=9mS z#egW2AoziR^!s41l&C}}|G^@m-+w%NxGqSK0cek=Jf^7~eJ-UfOxX}LAw*)T2-gZu ztPc!lEt|J}`57y~ebdinYUg^*?a;WScAP+6Xe+d3jQ0>c;ZTeB9#d}4Qd-7|JSOXD z4vze(?}sbn1l|X2lDBX36*kf1{YJ>^w2((Kc66u&KU@ddlnYatnRc^O4Ji#86zCvJ z+?d<2q{R6you(x^46-Rnb2@)UZZUTYY5zQ%qA@qLo6UqUJz-89k|Az%T5onr{YeY1 zS{p3N4j(CLPB3{xxB?^5%*4J;{`vB$`E4+ESTPL7&deWVZ^|~8*Yls|bI$Jj*gY${ z90n3-X3_kERRq%ssUtr5o_0`(-r@%HZ8cq>Y=7IShK_&J_bkN3#S}2VSw1oLe*&u z8YaSdn0OTf<@v<0#JNoEOoyys_!wBh6v1R&(IC(sFhgzCQTu!h?`TJqkInhGFZ;%f zQ4IJ1&#}2e`?=gjzJKrqfuw$nnKU#oXz7~37~s@{IYWFK3{4S2sGeSdri1=s5CcOo z6U=p(Z#=_Z_wYam?q)$3&p7&vt$_m{J3}x<Ej@SWu87d?G~xo^;I7V~Y@=!HB0A^fQQh%=ZY#DG@9gaE!z!Q+U@PP~f?8h45^_8FxFI`hkU> zYC#7A;eLSuCHF}iK3zmvX#99@@$7-1iK5Ti#BUY~(mGDS;S6IplmlEF?-ia+ zE$pZU@IP1@pdcZia=Nvc($RU%TjTv;YXC^!BX^DymlNM#E!3N@k9|1rYRj^Hnk&a1 z#n`p!Nq3;cLk}5qJlfl|5}W8@_Q3bR>v--7p9L6{jMk;oAu+=96N3!CU7gE9!Ww3z zw@;nYSv+Qo!2|QAt$A*}a2h@0Ri_+alNMZY-|Dgeg3rj{+%%UNFz`(>m?vu8_S!K9 zayX3PTknND2Z%uwW>PcrLm%K22gj0yoNt9`jv8sG(>y5SoF;fD&*SM0%w2IOQy6%@JP}RArB65FBS}y!RgM`}kv;&rWdY2L@qtKjCN!4Qwm>B+O%TDsYFN zhNd+<9fH2F`0gOvq%wWlYAJA(4+5yii>QX{M;xmKi z8BSZ!YS*$K0mhk4Pd}{~1?q=}sqX`hfiI3R9iYi(EHpivNXJ(XU^;l$Ab1tx=gRy% z={i!{V%{DM8FR*F4HlLQc#x}g7uYdRTU_kxQd1f1w27%DJTqx@3%ejy+Dg!z3)^*Es`H{T2FCmuUH zPGAZIj-uLnp{vzf2vcVhvDD{MwT)x@QD&sb)2HM4;ZAkaS?iBy$Da7uG}Mvmi+gav zwBpJh?eRCm!s25hBVFnxo2cSh1Bz#@)unP7nHg=2JaZ=g&yW!H>^OUE_JL6O+20^Z zkzFH4#%~@lAU-ZOR^_pJrtys8x+xK_`;dV9ty?K&OH~^B4cawgMEs7SLtU;T=JxG) zW`ObRCGK+B^elk;ar5|b%W-5$vu5$frcH|nu6QR{C`r9NHPY zhn*#8t&Z|Tn@KJYv;jv>d+^Lot>a*F{eUKlsR4W~04E`6hp=9_O3<|0vf0#ZX>hfU z^vDm(3w|jPa82%=aOP7atd)VEI|+0AkvvraKMZL9FvXjvt=w^~vmK=wc`Y? z5AS&W?YOh;$%uT8#tE#8(24zNQZqif+Wt(-*j??Yob?j!zM+uvynONUhiY@gunp@D+o1MJ0P_SCFw6C?scD>y*~O0kA6=i2ypnE4qu?) z^Ur3?=;4DRbZ-%GG)_Q3&z3=}Ui!em#4`m#f`A|(2nYg#fFO_s5b!AHxh#OEbU_dh z1Ox#=KoGd62nb`pr|`?g1pz@o5D)|e0rw&xjNQF>N^OFGARq_`0)oIjMd1Gd^HTA4 Tiim^N00000NkvXXu0mjfdjr9Y literal 0 HcmV?d00001 diff --git a/run.sh b/run.sh index 0c794ea..aad51a1 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,3 @@ #!/usr/bin/env bash -bundle exec jekyll serve --draft --future & -sleep 2 -open http://0.0.0.0:4000 \ No newline at end of file +sleep 3 && open --url http://0.0.0.0:4000 & +bundle exec jekyll serve --draft --future \ No newline at end of file