What my App does

I had an inspiration last night. I realized — after working on it for the better part of two years, including several reboots — what it is that my app does. I’m talking about the worksheet generator, which seems closer to becoming something now than it ever has before.

First, I think I have to mention what it isn’t.

What the worksheet generator isn’t

It isn’t just another application or website that promises to relieve you of the burden of preparing for your classes. I pay for such websites, and I think they have their place, but I’m not going to be able to compete with them, even by doing the job marginally better.

The worksheet generator will not be just one more place that you can go because you didn’t prepare a lesson and you want to print something out for that class that is starting soon.

Working just as much – or more – for more results

I realized that, if I had to tell another ESL/EFL teacher what it was that my app did, it would be to say that it does a lot of the ‘stupid work,’ so that the energy I invest in prep goes farther. There are a couple of ways that this manifests itself.

  • Organization: Never my strong point, this is what the project originally was supposed to help me with. The idea is to have a virtual record of what you’ve done with each group, in terms of grammar and vocabulary. The strength of this organization will be in helping to review regularly. And that brings me to the second thing:
  • Permanence: Maybe this is just one aspect of organization, but it’s something I’ve known that I’m bad at. I’m great at creating a single, excellent, engaging lesson. I’m not so great at tying them together into a series that makes sense in a meaningful way. The extent of my understanding of ‘permanence’ has been to say that, if we’re working on the simple past, I can do a series of lessons about the past.

    What I’d like to do in the future is to have a more clear sense of repetition, by which vocabulary that came up once is reviewed again and again in the exercises of the following week. Even more, I want to say “hey, we practiced this structure three weeks ago, let’s do it again with the vocabulary from last week.” And that brings me to the last main point:

  • Modularity: I don’t know if it’s just me, or if every teacher is lazy, but I suspect it’s the latter. If you’ve ever had a teacher, you probably know what I’m talking about. I’m talking about teachers who create one set of PowerPoint slides and then teach them forever. Or,  in my case, who prepare an exercise for one class and then modify it only slightly for another (generally to realize that I overlooked an inside joke meant for the first class and completely without meaning for the second).

    The thing is this: Most of the worksheets I create in OpenOffice are great for the class they’re created for, and only the top half of page two is really great for the next class. So, I could just copy and paste but, inevitably, searching through everything I’ve made to get enough material feels like more work than creating a wholly new worksheet.

    When I talk about modularity, I’m talking about defining — in XML or in code — the basic structure of some exercises and stringing them together (simple past is comprised of these 10 or so modules, some are text explaining regular, irregular and the ‘be’ verbs in the past, others are exercises practicing them…) and then just saying to my app “hey, we’re starting this block on the simple past” and it adding one module to each worksheet until the block has run its course.

    It’s important to note here that this is going to be in addition to the other ‘modules’ in each lesson’s worksheet — whether they’re vocabulary review or a reading text to take home as homework. And, further, that a module is going to define how the exercise is created, but I’ll still be prompted to generate new content for it (“Enter a sentence in the simple past using word ‘ginormous’.”) (Fun fact: I’ve never taught that word in my lesson.)

Looking at what I’ve written, I’m beginning to think that maybe the unordered list might not have been the best way to get that done. Still, I think that summarizes what will eventually make the worksheet generator different from the “we do the work so you don’t have to” websites.

Advertisements

Colors in Reportlab

Looking for an exhaustive list of the colors in ReportLab, I was surprised that there wasn’t a list of them — with examples — in the ReportLab docs. Then, when I didn’t quickly find one with Google, I thought I would turn this into a test of my coding chops.

So, this might seem simple to you, but I needed quite a few tries to get it going. Which means, of course, that I’m pretty proud of it.

Here’s the code I wrote:

from reportlab.lib.pagesizes import A4
from reportlab.lib.enums import TA_LEFT
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.colors import getAllNamedColors

doc = []
document = SimpleDocTemplate('colors.pdf', pagesize = A4, rightMargin = 72, leftMargin = 72, topMargin = 72,
                                     bottomMargin = 34)
styles = getSampleStyleSheet()

