Skip to main content

Bring across all my TextExpander snippets

ID
192e10c
date
2024-04-09 10:48:17+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
50eac99
message
Bring across all my TextExpander snippets
changed files
11 files, 310 additions, 20 deletions

Changed files

create_snippets.py (2538) → create_snippets.py (6779)

diff --git a/create_snippets.py b/create_snippets.py
index 3ee659f..034def5 100755
--- a/create_snippets.py
+++ b/create_snippets.py
@@ -9,6 +9,11 @@ import uuid
 import zipfile
 
 
+def read(name):
+    with open(os.path.join('expansions', name)) as infile:
+        return infile.read()
+
+
 # fmt: off
 SNIPPETS = {
     # =========================
@@ -27,7 +32,145 @@ SNIPPETS = {
     # =========================
     # English words and phrases
     # =========================
+    "a11e": "accessible",
+    "a11y": "accessibility",
+    "acc't": "account",
+    "acct": "account",
+    "afaict": "as far as I can tell",
+    "atm": "at the moment",
+    "avg": "average",
+    "bdy": "boundary",
+    "cafe": "café",
+    "cliche": "cliché",
+    "ctd": "continued",
+    "cts": "continuous",
+    "Das Ubermensch": "Das Übermensch",
+    "defn": "definition",
+    "deja vu": "déjà vu",
+    "dept.": "department",
+    "distn": "distribution",
+    "eqn": "equation",
+    "expt": "experiment",
+    "fdn": "foundation",
+    "fiance": "fiancé",
+    "fn": "function",
+    "Gdn": "Garden",
+    "gov't": "government",
+    "govt": "government",
+    "i14n": "intersectional",
+    "i18n": "internationalisation",
+    "iff": "if and only if",
     "ina11e": "inaccessible",
+    "indpt": "independent",
+    "intl.": "international",
+    "iptic": "in particular",
+    "l12n": "localisation",
+    "lmk": "let me know",
+    "mgmt": "management",
+    "mgr": "manager",
+    "naive": "naïve",
+    "natl.": "national",
+    "nbhd": "neighbourhood",
+    "nee ": "née ",
+    "o/w": "otherwise",
+    "ofc": "of course",
+    "ptic": "particular",
+    "rec'd": "recommended",
+    "reln": "relation",
+    "reqd": "required",
+    "s.t.": "such that",
+    "soln": "solution",
+    "spose": "suppose",
+    "stdlib": "standard library",
+    "thm": "theorem",
+    "w/b": "week beginning",
+    "w/e": "week ending",
+    "w/o": "without",
+    "y'day": "yesterday",
+
+    # =============================
+    # Fix my common typing mistakes
+    # =============================
+    "cunt": "count",
+    "EVentbridge": "EventBridge",
+    " ot ": " to ",
+    "thier": "their",
+    "WHy": "Why",
+
+    # ============
+    # Proper nouns
+    # ============
+    "Agnes": "Agnès",
+    "B'ham": "Birmingham",
+    "BackBlaze": "Backblaze",
+    "Bezier": "Bézier",
+    "CF": "CloudFront",
+    "CO2": "CO₂",
+    "China Mieville": "China Miéville",
+    "Cloudwatch": "CloudWatch",
+    "Ebay": "eBay",
+    "El Otro Periodico": "El Otro Periódico",
+    "Elasticache": "ElastiCache",
+    "Eventbridge": "EventBridge",
+    "Facetime": "FaceTime",
+    "FastMail": "Fastmail",
+    "Gitbook": "GitBook",
+    "Hashicorp": "HashiCorp",
+    "Maciej Ceglowski": "Maciej Cegłowski",
+    "Paypal": "PayPal",
+    "Phylopic": "PhyloPic",
+    "Postgresql": "PostgreSQL",
+    "Powerpoint": "PowerPoint",
+    "Raphaelle": "Raphaëlle",
+    "Regents Canal": "Regent’s Canal",
+    "Rubocop": "RuboCop",
+    "Sqlite": "SQLite",
+    "SQlite": "SQLite",
+    "Sean": "Seán",
+    "Sharepoint": "SharePoint",
+    "Skoda": "Škoda",
+    "Smugmug": "SmugMug",
+    "Taf": "Tâf",
+    "Textexpander": "TextExpander",
+    "Whatsapp": "WhatsApp",
+    "WikiData": "Wikidata",
+    "Wordpress": "WordPress",
+    "Youtube": "YouTube",
+    "Zoe": "Zoë",
+    "bhalaj": "bhålaj",
+    "ldn": "london",
+    "wall-e": "WALL·E",
+
+    # =================================
+    # Symbols and other bits of Unicode
+    # =================================
+    "180^": "180°",
+    "^C": "°C",
+    "^F": "°F",
+    "^deg": "°",
+    "^ft": "′",
+    "^in": "″",
+    ":+1:": "👍",
+    ":wave:": "👋",
+    ";1/2": "½",
+    ";3/4": "¾",
+    ";alt": "⌥",
+    ";approx": "≈",
+    ";bullet": "•",
+    ";cmd": "⌘",
+    ";ctrl": "⌃",
+    ";dot": "·",
+    ";eur": "€",
+    ";minus": "−",
+    ";opt": "⌥",
+    ";pi": "π",
+    ";pm": "±",
+    ";sec": "§",
+    ";shift": "⇧",
+    ";sqrt": "√",
+    ";times": "×",
+    ";tm": "™",
+    ";zwsp": "\u200b",  # zero-width space
 
     # =============
     # Personal info
@@ -37,30 +180,47 @@ SNIPPETS = {
 
     ";ale": "alexwlchan",
 
-    # =================================
-    # Programming: shebangs for scripts
-    # =================================
+    # ====================
+    # Programming snippets
+    # ====================
     "!bash": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\n",
     "!osa": "#!/usr/bin/env osascript\n",
     "!py": "#!/usr/bin/env python3\n\n",
     "!rb": "#!/usr/bin/env ruby\n",
     "!swift": "#!/usr/bin/env swift\n",
 
-    # ============================
-    # Programming: Python snippets
-    # ============================
+    "!rect": '<rect width="500" height="250" fill="yellow"/>',
+    "!svg": read("template.svg"),
+
+    "!before": read("before_and_after.html"),
+
+    "!mit": read("mit_license.txt"),
+
+    # Git trailer
+    # See https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors
+    ";co": "Co-authored-by:",
+
+    # ===================================
+    # Python-related programming snippets
+    # ===================================
     "!j": "import json\n",
-}
-# fmt: on
+    "!pp": "from pprint import pprint; pprint({cursor})",
 
+    "@param": "@pytest.mark.parametrize({cursor})",
 
+    "!flapi": read("flapi.py"),
 
-# fmt: off
-LONGER_SNIPPETS = {
-    # ==============================
-    # Programming: fragments of code
-    # ==============================
-    "!flapi": "flapi.py",
+    "py!aws": read("get_boto3_session.py"),
+    "py!dy": read("list_dynamodb_rows.py"),
+    "py!h": read("create_hash.py"),
+    "py!jd": read("datetime_encoder.py"),
+    "py!pth": read("get_file_paths.py"),
+    "py!sec": read("get_secrets_manager_secret.py"),
+    "py!s3": read("list_s3_objects.py"),
+
+    # I can never remember the order of args to this function,
+    # so when I start typing it, add a comment to help me out.
+    "datetime.datetime.strp": "datetime.datetime.strptime({cursor})  # date_string, format",
 }
 # fmt: on
 
@@ -97,10 +257,4 @@ if __name__ == "__main__":
         for shortcut, expansion in SNIPPETS.items():
             add_snippet(zf, shortcut, expansion)
 
-        for shortcut, filename in LONGER_SNIPPETS.items():
-            with open(pathlib.Path("expansions") / filename) as f:
-                expansion = f.read()
-
-            add_snippet(zf, shortcut, expansion)
-
     subprocess.check_call(["open", "Alex’s snippets.alfredsnippets"])

expansions/before_and_after.html (0) → expansions/before_and_after.html (101)

diff --git a/expansions/before_and_after.html b/expansions/before_and_after.html
new file mode 100644
index 0000000..bfc6fb8
--- /dev/null
+++ b/expansions/before_and_after.html
@@ -0,0 +1,10 @@
+<table>
+<tr>
+<th>Before</th>
+<th>After</th>
+</tr>
+<tr>
+<td>BEFORE</td>
+<td>AFTER</td>
+</tr>
+</table>

expansions/create_hash.py (0) → expansions/create_hash.py (287)

diff --git a/expansions/create_hash.py b/expansions/create_hash.py
new file mode 100644
index 0000000..5950741
--- /dev/null
+++ b/expansions/create_hash.py
@@ -0,0 +1,14 @@
+import hashlib
+
+
+def create_hash(path, *, algorithm=hashlib.sha256):
+    """
+    Returns the hex checksum of the given path.
+    """
+    h = algorithm()
+
+    with open(path, "rb") as infile:
+        while chunk := infile.read(8192):
+            h.update(chunk)
+
+    return h.hexdigest()

expansions/datetime_encoder.py (0) → expansions/datetime_encoder.py (169)

diff --git a/expansions/datetime_encoder.py b/expansions/datetime_encoder.py
new file mode 100644
index 0000000..8b6c1a5
--- /dev/null
+++ b/expansions/datetime_encoder.py
@@ -0,0 +1,7 @@
+import datetime
+
+
+class DatetimeEncoder(json.JSONEncoder):
+    def default(self, obj):
+        if isinstance(obj, datetime.datetime):
+            return obj.isoformat()

expansions/get_boto3_session.py (0) → expansions/get_boto3_session.py (484)

diff --git a/expansions/get_boto3_session.py b/expansions/get_boto3_session.py
new file mode 100644
index 0000000..ee8a15b
--- /dev/null
+++ b/expansions/get_boto3_session.py
@@ -0,0 +1,15 @@
+import boto3
+
+
+def get_aws_session(*, role_arn: str) -> boto3.Session:
+    sts_client = boto3.client("sts")
+    assumed_role_object = sts_client.assume_role(
+        RoleArn=role_arn, RoleSessionName="AssumeRoleSession1"
+    )
+    credentials = assumed_role_object["Credentials"]
+
+    return boto3.Session(
+        aws_access_key_id=credentials["AccessKeyId"],
+        aws_secret_access_key=credentials["SecretAccessKey"],
+        aws_session_token=credentials["SessionToken"],
+    )

expansions/get_file_paths.py (0) → expansions/get_file_paths.py (598)

diff --git a/expansions/get_file_paths.py b/expansions/get_file_paths.py
new file mode 100644
index 0000000..bd9e1c9
--- /dev/null
+++ b/expansions/get_file_paths.py
@@ -0,0 +1,25 @@
+import pathlib
+
+
+def get_file_paths_under(root=".", *, suffix=""):
+    """
+    Generates the absolute paths to every matching file under ``root``.
+    """
+    root = pathlib.Path(root)
+
+    if root.exists() and not root.is_dir():
+        raise ValueError(f"Cannot find files under file: {root!r}")
+
+    if not root.is_dir():
+        raise FileNotFoundError(root)
+
+    for dirpath, _, filenames in root.walk():
+        for f in filenames:
+            p = dirpath / f
+
+            if p.is_file() and f.lower().endswith(suffix):
+                yield p
+
+
+for p in get_file_paths_under():
+    {cursor}

expansions/get_secrets_manager_secret.py (0) → expansions/get_secrets_manager_secret.py (273)

diff --git a/expansions/get_secrets_manager_secret.py b/expansions/get_secrets_manager_secret.py
new file mode 100644
index 0000000..26fa516
--- /dev/null
+++ b/expansions/get_secrets_manager_secret.py
@@ -0,0 +1,9 @@
+def get_secret_string(sess: boto3.Session, **kwargs) -> str:
+    """
+    Look up a SecretString from Secrets Manager, and return the string.
+    """
+    secrets = sess.client("secretsmanager")
+
+    resp = secrets.get_secret_value(**kwargs)
+
+    return resp["SecretString"]

expansions/list_dynamodb_rows.py (0) → expansions/list_dynamodb_rows.py (661)

diff --git a/expansions/list_dynamodb_rows.py b/expansions/list_dynamodb_rows.py
new file mode 100644
index 0000000..cca105b
--- /dev/null
+++ b/expansions/list_dynamodb_rows.py
@@ -0,0 +1,19 @@
+import boto3
+
+
+def scan_table(sess: boto3.Session, *, TableName: str, **kwargs):
+    """
+    Generates all the items in a DynamoDB table.
+
+    :param dynamo_client: A boto3 client for DynamoDB.
+    :param TableName: The name of the table to scan.
+
+    Other keyword arguments will be passed directly to the Scan operation.
+    See https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Client.scan
+
+    """
+    dynamo_client = sess.resource("dynamodb").meta.client
+    paginator = dynamo_client.get_paginator("scan")
+
+    for page in paginator.paginate(TableName=TableName, **kwargs):
+        yield from page["Items"]

expansions/list_s3_objects.py (0) → expansions/list_s3_objects.py (196)

diff --git a/expansions/list_s3_objects.py b/expansions/list_s3_objects.py
new file mode 100644
index 0000000..d23b1b0
--- /dev/null
+++ b/expansions/list_s3_objects.py
@@ -0,0 +1,5 @@
+def list_s3_objects(sess: boto3.Session, **kwargs):
+    s3 = sess.client("s3")
+
+    for page in s3.get_paginator("list_objects_v2").paginate(**kwargs):
+        yield from page.get("Contents", [])

expansions/mit_license.txt (0) → expansions/mit_license.txt (1063)

diff --git a/expansions/mit_license.txt b/expansions/mit_license.txt
new file mode 100644
index 0000000..2cd802b
--- /dev/null
+++ b/expansions/mit_license.txt
@@ -0,0 +1,19 @@
+Copyright (c) {isodate:yyyy} Alex Chan
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.

expansions/template.svg (0) → expansions/template.svg (273)

diff --git a/expansions/template.svg b/expansions/template.svg
new file mode 100644
index 0000000..fdd81c1
--- /dev/null
+++ b/expansions/template.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+  <defs>
+    <style>
+      line {
+        stroke: currentColor;
+        stroke-width: 1.5;
+        stroke-linecap: round;
+      }
+    </style>
+  </defs>
+
+  <rect width="200" height="200" fill="yellow"/>
+</svg>