home ¦ Archives ¦ Atom ¦ RSS


Link parkin’: XMLStarlet

XMLStarlet is a set of command line utilities (tools) which can be used to transform, query, validate, and edit XML documents and files using simple set of shell commands in similar way it is done for plain text files using UNIX grep, sed, awk, diff, patch, join, etc commands.

XMLStarlet is a venerable Swiss Army Knife for carving up XML. I was having some issues with xmllint checking Discogs Data XML files for well-formedness and stumbled upon XMLStarlet. On initial test drive, seemed to be doing a bit better with the large size of the Discogs files.

hyperfine cli benchmarking

TIL hyperfine:

A command-line benchmarking tool.

Might not read obvious, but it’s a command-line tool that looks really handy for benchmarking other command-line tools. Should replace the time command, but it’s way more than that. Has a ton of features including things like “statistical analysis across multiple runs” and exporting results to CSV, JSON, Markdown, etc.


Link parkin’: sqlite-xsv

sqlite-xsv is a new SQLite extension for querying CSVs, TSVs, and other-SVs, written in Rust, based on sqlite-loadable and the excellent csv crate.

Current in SQLite, you have 2 options for parsing CSVs: the official SQLite CSV virtual table and sqlean’s vsv counterpart. Both work as advertised, and are perfectly fine by itself!

However, sqlite-xsv provides various ergonomic benefits over these two extensions, such as:

Another Alex Garcia special

h/t Simon Willison

Overcast Renewal

My subscription for the Overcast podcast player renewed today, for which I have no qualms. Really happy with the app, glad to support an independent developer, and would highly recommend to others.

I’m sure there other fine iOS podcast apps out there, but Overcast works really well for me.

Working Locally with Clickhouse

ClickHouse is a database tool I haven’t had a chance to sink my teeth into but heard good things about. Extracting, converting, and querying data in local files using clickhouse-local is an overview of using ClickHouse to work on data hosted on a node.

Sometimes we have to work with files, like CSV or Parquet, resident locally on our computers, readily accessible in S3, or easily exportable from MySQL or Postgres databases. Wouldn’t it be nice to have a tool to analyze and transform the data in those files using the power of SQL, and all of the ClickHouse functions, but without having to deploy a whole database server or write custom Python code?

Fortunately, this is precisely why clickhouse-local was created! The name “local” indicates that it is designed and optimized for data analysis using the local compute resources on your laptop or workstation. In this blog post, we’ll give you an overview of the capabilities of clickhouse-local and how it can increase the productivity of data scientists and engineers working with data in these scenarios.

Also noting yet another database with embedded HTTP request capabilities.

Plug for Libby

Just wanted to give a plug for the Libby app by OverDrive.

Borrow ebooks, audiobooks, magazines, and more from your local library for free! Libby is the newer library reading app by OverDrive, loved by millions of readers worldwide.

Works great with the Kindle App on an iPad. Excellent alternative to forking over full price for an eBook you might not be sure about.

Also, public libraries everywhere and their staffs deserve all the love they can get.

Algorithmic Boundaries

Thanks to Tim Bray for putting together an overview and thoughts on some of the issues the fediverse community has with global content surveillance a.k.a. content search and indexing. Even though I’m out as a social media participant, I’m mentally noodling on technical means that can support some of the proposed social goals, without going full bore, end-to-end encryption. Wondering if some notion of crawler/indexer algorithmic transparency and auditing via computational means could help.

I wonder if these discussions will ever intersect with work Princeton University’s Arvind Narayanan project on Algorithmic Amplification and Society.

The distribution of online speech today is almost wholly algorithm-mediated. To talk about speech, then, we have to talk about algorithms. In computer science, the algorithms driving social media are called recommendation systems, and they are the secret sauce behind Facebook and YouTube, with TikTok more recently showing the power of an almost purely algorithm-driven platform.

Relatively few technologists participate in the debates on the societal and legal implications of these algorithms. As a computer scientist, that makes me excited about the opportunity to help fill this gap by collaborating with the Knight First Amendment Institute at Columbia University as a visiting senior research scientist — I’m on sabbatical leave from Princeton this academic year. Over the course of the year, I’ll lead a major project at the Knight Institute focusing on algorithmic amplification.

