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 0000000..14d100a Binary files /dev/null and b/problems-answers.pdf differ diff --git a/problems.pdf b/problems.pdf new file mode 100644 index 0000000..d94e438 Binary files /dev/null and b/problems.pdf differ