Just a Theory

By David E. Wheeler

Testing Perl Projects on Travis Windows

A few months ago, Travis CI announced early access for a Windows build environment. In the last couple weeks, I spent some time to figure out how to test Perl projects there by installing Strawberry Perl from Chocolatey.

The result is the the sample project winperl-travis. It demonstrates three .travis.yml configurations to test Perl projects on Windows:

  1. Use Windows instead of Linux to test multiple versions of Perl. This is the simplest configuration, but useful only for projects that never expect to run on a Unix-style OS.
  2. Add a Windows build stage that runs the tests against the latest version of Strawberry Perl. This pattern is ideal for projects that already test against multiple versions of Perl on Linux, and just want to make sure things work on windows.
  3. Add a build stage that tests against multiple versions of Strawberry Perl in separate jobs.

See the results of each of the three approaches in the CI build. A peek:

winperl-travis CI build results

The Travis CI-default “Test” stage is the default, and runs tests on two versions of Perl on Windows. The “Windows” stage tests on a single version of Windows Perl, independent of the “Test” stage. And the “Strawberry” stage tests on multiple versions of Windows Perl independent of the “Test” stage.

If, like me, you just want to validate that your Perl project builds and its tests pass on Windows (option 2), I adopted the formula in text-markup project. The complete .travis.yml:

language: perl
perl:
  - "5.28"
  - "5.26"
  - "5.24"
  - "5.22"
  - "5.20"
  - "5.18"
  - "5.16"
  - "5.14"
  - "5.12"
  - "5.10"
  - "5.8"