for color in getAllNamedColors():
    styles.add(ParagraphStyle(name=color, alignment=TA_LEFT, fontSize=12, leading=22, fontName='Helvetica',
                              backColor=color))
    doc.append(Paragraph('Test color '+color, styles[color]))

document.build(doc)

And here is the result of the code: colors.pdf

The power of integration

So, this is nominally a podcast recommendation for a recent episode of This American Life, called “The problem we all live with.” And, it’s a great episode of a great podcast. Even more, it touches on something I think often about: why we sometimes don’t do the right thing, even though the data are in front of us.

However, the real reason I’m sharing is that a recent discussion of Black Lives Matter got derailed into a discussion of whether or not systemic racism even exists. (To be fair, the phrase white privilege was thrown about a bit, but I don’t think it’s a good one.) And, if I have to say it, I’m coming down on the side of systemic racism does exist and, no, cops shouldn’t kill black people with impunity.

This podcast is great, though, because I think it demonstrates that the system is not so much “built to favor white people” as a lot of people in the discussion suggested, but that there are a lot of things coming together from overt racism, to a fear of ‘urban kids’ and the crime we’re sure they’ll bring with them, to the inequity of how education is funded in the U.S.

When I think of systemic racism, it’s stories like this one — as well as the innumerable black bodies laying in streets — that I think of. Give it a listen.

Revisiting Tomato Paste

So, if anybody is curious about the results of the infamous tomato paste experiment, I’m declaring it a success. As a really quick recap, I spent the summer grossing out friends and family by eating a table spoon of tomato paste every day in the hopes that it would let me avoid using sunblock.

Of course, I have no way to objectively measure the degree of my exposure to the sun, but it was a sunny summer, both in the U.S. where I spent the hottest four weeks, and here in Germany. And, in that time, I never used sunblock. To be fair, I didn’t try to maximize my sun exposure (it was hot!) except in some early mornings, late evenings. But, neither did I ever say “I can’t go out, I’m not wearing sunblock.”

So, there is bad news for my kids. Next summer they’re stuck eating tomato paste, too. (Though I’m not the only one who decides and when and how much sunblock they have to wear.)

A quick and dirty grammar check in NLTK

So, while working on my app to generate worksheets (update: I’m using it! Like, in practice!) I’ve wanted a way to identify the grammar in a sentence. A quick Google search on doing this in NLTK came up mostly with diagramming sentences and looked more complicated than I am ready to tackle. (Though I will learn!)

So, with a bit of poking around, I found a method that I liked. It works with NLTK’s pos_tag function. And that, in turn, works like this:

>>> import nltk
>>> t = "This is a test."
>>> tokens = nltk.word_tokenize(t)
>>> nltk.pos_tag(tokens)
[('This', 'DT'), ('is', 'VBZ'), ('a', 'DT'), ('test', 'NN'), ('.', '.')]

NLTK actually includes a help interface to help with the ‘DT’ and ‘VBZ’ tags. It didn’t take me long to put together a few ‘patterns’ that basically represented the grammars I was looking for:

self.grammar_lookup = {
            'SIMPLE_PAST': ['VBD', "VBD-did/RB-n't/VB", "VBD-did/VB", "VBD-did/RB-not/VB"],
            'SIMPLE_PRESENT': ['VBZ', 'VBP', "VBP-do/RB-n't/VB", "VBP-do/RB-not/VB", "VBZ-does/RB-n't/VB",
                               "VBZ-does/RB-not/VB"],
            'SIMPLE_FUTURE': ['MD-will/VB', "MD-wo/RB/VB", "MD-will/RB/VB"],
            'COMPARISON_AS': ["RB-not/RB-as/JJ/IN-as", "RB-n't/RB-as/JJ/IN-as", 'RB-as/JJ/IN-as',
                              "RB-not/RB-as/RB/IN-as", "RB-n't/RB-as/RB/IN-as", 'RB-as/RB/IN-as'],
            'COMPARISON_THAN': ["JJR/IN-than", "RBR/IN-than"]
        }

As an explanatory note, the ones with a hyphen in them represent the part of speech tag and the specific word. I did that because I want to reduce the chance of false positives.