While Narayanan seems focused on amplification via algorithmic-mediated speech, the consideration of obfuscation feels like a worthy part of the discussion.

Pelican: Draft View vs Published View

One thing that’s been annoying me recently with the static site publishing tool that I use, pelican, is how it treats draft posts and publishing. Out of the box, I haven’t been able to easily generate one development site that includes all draft (and unlabeled status) posts and another “officially” published site that omits draft posts. The development site being handled by pelican’s built-in HTTP server will provide a drafts folder, but I find it’s usage clunky.

However, there may be a workaround using pelican’s default Makefile and some smart configuration:

  • Have the devserver and devserver-global targets in the Makefile generate output into a distinct dev output dir, where dev content winds up getting served from
  • Modify the pelicanconf.py config files DEFAULT_METADATA to set status to publish
  • Leave the publishconf.py as is, or make sure the default status is draft
  • Maybe put all draft posts in a draftposts subdir so they get tagged in the dev server and are easier to find.

That should cleanly isolate the dev content from the published content, yet put all the draft material in one place during dev. It should at the same time prevent accidental publishing of draft material to the production site. I’m going to try an experiment using this approach for a few days and report back.

P. S. Combined with TailScale, mobile life and authoring on the go should become quite palatable. Hmmm 🤔, how well does self-hosted filesharing tech like NFS and SMB work over TailScale? Given my personal toolkit, worst comes to worst, I could also employ Emacs TRAMP over SSH.

Oh wait. Is it even possible to get Emacs on an iPad? There’s gotta be a way right! Apparently not.

Awesome macOS CLI

Link parkin’: Awesome macOS Command Line

A curated list of shell commands and tools specific to macOS.

Does what it says on the tin.

Bonus: Awesome Command Line Apps

A curated list of useful command line apps, in celebration of the TUI.

Love me some TUI.

TailScale On the Road

So I’ve taken my iPad out and about, putting TailScale to the test for remote access to a home machine. Results have been somewhat mixed.

First off, that it works at all, making it seamlessly possible to ping all my devices as if they were on a virtual LAN across the worldwide Internet, is impressive. I did manage to get some quality extended remote SSH time on the home machine. I’ve been able to do this successfully multiple times.

But for whatever reason, on one of my trips things went really sideways. Don’t know what happened but I lost access, maybe it was the café Wi-Fi connectivity, and then TailScale was a bit out of sorts. Couldn’t resolve hosts using TailScale DNS for a few moments, then when resolution came back, couldn’t complete connections. I switched to my phone as a mobile hotspot and things were still a bit borked. Quite a bit of teasing potential for a full recovery, but things just never got right. Finally just gave up and went back home.

I’m freely admitting not doing any serious diagnosis, so please TailScale support, no need to ring my number. I’ll be a good customer and attempt an appropriate bug report if it happens again.

Dragged Into GitHub Actions

Thanks to Simon Willison’s click-app cookiecutter template I’ve been sucked into GitHub actions. His cookiecutter has Python testing baked in and GH Actions that leverage that testing turned on by default. Normally I wouldn’t care, but the repo workflow sends you email and can badge itself as red when failing, which annoys me enough that I have to go fix it.

Not at all bad though to get introduced to GH Actions since they seem to be core knowledge for good GH citizenship. Just takes some getting used to while being part of a GitLab CI/CD shop as part of the day job.

Carmo and Mastodon

Still no plans for me to get back into social media, but I appreciate how deeply Rui Carmo dives into his Mastodon experience:

It’s the quiet week before New Year’s, so I thought it worthwhile to tag some loose notes together and take a snapshot of what I’m doing with Mastodon and how it differs from Twitter. Everyone else seems to be doing it, so why not?

I will admit a slight interest in the technical underpinnings of the fediverse, especially ActivityPub and how information is disseminated.

Plug here for Carmo’s Tao of Mac blog. Great technical information and great writing.

GNU Screen Locking Fix

Since I started using an iPad Pro cover, SSH, and screen, this problem has been biting me in the rear:

I have been using GNU Screen for a while now. I usually create multiple new windows (Ctrl-a c) every day. Then I flip back and forth between the multiple screens (Ctrl-a n) or just toggle between the last 2 windows used (Ctrl-a a). Sometimes my finger slips and I hit Ctrl-a x which provides you with a password prompt. This is GNU Screen’s lockscreen function.

