it's a click cli now, and PDF output is working as an option

This commit is contained in:
2020-06-12 11:23:15 +02:00
parent f15f5991f8
commit d337f56cbe
8 changed files with 548 additions and 35 deletions

View File

@@ -14,17 +14,13 @@ A command line application and Python library for generating binary numbers for
```make_binary_problems <number_of_bits: integer> <number_of_problems: integer>``` ```make_binary_problems <number_of_bits: integer> <number_of_problems: integer>```
## TODO ## TODO
- use click to make it a proper CLI, now that there will be multiple options - add nice spacing for easy reading
- make it produce printable sheets, probably as PDFs - pdf options
- add nice spacing for easy reading - problem numbers [true]
- "enscript --columns=4 --output=file --no-header ~/tempfile.ps ~/tempfile.txt" - number of columns [auto]
- "ps2pdf ~/tempfile.ps ~/problems_and_answers.pdf" - font
- include column labels
- options - print
- create_pdf
- number of columns [auto]
- font
- include column labels
### Other Projects That Could Depend On This ### Other Projects That Could Depend On This
- do the problems online/on computer - do the problems online/on computer

View File

@@ -1 +1 @@
BEGIN_ANSWERS_TOKEN = "*BEGIN ANSWERS*" BEGIN_ANSWERS_TOKEN = "\n*BEGIN ANSWERS*\n"

View File

@@ -1,19 +1,37 @@
import sys import sys
import click
from click import ClickException
from app.generate import generate_answers, generate_problems 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(): @click.command()
if len(sys.argv) < 3: @click.argument("bits", type=click.INT)
print( @click.argument("num-problems", type=click.INT)
"please supply the number of bits and the number of exercises you'd like, " @click.option("--pdf", default=False, is_flag=True)
"space-separated, like so:\n\n $ binary 8 100\n" @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]) if pdf and output_filepath and not output_filepath.endswith("pdf"):
num_problems = int(sys.argv[2]) raise ClickException("Please include an output filepath ending in '.pdf'")
problems = generate_problems(bits, num_problems) problems = generate_problems(bits, num_problems)
answers = generate_answers(problems) answers = generate_answers(problems)
@@ -23,5 +41,16 @@ def main():
[f"{problem} | {answer} " for problem, answer in zip(problems, answers)] [f"{problem} | {answer} " for problem, answer in zip(problems, answers)]
) )
sys.stdout.write(BEGIN_ANSWERS_TOKEN.join((problems_string, answers_string))) if pdf:
return problems_string, answers_string 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)

39
app/pdf.py Normal file
View File

@@ -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")

View File

@@ -1,4 +1,3 @@
from app.generate import generate, generate_problems from app.generate import generate, generate_problems
from app.check import check from app.check import check
@@ -6,9 +5,9 @@ from app.check import check
def test_generate(): def test_generate():
first_problem = generate(bits=5) first_problem = generate(bits=5)
assert len(first_problem) == 5 assert len(first_problem) == 5
assert all(char in ('1', '0') for char in first_problem) assert all(char in ("1", "0") for char in first_problem)
assert '1' in first_problem assert "1" in first_problem
assert '0' in first_problem assert "0" in first_problem
def test_generate_problems(): def test_generate_problems():
@@ -16,14 +15,12 @@ def test_generate_problems():
first_problem = problems[0] first_problem = problems[0]
assert len(problems) == 100 assert len(problems) == 100
assert len(first_problem) == 5 assert len(first_problem) == 5
assert all(char in ('1', '0') for char 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_check(): def test_check():
assert check('110') == 6 assert check("110") == 6
assert check('000') == 0 assert check("000") == 0
assert check('001') == 1 assert check("001") == 1
assert check('011') == 3 assert check("011") == 3
assert check('010') == 2 assert check("010") == 2

452
file Normal file
View 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

BIN
problems-answers.pdf Normal file

Binary file not shown.

BIN
problems.pdf Normal file

Binary file not shown.