{"id":23535024,"url":"https://github.com/ripta/rt","last_synced_at":"2026-03-17T10:03:01.595Z","repository":{"id":187333891,"uuid":"270555054","full_name":"ripta/rt","owner":"ripta","description":"Here be my CLI tools.","archived":false,"fork":false,"pushed_at":"2026-02-21T05:04:07.000Z","size":547,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-21T12:16:19.845Z","etag":null,"topics":["ooo-guuurl","personal-tools"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ripta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-06-08T06:24:51.000Z","updated_at":"2026-02-21T05:04:10.000Z","dependencies_parsed_at":"2023-08-09T23:30:54.198Z","dependency_job_id":"8d0d16b7-3186-4d38-b467-65a9b4ac8448","html_url":"https://github.com/ripta/rt","commit_stats":null,"previous_names":["ripta/rt"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/ripta/rt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ripta%2Frt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ripta%2Frt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ripta%2Frt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ripta%2Frt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ripta","download_url":"https://codeload.github.com/ripta/rt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ripta%2Frt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30622115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-17T08:10:05.930Z","status":"ssl_error","status_checked_at":"2026-03-17T08:10:04.972Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ooo-guuurl","personal-tools"],"created_at":"2024-12-26T01:14:26.152Z","updated_at":"2026-03-17T10:03:01.589Z","avatar_url":"https://github.com/ripta.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"rt: Ripta's collection of tools\n\nExpectations:\n\n- tools read from STDIN, write to STDOUT, and hopefully print errors to STDERR;\n- tools are meant to be combined with others, e.g., `hs` might be less useful\n  to you, because it prints file hashes in binary output instead of hex (but\n  `enc hex` converts it to hex strings).\n\nYou can install the all-in-one hyperbinary, which excludes any tools with CGO dependencies:\n\n```\ngo install github.com/ripta/rt/hypercmd/rt@latest\n```\n\nor install all tools as individual binaries:\n\n```\ngo install github.com/ripta/rt/cmd/...@latest\n```\n\nor pick-and-choose each tool to individually install:\n\n* [cg](#cg) to run a command and annotate its output with timestamps\n* [enc](#enc) to encode and decode STDIN\n* [grpcto](#grpcto) to frame and unframe gRPC messages\n* [hs](#hs) to hash STDIN\n* [lipsum](#lipsum) to generate placeholder text\n* [place](#place) for macOS Location Services (requires macOS and CGO)\n* [streamdiff](#streamdiff) to help you pick out field changes off a stream of JSON\n* [structfiles](#structfiles-sf) to examine and compare a pile of structured files\n* [toto](#toto) to inspect some protobuf messages\n* [uni](#uni) for unicode utils\n* [yfmt](#yfmt) to reindent YAML while preserving comments\n\nor, last but not least, install a lighter version of the hyperbinary, which excludes\ntools with CGO, terminal, or filesystem requirements, but compiles to WASM:\n\n```\nGOOS=wasip1 GOARCH=wasm CGO_ENABLED=0 go build -o rt_lite.wasm -v ./hypercmd/rt_lite\n```\n\nPull requests welcome, though you should probably check first before sinking any time.\n\n\n\n`cg`\n----\n\nRun a command and annotate each line of its stdout and stderr with a timestamp\nand stream indicator (`O` for stdout, `E` for stderr, `I` for cg's own\nlifecycle messages).\n\nActs like the `annotate-output` script; `cg` is short for command guard.\n\n```\ngo install github.com/ripta/rt/cmd/cg@latest\n```\n\nBasic usage:\n\n```\n❯ cg -- echo hello\n19:02:59 I: cg v0.1.0\n19:02:59 I: prefix=\"15:04:05 \"\n19:02:59 I: Started echo hello\n19:02:59 O: hello\n19:02:59 I: Finished with exitcode 0\n```\n\nStdout and stderr are distinguished:\n\n```\n❯ cg -- sh -c 'echo out; echo err \u003e\u00262'\n19:03:04 I: cg v0.1.0\n19:03:04 I: prefix=\"15:04:05 \"\n19:03:04 I: Started sh -c 'echo out; echo err \u003e\u00262'\n19:03:04 O: out\n19:03:04 E: err\n19:03:04 I: Finished with exitcode 0\n```\n\nThe child's exit code is propagated:\n\n```\n❯ cg -- sh -c 'exit 42'; echo $?\n19:20:35 I: cg v0.1.0\n19:20:35 I: prefix=\"15:04:05 \"\n19:20:35 I: Started sh -c 'exit 42'\n19:20:35 I: Finished with exitcode 42\n42\n```\n\nUse `--format` to change the timestamp prefix. It takes the golang\n`time.Format` layout:\n\n```\n❯ cg --format '2006-01-02T15:04:05 ' -- echo hello\n2026-02-22T19:05:00 I: cg v0.1.0\n2026-02-22T19:05:00 I: prefix=\"2006-01-02T15:04:05 \"\n2026-02-22T19:05:00 I: Started echo hello\n2026-02-22T19:05:00 O: hello\n2026-02-22T19:05:00 I: Finished with exitcode 0\n```\n\nSignals SIGINT and SIGTERM are forwarded to the child process.\n\n`cg` also supports `--capture` to capture the child's stdout and stderr into\nseparate files, and `--buffered` to buffer the child's output and print it all\nat once when the child finishes, instead of streaming it in real time.\n\n\n`enc`\n----\n\n```\ngo install github.com/ripta/rt/cmd/enc@latest\n```\n\nEncode and decode strings using various encodings:\n\n* `a85` for ascii85;\n* `b32` for base32 (RFC 4848 standard encoding, `ABCDEFGHIJKLMNOPQRSTUVWXYZ234567`);\n* `b32c` for base32 with Crockford's alphabet (`0123456789ABCDEFGHJKMNPQRSTVWXYZ`);\n* `b58` for base58;\n* `b64` for base64 (RFC 4648 standard encoding, `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`);\n* `hex` for lowercase hexadecimal;\n* `url` for URL escape/unescape; and\n* `varsel` for encoding raw bytes into Unicode variation selectors (VS-1 to VS-256).\n\n\n`hs`\n----\n\n```\ngo install github.com/ripta/rt/cmd/hs@latest\n```\n\nHash the input and print the resulting hash in binary bytes. Run with `-h` to\nsee the list of supported hash functions that are compiled into the binary,\nwhich is approximately:\n\n* `sha1` for SHA-1;\n* `sha224` for SHA-224;\n* `sha256` for SHA-256;\n* `sha3` for SHA-3/512;\n* `sha384` for SHA-384; and\n* `sha512` for SHA-512.\n\nTo output hexadecimal, pipe the output to `enc hex`. My knowledge graph uses a\ndifferent representation for hashes, so it's useful to me to not have the hex\nrepresentation.\n\n```\n❯ head -n 2 hamlet.txt\nTo be, or not to be: that is the question:\nWhether 'tis nobler in the mind to suffer\n\n❯ cat hamlet.txt | hs sha256 | enc hex\ne26671d53d74b6751373ad34768580af77847aa1513203d9a06c292617ab5c4b%\n\n❯ cat hamlet.txt | hs sha256 | enc base64\n4mZx1T10tnUTc600doWAr3eEeqFRMgPZoGwpJherXEs=%\n```\n\n(ICYDK, that `%` at the end is zsh's `PROMPT_EOL_MARK`.)\n\n\n`grpcto`\n--------\n\n```\ngo install github.com/ripta/rt/cmd/grpcto@latest\n```\n\nFrame and unframe raw bytes in a gRPC envelope. For example, assuming a proto\nmessage crafted using either `toto` (included in this repo) or `protoc\n--encode` (the official protobuf compiler), you can frame the message using:\n\n```\necho 'hello:\"world\"' \\\n    | protoc --encode foo.bar.v1.Thing ./thing.proto \\\n    | grpcto frame \u003e message.raw\n```\n\nwhere the resulting `message.raw` can be sent directly to a running gRPC\nservice using `curl`:\n\n```\ncurl -X POST --data-binary @message.raw -o response.raw -H 'content-type: application/grpc' --raw https://localhost:8443/foo.bar.v1.Thinger/Thing\n```\n\nand the `response.raw` can be unframed and decoded using `protoc`:\n\n```\ncat response.raw \\\n    | grpcto unframe \\\n    | protoc --decode_raw\n```\n\n`lipsum`\n--------\n\nGenerate some placeholder text, beyond just `lorem ipsum`. It also does some\noptional rate-limiting, printing one word at a time.\n\n```\ngo install github.com/ripta/rt/cmd/lipsum@latest\n```\n\n`place`\n------\n\nTalk to macOS Location Services from the command line.\n\n```\ngo install github.com/ripta/rt/cmd/place@latest\n```\n\nQuery as plaintext:\n\n```\n❯ place\nLatitude: 34.009414\nLongitude: -118.162233\nAccuracy: 45.751999\nLast observed: 2022-02-02T21:24:40-08:00\n```\n\nor as JSON by giving `-j` or `--json`.\n\n`streamdiff`\n------------\n\nHelps you pick out field changes off a stream of JSON.\n\n```\ngo install github.com/ripta/rt/cmd/streamdiff@latest\n```\n\nIt's technically usable  on any stream as long as the format is one JSON per\nline.\n\nIt's convenient for viewing Kubernetes resource changes over time.\n\nFor example, you can start a watch (`-w`) on pods (`kubectl get pods`) and\npipe it to streamdiff. Most fields won't be printed, except when they change.\nConsider this output:\n\n```\n❯ kubectl get pods -o json -w | streamdiff\nT+23s Pod:pomerium-cache-6c9f84b747-cr2rx\n  (1/2): spec.nodeName \\ -\u003e gke-vqjp-preemptible-065-38c45f41-wtnb\n  (2/2): status.conditions \\ -\u003e [map[lastProbeTime:\u003cnil\u003e lastTransitionTime:2023-06-22T06:27:43Z status:True type:PodScheduled]]\n\nT+24s Pod:pomerium-cache-6c9f84b747-cr2rx\n  (1/6): status.conditions.0 \\ -\u003e map[lastProbeTime:\u003cnil\u003e lastTransitionTime:2023-06-22T06:27:43Z status:True type:Initialized]\n  (2/6): status.conditions.1 \\ -\u003e map[lastProbeTime:\u003cnil\u003e lastTransitionTime:2023-06-22T06:27:43Z message:containers with unready status: [cache] reason:ContainersNotReady status:False type:Ready]\n  (3/6): status.conditions.2 \\ -\u003e map[lastProbeTime:\u003cnil\u003e lastTransitionTime:2023-06-22T06:27:43Z message:containers with unready status: [cache] reason:ContainersNotReady status:False type:ContainersReady]\n  (4/6): status.startTime \\ -\u003e 2023-06-22T06:27:43Z\n  (5/6): status.containerStatuses \\ -\u003e [map[image:us.gcr.io/dc-02/gke-vqjp/pomerium-cache:v1.0.23.1390 imageID: lastState:map[] name:cache ready:false restartCount:0 started:false state:map[waiting:map[reason:ContainerCreating]]]]\n  (6/6): status.hostIP \\ -\u003e 10.52.0.34\n\nT+26s Pod:pomerium-cache-6c9f84b747-cr2rx\n  (1/8): status.containerStatuses.0.ready false -\u003e true\n  (2/8): status.containerStatuses.0.started false -\u003e true\n  (3/8): status.containerStatuses.0.state.waiting map[reason:ContainerCreating] -\u003e \\\n  (4/8): status.containerStatuses.0.state.running \\ -\u003e map[startedAt:2023-06-22T06:27:46Z]\n  (5/8): status.containerStatuses.0.containerID \\ -\u003e containerd://293972feb5b498c80a585137299990c77f44ea46d6236432aba08e72108c35dc\n  (6/8): status.phase Pending -\u003e Running\n  (7/8): status.podIP \\ -\u003e 10.53.1.92\n  (8/8): status.podIPs \\ -\u003e [map[ip:10.53.1.92]]\n```\n\nWhile there is still some noise, it clearly shows when the pod was assigned to\na node, when the pod finished initializing, and when it changed phases from\nPending to Running.\n\nIn addition to a running log (as above), you can also run `streamdiff -i`,\nwhich updates status on the same line instead of printing a new line for\nevery resource update. YMMV.\n\n```\n❯ kubectl get nodes -o json -w | streamdiff -i\n\\ Node:gke-vqjp-ondemand-370-504f82ce-r0d8\tstatus.conditions.0.{type: FrequentContainerdRestart; status: True -\u003e False} \n\\ Node:gke-vqjp-preemptible-065-38c45f41-kvjd\tstatus.conditions.0.lastHeartbeatTime: 2023-06-22T06:44:18Z -\u003e 2023-06-22T06:49:19Z\n| Node:gke-vqjp-preemptible-065-38c45f41-pklf\tstatus.conditions.0.lastHeartbeatTime: 2023-06-22T06:44:15Z -\u003e 2023-06-22T06:49:16Z\n/ Node:gke-vqjp-preemptible-065-38c45f41-wtnb\tstatus.conditions.0.lastHeartbeatTime: 2023-06-22T06:45:05Z -\u003e 2023-06-22T06:50:11Z\n```\n\n\n`structfiles` (`sf`)\n--------------------\n\nProof of concept tool to examine and compare a pile of structured files (e.g.,\nKubernetes manifests) strewn across multiple directories or files, with any\nnumber of documents per file.\n\nSupports YAML, JSON, TOML, HCLv2, GOB, CSV, MessagePack, and EDN as input and\noutput, with some caveats:\n\n- HCLv2 output is experimental, due to the way that HCLv2 is schema-driven and\n  the lack of a way to represent the schema in structfiles.\n- CSV does not support nested maps. CSV treats each row as a separate document.\n  The first row of a CSV file is assumed to be the header.\n- YAML, JSON, and GOB support multiple documents in one stream.\n- EDN decoding forces stringification of map keys, and does not yet support the\n  entire EDN spec, e.g., `{:foo #{a 2}}` still trips up the converter.\n- Logfmt does not support nested maps. Each log line is treated as a separate \n  document.\n\nResulting diff currently only in unified diff of YAML (see example).\n\n```\ngo install github.com/ripta/rt/cmd/sf@latest\n```\n\nFor a list of supported formats and format-specific options, run `sf formats`:\n\n```\nFORMAT    EXTENSIONS      INPUT   OPTIONS      OUTPUT   OPTIONS\ncsv       .csv            yes     sep:string   yes      sep:string\nedn       .edn            yes     -            yes      indent:int prefix:string\ngob       .gob            yes     -            yes      -\nhcl2      .hcl            yes     -            yes      -\njson      .json           yes     -            yes      indent:int no_indent:bool\nlogfmt    .logfmt         yes     -            yes      -\nmsgpack   .mpk .msgpack   yes     -            yes      -\ntoml      .toml           yes     -            yes      indent:int\nyaml      .yml .yaml      yes     -            yes      indent:int\n```\n\nThe simplest subcommand is `eval`, which reads one or more files and prints\nthe data back out, like a pretty-printer. The default format is JSON with\nan indentation of 2 spaces.\n\n```\n❯ cat $dangit\n{\"foo\":\n\"bar\"}\n\n❯ sf eval $dangit\n{\n  \"foo\": \"bar\"\n}\n\n❯ sf eval -f json $dangit\n{\n  \"foo\": \"bar\"\n}\n\n❯ sf eval -f json -o no_indent=true $dangit\n{\"foo\":\"bar\"}\n```\n\nOf course, you can use it to convert between formats by specifying the desired\noutput format:\n\n```\n❯ cat $nabbit\n{\"foo\":[1,2,\"bar\"]}\n\n❯ sf eval -f toml $nabbit\nfoo = [1.0, 2.0, \"bar\"]\n\n❯ rt sf eval -f hcl2 $nabbit\nfoo = [1, 2, \"bar\"]\n```\n\nSome formats may expect different shape data though:\n\n```\n❯ rt sf eval -f csv $nabbit\nError: interface conversion: interface {} is []interface {}, not string\n```\n\nAs a special case, you can also read from STDIN by specifying `stdin://` (or `-`),\nwhich assumes JSON or YAML. To optionally control the format parser, use `stdin://FORMAT`.\n\n```\n❯ mj foo=bar | sf eval -f yaml -\n---\nfoo: bar\n\n❯ mj foo=bar | sf eval -f yaml stdin://json\n---\nfoo: bar\n\n❯ generate-gob | sf eval -f json stdin://gob\n{\"vals\":[1,2,3]}\n```\n\nFor a more advanced example, compare two directories of Kubernetes manifests\ncontaining all-in-one  manifests (`foo_aio`) and one-resource-per-file\n(`foo_each`), using `-k`:\n\n```\n❯ sf diff -k ./samples/manifests/foo_aio ./samples/manifests/foo_each\n--- ./samples/manifests/foo_aio\n+++ ./samples/manifests/foo_each\n@@ -25,7 +25,7 @@\n             \"name\": \"web\",\n             \"ports\": [\n               {\n-                \"containerPort\": 80\n+                \"containerPort\": 8080\n               }\n             ]\n           }\n@@ -60,7 +60,7 @@\n         }\n       }\n     },\n-    \"schedule\": \"*/1 * * * *\"\n+    \"schedule\": \"* * * * *\"\n   }\n }\n {\n@@ -73,8 +73,8 @@\n   \"spec\": {\n     \"ports\": [\n       {\n-        \"port\": 80,\n+        \"port\": 8080,\n-        \"targetPort\": 80\n+        \"targetPort\": 8080\n       }\n     ],\n     \"selector\": {\n```\n\nYou can diff multiple files against one file by using the `::` delimiter. Arguments\nbefore the delimiter are taken as one input, while arguments after are taken as\nthe second input to the diff:\n\n```\n❯ sf diff -k ./samples/manifests/foo_aio :: ./samples/manifests/foo_each/*.yaml\n```\n\nYou can compare piles of structured files of differing formats and control the\noutput format being diffed with `-f`\n\n```\n❯ sf diff -f json ./samples/configs/yaml_each ./samples/configs/toml\n--- ./samples/configs/yaml_each\n+++ ./samples/configs/toml\n@@ -32,7 +32,7 @@\n       \"role\": \"backend\"\n     }\n   },\n-  \"title\": \"YAML Example One\"\n+  \"title\": \"TOML Example One\"\n }\n {\n   \"autoscaling_rules\": [\n@@ -68,5 +68,5 @@\n       \"role\": \"frontend\"\n     }\n   },\n-  \"title\": \"YAML Example Two\"\n+  \"title\": \"TOML Example Two\"\n }\n```\n\nFor tab-delimited output, use the CSV format and set the separator to\ntab: `-f csv -o sep=$'\\t'`\n\n\n`toto`\n------\n\nSome dynamic protobuf inspection tools.\n\n```\ngo install github.com/ripta/rt/cmd/toto@latest\n```\n\nYou can build file descriptor set, and use protoc to inspect it:\n\n```\ntoto compile samples\ncat samples/.file_descriptor_set | protoc --decode_raw\n```\n\nOr generate an example protobuf message and dynamically convert it to json:\n\n```\ntoto sample | toto recode -p samples/.file_descriptor_set -f json samples.data.v1.Envelope\n```\n\nThe `toto compile` step is necessary, because you can't currently parse proto\nfiles directly in go (or at least, I wasn't able to).\n\n`uni`\n-----\n\nUnicode-related stuff.\n\n```\n# For a smaller installation, excluding the Unicode Han Database:\ngo install github.com/ripta/rt/cmd/uni@latest\n\n# To include Unicode Han Database, which adds about 25MB to the binary:\ngo install -tags unihan github.com/ripta/rt/cmd/uni@latest\n```\n\nSize comparison:\n\n```\n❯ stat -f '%z %N' uni unihan\n 6572386 uni\n34410114 unihan\n```\n\nList characters:\n\n```\n❯ uni list java cecak\nU+A981 \tꦁ\t[EA A6 81   ]\t\u003cM,Mn\u003e\tJAVANESE SIGN CECAK\nU+A9B3 \t꦳\t[EA A6 B3   ]\t\u003cM,Mn\u003e\tJAVANESE SIGN CECAK TELU\n```\n\nList characters with fewer details:\n\n```\n❯ uni list java cecak -o hexbytes,name\n[EA A6 81   ]\tJAVANESE SIGN CECAK\n[EA A6 B3   ]\tJAVANESE SIGN CECAK TELU\n```\n\nShow only the aggregate count (`-c`), skipping output (`-o none`):\n\n```\n❯ uni list java cecak -o none -c\nMatched 2 runes\n```\n\nShow only characters in a specific character category, e.g.:\n\n```\n# All \"Pd\" (punctuation, dash)\n❯ uni list -C Pd\n\n# All \"S\" (symbols)\n❯ uni list -C S\n\n# All \"N\" (numbers) that aren't \"No\" (other)\n❯ uni list -C N,!No\n\n# All \"Lu\" (letters, uppercase) and \"Ll\" (letters, lowercase)\n❯ uni list -C Lu,Ll\n\n# All Cyrillic uppercase and lowercase letters (i.e., excluding modifiers and subscripts)\n❯ uni list -C Lu,Ll cyrillic\n\n# All iotified Cyrillic letters not containing 'small'\n❯ uni list cyrillic iotified !small\n```\n\nShow only characters in a specific script, e.g.:\n\n```\n# All Sundanese characters, by codepoint name:\n❯ uni list sundanese\n\n# All Sundanese characters, by script name, which needs the --all flag:\n❯ uni list -S Sundanese --all\n```\n\nShow only certain codepoints by character or codepoint:\n\n```\n# All lowercase ASCII characters:\n❯ uni list -r a-z\n\n# Uppercase A-G and lowercase a-g ASCII characters:\n❯ uni list -r A-G,a-g\n\n# Special characters from colon (codepoint 3A) to at sign (codepoint 40):\n❯ uni list -r u+3a-40\n\n# Emojis between 🤤 and 🤗 (order does not matter):\n❯ uni list -r 🤤-🤗\n❯ uni list -r 🤗-🤤\n\n# Combine filters: emojis between 🤤 and 🤗 whose name includes \"hand\":\n❯ uni list -r 🤤-🤗 hand\n```\n\nDon't forget to escape `!` in your shell if necessary.\n\nList all character categories, their names, and counts:\n\n```\n❯ uni cats\nKEY   NAME                    RUNE COUNT\nC     Other                   139751\nCc    Control                 65\nCf    Format                  170\nCo    Private Use             137468\n[...]\n```\n\nList all scripts and counts:\n\n```\n❯ uni scripts\nNAME                     RUNE COUNT\nAdlam                    88\nAhom                     65\nAnatolian_Hieroglyphs    583\n[...]\n```\n\nDescribe characters:\n\n```\n❯ echo 𝗀𝘨| uni describe\nU+1D5C0 𝗀       [F0 9D 97 80]   \u003cL,Ll\u003e  MATHEMATICAL SANS-SERIF SMALL G\nU+1D628 𝘨       [F0 9D 98 A8]   \u003cL,Ll\u003e  MATHEMATICAL SANS-SERIF ITALIC SMALL G\nU+000A  \"\\n\"    [0A         ]   \u003cC,Cc\u003e  \u003ccontrol\u003e\n```\n\nMap characters for fun:\n\n```\n❯ echo Hello World | uni map smallcaps\nHᴇʟʟᴏ Wᴏʀʟᴅ\n\n❯ echo Hello World | uni map italics\n𝐻𝑒𝑙𝑙𝑜 𝑊𝑜𝑟𝑙𝑑\n```\n\nCanonically compose runes:\n\n```\n❯ echo 감 | uni nfc\n감\n\n❯ echo 감 | uni nfd\n감\n```\n\nSometimes it may be useful to decompose runes before describing:\n\n```\n❯ echo 쭈꾸쭈꾸 | uni d\nU+CB48  쭈      [EC AD 88   ]   \u003cL,Lo\u003e  \u003cHangul Syllable\u003e\nU+AFB8  꾸      [EA BE B8   ]   \u003cL,Lo\u003e  \u003cHangul Syllable\u003e\nU+CB48  쭈      [EC AD 88   ]   \u003cL,Lo\u003e  \u003cHangul Syllable\u003e\nU+AFB8  꾸      [EA BE B8   ]   \u003cL,Lo\u003e  \u003cHangul Syllable\u003e\nU+000A  \"\\n\"    [0A         ]   \u003cC,Cc\u003e  \u003ccontrol\u003e\n\n❯ echo 쭈꾸쭈꾸 | uni nfd | uni describe\nU+110D  ᄍ      [E1 84 8D   ]   \u003cL,Lo\u003e  HANGUL CHOSEONG SSANGCIEUC\nU+116E          [E1 85 AE   ]   \u003cL,Lo\u003e  HANGUL JUNGSEONG U\nU+1101  ᄁ      [E1 84 81   ]   \u003cL,Lo\u003e  HANGUL CHOSEONG SSANGKIYEOK\nU+116E          [E1 85 AE   ]   \u003cL,Lo\u003e  HANGUL JUNGSEONG U\nU+110D  ᄍ      [E1 84 8D   ]   \u003cL,Lo\u003e  HANGUL CHOSEONG SSANGCIEUC\nU+116E          [E1 85 AE   ]   \u003cL,Lo\u003e  HANGUL JUNGSEONG U\nU+1101  ᄁ      [E1 84 81   ]   \u003cL,Lo\u003e  HANGUL CHOSEONG SSANGKIYEOK\nU+116E          [E1 85 AE   ]   \u003cL,Lo\u003e  HANGUL JUNGSEONG U\nU+000A  \"\\n\"    [0A         ]   \u003cC,Cc\u003e  \u003ccontrol\u003e\n```\n\nSort input with different collation (`-l`):\n\n```\n❯ cat input.txt\nŒthelwald\nZeus\nAchilles\n\n❯ cat input.txt | uni sort -l en-US\nAchilles\nŒthelwald\nZeus\n\n❯ cat input.txt | uni sort -l da\nAchilles\nZeus\nŒthelwald\n\n❯ cat input.txt | uni sort -l da -r\nŒthelwald\nZeus\nAchilles\n```\n\n\n`yfmt`\n------\n\nReindent YAML while preserving comments.\n\n```\ngo install github.com/ripta/rt/cmd/yfmt@latest\n```\n\nThis tool treats comments as nodes and therefore will _not_ preserve comment\nindentation. For example:\n\n```\n❯ cat in.yaml\n# does this work?\nfoo:\n   - 123   # I hope\n           # maybe\n   - 456\n\n❯ yfmt \u003c in.yaml\n# does this work?\nfoo:\n  - 123 # I hope\n  # maybe\n  - 456\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fripta%2Frt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fripta%2Frt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fripta%2Frt/lists"}