This is proving a pain in the ass to nicely generalize. Not impossible, but a few more hours to polish up. I propose we open a new issue to do it, and don't hold up this PR on it.

If you think it's worth waiting I'll do it before this is merged, but would appreciate the rest being tested while I work on it.