before_install:
  - sudo pip install docutils
  - sudo apt-get install asciidoc
  - eval $(curl https://travis-perl.github.io/init) --auto

jobs:
  include:
    - stage: Windows
      os: windows
      language: shell
      before_install:
        - cinst -y strawberryperl
        - export "PATH=/c/Strawberry/perl/site/bin:/c/Strawberry/perl/bin:/c/Strawberry/c/bin:$PATH"
      install:
        - cpanm --notest --installdeps .
      script:
        - cpanm -v --test-only .

The files starts with the typical Travis Perl configuration: select the language (Perl) and the versions to test. The before_install block installs a couple of dependencies and executes the travis-perl helper for more flexible Perl testing. This pattern practically serves as boilerplate for new Perl projects.

The new bit is the jobs.include section, which declares a new build stage named “Windows”. This stage runs independent of the default phase, which runs on Linux, and declares os: windows to run on Windows.

The before_install step uses the pre-installed Chocolatey package manager to install the latest version of Strawberry Perl and update the $PATH environment variable to include the paths to Perl and build tools. Note that the Travis CI Window environment runs inside the Git Bash shell environment; hence the Unix-style path configuration.

The install phase installs all dependencies for the project via cpanminus, then the script phase runs the tests, again using cpanminus.

And with the stage set, the text-markup build has a nice new stage that ensures all tests pass on Windows.

The use of cpanminus, which ships with Strawberry Perl, keeps things simple, and is essential for installing dependencies. But projects can also perform the usual gmake test1 or perl Build.PL && ./Build test dance. Install [Dist::Zilla] via cpanminus to manage dzil-based projects. Sadly, prove currently does not work under Git Bash2.

Perhaps Travis will add full Perl support and things will become even easier. In the meantime, I’m pleased that I no longer have to guess about Windows compatibility. The new Travis Windows environment enables a welcome increase in cross-platform confidence.


  1. Although versions of Strawberry Perl prior to 5.26 have trouble installing Makefile.PL-based modules, including dependencies. I spent a fair bit of time trying to work out how to make it work, but ran out of steam. See issue #1 for details.

  2. I worked around this issue for Sqitch by simply adding a copy of prove to the repository.

Removing Sqitch Deprecations

Ahead of the release of Sqitch v1.0 sometime in 2019, I’d like to remove all the deprecated features and code paths. Before I do, I want to get a sense of the impact of such removals. So here’s a comprehensive list of the deprecations currently in Sqitch, along with details on their replacements, warnings, and updates. If the removal of any of these items would create challenges for your use Sqitch, get in touch.

What would be removed:

  • The core configuration and directory-specification options and attributes:

    • --engine
    • --registry
    • --client
    • --top-dir, top_dir
    • --deploy-dir, deploy_dir
    • --revert-dir, revert_dir
    • --verify-dir, verify_dir

    The preferred solution is configuration values at the target, engine, or core level (settable via the options on the target, engine, and init commands, or via the config command).

    But I admit that there are no overriding options for the directory configurations in the deploy/revert/verify/rebase/checkout commands. And I’ve used --top-dir quite a lot myself! Perhaps those should be added first. If we were to add those, I think it’d be okay to remove the core options — especially if I ever get around to merging options to allow both core and command options to be specified before or after the command name.

  • The @FIRST and @LAST symbolic tags, which were long-ago supplanted by the more Git-like @ROOT and @HEAD, and warnings have been emitted for at least some of their uses for six years now.

  • Engine configuration under core.$engine. This configuration was supplanted by engine$engine four years ago, and came with warnings, and a fix via the sqitch engine update-config action. That action would also go away.

  • The core database connection options:

    • –db-host
    • –db-port
    • –db-username
    • –db-name

    These options were supplanted by database URIs over four years ago. At that time, they were adapted to override parts of target URIs. For example, if you have a target URI of db:pg://db.example.com/flipr, you can specify that target, but then also pass --db-name to just change the database name part of the URI. I’ve found this occasionally useful, but I don’t think the complexity of the implementation is worth it.

  • The old target options, which were renamed “change” targets back when the term “target” was adopted to refer to databases rather than changes. Sqitch has emitted warnings for five years when the old names were used:

    • The --onto-target and --upto-target options on rebase were renamed --onto-change and --upto-change.
    • The --to-target and --target options on deploy and revert were renamed --to-change.
    • The --from-target and --to-target options on verify were renamed --from-change and --to-change.
  • The script-generation options on the add command were deprecated four years ago in favor of --with and --without options, with warnings for the old usages:

    • --deploy became --with deploy
    • --revert became --with revert
    • --verify became --with verify
    • --no-deploy became --without deploy
    • --no-revert became --without revert
    • --no-verify became --without verify

    The same change replaced the template-specification options with a single --use option:

    • --deploy-template $path became --use deploy=$path
    • --revert-template $path became --use revert=$path
    • --verify-template $path became --use verify=$path

    The corresponding config variables, add.deploy_template, add.revert_template, and add.verify_template were replaced with a config section, add.templates. No warnings were issued for the old names, though.

  • The set-* actions on the engine and target commands were replaced three years ago (engine change, target change) with a single alter action, with warnings, and able to be passed multiple times:

    • set-target became alter target
    • set-uri became alter uri
    • set-registry became alter registry
    • set-client became alter client
    • set-top-dir became alter top-dir
    • set-plan-file became alter plan-file
    • set-deploy-dir became alter deploy-dir
    • set-revert-dir became alter revert-dir
    • set-verify-dir became alter verify-dir
    • set-extension became alter extension
  • The data hashed to create change IDs was modified six years ago. At that time, code was added to update old change IDs in Postgres databases; no other engines were around at the time.

If removing any of these features would cause trouble for you or the organizations you know to be using Sqitch, please get in touch.

More about…

Fascism Is Violence

Aleksandar Hemon, writing for Literary Hub:

[Fascism’s] ideas are enacted first and foremost upon the bodies and lives of the people whose presence within “our” national domain is prohibitive. In Bannon/Trump’s case, that domain is nativist and white. Presently, their ideas are inflicted upon people of color and immigrants, who do not experience them as ideas but as violence. The practice of fascism supersedes its ideas, which is why people affected and diminished by it are not all that interested in a marketplace of ideas in which fascists have prime purchasing power.

The error in Bannon’s headlining The New Yorker Festival would not have been in giving him a platform to spew his hateful rhetoric, for he was as likely to convert anyone as he himself was to be shown the light in conversation with Remnick. The catastrophic error would’ve been in allowing him to divorce his ideas from the fascist practices in which they’re actualized with brutality.

The New Yorker Festival bills itself as “Conversations on culture and politics,” but the important thing to understand about fascism — and its cohorts nationalism and white supremacism — is that it’s not a conversation. It’s not a set of ideas. No. Fascism is violence. One does not dialog with fascism. Fascism is a violent terror to be stopped.

Flaked, Brewed, and Docked

I released Sqitch v0.9998 this week. Despite the long list of changes, only one new feature stands out: support for the Snowflake Data Warehouse platform. A major work project aims to move all of our reporting data from Postgres to Snowflake. I asked the team lead if they needed Sqitch support, and they said something like, “Oh hell yes, that would save us months of work!” Fortunately I had time to make it happen.

Snowflake’s SQL interface ably supports all the functionality required for Sqitch; indeed, the implementation required fairly little customization. And while I did report a number of issues and shortcomings to the Snowflake support team, they always responded quickly and helpfully — sometimes revealing undocumented workarounds to solve my problems. I requested that they be documented.

The work turned out well. If you use Snowflake, consider managing your databases with Sqitch. Start with the tutorial to get a feel for it.

Bundle Up

Of course, you might find it a little tricky to get started. In addition to long list of Perl dependencies, each database engines requires two external resources: a command-line client and a driver library. For Snowflake, that means the SnowSQL client and the ODBC driver. The PostgreSQL engine requires psql and DBD::Pg compiled with libpq. MySQL calls for the mysql client and DBD::mysql compiled with the MySQL connection library. And so on. You likely don’t care what needs to be built and installed; you just want it to work. Ideally install a binary and go.

I do, too. So I spent the a month or so building Sqitch bundling support, to easily install all its Perl dependencies into a single directory for distribution as a single package. It took a while because, sadly, Perl provides no straightforward method to build such a feature without also bundling unneeded libraries. I plan to write up the technical details soon; for now, just know that I made it work. If you Homebrew, you’ll reap the benefits in your next brew install sqitch.

Pour One Out

In fact, the bundling feature enabled a complete rewrite of the Sqitch Homebrew tap. Previously, Sqitch’s Homebrew formula installed the required modules in Perl’s global include path. This pattern violated Homebrew best practices, which prefer that all the dependencies for an app, aside from configuration, reside in a single directory, or “cellar.”

The new formula follows this dictum, bundling Sqitch and its CPAN dependencies into a nice, neat package. Moreover, it enables engine dependency selection at build time. Gone are the separate sqitch_$engine formulas. Just pass the requisite options when you build Sqitch:

brew install sqitch --with-postgres-support --with-sqlite-support

Include as many engines as you need (here’s the list). Find yourself with only Postgres support but now need Oracle, too? Just reinstall:

export HOMEBREW_ORACLE_HOME=$ORACLE_HOME
brew reinstall sqitch --with-postgres-support --with-oracle-support

In fact, the old sqitch_oracle formula hasn’t worked in quite some time, but the new $HOMEBREW_ORACLE_HOME environment variable does the trick (provided you disable SIP; see the instructions for details).

I recently became a Homebrew user myself, and felt it important to make Sqitch build “the right way”. I expect this formula to be more reliable and better maintained going forward.

Still, despite its utility, Homebrew Sqitch lives up to its name: It downloads and builds Sqitch from source. To attract newbies with a quick and easy method to get started, we need something even simpler.

Dock of the Bae

Which brings me to the installer that excites me most: The new Docker image. Curious about Sqitch and want to download and go? Use Docker? Try this:

curl -L https://git.io/fAX6Z -o sqitch && chmod +x sqitch
./sqitch help

That’s it. On first run, the script pulls down the Docker image, which includes full support for PostgreSQL, MySQL, Firebird, and SQLite, and weighs in at just 164 MB (54 MB compressed). Thereafter, it works just as if Sqitch was locally-installed. It uses a few tricks to achieve this bit of magic:

  • It mounts the current directory, so it acts on the Sqitch project you intend it to
  • It mounts your home directory, so it can read the usual configuration files
  • It syncs the environment variables that Sqitch cares about

The script even syncs your username, full name, and host name, in case you haven’t configured your name and email address with sqitch config. The only outwardly obvious difference is the editor:1 If you add a change and let the editor open, it launches nano rather than your preferred editor. This limitation allows the image ot remain as small as possible.

I invested quite a lot of effort into the Docker image, to make it as small as possible while maximizing out-of-the-box database engine support — without foreclosing support for proprietary databases. To that end, the repository already contains Dockerfiles to support Oracle and Snowflake: simply download the required binary files, built the image, and push it to your private registry. Then set $SQITCH_IMAGE to the image name to transparently run it with the magic shell script.

Docker Docket

I plan to put more work into the Sqitch Docker repository over the next few months. Exasol and Vertica Dockerfiles come next. Beyond that, I envision matrix of different images, one for each database engine, to minimize download and runtime size for folx who need only one engine — especially for production deployments. Adding Alpine-based images also tempts me; they’d be even smaller, though unable to support most (all?) of the commercial database engines. Still: tiny!

Container size obsession is a thing, right?

At work, we believe the future of app deployment and execution belongs to containerization, particularly on Docker and Kubernetes. I presume that conviction will grant me time to work on these improvements.


  1. Well, that and connecting to a service on your host machine is a little fussy. For example, to use Postgres on your local host, you can’t connect to Unix sockets. The shell script enables host networking, so on Linux, at least, you should be able to connect to localhost to deploy your changes. On macOS and Windows, use the host.docker.internal host name.

The Farce of Consent

Cornell Tech Professor of Information Science Helen Nissenbaum, in an interview for the Harvard Business Review:

The farce of consent as currently deployed is probably doing more harm as it gives the misimpression of meaningful control that we are guiltily ceding because we are too ignorant to do otherwise and are impatient for, or need, the proffered service. There is a strong sense that consent is still fundamental to respecting people’s privacy. In some cases, yes, consent is essential. But what we have today is not really consent.

It still feels pretty clear-cut to me. I chose to check the box.

Think of it this way. If I ask you for your ZIP code, and you agree to give it to me, what have you consented to?

I’ve agreed to let you use my ZIP code for some purpose, maybe marketing.

Maybe. But did you consent to share your ZIP code with me, or did you consent to targeted marketing? I can combine your ZIP code with other information I have about you to infer your name and precise address and phone number. Did you consent to that? Would you? I may be able to build a financial profile of you based on your community. Did you consent to be part of that? I can target political ads at your neighbors based on what you tell me. Did you consent to that?

Well this is some essential reading for data privacy folx. Read the whole thing.

I myself have put too much emphasis on consent when thinking about and working on privacy issues. The truth is we need to think much deeper about the context in which consent is requested, the information we’re sharing, what it will be used for, and — as Nissenbaum describes in this piece — the impacts on individuals, society, and institutions. Adding her book to my reading list.

(Via Schneier on Security)

ACM Code of Ethics

From the Preamble to the ACM Code of Ethics and Professional Conduct, updated this summer after two years of development:

The Code as a whole is concerned with how fundamental ethical principles apply to a computing professional’s conduct. The Code is not an algorithm for solving ethical problems; rather it serves as a basis for ethical decision-making. When thinking through a particular issue, a computing professional may find that multiple principles should be taken into account, and that different principles will have different relevance to the issue. Questions related to these kinds of issues can best be answered by thoughtful consideration of the fundamental ethical principles, understanding that the public good is the paramount consideration. The entire computing profession benefits when the ethical decision-making process is accountable to and transparent to all stakeholders. Open discussions about ethical issues promote this accountability and transparency.

The principles it promotes include “Avoid harm”, “Be honest and trustworthy”, “Respect privacy”, and “Access computing and communication resources only when authorized or when compelled by the public good”. The ACM clearly invested a lot of time and thought into the updated code. Thinking about joining, just to support the effort.

They Is Smart

Self-professed language nerd Amy Devitt says They is Smart:

Mixing singular and plural is pretty common in most people’s speech and even writing:

“The analysis of all the results from five experiments support that claim.”

And one common expression mixing singular and plural even sounds a lot like “They is” (and is often pronounced that way):

“There’s two kinds of people in this world.”
“There’s lots of reasons we shouldn’t go to that party.”

So maybe it won’t sound so weird after all:

“Sam volunteers at the homeless shelter. They’s someone I really admire.”

Some varieties of English already match plural “they” with a singular verb:

“they wasn’t satisfied unless I picked the cotton myself” (Kanye West line in New Slaves)
“They is treatin’ us good.” (Dave Chappelle Terrorists on the Plane routine)
“They wasn’t ready.” ​(Bri BGC17 commenting on Oxygen Bad Girls Club experience)

So why not singular “they” with a singular verb?

“They wasn’t going to the party alone.”

Using singular verbs when we’re using “they” to refer to one person might not be so weird after all.

We have the same issue in some ways with singular “you.” Standard English varieties tend to use a plural verb even with singular “you.” So “you are a fine person,” not “you is a fine person.”

Except lots of varieties and lots of speakers do use “you is.”

I’ve been thinking about this piece a lot since I read it a couple weeks ago. It wasn’t that long ago that I surrendered to using “they” as a gender-neutral singular pronoun; today I’m embarrassed that my grammarian elitism kept me from denying it for so long. No more of that! If anyone asks me to use singular verbs with singular “they”, I’ll happily do it — and correct my habitual mouth when it fails me. For they who wants to be referred to using singular verbs, I will use singular verbs.

Intellectually, I find this whole idea fascinating, mostly because it never occurred to me, feels unnatural in my stupid mouth, but seems so obvious given the examples. Some dialects have used this form since forever; the internal logic is perfect, and only cultural elitism and inertia have repressed it. They wasn’t satisfied indeed.

But logic is a flexible thing, given varying semantics. In an addendum to the piece, Amy writes:

Edited: Chatting with my linguist friends Anne, Peter, and Jim gave me a new way to talk about this topic. The form of the verb “are” (“They are”) might be plural, but in the context of a singular “they” the verb would have singular meaning, too. We do that with “you.” You are a good friend, Sue. The “are” is singular just as the “you” is. So if we do start using “they” as the sole singular pronoun, we wouldn’t have to change the form of the verb to make it singular. It would already be heard as singular.

We are creative and flexible in using language. What a wonderful thing!

So just as “they” can be used as a singular pronoun, plural conjugations become singular when used with singular “they” if we just say they are. Everybody’s right! So make a habit of using the most appropriate forms for your audience.

More about…

pgenv

For years, I’ve managed multiple versions of PostgreSQL by regularly editing and running a simple script that builds each major version from source and installs it in /usr/local. I would shut down the current version, remove the symlink to /usr/local/pgsql, symlink the one I wanted, and start it up again.

This is a pain in the ass.

Recently I wiped my work computer (because reasons) and started reinstalling all my usual tools. PostgreSQL, I decided, no longer needs to run as the postgres user from /usr/local. What would be much nicer, when it came time to test pgTAP against all supported versions of Postgres, would be to use a tool like plenv or rbenv to do all the work for me.

So I wrote pgenv. To use it, clone it into ~/.pgenv (or wherever you want) and add its bin directories to your $PATH environment variable:

git clone https://github.com/theory/pgenv.git
echo 'export PATH="$HOME/.pgenv/bin:$HOME/.pgenv/pgsql/bin:$PATH"' >> ~/.bash_profile

Then you’re ready to go:

pgenv build 10.4

A few minutes later, it’s there:

$ pgenv versions
pgsql-10.4

Let’s use it:

$ pgenv use 10.4
The files belonging to this database system will be owned by user "david".
This user must also own the server process.
#    (initdb output elided)
waiting for server to start.... done
server started
PostgreSQL 10.4 started

Now connect:

$ psql -U postgres
psql (10.4)
Type "help" for help.

postgres=# 

Easy. Each version you install – as far back as 8.0 – has the default super user postgres for compatibility with the usual system-installed version. It also builds all contrib modules, including PL/Perl using /usr/bin/perl.

With this little app in place, I quickly built all the versions I need. Check it out:

$ pgenv versions
     pgsql-10.3
  *  pgsql-10.4
     pgsql-11beta2
     pgsql-8.0.26
     pgsql-8.1.23
     pgsql-8.2.23
     pgsql-8.3.23
     pgsql-8.4.22
     pgsql-9.0.19
     pgsql-9.1.24
     pgsql-9.2.24
     pgsql-9.3.23
     pgsql-9.4.18
     pgsql-9.5.13
     pgsql-9.6.9

Other commands include start, stop, and restart, which act on the currently active version; version, which shows the currently-active version (also indicated by the asterisk in the output of the versions command); clear, to clear the currently-active version (in case you’d rather fall back on a system-installed version, for example); and remove, which will remove a version. See the docs for details on all the commands.

How it Works

All this was written in an uncomplicated Bash script. I’ve ony tested it on a couple of Macs, so YMMV, but as long as you have Bash, Curl, and /usr/bin/perl on a system, it ought to just work.

How it works is by building each version in its own directory: ~/.pgenv/pgsql-10.4, ~/.pgenv/pgsql-11beta2, and so on. The currently-active version is nothing more than symlink, ~/.pgenv/pgsql, to the proper version directory. There is no other configuration. pgenv downloads and builds versions in the ~/.pgenv/src directory, and the tarballs and compiled source left in place, in case they’re needed for development or testing. pgenv never uses them again unless you delete a version and pgenv build it again, in which case pgenv deletes the old build directory and unpacks from the tarball again.

Works for Me!

Over the last week, I hacked on pgenv to get all of these commands working. It works very well for my needs. Still, I think it might be useful to add support for a configuration file. It might allow one to change the name of the default superuser, the location Perl, and perhaps a method to change postgresql.conf settings following an initdb. I don’t know when (or if) I’ll need that stuff, though. Maybe you do, though? Pull requests welcome!

But even if you don’t, give it a whirl and let me know if you find any issues.

More about…

Deep Expertise

Zeynep Tufekci, in an editorial for The New York Times, following the news that Elon Musk got defensive about his rejected mini-sub plan to rescue the kids in the Thai Cave last week:

The Silicon Valley model for doing things is a mix of can-do optimism, a faith that expertise in one domain can be transferred seamlessly to another and a preference for rapid, flashy, high-profile action. But what got the kids and their coach out of the cave was a different model: a slower, more methodical, more narrowly specialized approach to problems, one that has turned many risky enterprises into safe endeavors — commercial airline travel, for example, or rock climbing, both of which have extensive protocols and safety procedures that have taken years to develop.

This “safety culture” model is neither stilted nor uncreative. On the contrary, deep expertise, lengthy training and the ability to learn from experience (and to incorporate the lessons of those experiences into future practices) is a valuable form of ingenuity.

So important.

Sadly, Musk seems unable to take this lesson. Instead, following criticism from one of the British divers who led the effort to save the kids, Musk posted a series of profoundly disgusting tweets suggesting that the diver is a pedophile.

Stay classy, Silicon Valley cult-of-personality.

Irredeemable

Virginia Heffernan finds that Donald Trump Has No Values:

He has no honor among thieves, no cosa nostra loyalty, no Southern code against cheating or lying, none of the openness of New York, rectitude of Boston, expressiveness and kindness of California, no evangelical family values, no Protestant work ethic. No Catholic moral seriousness, no sense of contrition or gratitude. No Jewish moral and intellectual precision, sense of history. He doesn’t care about the life of the mind OR the life of the senses. He is not mandarin, not committed to inquiry or justice, not hospitable. He is not proper. He is not a bon vivant who loves to eat, drink, laugh. There’s nothing he would die for — not American values, obviously, but not the land of Russia or his wife or young son.

Read the whole thing — it’s short, and utterly devastating. There really is nothing redeemable about Donald Trump. Not one thing.

(Via Charlotte Clymer)

Lincoln on the Declaration

Abraham Lincoln, interpreting the Declaration of Independence, in 1858:

We hold these truths to be self evident: that all men are created equal; that they are endowed by their Creator with certain unalienable rights; that among these are life, liberty and the pursuit of happiness.”

This was their majestic interpretation of the economy of the Universe. This was their lofty, and wise, and noble understanding of the justice of the Creator to His creatures.

Yes, gentlemen, to all His creatures, to the whole great family of man. In their enlightened belief, nothing stamped with the Divine image and likeness was sent into the world to be trodden on, and degraded, and imbruted by its fellows.

They grasped not only the whole race of man then living, but they reached forward and seized upon the farthest posterity. They erected a beacon to guide their children and their children’s children, and the countless myriads who should inhabit the earth in other ages. Wise statesmen as they were, they knew the tendency of prosperity to breed tyrants, and so they established these great self-evident truths, that when in the distant future some man, some faction, some interest, should set up the doctrine that none but rich men, or none but white men, were entitled to life, liberty and the pursuit of happiness, their posterity might look up again to the Declaration of Independence and take courage to renew the battle which their fathers began—so that truth, and justice, and mercy, and all the humane and Christian virtues might not be extinguished from the land; so that no man would hereafter dare to limit and circumscribe the great principles on which the temple of liberty was being built.

See also his 1957 Speech on the Dred Scott Decision.

(Via James Gleick)

What Elizabeth Warren Saw at the Border

Elizabeth Warren describes what she saw in the immigration camps near McAllen, Texas, this past Sunday:

At the time of separation, most of the mothers were told their children would be back. One woman had been held at “the icebox,” a center that has earned its nickname for being extremely cold. When the agent came to take her child, she was told that it was just too cold for the child in the center, and that they were just going to keep the child warm until she was transferred. That was mid-June. She hasn’t seen her child since.

One mother had been detained with her child. They were sleeping together on the floor of one of the cages, when, at 3:00am, the guards took her away. She last saw her 7-year-old son sleeping on the floor. She cried over and over, “I never got to say goodbye. I never got to say goodbye.” That was early-June, and she hasn’t seen him since.

There’s no other way to put it: The Trump administration’s immigration policies are a humanitarian crime. The people who implemented it should be prosecuted for human rights violations.

Democracy Over Civility

Michelle Goldberg in a bracing piece for The New York Times:

Millions and millions of Americans watch helplessly as the president cages children, dehumanizes immigrants, spurns other democracies, guts health care protections, uses his office to enrich himself and turns public life into a deranged phantasmagoria with his incontinent flood of lies. The civility police might point out that many conservatives hated Obama just as much, but that only demonstrates the limits of content-neutral analysis. The right’s revulsion against a black president targeted by birther conspiracy theories is not the same as the left’s revulsion against a racist president who spread birther conspiracy theories.

The demand for civility in the face of deplorable lies and inhumane policies enables those lies and policies. Angry voices on the left will return to civility once violent rhetoric has ceased and civil rights have been restored.

(Via @jonfavs)

Facebook Identity Theft

I get email:

Action Required: Confirm Your Facebook Account

Needless to say, I did not just register for Facebook.

Hrm. That’s weird, since my Facebook account dates back to 2007. Wait, there’s another email:

(219) 798-8705 added to your Facebook account

That’s not my phone number.

I’ve never seen that phone number before in my life. In fact, I removed my phone number from Facebook not long ago for privacy reasons. So what’s going on?

A quick look at the email address tells the story: It’s my Gmail address. Which I never use. Since I never use it, it’s not associated with any account, including Facebook. What’s happened is someone created a new Facebook account with my Gmail address. If I were to click the “Confirm your account” button, I would give someone else a valid Facebook account using my identity. It’d be even worse if I also approved the phone number. Doing so would cede complete control over this Facebook account to someone else. These kinds of messages are so common that it wouldn’t surprise me if some people just clicked those links and entered the confirmation code.

It’s only Facebook, you might think. But Facebook, isn’t “only” anything anymore. It’s a juggernaut. Facebook is so massive, and has promoted itself so heavily as an identity platform, that many organizations rely on it for identity proofing vias social logins. That means someone can “prove” they’re me by logging into that Facebook account. Via that foothold, they can gradually control other online accounts and effectively control the identity associated with my Gmail address.

That would not be good.

So after inspecting the email to make sure that its URLs are all actually on facebook.com, I visit the “please secure your account” link:

Secure your account?

This isn’t right…

This is a little worrying. It’s not that I think someone else is logging into my account. It’s that someone else has created an account using my Gmail address, and therefore a slice of my identity. Still, locking it down seems like a good idea. I hit the “Secure Account” button.

Secure your account?

What? Fuck no.

Now we’ve reached to the point point where I’m at risk of actually associating my physical photo ID with an account someone else created and controls? Fuck no. I don’t want to associate a photo ID with my real Facebook account, let alone one set up by some rando cybercriminal. Neither should you.

I close that browser tab, switch to another browser, and log into my real Facebook account. If the problem is that someone else wants proof of control over my Gmail address, I have to take it back. So I add my Gmail address to the settings for my real Facebook account, wait for the confirmation email, and hit the confirmation link.

Contact Email Confirmation

That should do it.

Great, that other account no longer has any control over my Gmail address. Hope it doesn’t have any other email addresses associated with it.

Oh, one more step: Facebook decided this new address should be my primary email address, so I had to change it back.

I don’t know how people without Facebook accounts would deal with this situation. Facebook needs to give people a way to say: “This is not me, this is not my account, I don’t want an account, please delete this bogus account.” It shouldn’t require uploading a photo ID, either.

Tony

David Simon, pitch-perfect as usual, on his friend Tony:

Go, move, see, feel, eat – grow. The Church of Bourdain was founded not merely on the ever-more-vulnerable national credo that all Americans are created equal, but on the much more ambitious insistence that this declaration might be applied wherever you wandered and with whomsoever you cooked or shared a meal. He remains, for many of us, the American that we wish ourselves to be in the world’s sight. To have him widely displayed as our countryman, open to and caring about the rest of the world, and being so amid our current political degradation — this was ever more important and heroic. To lose him now, amid so many fear-mongering, xenophobic tantrums by those engaged in our misrule, is hideous and grievous.

But make no mistake: It wasn’t love of food that led Bourdain to the embrace of a shared human experience, of a world merely hiding its great commonalities behind vast and obvious culinary variations. It was the other way around. Tony was intensely political, a man always aware of those at the margins, or those who seem never to be reached by wealth or status or recognition.

Don’t miss the Kissinger story.

Plain Text Figures

A couple weeks ago, I implemented JSON Feed for Just a Theory (subscribe here). A nice feature of the format is that support for plain text content in addition to the expected HTML content. It reminds me of the Daring Fireball plain text feature: just append .text to any post to see its Markdown representation, like this. I’m a sucker for plain text, so followed suit. Now you can read the wedding anniversary post in plain text simply by appending copy.text to the URL (or via the JSON Feed).

Markdowners will notice something off about the formatting: the embedded image looks nothing like Markdown. Here it is:


{{% figure
  src     = "dance.jpg"
  title   = "dance.jpg"
  alt     = "First Dance"
  caption = "First dance, 28 May 1995."
%}}

This format defines an HTML figure in the Hugo figure shortcode format. It’s serviceable for writing posts, but not beautiful. In Markdown, it would look like this:

![First Dance](dance.jpg "First Dance")

Which, sadly, doesn’t allow for a caption. Worse, it’s not great to read: it’s too similar to the text link format, and doesn’t look much like an image, let alone a figure. Even Markdown creator John Gruber doesn’t seem to use the syntax much, preferring the HTML <img> element, as in this example. But that’s not super legible, either; it hardly differs from the shortcode format. I’d prefer a nicer syntax for embedded images and figures, but alas, Markdown hasn’t one.

Fortunately, the copy.text output needn’t be valid Markdown. It’s a plain text output intended for reading, not for parsing into HTML. This frees me to make figures and images appear however I like.

Framed

Still, I appreciate the philosophy behind Markdown, which is best summarized by this bit from the docs:

The overriding design goal for Markdown’s formatting syntax is to make it as readable as possible. The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like it’s been marked up with tags or formatting instructions.

So how do you make an embedded image look like an image without any obvious tags? How about we frame it?

        {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
        {                                                          }
        {                      [First Dance]                       }
        {  https://justatheory.com/2018/05/twenty-three/dance.jpg  }
        {                                                          }
        {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
        {  First dance, 28 May 1995.                               }
        {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

Think of the braces and tildes like a gilded frame. In the top section, we have the bracketed alt text like a descriptive card, followed by the image URL. Below the image area, separated by another line of tildes, we have the caption. If you squint, it looks like an image in a frame, right? If you want to include a link, just add it below the image URL. Here’s an example adapted from this old post:

  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  {                                                                      }
  {                      [*Vogue* on the new iPad]                       }
  {   https://farm8.staticflickr.com/7198/7007813933_bd7e86947c_z.jpg    }
  {     (https://www.flickr.com/photos/theory/7007813933/sizes/l/)       }
  {                                                                      }
  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  {  Image content from *Vogue* on the new iPad. Not shown: the second   }
  {  that it's blurry while the image engine finishes loading and        }
  {  displaying the image.                                               }
  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

The link appears in parentheses (just like in the text link format). The format also preserves the alt text and caption Markdown formatting. Want to include multiple images in a figure? Just add them, as long as the caption, if there is one, appears in the last “box” in the “frame”:

  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  {                                                                      }
  {                [*The New Yorker* on the 1st gen iPad]                }
  {   https://farm8.staticflickr.com/7059/6861697774_a7ac0d9356_z.jpg    }
  {      (https://www.flickr.com/photos/theory/6861697774/sizes/o/)      }
  {                                                                      }
  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  {                                                                      }
  {      [*The New Yorker* on the 3rd gen iPad with retina display]      }
  {   https://farm8.staticflickr.com/7110/7007813821_6293e374eb_z.jpg    }
  {      (https://www.flickr.com/photos/theory/7007813821/sizes/o/)      }
  {                                                                      }
  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  {  Text content from *The New Yorker* on the first generation iPad     }
  {  (top) and the third generation iPad with retina display (bottom).   }
  {  Looks great because it's text.                                      }
  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

You can tell I like to center the images, though not the caption. Maybe you don’t need a caption or much else. It could be quite minimal: just an image and alt text:

        {                      [First Dance]                       }
        {  https://justatheory.com/2018/05/twenty-three/dance.jpg  }

Here I’ve eschewed the blank lines and tildes; the dont’ feel necessary without the caption.

This format could be parsed reasonably well, but that’s not really the goal. The point is legible figures that stand out from the text. I think this design does the trick, but let’s take it a step further. Because everything is framed in braces, we might decide to put whatever we want in there. Like, I dunno, replace the alt text with an ASCII art1 version of the image generated by an conversion interface? Here’s my wedding photo again:

{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
{                                                                                    }
{  NNNmmdsyyyyyyysssssdhyssooo+++++++++++++ooymNNNNyo+/::::-----------------------:  }
{  NNmNmdssyyyyyssssssdhyssooo++++///++++++ooymNmmmyo+/:::--------...-------------:  }
{  mmddddsyyyyyyssssssdhyssoo++++/////++++osydmmmmNyo+/::--------.....-------------  }
{  Nmddmmsyyyyyyssssssdhysooo+++///////++osso+oymNNyo+/::--------.......-----------  }
{  mmmmmmyyyyyyysssssshhysooo+++//////+ys+//:///sdmho+/::-------..........---------  }
{  mmmmmmyyyyyyssssosshhysooo+++////+ydmy/:/:/+ossydo+/::------...........---------  }
{  mmmmmmyyyyyysssoosshhysosshdy+/+odmNNmdyddmmNNmdmms+::------............--------  }
{  mmmmmmyyyyyyssooosshdhyso:/ymhhdmNNNNNmyhNNNNNNNNNmmo:------.............-------  }
{  mdddmmhyyyysssooossdmdmho.-hmmNNNNNNNmdyhmNNNNNNNNNmh+/+/--..............-------  }
{  mmmmddhyyyysssoooymmNmNmo--yNNmNmmmmmhhyhdhydNNNNmmmdysshy:..............-------  }
{  mmmddmhyyyssssoosdNNNNNmssydmNddhssossyyhs::+ssyhmmh+///ohh-..............------  }
{  Nmmmmmdyyyssssoohhdmddhs:-:hdhyhdso+++///--::/:::+o/://oosy:-.............------  }
{  NNNmmmdyyyssssosdhhyyh+//oohdmmmh///+/::::::---:++/://+hddmdho:...........------  }
{  NNNmmmdyyyssssosmmdmdy+.-/mmdmho+//////::::::/sddddhs/.:sdmmmmy-..........------  }
{  NNmmmmdhyyssssooydmmd+/+sydNmmh+/+yddyo/://oydmmmmmmdy:..:ymds:............-----  }
{  mmmmmmdhyysssoooo+oo+ohdmmmmmddhhsyddddysyddddmmmdddho-..`./h/.............-----  }
{  mmNNNNmhyysssoooo:-/.-ymmmmmmmmmmmNmdddmmmdmdddhhhhs-```````/h-............-----  }
{  NNNNNNmhyysssooymddmddmmmdddddmdmmNmyddmddddhhhhhy:`   ` ```.oo............-----  }
{  NNNNNNmhyyssssymmmNNmmmddhhddddmddddhddmdhddhhhhs-     ` ```.:y............-----  }
{  NNmdmNmhyysssydNmmNmmmddddddddddddhdddhddhhhhhho.      ``````.y............-----  }
{  mmmddmmdhyssssdmmhdmmmddddddhhdhhhhhddhhhdhhho-`       `` ```.s...........------  }
{  ddddhdddhyysssymNmmmmmddddddddhhdhhhhdddddy+.``        `` ```.s............-----  }
{  NNNmmddhyyssssosdddmmmmdddddddhhdddmmmmd+..```        `` ````-s............-----  }
{  NNNmmhysssssssooooymmmmdddmmmdhhdmdmdddd/``.`        ``  ```.-o...........------  }
{  mNNmddhhysssssssssydmmmmdmmmmmddmmddddhdd+``        `` `````.-/...........------  }
{  NNNmmddhhhhyyyyyyyyhmmmmmmmmmmddddddddhhy.``    ` ``` ``````./-...........------  }
{  NNNmmmysssssssssssssdmmmmmmmddddddddddh+.`     ` ```   `````.+...........-------  }
{  mmmNmmhyyyyyhhhhhhhhdmmmmmmddddddddddy:`````` `````   ``````-:...........-------  }
{  mmmmmmmmmmmmmmmmmmddhdmmmddddddddddy/.`````` ````       ```./...........-------:  }
{  mmmmmmmmmmmmmmmmmmhyssdmmmmdddddho:.``````````-```  ``````./:...........-------:  }
{  mmmmmNmmmmmmmmmddddhysydmmddho:-...`````````:oh/```` ````.-:............-------:  }
{  mmmmNNmmmmmmmmmmmmmddyoydo:.``.`````````.:+ydddh-```````-/--............-------:  }
{  NNNNNNNmmmmmmmmmmmmdyyyoo.````...`````:ohdmdddddh+oosyyhdmo--..........--------:  }
{  NNNNNNNNmmmmmmmNmmmmhys+//-```...`.-+yddddmmmddddmmmmmmmmmm+--.......----------:  }
{  NNNNNNNNNmmmmNNNNNmmsyysohdyosyhyyhddddddddmmmmmdmmmmmmmmmmh--.......---------::  }
{  NNNNNNNNNNNNNNNNNNNNyyhhdmmdddmmdddddmddddmmmmmmmmmmmmmmmmmd-----------------:::  }
{  NNNNNNNNNNNNNNNNNNNNmmmmmmmmdddddddmmmmmmmmmmmmmmmmmmmmmmmmy-----------------:::  }
{  NNNNNNNNNNNNNNNNNNNNmmmmmmmmddddddmmmmmmmmmmmmmmmmmmmmmmmmm+-----------------:::  }
{  NNNNNNNNNNNNNNNNmNNNmmmmmmmmmdmdmmmmmmmmmmmmmmmmmmmmmmmmmmh:----------------::::  }
{  NNNNNNNNNNNNNNNNNNNmmmmmmdmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmo----------------:::::  }
{  NNNNNNNNNNNNNNNNNNNmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm/---------------::::::  }
{  NNNNNNNNNNNNNNNNNNNNNNmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmNNmy::::::::--:-:::::::://  }
{  NNNNNNNNNNNNNNNNNNNNNNmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmNmmo///:::::::::::::::////  }
{  NNNNNNNNNNNNNNNNNNNNNNmdddmmmmmmmmmmmmmmmmmmmmmmmmmmmNmmy///////////////////////  }
{  NNNNNNNNNNNNNNNNNNNNNNmdddddddddddddmmmmmmmmmmmmmmmmmNmm/::::::::::::::::::::::/  }
{  NNNNNNNNNNNNNNNNNNNNNNmdddddddddhddddddmmmmmmmmmmmmmmmmy::::::::::--:::::::::///  }
{  NNNNNNNNNNNNNNNNNNNNNNmmdddddddddddddddmmmmmmmmmmmNNNmNyo++++/////////////++++oo  }
{  NNNNNNNNNNNNNNNNNNNNNNmmdddddddddddddddmmmmmmmmmmNNNmmNhyyyyyyssssssssssssssssss  }
{  NNNNNNNNNNNNNNNNNNNNNmmmdddddddddddddddmmmmmmmmmNmNNmmmysssssooooo+++++/////////  }
{  NNNNNNNNNNNNNNNNNNNNNmmmmddddddddddddddmmmmmmmNNNNNmmmd/::::::::::::::://///////  }
{  NNNNNNNNNNNNNNNNNNNNNmmmmdmddddddddddddmmmmmmNNNmNmmmmd::::::::::::::://////////  }
{  NNNNNNNNNNNNNNNNNNNNNmmmmdmmmdddddddddmmmmmmNmmmNmmmmmh::::::::://///////++os+++  }
{  NNNNNNNNNNNNNNNNNNNNNmmmmdmmmmddddddddmmNmNNmmmNmNNmmNh/::::///////////+oo+++++o  }
{  NNNNNNNNNNNNNNNNNNNNmmmmmmmddddddddddmmNmmmmmmmNNNNmmNy////////+oossyyhhhdddmmmN  }
{                                                                                    }
{               https://justatheory.com/2018/05/twenty-three/dance.jpg               }
{                                                                                    }
{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
{  First dance, 28 May 1995.                                                         }
{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

Silly? Maybe. But I’m having fun with it. I expect to wrangle Hugo into emitting something like this soon.


  1. Surely someone has come up with a way to improve on ASCII art by using box elements or something?