Normally you just enter your user password and the screen will unlock. Screen is using /local/bin/lck or /usr/bin/lock by default. This is all fine and dandy if you have a user account password to enter. If you have servers that only use SSH keys and don’t use passwords you will have no valid password to enter. In other words you are stuck at this lock screen. One way around it is to login to the same machine and re-attach to the same screen session (normally “screen -r” if you have only 1 session open). Then kill the session with the lock screen. This is annoying to have to do.

It really is a pain, but it has an extremely simple solution. Basically you disable the default binding for locking and go on about your business.

Discogs Data Total Size

The next question I have about the Discogs Data is what’s the total amount to download? An initial step is updating my URL gathering script to grab the Content-Length header from an http probe of each url and start generating csv compatible output.

read more ...

782 Discogs Data Files

I wanted to know how many distinct data files (checksums and compressed XML data) were referenced from discogs.data.com. This is prelude to trying to do a, extremely polite, crawl of all the files for some longitudinal analysis. So I threw together a little script and learned a few things.

The answer turned out be 782.

read more ...

Doctorow’s Memex Method

Apologies for the extensive quoting, but I don’t think Cory Doctorow would actually mind (too much). Pointing back to a 2021 piece on Medium (full post) and Pluralistic (condensed summary, podcast teaser) entitled The Memex Method. It’s sort of the inspiration for some of the personal content hacking I’ve gotten interested in over the last few month.

Blogging is the process by which I take everything that seems significant and fix it in my memory; the process of explaining why something seems significant for strangers is powerfully mnemonic in exactly the way that scrawling tones in a private notebook isn’t.

The fulltext, searchable, tagged database of everything I’ve ever given real thought to is how I synthesize whatever new things snag my attention into longer, more reflective pieces – which go into the searchable, tagged database, too.

From Medium Memex, combined with Pluralistic — the solo blog I started after I left Boing Boing — is a vast storehouse of nearly everything I found to be significant since 2001. When one of those nucleation events occurs, the full-text search and tag-based retrieval tools built into Wordpress allow me to bring up everything I’ve ever written on the subject, both to refresh my memory as to the salient details and to provide webby links to expansions of related ideas.

Not just because it created a daily writing habit, nor because it helped me organize my thoughts – but also because it is iterative, a way of structuring and auditioning arguments for an audience that refines how to present technical, difficult material.

I’m impressed that simply combining tagging with fulltext search can provide so much creative fuel.

Welcome Back

What with the earthquakes occurring in the social media space, quite a few folks are reemerging in the blogosphere. Glad to see these gentlemen (hmmm?) routinely pop up again in my feed reader:

Also have to mention the return of Jason Kottke, kottke.org, who oddly enough I hadn’t ever really cottoned to not being so much into popular culture items. But I’m getting interested in linkblogging and he has always been the epitome of fine linkblogging.

Whisper is Python

TIL the original Whisper is mostly Python and I was also introduced to Sumana Harihareswara

Whisper, from OpenAI, is a new open source tool that “approaches human level robustness and accuracy on English speech recognition”; “Moreover, it enables transcription in multiple languages, as well as translation from those languages into English.” …

Whisper is an open source software tool written mostly in the Python programming language. Instructions on how to download, install, and run it are relatively straightforward, if you are comfortable running commands in a terminal. It depends on Python, a few Python libraries, and Rust. In case you want to try Whisper but you don’t want to fiddle with installing it on your computer, the machine learning company Replicate is hosting a web-based version of Whisper so you can upload a sound file and get a transcription. But of course then you don’t get the privacy benefits of running it entirely on your own machine.

I will definitely be giving this a test drive during my holiday time off.

Meanwhile, Harihareswara seems like quite the interesting individual. I appreciate her thoughtfulness regarding the ethics of using Whisper. Will be adding her blog to my feeds.

Via Simon Willison

An iPad Cover

A while back, I was reading about Rui Carmo’s adventures doing development on his iPad Pro. Since I have the same iPad model, I cribbed his recommendation for the Logitech Combo Pro as a cover/keyboard. I haven’t chased Carmo’s development aims but just as a keyboard, the Combo Pro works well.

