BOXES v0.13
I hate magic numbers. I love The Magic Numbers but I hate how many numbers are just there in our code. Let's look at some of it:
59# We add a "separation" constant so you can see the boxes individually
60separation = .05
213 pages = [Box(i * 35, 0, 30, 50) for i in range(10)]
221 draw_boxes(text_boxes, pages, output, ("100cm", "50cm"), True)
Magic numbers are what's called a code smell. When you read the code, you
should be wrinkling your nose, like when you get sushi and it smells just a
little bit off. Other code smells include global variables, and separation
is actually both things, a global magic number!
We are deciding that 0.05 is a reasonable separation between letters. We are deciding there are 10 pages , they are 30x50 and next to each other with a specific separation.
Magic numbers are bad because they encode a choice. They are literally us choosing something and making it part of the code. By being inside the code, they are outside the reach of the user. In some cases the magic numbers have a good reason for existing. In others they should just be a default for something the user can change.
There are additional decisions not in numbers, such as using the Arial font, but we will get around to them later.
So, let's get rid of the stench, and make those things part of the interface we provide the user.
Let's go in order.
For separation
we can make an optional argument defaulting to that value.
The pages
list, we could take a page size argument at least. And for the
drawing size, we will just make the drawing big enough to show all the boxes.
A bit trickier is the number of pages, which was hardcoded to 10. We will just create a large number of pages and remove the unused ones.
The first two are just a matter of adding them to the docopt data. The parts
between []
brackets are optional.
1"""
2Usage:
3 boxes <input> <output> [--page-size=<WxH>] [--separation=<sep>]
4 boxes --version
5"""
Then getting the values out of the arguments (or use defaults), and pass them along to our existing functions:
230if __name__ == "__main__":
231 arguments = docopt(__doc__, version="Boxes 0.13")
232
233 if arguments["--page-size"]:
234 p_size = [int(x) for x in arguments["--page-size"].split("x")]
235 else:
236 p_size = (30, 50)
237
238 if arguments["--separation"]:
239 separation = float(arguments["--separation"])
240 else:
241 separation = 0.05
242
243 convert(
244 input=arguments["<input>"],
245 output=arguments["<output>"],
246 page_size=p_size,
247 separation=separation,
248 )
Adding the arguments to convert
and passing them along where they make sense.
Additionally, we calculate the drawing size instead of just hardcoding it.
217def convert(input, output, page_size=(30, 50), separation=0.05):
218 pages = create_pages(page_size)
219 text_boxes = create_text_boxes(input)
220 layout(text_boxes, pages, separation)
221 draw_boxes(
222 text_boxes,
223 pages,
224 output,
225 (f"{pages[-1].w + pages[-1].x}cm", f"{pages[-1].h}cm"),
226 True,
227 )
And making the obvious changes in each function. In create_pages
we use the
page size:
210def create_pages(page_size):
211 # A few pages all the same size
212 w, h = page_size
213 pages = [Box(i * (w + 5), 0, w, h) for i in range(1000)]
214 return pages
In layout
we pass separation
as argument, which requires no changes, and
add a little code to delete the unused pages:
159 # Remove leftover boxes
160 del (pages[page + 1:])
I suggest you take a close look at the diff for this lesson. to see all these small changes in context.
What have we gained for this effort? Well, we can now do things that used to require editing code. Such as using very small pages:
python boxes.py pride-and-prejudice.txt lesson2.svg --page-size=10x20
And that, unfortunately, exposes a whole lot of bugs.
We will get around to them. Don't worry. We just need to do a little more groundwork.
Further references:
- Full source code for this lesson boxes.py
- Difference with code from last lesson