From there, it was a fairly straightforward process to start iterating over sentences and looking for these patterns. I’m not especially proud of the code that does this and I think that any serious coder could do it more elegantly, so I’m not going to post that. Still, I’m proud of myself for finding a solution to my problem that works…

…for now. (As you can see, I’ve only tested it on five grammars. I’ll add more as I need them.)

Numbered Lists (sort of) in Reportlab

So, for the first time, I think I may have stumbled upon something I wanted to do in code for which Google searches didn’t give me a quick and easy answer. (When it’s not quick, it generally because I’m slow to understand.)

So, since I worked out a solution for myself and am more than usually proud of it, here’s what I have.

The challenge:
I’m automatically generating worksheets in PDF format. For them, I’d like to have exercises in a nice numbered list. You know the format, it’s pretty easy to do in most software.

  1. This is an exercise.
  2. So is this.
  3. And here is a blank to fill ____________.

However, even though Reportlab includes numbering in the bullets, I spent most of yesterday evening trying to tweak different indentation settings to get it to do what I want. And it didn’t. (Again, maybe I’m slow to understand.)

The problem was that either the text didn’t wrap right (it should wrap to be beneath the beginning of the text, not to beneath the number) or I couldn’t get the spacing between the number and the content to look right.

My solution:
I wound up using a table for this. It’s not what I wanted and I feel like the way I have it is a bit of a hack, with different methods checking the length of the list that feeds the table, so that I can add lines via a method an arbitrary number of times. Nonetheless, I’m pretty happy with how the result looks.

from reportlab.lib.pagesizes import A4
from reportlab.lib.enums import TA_LEFT
from reportlab.platypus import SimpleDocTemplate, Paragraph, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle, ListStyle
from reportlab.lib.units import cm

# We need some text to use. I happen to like this one
lines=['Sing, Goddess, sing of the rage of Achilles, son of Peleus--',
       'that murderous anger which condemned Achaeans',
       'to countless agonies and threw many warrior souls',
       'deep into Hades, leaving their dead bodies',
       'carrion food for dogs and birds--',
       'all in fulfilment of the will of Zeus.']

style=ParagraphStyle(name='Normal', alignment=TA_LEFT, fontSize=12, leading=18, fontName='Helvetica')
doc=SimpleDocTemplate('iliad.pdf', pageSize=A4)
document=[]

# Add a title, straightforward enough.
document.append(Paragraph('<b>The Iliad</b> by Homer', style))

# Now, add numbered lines
epic=[]
for line in lines:
    epic.append([Paragraph('<b>'+str(len(epic)+1)+'.</b>', style),
                 Paragraph(line, style)])

t=Table(epic, colWidths=(1.2*cm, None))
t.setStyle(TableStyle([
    ('VALIGN', (0,0), (-1,-1), 'TOP')
]))

document.append(t)

doc.build(document)

That gives you this result: iliad.pdf.

About it
Obviously, it’s a table which is made up of a list of lists. The various ‘cells’ (entries in one list) need to be wrapped in Paragraph objects in order to get the wrapping and formatting right. (It seems the <b>bold</b> tags are processed by the Paragraph object, if you’re curious.)

As for the TableStyle, that sets the vertical alignment to ‘TOP’ as it defaults to middle, which looks odd when the line it’s numbering wraps.

Lastly, it was interesting — and helpful — to me that the table cells could be numbered with the negative notation that is familiar from things like lists and strings. That is to say that (0,0) is the top left and (-1, -1) is the bottom right, because it counts from the end of the list. That was nice. (I was ready to start doing janky things with len(list) to get it right.)

Maybe I overestimate the problem I solved…
I would be surprised if I’m the only one who has done this, but it is the best I could come up with. I’d be happy to know if I overlooked some obvious solution / reference. How have you (or others) solved the same problem?

From Tree To Shining Tree

In Radio Lab’s latest (lastest in my queue? I’m falling behind) episode, you get to hear about trees communicating, sharing, saving, adapting. I don’t think that any of it is anything that I hadn’t heard before in one place or another, but there wasn’t a moment of it I would have cut… and I was sad when it was over.

Give it a listen! The episode is named From Tree To Shining Tree.