My singular complaint is that I use my iPad as a reading device a lot, sort of like an overpriced Kindle. When in this mode, the Combo Pro is a little inconvenient. Either the keyboard lays in front, consuming space unnecessarily, or it folds back without providing protection for the iPad screen. Detaching the keyboard to switch modes is a little unwieldy.

However, the iPad Pro is a much more powerful device than a garden variety Kindle. I did not pursue Carmo’s goal of turning my iPad into a full on self-hosted development platform. Instead, I just took the baby step of using it as a tablet terminal for SSHing into remote machines. TailScale has amplified this by making access to my virtual, personal home LAN seamless. Panic’s Prompt is my current iOS SSH tool of choice. I’m creating this post from my iPad Pro, using Prompt and the Combo Pro at this very moment. Next stop is taking this on the road, outside of my home.

Minor tip. For this keyboard, map ESC to the CAPS-lock key. Doubly so if you’re an Emacs user.

A Quamina Bug

Have to take a moment to acknowledge doing my duty as a good open source citizen. Hey it was only reporting a tiny bug (bug: Quote escaping termination failure) in Tim Bray’s Quamina library, but every little bit counts. Also gave me a good opportunity to limber up my issue reporting muscles.

Python keyring

keyring looks like a nice cross-platform Python module for storing encrypted passwords using platform appropriate mechanisms.

The Python keyring library provides an easy way to access the system keyring service from python. It can be used in any application that needs safe password storage.

TIL pimox

TIL pimox

Pimox is a port of Proxmox to the Raspberry Pi allowing you to build a Proxmox cluster of Rapberry Pi’s or even a hybrid cluster of Pis and x86 hardware.

Proxmox VE is the thrifty persons VMWare ESXi or an ops-limited persons OpenStack. I’ve definitely enjoyed it at work for managing VMs on just a single node. There’s a stack of RPi 3s on my desk where pimox would be applicable.

Pimox installation doesn’t look to be easy peasy, but at least a manageable, cuddly afternoon with the CLI.

The TailScale Manifesto

I was browsing around the TailScale blog and bounced over to one of their recommended posts, Remembering the LAN which is really this post. I’m of an age similar to David Crawshaw and can empathize with this sentiment:

The LAN was a magical place to learn about computers. Besides the physical aspect of assembling and disassembling machines, I could safely do things unthinkable on the modern internet: permission-less file sharing, experimental servers with no security, shared software where any one machine could easily bring down the network by typing in an innocuous command. Even when I did bring down the network the impact never left the building. I knew who I had to apologise to.

And his essay answered the question of TailScale’s campfire origin story:

We can have the LAN-like experience of the 90’s back again, and we can add the best parts of the 21st century internet. A safe small space of people we trust, where we can program away from the prying eyes of the multi-billion-person internet. Where the outright villainous will be kept at bay by good identity services and good crypto.

A LAN-like experience across the global Internet. A worthy goal indeed.

Also diggin’ “Software I’m thankful for”. Except for vim, said the Emacs guy 🤣

Steampipe `net_http_request`

Link parkin’: Why build an HTTP client into a database? So you can ingest web data directly!

Steampipe tables can do all sorts of surprising things! Did you know, for example, that the Net plugin’s net_http_request encapsulates a full-blown HTTP client?

I don’t think it’s a “trend” but noting that in my feeds, exploiting an HTTP client mechanism inside of an SQL engine is popping up often.

Great piece by Jon Udell via his own blog.

Compact Speech Recognition

Link parkin’: whisper.cpp

High-performance inference of OpenAI’s Whisper automatic speech recognition (ASR) model:

Having such a lightweight implementation of the model allows to easily integrate it in different platforms and applications. As an example, here is a video of running the model on an iPhone 13 device - fully offline, on-device:

Really compact C++ version of a production speech-to-text model. If I can get it to build, I’ll try it against some podcasts to see how things come out. If halfway decent it could become a piece of a comprehensive personal knowledge extraction memex.

Chiang and Borges

Having just completed a collection of Borges short stories, The Aleph and Other Stories, I recently had a lightbulb moment. The brilliance of Ted Chiang’s short story Exhalation (thank you Wayback Machine) partially derives from channeling Borges so much. Deeply philosophical, first person narrative, brutally compact, with a mind bending twist at the end.

Both authors highly recommended.

Newsletter Knowledge

