Do you know those situations where you copy something to the clipboard—such as a phone number, a credit card number, or an IBAN—to paste it into a web form, but the form is annoyingly strict and won’t allow spaces, dashes, or parentheses?
To solve this, I came up with a little Hammerspoon module that binds to ⌃⌘V and strips everything except ASCII letters, digits, and plus signs.
Examples:
- +49 30 23125-123 → +493023125123
- (030) 23125-123 → 03023125123
- GB33 BUKB 2020 1555 5555 55 → GB33BUKB20201555555555
Bonus features:
- It uses
hs.eventtap.keyStrokes(), so it doesn’t perform an actual paste but instead simulates typing the string. This helps with form fields that, for whatever reason, prevent you from pasting into them. - If you accidentally apply it to a canonical UUID or a string containing an @ sign—typically an email address—it preserves the punctuation and only trims surrounding whitespace.
local module = {}
-- Compact identifiers commonly consist only of ASCII letters, digits, and +.
local function sanitizeIdentifier(text)
return text:gsub("[^A-Za-z0-9+]", "")
end
local function trim(text)
return text:match("^%s*(.-)%s*$")
end
local function shouldPreserveFormatting(text)
-- Punctuation is meaningful in email addresses and canonical UUIDs.
if text:find("@", 1, true) then
return true
end
return text:match("^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$") ~= nil
end
local function pasteIdentifier()
local text = hs.pasteboard.getContents()
if not text then
hs.alert.show("Clipboard does not contain text")
return
end
local trimmedText = trim(text)
local value = shouldPreserveFormatting(trimmedText) and trimmedText or sanitizeIdentifier(text)
module.pasteTimer = hs.timer.doAfter(0.05, function()
module.pasteTimer = nil
-- Let the shortcut's physical Ctrl and Cmd keys come up first. Typing the
-- cleaned value directly avoids swapping and synchronizing the clipboard.
hs.eventtap.keyStrokes(value)
end)
end
function module.start()
module.hotkeys = {
hs.hotkey.bind({ "ctrl", "cmd" }, "v", pasteIdentifier),
}
end
return moduleSave it as identifier_paste.lua and include in your init.lua like so:
require("modules.identifier_paste").start()
