nandi/freeqsay-nimpublic Fork 0
b248dcf49f97f0bc4ec74cdc78dfe62d9bf11257
Commits
Clone
git clone https://git.rickub.com/nandi/freeqsay-nim.git
git clone ssh://git@rickub.com/nandi/freeqsay-nim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

freeqsay.nim · 114 lines · 3.4 KBNim Blame HistoryRaw
freeqsay: Nim port of the Clojure implementation fa53aa2 nandi 23h ago1## freeqsay — cowsay with your FreeqWorld face.
2##
3## freeqsay <handle> <message...>
4## echo "hi" | freeqsay <handle>
5## freeqsay did:plc:… "offline, no resolve"
6##
7## As a library, this module re-exports the whole port: `freeqsay.say` (balloon
8## + face), `avatar` (traits and the 16×24 sprite), `ansi` (truecolor
9## half-block rendering), `png` (RGBA → PNG), `resolve` (handle → DID),
10## `balloon`, `sha256`.
11
12import freeqsay/[ansi, avatar, balloon, png, resolve, say, sha256]
13export ansi, avatar, balloon, png, resolve, say, sha256
14
15when isMainModule:
16 import std/[os, strutils]
17
18 const usageText = """
19freeqsay — cowsay with a FreeqWorld character
20
21Usage:
22 freeqsay <handle> <message...>
23 freeqsay <handle> # message from stdin
24 echo "hello" | freeqsay <handle>
25
26 Resolves a Bluesky handle to a DID, derives the deterministic avatar,
27 and prints a cowsay balloon above the character. A raw did:… works too.
28
29Options:
30 -W, --width <n> Balloon wrap width (default 40)
31 --scale <n> Character pixel scale (default 2)
32 --facing <dir> south | north | east | west (default south)
33 --think Thought bubble instead of speech
34 -o, --output <path> Write to file (else stdout)
35 -h, --help Show help
36
37Examples:
38 freeqsay jay.bsky.team hello from the timeline
39 freeqsay @pfrazee.com "ships free software"
40 freeqsay someone.bsky.social -W 20 short lines work
41 echo "piped in" | freeqsay jay.bsky.team
42"""
43
44 proc usage(code = 1) =
45 stderr.writeLine usageText
46 quit(code)
47
48 proc positiveInt(s: string): int =
49 try:
50 result = parseInt(s.strip())
51 except ValueError:
52 usage()
53 if result < 1: usage()
54
55 type Args = object
56 handle, output: string
57 messageParts: seq[string]
58 width, scale: int
59 facing: Facing
60 think: bool
61
62 proc parseArgs(argv: seq[string]): Args =
63 result = Args(width: 40, scale: 2, facing: fSouth)
64 var i = 0
65 while i < argv.len:
66 let a = argv[i]
67 proc value(): string =
68 # A flag with no value left is a usage error, not an index crash.
69 if i + 1 >= argv.len: usage()
70 inc i
71 argv[i]
72 case a
73 of "-h", "--help": usage(0)
74 of "--think": result.think = true
75 of "-W", "--width": result.width = positiveInt(value())
76 of "--scale": result.scale = positiveInt(value())
77 of "--facing":
78 let f = value()
79 if f notin ["south", "north", "east", "west"]: usage()
80 result.facing = parseEnum[Facing](f)
81 of "-o", "--output": result.output = value()
82 else:
83 if a.startsWith("-"):
84 stderr.writeLine "unknown flag: " & a
85 usage()
86 elif result.handle.len == 0:
87 result.handle = a
88 else:
89 result.messageParts.add a
90 inc i
91
92 proc main() =
93 let args = parseArgs(commandLineParams())
94 if args.handle.len == 0: usage()
95 var message = args.messageParts.join(" ").strip()
96 if message.len == 0:
97 message = readAll(stdin).strip()
98 if message.len == 0: usage()
99
100 let result = freeqsay(args.handle, message, width = args.width,
101 think = args.think, scale = args.scale,
102 facing = args.facing)
103 let text = result.output & "\n"
104 if args.output.len > 0:
105 writeFile(args.output, text)
106 else:
107 stdout.write text
108 stdout.flushFile()
109
110 try:
111 main()
112 except CatchableError as e:
113 stderr.writeLine e.msg
114 quit(1)