From d337f56cbe6d1f18db1b5b715f838d815cb457c4 Mon Sep 17 00:00:00 2001 From: zevav Date: Fri, 12 Jun 2020 11:23:15 +0200 Subject: [PATCH] it's a click cli now, and PDF output is working as an option --- README.md | 18 +- app/config.py | 2 +- app/main.py | 51 ++++- app/pdf.py | 39 ++++ app/test/test_generate.py | 21 +- file | 452 ++++++++++++++++++++++++++++++++++++++ problems-answers.pdf | Bin 0 -> 4900 bytes problems.pdf | Bin 0 -> 3637 bytes 8 files changed, 548 insertions(+), 35 deletions(-) create mode 100644 app/pdf.py create mode 100644 file create mode 100644 problems-answers.pdf create mode 100644 problems.pdf diff --git a/README.md b/README.md index 8dd8a27..e8168af 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,13 @@ A command line application and Python library for generating binary numbers for ```make_binary_problems ``` ## TODO -- use click to make it a proper CLI, now that there will be multiple options -- make it produce printable sheets, probably as PDFs - - add nice spacing for easy reading - - "enscript --columns=4 --output=file --no-header ~/tempfile.ps ~/tempfile.txt" - - "ps2pdf ~/tempfile.ps ~/problems_and_answers.pdf" - - - options - - create_pdf - - number of columns [auto] - - font - - include column labels +- add nice spacing for easy reading +- pdf options + - problem numbers [true] + - number of columns [auto] + - font + - include column labels + - print ### Other Projects That Could Depend On This - do the problems online/on computer diff --git a/app/config.py b/app/config.py index 445c913..056c57b 100644 --- a/app/config.py +++ b/app/config.py @@ -1 +1 @@ -BEGIN_ANSWERS_TOKEN = "*BEGIN ANSWERS*" +BEGIN_ANSWERS_TOKEN = "\n*BEGIN ANSWERS*\n" diff --git a/app/main.py b/app/main.py index 269e906..b6ae36a 100755 --- a/app/main.py +++ b/app/main.py @@ -1,19 +1,37 @@ import sys +import click +from click import ClickException + from app.generate import generate_answers, generate_problems -from config import BEGIN_ANSWERS_TOKEN +from app.config import BEGIN_ANSWERS_TOKEN +from app.pdf import make_pdf -def main(): - if len(sys.argv) < 3: - print( - "please supply the number of bits and the number of exercises you'd like, " - "space-separated, like so:\n\n $ binary 8 100\n" +@click.command() +@click.argument("bits", type=click.INT) +@click.argument("num-problems", type=click.INT) +@click.option("--pdf", default=False, is_flag=True) +@click.option("--silent", default=False, is_flag=True) +@click.option("--include-answers", default=True, is_flag=True) +@click.option("--output-filepath") +def main( + bits: int, + num_problems: int, + pdf: bool = False, + silent: bool = False, + include_answers: bool = True, + output_filepath: str = None, +) -> None: + + if pdf and silent: + raise ClickException( + "please specify either `pdf` or `silent`, not both (otherwise there " + "won't be any outcome of running the app!" ) - sys.exit() - bits = int(sys.argv[1]) - num_problems = int(sys.argv[2]) + if pdf and output_filepath and not output_filepath.endswith("pdf"): + raise ClickException("Please include an output filepath ending in '.pdf'") problems = generate_problems(bits, num_problems) answers = generate_answers(problems) @@ -23,5 +41,16 @@ def main(): [f"{problem} | {answer} " for problem, answer in zip(problems, answers)] ) - sys.stdout.write(BEGIN_ANSWERS_TOKEN.join((problems_string, answers_string))) - return problems_string, answers_string + if pdf: + make_pdf( + problems=problems_string, + answers=answers_string, + output_path=output_filepath or "problems.pdf", + include_answers=include_answers, + ) + + if not silent: + if include_answers: + click.echo(BEGIN_ANSWERS_TOKEN.join((problems_string, answers_string))) + else: + click.echo(problems_string) diff --git a/app/pdf.py b/app/pdf.py new file mode 100644 index 0000000..769791d --- /dev/null +++ b/app/pdf.py @@ -0,0 +1,39 @@ +import os +import subprocess +import tempfile + + +def make_pdf( + problems: str, answers: str, output_path: str, include_answers: bool +) -> None: + if include_answers: + pdf_jobs = (("problems", problems), ("answers", answers)) + else: + pdf_jobs = ("problems", problems) + + for job_name, job in pdf_jobs: + + if job_name == "answers": + path, filename = os.path.split(output_path) + new_filename = f"{filename.split('.pdf')[0]}-answers.pdf" + output_path = os.path.join(path, new_filename) + + with tempfile.NamedTemporaryFile(mode="w") as txt_file: + txt_file.write(job) + txt_file.flush() + + command = ( + f"enscript --columns=4 --no-header --output=tempfile.ps {txt_file.name}" + ) + print(f"command: '{command}'") + + output = subprocess.check_output(command, shell=True) + print(output) + + command = f"ps2pdf tempfile.ps {output_path}" + print(f"command: '{command}'") + + output = subprocess.check_output(command, shell=True) + print(output) + print("made pdf", output_path) + os.unlink("tempfile.ps") diff --git a/app/test/test_generate.py b/app/test/test_generate.py index 36566d5..ea894f7 100644 --- a/app/test/test_generate.py +++ b/app/test/test_generate.py @@ -1,4 +1,3 @@ - from app.generate import generate, generate_problems from app.check import check @@ -6,9 +5,9 @@ from app.check import check def test_generate(): first_problem = generate(bits=5) assert len(first_problem) == 5 - assert all(char in ('1', '0') for char in first_problem) - assert '1' in first_problem - assert '0' in first_problem + assert all(char in ("1", "0") for char in first_problem) + assert "1" in first_problem + assert "0" in first_problem def test_generate_problems(): @@ -16,14 +15,12 @@ def test_generate_problems(): first_problem = problems[0] assert len(problems) == 100 assert len(first_problem) == 5 - assert all(char in ('1', '0') for char in first_problem) - assert '1' in first_problem - assert '0' in first_problem + assert all(char in ("1", "0") for char in first_problem) def test_check(): - assert check('110') == 6 - assert check('000') == 0 - assert check('001') == 1 - assert check('011') == 3 - assert check('010') == 2 + assert check("110") == 6 + assert check("000") == 0 + assert check("001") == 1 + assert check("011") == 3 + assert check("010") == 2 diff --git a/file b/file new file mode 100644 index 0000000..fd51aa3 --- /dev/null +++ b/file @@ -0,0 +1,452 @@ +%!PS-Adobe-3.0 +%%BoundingBox: 24 24 571 818 +%%Title: Enscript Output +%%For: Zev B Averbach +%%Creator: GNU Enscript 1.6.6 +%%CreationDate: Fri Jun 12 11:18:09 2020 +%%Orientation: Portrait +%%Pages: (atend) +%%DocumentMedia: A4 595 842 0 () () +%%DocumentNeededResources: (atend) +%%EndComments +%%BeginProlog +%%BeginResource: procset Enscript-Prolog 1.6 6 +% +% Procedures. +% + +/_S { % save current state + /_s save def +} def +/_R { % restore from saved state + _s restore +} def + +/S { % showpage protecting gstate + gsave + showpage + grestore +} bind def + +/MF { % fontname newfontname -> - make a new encoded font + /newfontname exch def + /fontname exch def + + /fontdict fontname findfont def + /newfont fontdict maxlength dict def + + fontdict { + exch + dup /FID eq { + % skip FID pair + pop pop + } { + % copy to the new font dictionary + exch newfont 3 1 roll put + } ifelse + } forall + + newfont /FontName newfontname put + + % insert only valid encoding vectors + encoding_vector length 256 eq { + newfont /Encoding encoding_vector put + } if + + newfontname newfont definefont pop +} def + +/MF_PS { % fontname newfontname -> - make a new font preserving its enc + /newfontname exch def + /fontname exch def + + /fontdict fontname findfont def + /newfont fontdict maxlength dict def + + fontdict { + exch + dup /FID eq { + % skip FID pair + pop pop + } { + % copy to the new font dictionary + exch newfont 3 1 roll put + } ifelse + } forall + + newfont /FontName newfontname put + + newfontname newfont definefont pop +} def + +/SF { % fontname width height -> - set a new font + /height exch def + /width exch def + + findfont + [width 0 0 height 0 0] makefont setfont +} def + +/SUF { % fontname width height -> - set a new user font + /height exch def + /width exch def + + /F-gs-user-font MF + /F-gs-user-font width height SF +} def + +/SUF_PS { % fontname width height -> - set a new user font preserving its enc + /height exch def + /width exch def + + /F-gs-user-font MF_PS + /F-gs-user-font width height SF +} def + +/M {moveto} bind def +/s {show} bind def + +/Box { % x y w h -> - define box path + /d_h exch def /d_w exch def /d_y exch def /d_x exch def + d_x d_y moveto + d_w 0 rlineto + 0 d_h rlineto + d_w neg 0 rlineto + closepath +} def + +/bgs { % x y height blskip gray str -> - show string with bg color + /str exch def + /gray exch def + /blskip exch def + /height exch def + /y exch def + /x exch def + + gsave + x y blskip sub str stringwidth pop height Box + gray setgray + fill + grestore + x y M str s +} def + +/bgcs { % x y height blskip red green blue str -> - show string with bg color + /str exch def + /blue exch def + /green exch def + /red exch def + /blskip exch def + /height exch def + /y exch def + /x exch def + + gsave + x y blskip sub str stringwidth pop height Box + red green blue setrgbcolor + fill + grestore + x y M str s +} def + +% Highlight bars. +/highlight_bars { % nlines lineheight output_y_margin gray -> - + gsave + setgray + /ymarg exch def + /lineheight exch def + /nlines exch def + + % This 2 is just a magic number to sync highlight lines to text. + 0 d_header_y ymarg sub 2 sub translate + + /cw d_output_w cols div def + /nrows d_output_h ymarg 2 mul sub lineheight div cvi def + + % for each column + 0 1 cols 1 sub { + cw mul /xp exch def + + % for each rows + 0 1 nrows 1 sub { + /rn exch def + rn lineheight mul neg /yp exch def + rn nlines idiv 2 mod 0 eq { + % Draw highlight bar. 4 is just a magic indentation. + xp 4 add yp cw 8 sub lineheight neg Box fill + } if + } for + } for + + grestore +} def + +% Line highlight bar. +/line_highlight { % x y width height gray -> - + gsave + /gray exch def + Box gray setgray fill + grestore +} def + +% Column separator lines. +/column_lines { + gsave + .1 setlinewidth + 0 d_footer_h translate + /cw d_output_w cols div def + 1 1 cols 1 sub { + cw mul 0 moveto + 0 d_output_h rlineto stroke + } for + grestore +} def + +% Column borders. +/column_borders { + gsave + .1 setlinewidth + 0 d_footer_h moveto + 0 d_output_h rlineto + d_output_w 0 rlineto + 0 d_output_h neg rlineto + closepath stroke + grestore +} def + +% Do the actual underlay drawing +/draw_underlay { + ul_style 0 eq { + ul_str true charpath stroke + } { + ul_str show + } ifelse +} def + +% Underlay +/underlay { % - -> - + gsave + 0 d_page_h translate + d_page_h neg d_page_w atan rotate + + ul_gray setgray + ul_font setfont + /dw d_page_h dup mul d_page_w dup mul add sqrt def + ul_str stringwidth pop dw exch sub 2 div ul_h_ptsize -2 div moveto + draw_underlay + grestore +} def + +/user_underlay { % - -> - + gsave + ul_x ul_y translate + ul_angle rotate + ul_gray setgray + ul_font setfont + 0 0 ul_h_ptsize 2 div sub moveto + draw_underlay + grestore +} def + +% Page prefeed +/page_prefeed { % bool -> - + statusdict /prefeed known { + statusdict exch /prefeed exch put + } { + pop + } ifelse +} def + +% Wrapped line markers +/wrapped_line_mark { % x y charwith charheight type -> - + /type exch def + /h exch def + /w exch def + /y exch def + /x exch def + + type 2 eq { + % Black boxes (like TeX does) + gsave + 0 setlinewidth + x w 4 div add y M + 0 h rlineto w 2 div 0 rlineto 0 h neg rlineto + closepath fill + grestore + } { + type 3 eq { + % Small arrows + gsave + .2 setlinewidth + x w 2 div add y h 2 div add M + w 4 div 0 rlineto + x w 4 div add y lineto stroke + + x w 4 div add w 8 div add y h 4 div add M + x w 4 div add y lineto + w 4 div h 8 div rlineto stroke + grestore + } { + % do nothing + } ifelse + } ifelse +} def + +% EPSF import. + +/BeginEPSF { + /b4_Inc_state save def % Save state for cleanup + /dict_count countdictstack def % Count objects on dict stack + /op_count count 1 sub def % Count objects on operand stack + userdict begin + /showpage { } def + 0 setgray 0 setlinecap + 1 setlinewidth 0 setlinejoin + 10 setmiterlimit [ ] 0 setdash newpath + /languagelevel where { + pop languagelevel + 1 ne { + false setstrokeadjust false setoverprint + } if + } if +} bind def + +/EndEPSF { + count op_count sub { pos } repeat % Clean up stacks + countdictstack dict_count sub { end } repeat + b4_Inc_state restore +} bind def + +% Check PostScript language level. +/languagelevel where { + pop /gs_languagelevel languagelevel def +} { + /gs_languagelevel 1 def +} ifelse +%%EndResource +%%BeginResource: procset Enscript-Encoding-88591 1.6 6 +/encoding_vector [ +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/space /exclam /quotedbl /numbersign +/dollar /percent /ampersand /quoteright +/parenleft /parenright /asterisk /plus +/comma /hyphen /period /slash +/zero /one /two /three +/four /five /six /seven +/eight /nine /colon /semicolon +/less /equal /greater /question +/at /A /B /C +/D /E /F /G +/H /I /J /K +/L /M /N /O +/P /Q /R /S +/T /U /V /W +/X /Y /Z /bracketleft +/backslash /bracketright /asciicircum /underscore +/quoteleft /a /b /c +/d /e /f /g +/h /i /j /k +/l /m /n /o +/p /q /r /s +/t /u /v /w +/x /y /z /braceleft +/bar /braceright /tilde /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/.notdef /.notdef /.notdef /.notdef +/space /exclamdown /cent /sterling +/currency /yen /brokenbar /section +/dieresis /copyright /ordfeminine /guillemotleft +/logicalnot /hyphen /registered /macron +/degree /plusminus /twosuperior /threesuperior +/acute /mu /paragraph /bullet +/cedilla /onesuperior /ordmasculine /guillemotright +/onequarter /onehalf /threequarters /questiondown +/Agrave /Aacute /Acircumflex /Atilde +/Adieresis /Aring /AE /Ccedilla +/Egrave /Eacute /Ecircumflex /Edieresis +/Igrave /Iacute /Icircumflex /Idieresis +/Eth /Ntilde /Ograve /Oacute +/Ocircumflex /Otilde /Odieresis /multiply +/Oslash /Ugrave /Uacute /Ucircumflex +/Udieresis /Yacute /Thorn /germandbls +/agrave /aacute /acircumflex /atilde +/adieresis /aring /ae /ccedilla +/egrave /eacute /ecircumflex /edieresis +/igrave /iacute /icircumflex /idieresis +/eth /ntilde /ograve /oacute +/ocircumflex /otilde /odieresis /divide +/oslash /ugrave /uacute /ucircumflex +/udieresis /yacute /thorn /ydieresis +] def +%%EndResource +%%EndProlog +%%BeginSetup +%%IncludeResource: font Courier-Bold +%%IncludeResource: font Courier +/HFpt_w 10 def +/HFpt_h 10 def +/Courier-Bold /HF-gs-font MF +/HF /HF-gs-font findfont [HFpt_w 0 0 HFpt_h 0 0] makefont def +/Courier /F-gs-font MF +/F-gs-font 10 10 SF +/#copies 1 def +% Pagedevice definitions: +gs_languagelevel 1 gt { + << + /PageSize [595 842] + >> setpagedevice +} if +/d_page_w 547 def +/d_page_h 794 def +/d_header_x 0 def +/d_header_y 794 def +/d_header_w 547 def +/d_header_h 0 def +/d_footer_x 0 def +/d_footer_y 0 def +/d_footer_w 547 def +/d_footer_h 0 def +/d_output_w 547 def +/d_output_h 794 def +/cols 4 def +%%EndSetup +%%Page: (1) 1 +%%BeginPageSetup +_S +24 24 translate +/pagenum 1 def +/fname (/Users/zev/hi.txt) def +/fdir (/Users/zev) def +/ftail (hi.txt) def +/user_header_p false def +/user_footer_p false def +%%EndPageSetup +5 781 M +(11101010 | 234 ) s +5 759 M +(01011011 | 91 ) s +5 737 M +(00001000 | 8 ) s +5 715 M +(01110100 | 116 ) s +5 693 M +(11000101 | 197 ) s +_R +S +%%Trailer +%%Pages: 1 +%%DocumentNeededResources: font Courier-Bold Courier +%%EOF diff --git a/problems-answers.pdf b/problems-answers.pdf new file mode 100644 index 0000000000000000000000000000000000000000..14d100a7330409ba65f3a62742d99eb42ba58a85 GIT binary patch literal 4900 zcmcIod0Z3M6Q^oaqWy_juc~zgF`_1$%}F8&4IvygQ3M18QI}*BwuEflY#_+50sy1)xv4}_2-`fxnk5BcF{uTo7&6}AwZ|3`EXW|j; z=g&qs0;b2I+l%s<9v(raL_&*FIAM}PW|WG94Hk$U%!b&yP=&uL1pHSL319(_#f>CqYNm3*wY+*W5)$6CWNX0r>`PR83?g0eyZ>wrAlVj>61vD!T`lW=?E(&^8v$g zR?2Lp{Ba{jPd{WQAEprELPYGBOmA;kjhPHo93%pi5Ey8oFcO0OjVOirVOm0m$>mH7 zMPg_I)3*AnWtC&AxGt)c)U45)j`B_~yjr2kFZigwN1yb}%?#P#MY0~}7(>Q?Qm?y^ z^!J@Vnd9!9YQA&#m((3Zm$j3pY^zL_p1&Gxy7br_+kJ5MMb9T!@Ah%|UG?Hn!Q$Nm zr*Jx@?yShVnfs{L$N5ELuyf{wrDBh`#k=s{g%PU4HBY#aNyA)cxw{u#_P_#l4|=ZR zo$Z?4r}(RKjXJw9Zx*?7(-PghQ-SX6bD!i8a zd4ca4g=|<8j+2>CukJC~vK)>7FlK+x<=GW6B`YPpQ+FN-3u|^s#`-Q<lI*qGdTzM|4FC&u#8{vT?I*nD3Kk z4b>fPh367if;|=}CZ=v3RZx@mlXl0*yns!Q!Z$v9V*at|1w-HKkIR@z=LiNE(E+5x zM*rt~iJ1NEQFQRVio|9V3N>=VpKE?kPg9Nn2iE*{sdr_*qH%9U?rFWaSKEPD})J14r6G2 zEkFaJz#7PIjs=3p;5rKw&7_SDVa7l}gB55*+KD1JFA7;^#xS1kI$ct8B zH^sgJe1MY{N*RZe5T6IDQO6kJ3E&7^N5xs9nIbOj?SJ^ObI@Gx`?S-+Y&U+F?*H35 zun|OFWdd0NHyOYoO$w6*Z<~i&V<{SMbeNG>j;7t_aI_tcw5MNr)$Yo%Xae>w=lcS{ z0=%+$LI7Dr1bOj`XY6!VM}P@yP+R*MZn1!qwxiI|26tgN9E*~$eKS~(Ct|Q3 zKn6D9CJZLP#{zP?3AW%i*kVSt7)-^H82Fn+z|%33@U)+q&ewbhp<~$&mG-&ATP?Y` zeJTcaxA(EKHFqOCkwh+U%hEZ^Qq-Xl0}uULbUfw8h_N@D((hg{AtRRbPoASz^PfqMr{5hs&fUK3M>5&m0vK(>)H_G&9AHdGE%myzh&#r zZNKJPUQ)5*dF8dg9~_ZLcHe7me)4_BN2xw(XuqCt#E8bi?Bb>YH6imSUO%++aK>%t zgL0Ycp5P9_o_p4$bz!_H{+6+`S7y}pg2v>)=gj+u%9>*5YA5*res^(QiSlZFm%8ko z=bN)tm*e-lsh-v5-(cl;`ZI6mr0?Pw5$g)G8Z%TO6Q_MK`GmZF$dX9TvUyvJ=bbFv zet6%m?3Z_cZg~7s;N&!O3%cX}oL{Wt^P5f(9k_!#r5mA6-JG2J%v3pboVVB2rTcYq zc$$xG@Fb_xWp$CubN7t9*PJ=~fvEA=v)TM*={1GNj{Nub0hjxEeEDZu-_W7VMT)fg za$EOqXUroy8Qc#>rkSe-E1guqxAzAO9ccVCG3Lu@l{qV^+9nfW8Fgd+#vC_UGVVnU ztGr9EbNJ<|v|BaxC3}5LPp3cGs7^RB`?);UVPezHB4D?i{aD1-28EFUCpSj zXR-=<>>3(f6(DJto#y|d{G$_AIBGv*^ZD5u1|Csgx48CyQd++48^O8it&Lm4wFO0M z562eEr<`0v`NND2>1d04qccK*`CpIjory?bX!=hDV}I z-IfhI_H~%sx#Z}Kn=yuPa;e+sjD>%EJAiR^dDlm!&LS7a3%Mx0Z2gM4#|!VAEZtsP z?bR4I`{>zA=3Lp!^e(RMyRMx1xc`;R$m(x=ddP))O0)IXdo9$uSr+Qt4I>sV5C3|9 zqsMg4%+B+Qs(tgCYO2##@wJT;&fUd}SB`Q&$SbO-<7gxkLs(gjDIo!GxUmhMgW;<8y|J6?czsbBC$4B&# zNw0mEHU|ox^a)efG2)=#PiGo`S~n%&>9JJnLTTcvQ5T5XhRs>YC--k(9QeD$OL~EC zi~noWwI-1?_}0`@lH>cHibh`)A9~h!K$?Cjwsb1T>7`R{{oD?8x$$~oWUmI=zjs>e zgjY#4QloB{L3wQ%)Z51f(sn#XL9v(tH_2G{DtEIWTqk2i2sK=dS&7BrfzwGWbo$sZ z?euu9M91=xGrfIKP?2G58)|0j?PatD0?~Bgj1{Q~hA4tC#^V&k<48CN8{v^5<)&{K&L~o47(k+Oan?noePTs8sEC4-xa6B-Qf^t%4)ncTK z6%ee30^$e@)lyj;E`%Tt)fr%12wLk$K`R^ToD`L zg&~NPhe-LtkzAgX%k_re-~^!pqDs*8fF|ezMB8z;6D7zn5I}jr*a?uz{qkj(`TTP)=2F%eJC;|e7o8V{Ye%?`7bG}>{e)54e$qqnqxkRZSiomQ$R$OM!E z$TyooL1yohVE_x3+#4)XIAz4-c2p~jRLj7K)A*7aQIkQ&vaxlT9<>@Nmb}fUE&Xqc z3yrwEWndSFU#SEmEgesSY|$EuO>0#zkHQimUr3RNk+G;Y?hQqDMp{vu_&Z`{CxLqi z!Unh9ayb+Fm!5x5(W}%35~hrmL?^DdQYY_K#ol^ZhrjEtdV-pp_9@5*^eq|gC#A0# zSA_5o@N2(aEH0M?*uX7(4@Vds`QdNG6i6zpR2)GLe?3I48-s^9w1 literal 0 HcmV?d00001 diff --git a/problems.pdf b/problems.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d94e438ed01d185ad42ef8b4f42aecb0ac326612 GIT binary patch literal 3637 zcmb_feQZuQ7B~RzSxNg zXz)j$6y@G?&(}S_bIv`^tMvIP)>LT7J9=*89}IbUCc2AjU+kr2;qMmmbomLW>@mDZGdOI-;e z+^mQi28yZ)a#C{@!*XF#OC&YF7{g?HHj$N}3U2k0W!;^jE+aEse9iNn%_XHW^#e2qO z-?DS*{AEiwo;`F{`E+1hPyJh~el>Z@^KCmefB45GiRtaH{^Yc8_nsdG+UD~!xBO<# zpKHo3^Cs{7wego8_q3|Ek}mV6C1(e#a@^;iHC?+g<_D<_k7G$l2Lx6mh$M96KW8-? z(xk$X_*S^y9Tm~@ zML|V%2I6ecP>(=E5;U?|qU^9}TGGpFd`ymHRHR)EV2u~x1hGUQ+ct48ooGRu!A4F; z93$KGVF|Bdav!?3q@udkI~QD0M6Bp{^kFrmhzU(rP!Um}yP}^!3`9lMyis03g++7) zpIO`K#iF1^)jETfVG#U5xJo5RXHkm4k>rt7ad=;qY|scuBsT1bcf?lOI~_yf-iRx@ z{RMm+-_GL_Kp8-$SPLq&n~{~Z){~NgPGGfItabzKQ9}f2H~`%EL>U$%(e##>AZH+K zpvxiJV#p&!VvrvOx=O?G8e}trf~JUmLvl9*9pF>hJ5g2Po>>$UAV{BlT9z=~iWPab z?mp=mX2gE2ki)?Q9aH9wbkMRwU3Bu=z<23bN1$FOV z_pB*1-`K=o{b^Tk{pBqekIw9wsW@|7&JBIeV|~toXI9rvTqQs7?b7bSgFpYp6YJXk z(Q9kn>yK8xzHRT`=hW36yeQo2U%$5PiC5N_1h?K>?SC_GPm$TR&~yJg`SZKqrQZp3 z&%M}nEHHPj?t3yPXKhYN&an*}3f9%F zdE?Zzi`&Be8@FDY)@eRB`1hvb%NyT5-|p+2@$u@&mF|I;`UY%=ypeHRi`7 zgU6dLKYX}3w6A|s`!h}LFRu*0_1w5u7oYg}gM<54CO=wxZt4p4;Qm$iijxPf&v9M; z^u663JA(UPIMDIM=AFksS=6%k+Su{$@0NZa==!gDP3QH`wtsp4fx)MHdV2o7ri5@fNp6?g$)*I2yT}m&_?0n7??Fgc^fr<>AENA70 z@pKla5`3r;Yp4N7M9F15f1uNdM8RcTYzZ)dgcnD}vQ`CGwJxX*wKj(Ag0a|ba2E5> z9x;{T6I57#S5ySK?aK8AEBv{HA>~NL6LIJrC>4)Msw3qx>LLzU6EbZ?I*ZomGS&tv zktWNrMo~kY$!=mP)?9$X3RGaTtTZClWHo|HMF=~B&?AQw!p>!kYFffU)6LDzre?EA zRw6WOx7%rkqdATO4oYp2G(JU1>MXFQJ0jK&i%F_U_sJBJ$1Ay*%3m!$KQu>Mjo5?z4PZvt_IB62ktSjGT5cotO@c+XQkCUt-d=8%+ zO2(npmit`BWKt9yyqyy`vzeiAAkP>68EC>0+>eXdr7$dMj72biPnjoJ)3R95uB(y7%L}Ip}Fv`$uW|#-bholV6%oN^2W2`oqGjHZ&EVs%s zY!QTSG#?B`qiIDP3|yo5>_sqJkD}S^FjL-4W4T-HvNk5mhNkc^>nQMh2hmkxD~1># zn7d_|cFFLA=yEA6BZ4yw-sL`2=d>|C&g)?ro8MwDvUy5vR-eUhXMA1{