Skip to main content

create_snippets.py

1#!/usr/bin/env python3
2"""
3Create my snippet expansions for Alfred.
4"""
6import hashlib
7import json
8import os
9import uuid
10import zipfile
13def read(name: str) -> str:
14 """
15 Return the contents of text file in the `expansions` folder.
16 """
17 with open(os.path.join("expansions", name)) as infile:
18 return infile.read()
21# fmt: off
22SNIPPETS = {
23 "!bq": "<blockquote>{clipboard}</blockquote>",
25 # =========================
26 # Get the current date/time
27 # =========================
29 # pretty, e.g. 9 April 2024
30 ";dp": "{isodate:d MMMM yyyy}",
32 # short, e.g. 2024-04-09
33 ";ds": "{isodate:yyyy-MM-dd}",
35 # e.g. 2024-04-09 10:49:12 +01:00
36 ";dd": "{isodate:yyyy-MM-dd HH:mm:ss xxx}",
38 # e.g. \[10:49]
39 # I use this for timestamped entries in my Obsidian journal
40 ";dt": r"\[{isodate:HH:mm}] ",
42 # e.g. 2025-02-09T09:13:58Z
43 # I use this for timestamped entries in JavaScript/JSON files
44 ";dj": "{isodatetime}",
46 # =========================
47 # English words and phrases
48 # =========================
49 "a11e": "accessible",
50 "a11y": "accessibility",
51 "A11y": "Accessibility",
52 "acc't": "account",
53 "Acc't": "Account",
54 "acct": "account",
55 "Acct": "Account",
56 "afaict": "as far as I can tell",
57 " atm": " at the moment",
58 "avg": "average",
59 " bc": " because",
60 " Bc": " Because",
61 "bdy": "boundary",
62 "Bdy": "Boundary",
63 "bdies": "boundaries",
64 "Bdies": "Boundaries",
65 "cafe": "café",
66 "cliche": "cliché",
67 "ctd": "continued",
68 " cts": " continuous",
69 "Cts": "Continuous",
70 "Das Ubermensch": "Das Übermensch",
71 "defn": "definition",
72 "Deja vu": "Déjà vu",
73 "deja vu": "déjà vu",
74 "dept.": "department",
75 "distn": "distribution",
76 "digipres": "digital preservation",
77 "Digipres": "Digital preservation",
78 "eqn": "equation",
79 "expt": "experiment",
80 "fdn": "foundation",
81 "Fdn": "Foundation",
82 "fiance": "fiancé",
83 " fn": " function",
84 " Fn": " Function",
85 "gdn": "garden",
86 "Gdn": "Garden",
87 "gov't": "government",
88 "govt": "government",
89 "Govt": "Government",
90 "i14n": "intersectional",
91 "i18n": "internationalisation",
92 " iff": " if and only if",
93 "ina11e": "inaccessible",
94 "indpt": "independent",
95 "intl.": "international",
96 "iptic": "in particular",
97 "Iptic": "In particular",
98 "l12n": "localisation",
99 "lmk": "let me know",
100 "mgmt": "management",
101 "Mgmt": "Management",
102 " mgr": " manager",
103 "Mgr": "Manager",
104 "naive": "naïve",
105 "natl.": "national",
106 "nbhd": "neighbourhood",
107 "nee ": "née ",
108 " o/w": " otherwise",
109 " O/w": " Otherwise",
110 "ofc": "of course",
111 " ptic": " particular",
112 "rec'd": "recommended",
113 "reln": "relation",
114 "Reln": "Relation",
115 "reqd": "required",
116 "s.t.": "such that",
117 "soln": "solution",
118 "spose": "suppose",
119 "Spose": "Suppose",
120 "stdlib": "standard library",
121 " thm": " theorem",
122 ";xp": "experience",
123 ";Xp": "Experience",
124 "w/b": "week beginning",
125 "w/e": "week ending",
126 "w/o": "without",
127 "y'day": "yesterday",
129 # =============================
130 # Fix my common typing mistakes
131 # =============================
132 "cunt": "count",
133 "EVentbridge": "EventBridge",
134 "Flcikr": "Flickr",
135 " ot ": " to ",
136 "thier": "their",
137 "WHy": "Why",
139 # ============
140 # Proper nouns
141 # ============
142 "Agnes": "Agnès",
143 "Airdrop": "AirDrop",
144 "Anais Mitchell": "Anaïs Mitchell",
145 "B'ham": "Birmingham",
146 "BackBlaze": "Backblaze",
147 "Bezier": "Bézier",
148 "Borrowbox": "BorrowBox",
149 "CO2": "CO₂",
150 "China Mieville": "China Miéville",
151 "Cloudwatch": "CloudWatch",
152 "Ebay": "eBay",
153 "El Otro Periodico": "El Otro Periódico",
154 "Elasticache": "ElastiCache",
155 "Erdos": "Erdős",
156 "Eventbridge": "EventBridge",
157 "Facetime": "FaceTime",
158 "FastMail": "Fastmail",
159 "Gitbook": "GitBook",
160 "Hashicorp": "HashiCorp",
161 "Maciej Ceglowski": "Maciej Cegłowski",
162 "Paypal": "PayPal",
163 "Phylopic": "PhyloPic",
164 "Postgresql": "PostgreSQL",
165 "Powerpoint": "PowerPoint",
166 "Raphaelle": "Raphaëlle",
167 "Redmonk": "RedMonk",
168 "Regents Canal": "Regent’s Canal",
169 "Rubocop": "RuboCop",
170 "Sqlite": "SQLite",
171 "SQlite": "SQLite",
172 "Sean": "Seán",
173 "Sharepoint": "SharePoint",
174 "Skoda": "Škoda",
175 "Smugmug": "SmugMug",
176 "Spitlip": "SpitLip",
177 "Taf": "Tâf",
178 "Textexpander": "TextExpander",
179 "Tineye": "TinEye",
180 "Whatsapp": "WhatsApp",
181 "WikiData": "Wikidata",
182 "Wireguard": "WireGuard",
183 "Wordpress": "WordPress",
184 "Youtube": "YouTube",
185 "Zoe": "Zoë",
186 "bhalaj": "bhålaj",
187 " ldn": " london",
188 "wall-e": "WALL·E",
190 # =================================
191 # Symbols and other bits of Unicode
192 # =================================
193 "180^": "180°",
194 "^C": "°C",
195 "^F": "°F",
196 "^deg": "°",
197 "^ft": "′",
198 "^in": "″",
199 ":+1:": "👍",
200 ":wave:": "👋",
201 ";1/2": "½",
202 ";1/4": "¼",
203 ";3/4": "¾",
204 ";alt": "⌥",
205 ";approx": "≈",
206 ";bullet": "•",
207 ";cmd": "⌘",
208 ";ctrl": "⌃",
209 ";dot": "·",
210 ";eur": "€",
211 ";minus": "−",
212 ";opt": "⌥",
213 ";pi": "π",
214 ";pm": "±",
215 ";sec": "§",
216 ";shift": "⇧",
217 ";sqrt": "√",
218 ";times": "×",
219 ";tm": "™",
220 ";zwsp": "\u200b", # zero-width space
222 # This snippet draws a basic tree with Unicode characters, which
223 # I can copy/paste to build a more complex tree by hand.
224 "!tree": read("tree.txt"),
226 # =============
227 # Personal info
228 # =============
229 "@aa": "@alexwlchan.net",
230 ";ee": "alex@alexwlchan.net",
232 ";ale": "alexwlchan",
234 ";so": "Signed-off-by: Alex Chan <alexc@tailscale.com>",
236 # My Mastodon server
237 ";san": "social.alexwlchan.net",
239 # ====================
240 # Programming snippets
241 # ====================
242 "!bash": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\n",
243 "!osa": "#!/usr/bin/env osascript\n",
244 "!py": "#!/usr/bin/env python3\n\n",
245 "!rb": "#!/usr/bin/env ruby\n",
246 "!swift": "#!/usr/bin/env swift\n",
248 "!rect": '<rect width="500" height="250" fill="yellow"/>',
249 "!svg": read("template.svg"),
250 "!cogsvg": read("cog_template.svg"),
252 "!before": read("before_and_after.html"),
254 "!mit": read("mit_license.txt"),
256 # Git trailer
257 # See https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors
258 ";co": "Co-authored-by:",
260 ";sc": "Updates #cleanup\n\nSigned-off-by: Alex Chan <alexc@tailscale.com>",
262 # ===================================
263 # Python-related programming snippets
264 # ===================================
265 "!dt": "from datetime import datetime, timezone\n",
266 "!j": "import json\n",
267 "!pp": "from pprint import pprint; pprint({cursor})",
269 "@param": "@pytest.mark.parametrize({cursor})",
271 "!flapi": read("flapi.py"),
273 "py!aws": read("get_boto3_session.py"),
274 "py!dy": read("list_dynamodb_rows.py"),
275 "py!h": read("create_hash.py"),
276 "py!de": read("datetime_encoder.py"),
277 "py!pth": read("get_file_paths.py"),
278 "py!pd": read("get_directories.py"),
279 "py!sec": read("get_secrets_manager_secret.py"),
280 "py!s3": read("list_s3_objects.py"),
281 "py!ej": read("edit_javascript.py"),
283 # I can never remember the order of args to this function,
284 # so when I start typing it, add a comment to help me out.
285 "datetime.datetime.strp": (
286 "datetime.datetime.strptime({cursor}) "
287 "# date_string, format"),
289 # =================
290 # Obsidian snippets
291 # =================
292 ";nd": read("note_header.txt"),
294 # ============
295 # Misc phrases
296 # ============
297 "porque?": "¿Por qué no los dos?",
299# fmt: on
302def add_snippet(zf: zipfile.ZipFile, shortcut: str, expansion: str) -> None:
303 """
304 Add a single snippet to a snippets bundle.
305 """
306 h = hashlib.md5()
307 h.update(shortcut.encode("utf8"))
308 h.update(expansion.encode("utf8"))
310 snippet_id = uuid.UUID(hex=h.hexdigest())
312 snippet_data = {
313 "alfredsnippet": {
314 "snippet": expansion,
315 "uid": str(snippet_id),
316 "name": "",
317 "keyword": shortcut,
318 }
319 }
321 zf.writestr(f"{snippet_id}.json", data=json.dumps(snippet_data))
324if __name__ == "__main__":
325 with zipfile.ZipFile("Alex’s snippets.alfredsnippets", "w") as zf:
326 zf.write("info.plist")
328 for shortcut, expansion in SNIPPETS.items():
329 add_snippet(zf, shortcut, expansion)