Speaking of newsletters, a while ago I was trying to recover a web page on password handling in Python (Secure Password Handling in Python) that I had just left in a browser tab. I’ve got a bunch of Chrome profiles, always have multiple Chrome/Firefox/Safari windows open, each with multiple (multiple) tabs open. Due to a couple of reboots and default age limits on browser history it was looking grim on finding that link again. While useful, the page wasn’t search engine optimized enough to come in at the top of Google with the obvious keywords.


Then I remembered which Chrome profile likely had the tab. Popped over to the window and pulled up GMail for that account. Hit the GMail search bar with “credentials” and there it is, right in the PyCoder’s Weekly newsletter.

All to say, over time a collection of link newsletters actually represents a significant knowledge base of curated references. The links have been vetted by humans for collection. For many of them, there’s an authored text summary nearby. There’s a timestamp attached, along with other metadata.

What if you grabbed those newsletters out of mailboxes and shoved them into a text and/or semantic search engine? Possibly another component of a personal memex.

TIL Cathect

TIL a new word, cathect

(transitive, psychology) To focus ones emotional energies on someone
or something.

    * 2013, Carroll E. Izard, Human Emotions, page 193:

        Apparently it is possible for an individual to cathect any
        person, object, idea, or image. Of considerable importance to
        a possible analogy between cathexis and the emotion of
        interest, is Freuds notion that an individual can cathect
        thought or thinking as well as attention and perception.

Love it when my vocabulary is expanded.

Courtesy of Masha Gessen in Surviving Autocracy.

Newsletters and Linkblogging

I subscribe to a decent number of newsletters, like Python Weekly, that are basically curated collections of links. I want to do more linkblogging and these are great resources but for the following problems:

  • There’s not an easy way to pry out and blockquote text for individual links
  • The embedded links flow through tracking redirectors, so it’s hard to know the ultimate destination
  • Linkpost creation needs an easy way to do some type of via attribution

Sounds like time to get on the hacking bandwagon. One potentially useful hack is that I’m pushing a number of newsletters into a Feedbin mail address and reading them there. I wonder what they look like when grabbed out of the Feedbin api?

2022 Books Completed, Part 5

Thirty-six works completed for the year, by my current math. If I don’t completely flub my holiday time, I’ll notch forty this annum.

read more ...

A pyproject.toml Tutorial

In starting a new Python module for mucking around with the Feedbin API, I wanted to modernize a little and update to the latest Python practices for packaging. In particular, I’d been hearing a bit about pyproject.toml as the way to configure Python build tools. Rogier van der Greer has an excellent overview:

About three years ago I wrote a blog post about using setup.py to set up your python projects. Since then a lot has changed, mostly due to PEP 517, PEP 518 and the introduction of the pyproject.toml file.

The goal of this file is to allow you to define what build tools are needed in order to build your package – no longer assuming it must be Setuptools. This makes it easier to use alternatives to Setuptools, which means that Setuptools does no longer have to be the one build tool that can do everything.

Nevertheless, I like the feature set of Setuptools, and would like to continue to use it in the foreseeable future. But while documentation for using for example Poetry or Flit together with a pyproject.toml is easy to find, it is more difficult to find similar documentation for Setuptools. So let me help you out.


Link parkin’: sqlite-http

sqlite-http is a new SQLite extension that allows you to create HTTP requests in SQLite.

Think of fetch(), requests.get(), or curl, but entirely in SQL!

Also really enjoy how Alex Garcia created a blog post out of a notebook. Looks like he’s building up quite the collection of SQLite extensions.

C. f. Steampipe

Serial Project Hoarding

Wisdom from Simon Willison. Coping strategies for the serial project hoarder

This is the most important tip: avoid side projects with user accounts.

If you build something that people can sign into, that’s not a side-project, it’s an unpaid job. It’s a very big responsibility, avoid at all costs!

Almost all of my projects right now are open source things that people can run on their own machines, because that’s about as far away from user accounts as I can get.

I still have a responsibility for shipping security updates and things like that, but at least I’m not holding onto other people’s data for them.

Let me repeat for emphasis If you build something that people can sign into, that’s not a side-project, it’s an unpaid job.

Most definitely.

Tailscale First Impressions

Tailscale is a VPN for the rest of us:

A frustratingly simple VPN

Tailscale lets you easily manage access to private resources, quickly SSH into devices on your network, and work securely from anywhere in the world.

