My Book Picker (and Lister)

2018 Version

This is an updated version of a "geekery" post from last year. I've made substantial changes to the script it describes since then. I'm leaving the former (and much simpler) version in place, but also wanted to show off my new version.

But one thing hasn't changed at all: it's another example of the mental aberration that causes me to write Perl scripts to solve life's little everyday irritants. In this case two little irritants:

  1. I noticed that I had a lot of books on my shelves, acquired long past, that I never got around to reading. Either because (a) they were dauntingly long and dense (I'm thinking about Infinite Jest by David Foster Wallace); or because (b) they just fell through the cracks. Both poor excuses, but there you are.

  2. I sometimes want to methodically read a series of books in a particular order.

In other words, I needed a way to bring diligence and organization to my previous chaotic and sloppy reading habits.

I think of what I came up with as the "To-Be-Read" (hereafter TBR) database. That's a slightly lofty title, but anyway:

The high-level view: all the TBR books are in zero or more stacks, each stack containing zero or more titles. Each stack is maintained in the order I want to read the books therein. (This goes back to the issue mentioned above: sometimes a series really "should" be read in publishing order, for example C.J. Box's novels featuring protagonist Joe Pickett.)

So picking a book to read involves (a) choosing an "eligible" stack; and (b) "popping" the top book from the chosen stack. Very computer science-y.

The interesting part is the "choosing an eligible stack" step. There are a number of possible ways to do it. But first, more details on "eligibility".

The major problem with the previous version of this script was that too often it would pick a book "too soon" after I'd read something off the same stack. (An issue mentioned in last year's post.) As it turns out, I wanted to let some time go by between picks from the same stack. (For example, at least 30 days between books by Heinlein. Too much of a good thing, too soon…)

So: in this version, each stack has an "age": the time that's elapsed since I previously picked a book from that stack. And a "minimum age", the amount of time that must elapse after a pick before that stack becomes eligible again.

Another minor difference: I don't actually own some of the books in some of the stacks yet. I want to read them someday. But I'm waiting, typically for the price to come down, either via the Barnes & Noble remainder table or the Amazon used market. I'm RetiredOnAFixedIncome, after all.

So an eligible stack is one that:

  • is non-empty;
  • the top book is owned;
  • the stack is older than its specified minimum age.
OK, so how do we choose among eligible stacks? Possibilities:
  1. Pick the "oldest" stack; the one for which it's been the longest time since a book from it was previously picked.
  2. Pick the highest stack, the one with the most titles therein. (Because it needs the most work, I guess.)
  3. Just pick a stack at random.
  4. Pick a random stack weighted by stack height. That is, any stack can be picked, but one with eight titles in it is twice as likely to be picked as one with four titles. (This was the algorithm used in the previous version.)
  5. Pick a random stack, weighted by age. That is, a stack that's 90 days old is twice as likely to be picked as a 45-day old one.
  6. But what I'm doing is a combination of the last two: the stack-weighting function is the stack height times the stack age. So (for example) a 120-day-old stack with 5 titles is twice as likely to be picked as a 50-day-old stack with 6 titles. Because 120 * 5 = 600 and 50 * 6 = 300. This is totally arbitrary, but it seems to work for me so far.

Here's my current take on scripting that.

Each stack is implemented as a comma-separated values (CSV) file, headerless, one line per book, each line containing two fields:

  1. The book title;
  2. Whether I own the book yet (1/0 = yes/no).
For example, here's the current content of moore.csv, containing the to-be-read books of Christopher Moore:

"The Serpent of Venice",1
"Secondhand Souls",1
Noir,0

I.e., three books, the first two owned, the third one, Noir, unpurchased as yet. (I'll get it someday, and edit the file to change the 0 to 1.)

There is a "master" CSV file, stacks.csv. It has a header (for some reason that I forget). Each non-header line contains data for a single stack:

  1. The (nice human-readable) stack name;
  2. The stack ID (corresponding to the name of the stack file);
  3. The minimum time, in days, that should elapse between consecutive picks from that stack;
  4. The date when a book was most recently picked from the stack.
As I type, here's what it looks like:

name,id,minage,lastpicked
"Chronicles of Amber",amber,42,2018-04-15
"C.J. Box",box,30,2018-06-16
"Michael Connelly",connelly,30,2018-06-22
"Continental Op",continental_op,30,2018-06-09
"Conservative Lit 101",conservative_lit_101,60,2017-09-07
"Elmore Leonard",elmore,30,2018-06-28
"Dick Francis",francis,30,2018-04-20
"General Fiction",genfic,30,2018-06-13
"Steve Hamilton",hamilton,30,2018-04-29
"Robert A. Heinlein",heinlein,30,2018-06-19
Monkeewrench,monkeewrench,30,2018-05-28
"Christopher Moore",moore,30,2018-04-23
Mystery,mystery,30,2018-01-04
Non-Fiction,nonfic,30,2018-07-01
"Lee Child",reacher,30,2017-12-29
"Science Fiction",sci-fi,30,2018-05-30
Spenser,spenser,30,2017-05-01
"Don Winslow",winslow,30,2018-03-02

No comments from the peanut gallery about my lack of literary taste, please.

Picking a random stack according to a weighting function isn't hard. I'd pseudocode the algorithm like this:

Given: N eligible stacks (indexed 0..N-1), with Wi being the calculated weight of the ith list (assumed integer) …

Let T be the total weight, W0 + W1 + ⋯ + WN-1

Pick a random number r between 0 and T-1.

p = 0
while (r >= Wp)
     r -= Wp
     p++

… and on loop exit p will index the list picked.

To anticipate CS pedants: I know this is O(N) and using a binary search instead could make it O(log N). In practice, it's plenty fast enough. And other steps in the process are O(N) anyway.

Enough foreplay! The "picking" script, bookpicker, is here. A prettyprinted HTML version is here. Notes:

  • The Text::CSV Perl module is used for reading/writing CSV files. The Time::Piece and Time::Seconds modules are invaluable for doing the simple age calculations and comparisons.

  • You just run the script with no arguments or options; output is the title and the name of the picked list.

  • The user is responsible for maintaining the CSV files; no blank/duplicate lines, etc. I use My Favorite Editor (vim), but CSVs are also editable with Your Favorite Spreadsheet.

  • For the "picked" stack, the script writes a smaller file with the picked title missing. The old stack is saved with a .old appended to the name. The stacks.csv file is also updated appropriately with today's date for the last-picked field for the picked stack.

  • The weighting function and random number generation are constrained to integer values; I think it would work without that, but who wants to worry about rounding errors? Not I.

I also have a couple scripts to list out the contents of the to-be-read database.

  1. A script that produces plain text output (on stdout) is here. A prettyprinted HTML version is here.

  2. A script that produces an HTML page and displays it in my browser (Google Chrome) is here. A prettyprinted HTML version is here. It uses text color to signify eligible/ineligible stacks and owned/unowned books. Sample output (again, comments on my literary taste, or lack thereof, are welcome) is here.

    The HTML::Template module is used to make output generation easier, and the template used for that is here; note that it probably won't show up nicely in a browser.

    Getting it to show up in my browser is accomplished via chromix-too server/client/extension; if you don't have it, it's pretty easy to do something else instead.

Whew! I feel better getting this off my chest..


Last Modified 2018-07-03 3:40 PM EDT

Replacing TPGoogleReader

Futurama quote pattern

Note: No actual code here.

Back in July 2013, Google discontinued its "Reader" RSS/Atom feed aggregation service. Basically: you subscribed to a number of websites via their syndication feeds. Google would periodically query the feeds for new content. It would also keep track of what articles you had "read". (More accurately: marked as read. You didn't actually have to read them.) There are a number of services that do that sort of thing. I used Reader because of the independently-developed TPGoogleReader Chrome extension. Specifically, for one lousy feature of TPGoogleReader. You could get it to:

  1. Query Google Reader for your unread articles;

  2. Automatically open up a number of browser tabs showing unread articles, up to a specified maximum;

  3. And this is the critical part: when I closed an auto-opened tab, TPGoogleReader would open up the next unread article in a new tab in the background.

This made browsing a large number of sites an efficient breeze. When I finished reading one article, a tab-closing control-W all by itself would bring up a new background tab with the next unread article in my feed. No mouse-messing. Concentrate on reading content. Bliss.

It took a few years, and numerous false starts, but I'm back at that point again. Here's how:

  • I moved to a free Inoreader account to take over the RSS feed monitoring. They are reliable, active, and seem to be hanging around.

  • I wrote a "fetch" Perl script that uses the WebService::Google::Reader to log into Inoreader and download unread article data. As you might guess from the name, the module author originally developed for Google Reader, but graciously made the necessary changes to make it work with Inoreader.

    I run this script periodically via anacron.

  • The final bit of the puzzle was the Chromix-Too extension for the Google Chrome web browser. This consists of a JavaScript client/server pair that communicate over a Unix-domain socket. The client bit has a simple command interface, and I only use two of them:

    1. Tell me how many tabs the browser has open:

      chromix-too raw chrome.tabs.query '{}'

      The output is a mass of detailed JSON, but that's pretty easy to parse.

    2. Open a new tab in the background with a specified URL:

      chromix-too raw chrome.tabs.create '{"active":false,"url":"URL"}'

I'm leaving out a lot of details, but they are pretty straightforward (and of very little general interest): storing a local list of unread articles, figuring out whether it's appropriate to open one (and if so which one), time delays, etc. I wrap all this logic in a "reader" Perl script which I run whenever I have the browser running.

But I'm back to web-surfing Nirvana again, so that's good. The only downside (sort of) is that all this happens on a single (Linux) host. That's OK for me.


Last Modified 2018-01-06 4:43 AM EST

An HTML Calendar Generator

Awhile back I replaced the (increasingly unwieldy) monthly archive section over there in the right-hand column with a yearly archive section: one link per year that Pun Salad has been in existence. Each link takes you to a yearly calendar, which, in turn, contains links to the monthly archives (when you click on a month name) or daily posts (when you click on a day). Example output here for 2017.

The code to generate those calendars is embedded in the (very) special purpose CGI script that powers Pun Salad, but I thought the calendar generation code might be of interest to people. So, shorn of all the blog-specific stuff: Perl script here, prettyprinted HTML here.

Notes:

  • The script is run with a single year argument, and produces HTML on standard output.

  • The Perl module Time::Piece does most all of the heavy lifting for the necessary date calculations. It probably breaks down for years far in the past or future; I haven't messed with that too much. I tested that it gives the same calendar for 1901 as the Linux cal command does, so that's good.

  • The HTML::Template module is used to specify the HTML framework for the calendar. Obviously, that's where you might want to customize the appearance. The bare-bones template is here. (The code assumes the template resides in your top-level ~/Templates directory.)

  • The calendar is a table of months; each month is a table of days. This means, of course, that the generator is essentially a four-deep nested loop. Eek! A voice from my old structured programming days said: "you really shouldn't nest loops that deeply". So I broke out the month-generation into a Perl subroutine, and now I feel better about myself.

As usual, this is not earth-shattering code, but I hope someone finds it useful, if only for tutorial purposes.

Bing Desktop Backround Picture Downloading

For Fun and (No) Profit

For a few years now, I've made the Important Life Choice about my computer's desktop backgrounds (aka "wallpaper"): downloaded photos of spectacular vistas, amazing animals, breathtaking architecture, … I'm not particular. Rotate them every so often to avoid boredom. This is often called a "slideshow".

This, even though my open windows usually obscure the background. I know it's there though, and it makes me happy. (And the Start-D key combo works to minimize all windows if I really want to peruse it.)

The OS environments I use (Windows 10, Fedora Linux/Cinnamon) make it easy to configure a slideshow: just find the configuration page, point it to a directory containing the pictures you like, choose a switching interval, and that's it. (If your environment doesn't let you do this easily, maybe you should find a better environment.)

That leaves only one issue: setting up the picture directory. My personal choice is to have my Windows "Pictures" directory shared via VirtualBox's shared folders feature to the Linux guest. (Detail: to allow me to write to this directory from Linux, my account must be added to the vboxsf group. It's on my "things to do" list when creating a new Linux guest.) I keep 400 pictures in this directory; when more new pictures are added, the same number—the oldest ones—are removed.

I used to download daily pictures from the National Geographic site, but they made that difficult awhile back; I don't remember the details, and I haven't checked recently to see if they relented. Instead I grab Bing's home page picture; there's a new one every day, and downloading, while not exactly a breeze, is not too difficult.

The Perl script I use to download is get_bingpics (script here, prettyprinted HTML here). Notes:

  • There's a magic URL at Bing that can be queried (with proper parameters) to divulge the recent Bing pictures and their names. Specifically, the page will contain (at most) the eight most recent. The query I use asks for 16.

  • For some reason, I request the JSON version of the picture data. This is decoded (naturally enough) into a Perl data structure with the decode_json function from the JSON::PP module.

  • For the available images, the script checks each to see if it has already been downloaded. For each image not previously downloaded, it uses the LWP::Simple function getstor to download to the shared directory.

    Although I typically run this script daily, this design allows me to skip up to eight days without missing any pictures. (For example, if I'm on vacation.)

  • I run this script out of anacron daily, details left as an exercise for the reader.

The other part of this equation is getting rid of older pictures. That's accomplished by the remove_old_pics script (script here, prettyprinted HTML here). Notes:

  • It's pretty simple.

  • Its claim to geekery is using the Schwartzian Transform to obtain a list of JPEG files in the picture directory in order by modification time. Sweet!

  • The code can be easily tweaked to change directories, the types of files examined, and how many "new" ones to keep.

  • This too is run daily via anacron.

OK, so how many of you out there are shaking your heads at this and saying: "Doesn't this boy realize he needs professional help?" Let's see a show of hands…


Last Modified 2017-11-16 5:38 AM EST

My Book Picker (and Unpicker)

[2018/07/03 Update: A newer version is described here. I'm leaving this description, and the scripts it describes, in place, though, because it's simpler.]

Another example of the mental aberration that causes me to write Perl scripts to solve life's little everyday irritants. In this case two little irritants:

  1. I noticed that I had a lot of books on my shelves, acquired long past, that I never got around to reading. Either because (a) they were dauntingly long and dense (I'm thinking about Infinite Jest by David Foster Wallace); or because (b) they just fell through the cracks. Both poor excuses, but there you are.

  2. I sometimes want to methodically read a series of books in a particular order.

In other words, I needed a way to bring diligence and organization to my previous chaotic and sloppy reading habits.

Here's how I went about scripting that:

I conceptualized my "to be read" books as a collection of book stacks, like the picture at (your) right (except more of them). Each stack is a list of books:

  1. either organized around a specific theme (usually an author) or is a catchall (e.g. "non-fiction"); and

  2. maintained in the order I want to read them. (This goes back to the issue mentioned above: sometimes a series really "should" be read in publishing order, for example C.J. Box's novels featuring protagonist Joe Pickett.)

The implementation of this concept: each stack is a .list file in my Linux directory ~/var/reading_lists. As I type, sixteen of them:


(pas@oakland) ~/var/reading_lists: ls -l *.list
-rw------- 1 pas pas 183 Oct 20 17:47 amber.list
-rw------- 1 pas pas  41 May 17 18:05 asimov.list
-rw------- 1 pas pas 242 Jul 25 06:09 box.list
-rw------- 1 pas pas  93 Oct  9 12:27 connelly.list
-rw------- 1 pas pas  43 Sep  7 10:28 conservative_lit_101.list
-rw------- 1 pas pas  75 Sep 17 13:32 docford.list
-rw------- 1 pas pas  46 Jun 30 11:12 elmore.list
-rw------- 1 pas pas  83 Mar 29  2016 francis.list
-rw------- 1 pas pas 266 Oct 28 06:52 genfic.list
-rw------- 1 pas pas  65 Apr 13  2017 monkeewrench.list
-rw------- 1 pas pas 144 Oct 16 17:11 moore.list
-rw------- 1 pas pas 199 Oct 25 13:47 mystery.list
-rw------- 1 pas pas 523 Oct 16 13:12 nonfic.list
-rw------- 1 pas pas  56 Jul 18 15:04 reacher.list
-rw------- 1 pas pas 333 Aug 30 15:37 sci-fi.list
-rw------- 1 pas pas  45 Jun 11 15:50 winslow.list

Each list has one or more lines:


(pas@oakland) ~/var/reading_lists: wc -l *.list
   6 amber.list
   1 asimov.list
  11 box.list
   3 connelly.list
   1 conservative_lit_101.list
   5 docford.list
   4 elmore.list
   2 francis.list
   8 genfic.list
   4 monkeewrench.list
   5 moore.list
   6 mystery.list
  13 nonfic.list
   2 reacher.list
   9 sci-fi.list
   2 winslow.list
  82 total

… and each line in each file contains a different book title. Example with elmore.list, a list I created in lieu of watching the six seasons of Justified on Amazon Prime for the fourth time.


(pas@oakland) ~/var/reading_lists: cat elmore.list
Pronto
Riding the Rap
Fire in the Hole
Raylan

I.e., four books written by the late Elmore Leonard where Raylan Givens appears as a character.

The picking algorithm is simple and "works for me". When it's time to choose the next book to be read from this agglomeration, I pick a pile "at random" and take the book from the "top of the pile" (i.e., the one named in the first line of the file).

There is one more little tweak: the "random" pick is weighted by the length of the list. So (for example) since there are 82 books total in all lists above, and the nonfic.list has 13 lines, a book from that list would be picked with probability 1382. (Note the probabilities calculated this way add up to 1, the probability that some book from pile will be picked.)

That's not as hard as it might sound. I'd pseudocode the algorithm like this:

Given: N lists (indexed 0..N-1) with Bi books in the ith list…

Let T be the total number of books in the lists, B0 + B1 + … + BN-1

Pick a random number r between 0 and T-1.

i = 0
while (r >= Bi)
     r -= Bi
     i++

… and on loop exit i will index the list picked.

So: the "picking" script, bookpicker, is here. A prettyprinted HTML version is here. Notes:

  • You just run the script with no arguments or options.

  • I left "debugging" print statements in.

  • You're responsible for maintaining the lists; no blank/duplicate lines, etc.

  • For the "picked" list, the script writes a smaller file with the picked title missing. The old list is saved with a .old appended to the name. That's important, because next…

One last little gotcha: the randomization is sometimes a little too random. Specifically, sometimes after reading a book by a certain author, the picking script picks… the next book in the list by the same author. I don't want that. Variety is better.

So  there's also a script to "undo" a previous pick, bookpicker_unpick. If you run it before any other changes are made to the list files, it will find the most-recently-modified .list file, and "restore" the corresponding .list.old file. The script, is here. A prettyprinted HTML version is here.


Last Modified 2018-07-03 12:05 PM EDT

A BCD Clock Simulator

[Amazon Link]

I got a "binary-coded decimal clock" (made by the good folks at Anelace Inc.) a few Christmases back. Picture via Amazon link at right, if you're not seeing it, turn off your ad blocker. The time shown is 10:48:36.

[And make no mistake, Anelace is a Good Company. I shorted out the power supply adapter by clumsy accident. I emailed, asked where I could buy a replacement, they just sent me one, free. Whoa.]

When I worked at UNH, I kept it in my cubicle as a conversation piece. The thing that sticks in my mind today is how many IT managers needed me to explain what it was and how to interpret the LEDs. No geeks they.

Shortly afterward, on a lark, I wrote a small Perl script to simulate the clock display in a terminal window. I recently exhumed and updated the script to more modern standards. It's short and (I think) fun.

Here's a screen snapshot of what it looks like in action. The red dots inside indicate "on" LEDs. The LED array is updated every second, as is the time displayed at the bottom.

[Screenshot]

Plaintext source is here; Prettyprinted HTML source here.

Notes:

  • The Term::ANSIScreen Perl module available from CPAN handles text positioning, color, and formatting. I think most terminal emulation programs do ANSI commands these days.

  • The LED-on "dot" is a UTF-8 character. If your terminal program doesn't handle UTF-8, the code has a commented out alternate that might work: a space with a red background.

  • The Term::ReadKey module handles non-blocking terminal reads. This was implemented so that pressing the Q key will quit. [Pressing control-C might leave your terminal window in a funny "raw input" state. Fixing that left as an exercise for the reader.]

  • It could well be that a more judicious selection of fonts, characters, etc. would make the display more pleasing. Obviously, it's easy to play with.

  • There's a "sleep 1" in the script's main loop. Since the calculations inside the loop also take a finite amount of time, it's likely that a second will be skipped every so often. I haven't noticed that happening, though.

Questions? Comments? Let me know.


Last Modified 2017-10-23 7:10 AM EDT

Fedora 27 Beta

Some Informal Notes

Fedora

I've been using the Fedora Linux distribution since, well, since there was such a thing as Fedora. (Wikipedia dates this as November 2003.) Over the past couple years, I've taken to installing pre-release versions. Occasionally Alpha releases, but they stopped doing that. Fedora 27 Beta (F27) was released on October 3, I installed it on my home workstation that very day, and it has been "in production" since.

This is not an installation tutorial—other people out there do that better—but I did run into a "gotcha" that may affect a handful of folks. Unfortunately, it requires some background explanation.

I would probably fail a Linux geek purity test, because I don't install Fedora on "bare metal". Instead, I run Oracle's (free) VirtualBox software on a Windows 10 host, and install Fedora as a virtual guest. I started using this method back on my pre-retirement work computers, and it worked so well-like having two computers, one Windows, one Linux, at my fingertips—I continued the scheme at home, post-retirement.

Also: over the past few releases, I've grown fond of the Cinnamon desktop over the default GNOME desktop Fedora provides. Your mileage may vary, and that's fine, but there's a reason that (as I type) Googling "arrogant GNOME developers" gets "about 85,900 results".

I have, by now, ritualized the upgrade method. Which, oddly enough, doesn't involve an upgrade of the existing system. There are a lot of advantages to virtualization, and one of them is that it's easy to generate a new OS installation from scratch, keeping the previous one in reserve in case you mess up.

One of the goodies of Virtualbox is its so-called Guest Additions, which installs into the guest OS and provides (among other things) "shared folders", directories available to both the host and the virtual guest. That's useful to an easy upgrade, as we'll see.

An outline of my upgrade process:

  1. Save my custom configurations and data from Fedora N to a shared folder. (I have a script to do this, so I don't forget anything.)

  2. Shut down Fedora N.

  3. Install Fedora N+1 in a new virtual guest. (The sainted Fedora developers make this easy for Cinnamon-preferers: they provide a Fedora Cinnamon Spin on the same release schedule as default Fedora.)

  4. Install any and all necessary custom packages not included in the default install.

  5. Install VirtualBox's Guest Additions and restore the shared folder configuration.

  6. Restore the saved configurations and data from the shared folder in step 1 into the new guest.

And that's it! I'm eliding a lot of gory details. But…

In Step 4, it's not always obvious what non-default packages you should install, for two reasons: First, the default installation package set always changes between releases, so you might need to explicitly install something you didn't have to previously. Second: You don't want to install something you don't need. So, in practice, it's an iterative process; you observe some breakage due to something you missed, you go back to figure that out. (To a certain mindset, this detective work is kind of fun. As long as you're not racing against the clock to fix something critical to your organization. But I'm not in that position any more.)

But what happened this time is Step 5 failed silently. Why?!

Two reasons:

  1. Cinnamon (apparently) has a new default terminal emulation application: tilix. Which is fine (this isn't Russia) but as near as I can tell, they don't install any other terminal applications.

    Problem occurs when the Guest Addition script runs: as it turns out, it looks for a terminal emulation program using a list of fixed names: Konsole, gnome-terminal, mate-terminal and xterm. So the script fails. Silently.

    So: install xterm and try again…

  2. And we fail again, because the Guest Additions installation requires the dkms (Dynamic Kernel Module Support) package to be installed. Also no longer in the default set of installed packages. So install that and try again. (This also drags in the C compiler and kernel development packages.)

And then things worked. Yay!

Finally, not that it matters, but: tilix is not my cup of tea. I've grown used to/fond of a gnome-terminal feature: tabbed sessions in a single window. You can't do that in tilix, and the developers say: Sorry, no.

Reading Schedule Generator

[http://farm1.staticflickr.com/119/281194868_23e0c0e390_n.jpg]

June 2018 Update: Whoa. Embarassing bugfix, caused due to my minimal understanding of Javascript. I keep expecting it to work like Perl.

February 2018 Update: Stylesheet/template changes, described at end of article. Appropriate changes made to article body.

January 2018 Update: Some major form changes, described at end of article. Appropriate changes made to article body.

December 2017 Update: More changes, described at end of article.

October 2017 Update: I regret that I let this article get badly out of whack with reality. Notably, most of the links to my code, which were on the old UNH "pubpages" server, went stale when I retired. In addition, I've cleaned up the code some and fixed a nasty Daylight Savings Time-related bug. I hope. Further notes at the bottom of the article.

Note: this post is out of whack with normal Pun Salad content. Only recommended for:

  1. computer geeks who (like me) tend to approach everyday issues by asking: how could I write code to make this easier?; or
  2. psychologists who might be interested in whatever mental aberration causes the behavior exhibited in (a).

A few years back I noticed I was doing a miserable job of reading the magazines to which I was subscribed; the new issue would show up and I would have just read few if any articles in the previous one.

A similar problem with books: I would check them out of the library and return them unread. And my to-be-read pile of owned books just was getting bigger.

What worked for me was to set up a reading schedule for each "new" book or magazine. Simply read through a reasonable, fixed number of pages each day, until done. Lather, rinse, repeat.

I'm currently implementing this scheme by generating a calendar-format schedule for each item in HTML. Example for a book I read back in 2014, Freedom™ by Daniel Suarez:

[Sample Reading
Schedule]

I (1) print the schedule from my web browser, (2) cut it out, (3) attach to a card, and (4) use the result as a bookmark for the item. And each day, I try to meet the page goal for each item.

(Not that it matters, but for magazines I staple the schedule to one of the subscription cards that invariably accompany each issue. For books, I tape it to one of the plastic advertising cards the Yankee Candle company sends us, which are significantly more substantial.)

Now I'm not psycho about this: it's OK to get ahead of the goals if the material is compelling and I have the time. It's also OK to fall behind if there's just too much other stuff going on. (However, in my experience, just knowing that I've "fallen behind" is just enough self-nudging motivation to carve out some extra time to catch up later.)

Enough for the mechanics, on to the code. For a long time I ran a Perl script from a Linux command line to generate a schedule. But I realized that it would be (slightly) more flexible to set up an HTML form to get the schedule parameters, calling a CGI backend to display the result.

The form I'm using is here. (Do I have to mention that you can use your browser's source-viewing capabilities to view the HTML source? Nah, probably not.) But it looks like this [as of January 2018]:

[Blank
Reading Schedule Generator form]

[December 2017: removed paragraph about the jQuery datepicker, no longer used.]

The generated schedule uses a CSS stylesheet found here. No claims are made for its quality.

So you fill out the form, for example:

[Reading
Schedule Generator Form]

[Note: I urge you to try it yourself.]

… hit the submit button and the resulting page should produce the appropriate schedule. (I'm pretty sure it would work for you if you want to try it.)

The real work is performed by the Perl CGI script, which relies heavily on the smarts contained in the CGI.pm, HTML::Template, and Date::Manip Time::Piece modules. If you'd like to look at that (feel free to adapt it to your own preferences if you are so inclined): the plaintext Perl file is here and the html-prettyprinted version is here. The HTML template used by the script is also important and that's here.

Additional notes (October 2017).

  • As previously mentioned, the original version of the CGI script lived on the UNH "pubpages" webserver, where I enjoyed root access. It's now on the Pun Salad server, where I don't. This is problematic when you need to install some random CPAN perl module.

    Solution (or, at least, what I'm doing): use cpanm to install necessary modules "unprivileged" and put use local::lib; in scripts to find them. (This seems to work for CGIs, surprisingly.)

  • The most recent bug squashed was one I've struggled with for years: if your timezone does DST, not all days are 24 hours: the "spring forward" day has only 23; the "fall back" day has 25. This resulted in strange behavior when a reading schedule traversed one of those days.

    The fix turned out to be relatively easy: tell the script to pretend it's in the UTC timezone. Duh. That's accomplished by (for example) the line

    $startdate->config( 'setdate', 'now,UTC' );

December 2017 Update: Some minor surgery on this tool to report.

  • After years, I noticed that, in many cases, I wanted to spend a specific number of days reading a book or magazine. This involved me looking at a calendar, and finger-counting to figure out what end-date to specify in the form.

    Duh. That's an unforgivable user interface sin, is it not?

    So the input form now requests either (a) the end-date for the reading schedule, or (b) the number of days the schedule should run from the start date.

    If you enter both, the end date will be ignored, but why would you do that?

  • The form has been otherwise (ahem) brought into the 21st century. Title, start date, start page, and end page input fields now sport the HTML attribute required. The type attribute is used specify that the title should be text, the start/end pages should be number, and the start/end dates should be date.

    That means the web browser's native date-picker UI will be used for date entry instead of the previous jQuery datepicker. (Some old browsers don't support this, but since I'm not being paid to support old browsers, I can say "gee, that's too bad", without guilt.)

  • Some Javascript is still used for client-side verification: the start page has to be less than the end page, either the number-of-days or the end-date input has to be there, and if the end-date is specified, it has to be after the start date.

  • Web Security 101 saith: your CGI must still check the validity of its inputs. So all that code is still in the CGI. But…

  • I also modified the CGI script to use the Perl Time::Piece module for calendar arithmetic instead of Date::Manip. This greatly simplified the code, and (somewhat surprisingly) Time::Piece seems to do the right thing when the reading schedule overlaps a Daylight Saving time warp.

    I still think Date::Manip is vital for heavy-lifting Date/Time calculations. But it's overkill here.

January 2018 Update: Some minor surgery on the form. Did I say above that I had brought it into the 21st century? Not quite. My web design skills have always been weak, since that was never my job; I was pretty much a "get the content up" guy, and I only learned enough CSS to make things look the way I wanted at the time. That's the case here too. Summary:

  • Rearranged the form elements so they had a more intuitive ordering.

  • Switched to using an embeded stylesheet.

  • Used some advanced (non-table) styles for form layout.

I think it looks prettier now.

February 2018 Update: See above about my web design skills being weak. I can't say that the most recent changes make the design "good", but I think I can claim "better".

  • Changed to a standalone style sheet for the generated calendar bookmark.

  • The bookmark used tables for layout. I hear that's been frowned upon for, oh, the last 20 years or so. Layout now accomplished by CSS.

  • I changed the outer boundary of the bookmark from a dotted line to dashes. I think that looks better.


Last Modified 2018-06-10 11:06 AM EDT

Fedora 19

fedora poker chips I’ve been using Red Hat flavors of Linux since 1996 (with a brief flirtation with Slackware in 1995), and I’ve been married to the offspring Fedora distribution since early 2004. I’ve been tempted to stray to some other distro, but I’m just not adventuresome that way. In theory, some major malfeature could make me file for divorce, but that hasn’t happened yet.

[Notice the strained analogy in the above paragraph? It’s not far off.]

Fedora 19 is their latest version, in Beta as I type (scheduled for actual release on July 2). On a whim I decided to install it on machines at work and home. (The work machine is a VMWare virtual machine run under Windows 7 on a Dell Optiplex 780; my home box is an older Dell running Linux-only.)

Some notes:

  • Well first, an important one. Your mileage may vary. Poor Rand Simberg, for example, encountered display problems and data loss during/after his upgrade. (Although the latter can’t really be blamed on Fedora.)

    Bottom line: no matter how knowledgeable you are, an OS upgrade is good time to wear both suspenders and a belt. If you have a working environment with precious data, you’ll want to be able to fall back to it if things go wrong.

    But things didn’t go wrong in my case. So yay.

  • The Fedora 19 installer is not much changed from the Fedora 18 installer. Which at least one guy found to be “counter-intuitive, dangerous and useless, all at the same time.” But once I figured out what the deal was, I thought it was OK.

  • I made things slightly more challenging by opting to use the MATE desktop environment instead of the Xfce environment that I’d been using for the past few iterations of Fedora. (Cinnamon might have been another candidate, but I arbitrarily passed.)

    I’d switched to Xfce after hitting and bouncing off Gnome 3, which became the default with Fedora 15. What an awful decision for the Fedora honchos to make! And it didn’t help that the Gnome developers came off as snooty and unsympathetic; if we puny users could not appreciate their brilliant design, it was our fault. True fact: Googling gnome arrogance brings up 2.7 million hits, and they’re not all talking about the garden statues.

    I understand Gnome may have gotten better since then. Don’t care. Fortunately, you can easily choose which environment to install. And you can have multiple environments installed if you want to compare and contrast.

  • As stated, the work machine was a spankin’ new virtual machine; the only tricky part was setting it up to use a different IP address than the Windows host; VMWare makes this pretty easy, although you have to remember where it is and when you have to do it.

    The minor tricky part: getting all the necessary Perl modules back. I didn’t want to mindlessly re-install everything I had before, since that might well contain some unused cruft built up over the last few months with Fedora 18. I settled for an incremental approach (wait for things to break, then fix them). I really should come up with a better method the next time around.

  • My home machine isn’t virtualized, but has a tricky disk partitioning scheme: I divided the disk into three parts, each holding boot and root partitions: current release, previous release, and the one before that. So pre-upgrade I had Fedora 16, 17, and 18. The upgrade reformatted and replaced the F16 partitions with 19. Again, the installer made this pretty easy. (As above: just pay attention to where it is and when you have to do it.)


Last Modified 2013-06-27 5:18 AM EDT

Pun Salad Makes Wikipedia; More Perl Geekiness

Not that I sit up nights and ponder my referrer logs, but I noticed an odd one today: the Wikipedia entry for songwriter P. F. Sloan. And sure enough:

Well, … sometimes. If you keep an eye on the subtitle under "Pun Salad" at the top of the page, you'll notice it changes from time to time. Specifically: the subtitle on the primary page at punsalad.com changes whenever the page changes; clicking permalinks or archive links generate pages on the fly, and they get a random subtitle.

The subtitle is picked via a standard Perl idiom that chooses a random line from a text file (here called subheads):

    open( F, "<subheads" ) || die "Can't open subheads: $!\n";
    while (<F>) {
	rand($.) < 1 && chomp( $subhead = $_ );
    }
    close(F);
After that bit of code, the $subhead variable contains the chosen random line.

There are (as I type) currently 46 possible subtitles, so you have about a 2% chance (1/46) of seeing the one referred to by Wikipedia:

… has been seeking P. F. Sloan, but no one knows where he has gone.

This is a reference to an old Jimmy Webb song, described in the Wikipedia article as a "catchy, bittersweet composition, which seems to be about the costs and disappointments of being a creative groundbreaker." Yeah, I guess; I just like the song (you can probably give it a listen here). Wikipedia also describes some weirdness associated with it:

While Sloan helped Webb get started on his career, it was because of a personal dispute with him that Webb denied the existence of "P.F. Sloan" when asked about the song's title character during an article interview, saying that he had made the name up. Ironically, this led Eugene Landy, the controversial psychologist, to lay claim to being the real P.F. Sloan when he was asked by reporters why he considered himself able to direct Beach Boys lead singer Brian Wilson's musical career. Landy claimed to have written the songs attributed to "P.F. Sloan", and this soon led to his losing his license.

P. F. Sloan wrote some pretty decent songs, but he also wrote "Eve of Destruction", a three minute and thirty-eight second demonstration of how stupidly self-righteous the 1960s were.


Last Modified 2012-10-08 7:57 PM EDT