Oddly, while I probably could have put Tailscale to gainful use for quite some time, instead I just admired the technical depth of their blog posts. There’s been a lot of hype, criticism, discussion, etc. but the product seems to be a bit of a hit.

With a few spare cycles I finally decided to bite the bullet and give Tailscale a shot. In the last 24 hours, I’ve installed it on six machines, a mix of Linux, macOS, and iOS. Three are desktops/servers in my home, one’s a relatively stationary laptop, another’s a cloud node, and the last is an iPad. All easy, peasy 5-10 minute installs. The UX is to die for.

Haven’t attempted to use while away from homebase which is the next round of testing. So far, so good.

Once upon a time I noodled around with ZeroTier, which still seems to be chugging along. At that moment, the UX for ZeroTier was nowhere as smooth as Tailscale now, especially the outsourced identity management. I expect ZeroTier has sanded off a lot of the rough edges. The Tailscale folks even consider ZeroTier an admirable competitor.


Link parkin’: requests-cache

requests-cache is a transparent, persistent cache that provides an easy way to get better performance with the python requests library.

Necessary for messing around with the Feedbin api.

Dammit Doctorow!

So I just recently mentioned how I’m trying to fight off some media FOMO.

And then Cory Doctorow posts about all the books he reviewed in 2022.

Every year around this time, I round up all the books I reviewed in the previous 12 months; both as a convenience for readers and to remind myself that I don’t need to feel quite so horribly guilty about all the books I didn’t review (to those authors: rest assured, I still feel horribly guilty).

I’m already in the hole four titles, one completed, in hand from this list (Chokepoint Capitalism, A Half-Built Garden, The Persuaders, and finished Survival of the Richest ). Now I’m getting excellent blurbs on further material for the library, from a favorite blogger. Definitely eyeing a handful of the nonfiction recommendations and it may be time for me to come back around to Neal Stephenson.

I’m going to swipe from another of my faves, Tim Bray, in a post worthy of discussion here, and say Protect Me From What I Want!!.


TIL jsonpath-ng

A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators, as defined in the original JSONPath proposal. …

This library differs from other JSONPath implementations in that it is a full language implementation, meaning the JSONPath expressions are first class objects, easy to analyze, transform, parse, print, and extend.

And a nice, quick tutorial to go with it.

Mentioning Httpie

Link parkin’ HTTPie

Don’t know how it’s possible, but I haven’t mentioned this incredibly useful, Python implemented, CLI for interacting with HTTP(S) endpoints. Sort of a friendlier curl, but with a lot less muscle memory for me.

PSF Supporting Member

Here in the states we celebrate a Thursday holiday with food and entertainment gluttony, followed by a Friday of manufactured physical retail (traditionally) consumer gluttony, followed the next Monday with manufactured online consumer gluttony.

And then we try to cleanse the palate with “Giving Tuesday”.

I’ve pretty much trended to flipping the script on those days, although I do love a bit of food gluttony. This year I achieved nothing purchased on Black Friday and Cyber Monday, nor even over the weekend which used to be my consumer gluttony escape hatch. On Giving Tuesday I batched up a whole bunch of my annual giving which usually gets done the last week of December.

Not going to go into all of my charitable contributions, but just wanted to plug supporting membership in the Python Software Foundation. I’ve gotten a ton out of Python over my career and I really enjoy the programming language, so it’s only right I give back a little. I have no idea what benefits accrue from supporting membership but I’m doing my bit to publicize their availability and encourage other Pythonistas to join up.

Pi-hole Bliss

Some folks at work were waxing ecstatic about how a Pi-hole made such a huge difference in ad blocking. I thought installation would be a little bit more effort than value for my time. Then I found a small window of free time this weekend and thought “what the heck, let’s give it a shot.”

Commonly affiliated with Raspberry Pi devices, Pi-hole is actually quite easy to deploy on an Ubuntu device. After less than an hours worth of work, I had the whole stack up and running. And boy does it make a difference! The screen cap below highlights the amount of ad blockage for just a couple of hours and a few devices.

Pi-hole Dashboard Screen Capture

Sorry I waited so long!

© 2008 ‐ 2023 C. Ross Jam. Built using Pelican. Theme based upon Giulio Fidente’s original svbhack, and slightly modified by crossjam.